From a99f88244f3a789602946b01b31a55d2bd839027 Mon Sep 17 00:00:00 2001 From: imuge Date: Wed, 22 Sep 2021 10:58:25 +0800 Subject: [PATCH] new version 1.6.0-SNAPSHOT --- core | 2 +- deploy/deploy-gateway/pom.xml | 2 +- .../src/main/resources/config/gateway.conf | 6 +- deploy/deploy-peer/pom.xml | 2 +- .../main/resources/config/init/ledger.init | 16 +- .../src/main/resources/config/init/local.conf | 8 +- deploy/docker/docker-demo/pom.xml | 135 ---------- .../docker-demo/src/main/docker/Dockerfile | 37 --- .../src/main/docker/script/shutdown.sh | 4 - .../src/main/docker/script/start.sh | 34 --- .../docker-demo/src/main/docker/zip/conf.zip | Bin 91179 -> 0 bytes .../main/resources/docker-compose-all.yaml | 36 --- .../src/main/resources/start-net.sh | 5 - .../docker-demo/src/main/resources/zip.sh | 9 - deploy/docker/docker-sdk/pom.xml | 66 ----- .../com/jd/blockchain/ContractParams.java | 107 -------- .../main/java/com/jd/blockchain/SDKDemo.java | 68 ----- .../com/jd/blockchain/SDKDemo_Constant.java | 46 ---- .../java/com/jd/blockchain/SDK_Base_Demo.java | 192 -------------- .../jd/chain/contract/TransferContract.java | 32 --- .../contract-compile-1.3.0.RELEASE.car | Bin 5129 -> 0 bytes deploy/docker/pom.xml | 24 -- deploy/docker/readme.md | 44 ---- deploy/pom.xml | 7 +- docs/ca.md | 150 +++++++++++ docs/cli/ca.md | 248 ++++++++++++++++++ docs/cli/keys.md | 2 + docs/cli/participant.md | 36 +-- docs/cli/tx.md | 242 ++++++++++++++++- docs/jdchain_cli.md | 4 +- explorer | 2 +- feature/ledger-database/.gitkeep | 0 framework | 2 +- libs/bft-smart | 2 +- libs/binary-proto | 2 +- libs/httpservice | 2 +- libs/kvdb | 2 +- libs/utils | 2 +- pom.xml | 2 +- project | 2 +- samples/contract-samples/pom.xml | 2 +- samples/pom.xml | 6 +- samples/sdk-samples/pom.xml | 2 +- test | 2 +- 44 files changed, 695 insertions(+), 899 deletions(-) delete mode 100644 deploy/docker/docker-demo/pom.xml delete mode 100644 deploy/docker/docker-demo/src/main/docker/Dockerfile delete mode 100644 deploy/docker/docker-demo/src/main/docker/script/shutdown.sh delete mode 100644 deploy/docker/docker-demo/src/main/docker/script/start.sh delete mode 100644 deploy/docker/docker-demo/src/main/docker/zip/conf.zip delete mode 100644 deploy/docker/docker-demo/src/main/resources/docker-compose-all.yaml delete mode 100644 deploy/docker/docker-demo/src/main/resources/start-net.sh delete mode 100644 deploy/docker/docker-demo/src/main/resources/zip.sh delete mode 100644 deploy/docker/docker-sdk/pom.xml delete mode 100644 deploy/docker/docker-sdk/src/main/java/com/jd/blockchain/ContractParams.java delete mode 100644 deploy/docker/docker-sdk/src/main/java/com/jd/blockchain/SDKDemo.java delete mode 100644 deploy/docker/docker-sdk/src/main/java/com/jd/blockchain/SDKDemo_Constant.java delete mode 100644 deploy/docker/docker-sdk/src/main/java/com/jd/blockchain/SDK_Base_Demo.java delete mode 100644 deploy/docker/docker-sdk/src/main/java/com/jd/chain/contract/TransferContract.java delete mode 100644 deploy/docker/docker-sdk/src/main/resources/contract-compile-1.3.0.RELEASE.car delete mode 100644 deploy/docker/pom.xml delete mode 100644 deploy/docker/readme.md create mode 100644 docs/ca.md create mode 100644 docs/cli/ca.md delete mode 100644 feature/ledger-database/.gitkeep diff --git a/core b/core index c7160adc..c6d05f98 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit c7160adc704c53766427ecdf70f130e43f6cac43 +Subproject commit c6d05f98420213bf7ab1b4f431f6dcee2654bf5c diff --git a/deploy/deploy-gateway/pom.xml b/deploy/deploy-gateway/pom.xml index 31de50bf..f13dbf16 100644 --- a/deploy/deploy-gateway/pom.xml +++ b/deploy/deploy-gateway/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain deploy-root - 1.5.0.RELEASE + 1.6.0-SNAPSHOT deploy-gateway diff --git a/deploy/deploy-gateway/src/main/resources/config/gateway.conf b/deploy/deploy-gateway/src/main/resources/config/gateway.conf index a5cd04fd..529f99a5 100644 --- a/deploy/deploy-gateway/src/main/resources/config/gateway.conf +++ b/deploy/deploy-gateway/src/main/resources/config/gateway.conf @@ -25,11 +25,13 @@ peer.providers=com.jd.blockchain.consensus.bftsmart.BftsmartConsensusProvider data.retrieval.url= schema.retrieval.url= -#默认公钥的内容(Base58编码数据); +#默认公钥的内容(Base58编码数据),非CA模式下必填; keys.default.pubkey= +#默认网关证书路径(X509,PEM),CA模式下必填; +keys.default.ca-path= #默认私钥的路径;在 pk-path 和 pk 之间必须设置其一; keys.default.privkey-path= -#默认私钥的内容(加密的Base58编码数据);在 pk-path 和 pk 之间必须设置其一; +#默认私钥的内容;在 pk-path 和 pk 之间必须设置其一; keys.default.privkey= #默认私钥的解码密码; keys.default.privkey-password= diff --git a/deploy/deploy-peer/pom.xml b/deploy/deploy-peer/pom.xml index 42cbdf9f..129a1c82 100644 --- a/deploy/deploy-peer/pom.xml +++ b/deploy/deploy-peer/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain deploy-root - 1.5.0.RELEASE + 1.6.0-SNAPSHOT deploy-peer diff --git a/deploy/deploy-peer/src/main/resources/config/init/ledger.init b/deploy/deploy-peer/src/main/resources/config/init/ledger.init index f475f0e9..82ae9016 100644 --- a/deploy/deploy-peer/src/main/resources/config/init/ledger.init +++ b/deploy/deploy-peer/src/main/resources/config/init/ledger.init @@ -4,6 +4,12 @@ ledger.seed=932dfe23-fe23232f-283f32fa-dd32aa76-8322ca2f-56236cda-7136b322-cb323 #账本的描述名称;此属性不参与共识,仅仅在当前参与方的本地节点用于描述用途; ledger.name= +#身份认证模式:KEYPAIR/CA,默认KEYPAIR即公私钥对模式 +identity-mode=KEYPAIR + +#账本根证书路径,identity-mode 为 CA 时,此选项不能为空,支持多个,半角逗号相隔 +root-ca-path= + #声明的账本创建时间;格式为 “yyyy-MM-dd HH:mm:ss.SSSZ”,表示”年-月-日 时:分:秒:毫秒时区“;例如:“2019-08-01 14:26:58.069+0800”,其中,+0800 表示时区是东8区 created-time=2019-08-01 14:26:58.069+0800 @@ -76,6 +82,8 @@ cons_parti.0.name= cons_parti.0.pubkey-path= #第0个参与方的公钥内容(由keygen工具生成);此参数优先于 pubkey-path 参数; cons_parti.0.pubkey= +#第1个参与方的证书路径,identity-mode 为 CA 时,此选项不能为空 +cons_parti.0.ca-path= #第0个参与方的角色清单;可选项; #cons_parti.0.roles=ADMIN, MANAGER @@ -96,6 +104,8 @@ cons_parti.1.name= cons_parti.1.pubkey-path= #第1个参与方的公钥内容(由keygen工具生成);此参数优先于 pubkey-path 参数; cons_parti.1.pubkey= +#第1个参与方的证书路径,identity-mode 为 CA 时,此选项不能为空 +cons_parti.1.ca-path= #第1个参与方的角色清单;可选项; #cons_parti.1.roles=MANAGER @@ -116,6 +126,8 @@ cons_parti.2.name= cons_parti.2.pubkey-path= #第2个参与方的公钥内容(由keygen工具生成);此参数优先于 pubkey-path 参数; cons_parti.2.pubkey= +#第1个参与方的证书路径,identity-mode 为 CA 时,此选项不能为空 +cons_parti.2.ca-path= #第2个参与方的角色清单;可选项; #cons_parti.2.roles=MANAGER @@ -136,6 +148,8 @@ cons_parti.3.name= cons_parti.3.pubkey-path= #第3个参与方的公钥内容(由keygen工具生成);此参数优先于 pubkey-path 参数; cons_parti.3.pubkey= +#第1个参与方的证书路径,identity-mode 为 CA 时,此选项不能为空 +cons_parti.3.ca-path= #第3个参与方的角色清单;可选项; #cons_parti.3.roles=GUEST @@ -147,4 +161,4 @@ cons_parti.3.initializer.host=127.0.0.1 #第3个参与方的账本初始服务的端口; cons_parti.3.initializer.port=8830 #第3个参与方的账本初始服务是否开启安全连接; -cons_parti.3.initializer.secure=false +cons_parti.3.initializer.secure=false \ No newline at end of file diff --git a/deploy/deploy-peer/src/main/resources/config/init/local.conf b/deploy/deploy-peer/src/main/resources/config/init/local.conf index 46ae5b35..17615351 100644 --- a/deploy/deploy-peer/src/main/resources/config/init/local.conf +++ b/deploy/deploy-peer/src/main/resources/config/init/local.conf @@ -1,13 +1,17 @@ #当前参与方的 id,与ledger.init文件中cons_parti.id一致,默认从0开始 local.parti.id=0 -#当前参与方的公钥 +#当前参与方的公钥,用于非证书模式 local.parti.pubkey= +#当前参与方的证书信息,用于证书模式 +local.parti.ca-path= #当前参与方的私钥(密文编码) local.parti.privkey= +#当前参与方的私钥文件,PEM格式,用于证书模式 +local.parti.privkey-path= -#当前参与方的私钥解密密钥(原始口令的一次哈希,Base58格式),如果不设置,则启动过程中需要从控制台输入 +#当前参与方的私钥解密密钥(原始口令的一次哈希,Base58格式),如果不设置,则启动过程中需要从控制台输入; local.parti.pwd= #账本初始化完成后生成的"账本绑定配置文件"的输出目录 diff --git a/deploy/docker/docker-demo/pom.xml b/deploy/docker/docker-demo/pom.xml deleted file mode 100644 index 6eb80655..00000000 --- a/deploy/docker/docker-demo/pom.xml +++ /dev/null @@ -1,135 +0,0 @@ - - - - docker - com.jd.blockchain - 1.5.0.RELEASE - - 4.0.0 - docker-demo - - - - com.jd.blockchain - docker-sdk - ${project.version} - - - - - jdchain-demo - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.8 - 1.8 - UTF-8 - false - true - false - false - - - - - - - - - - - - - - - - - - - - - - - - - maven-resources-plugin - 3.0.2 - - - copy-resources - validate - - copy-resources - - - - UTF-8 - ${project.basedir}/src/main/docker/zip - false - - - ${project.basedir}/../../deploy-peer/target/ - false - - jdchain-peer-${project.version}.zip - - - - - ${project.basedir}/../../deploy-gateway/target/ - false - - jdchain-gateway-${project.version}.zip - - - - - - ${project.basedir}/../docker-sdk/target/ - false - - docker-sdk-${project.version}.jar - - - - - - - - - - - com.spotify - docker-maven-plugin - 1.2.2 - - - - - build-image - - package - - build - - - - - - - jdchain-demo - - ${project.version} - - - ${project.basedir}/target - - - - - - \ No newline at end of file diff --git a/deploy/docker/docker-demo/src/main/docker/Dockerfile b/deploy/docker/docker-demo/src/main/docker/Dockerfile deleted file mode 100644 index c6d8edde..00000000 --- a/deploy/docker/docker-demo/src/main/docker/Dockerfile +++ /dev/null @@ -1,37 +0,0 @@ -FROM centos:8.2.2004 - -# install tools -RUN yum install wget -y \ - && wget -O /etc/yum.repos.d/epel-7.repo http://mirrors.aliyun.com/repo/epel-7.repo \ - && yum install java net-tools nc crontabs expect unzip -y \ - && yum install langpacks-zh_CN.noarch -y \ - && yum install dos2unix -y \ - && echo "LANG=zh_CN.utf8" >> /etc/locale.conf \ - && source /etc/locale.conf \ - && yum clean all - -WORKDIR /export/jdchain -COPY zip/* /export/jdchain/ - -# env -ENV RELEASE_DIR=/export/jdchain -ENV RELEASE_VERSION=1.3.0 -#ENV DATA_DIR=/shared -ENV NAME=conf -ENV SERVER_NAME_PEER=deploy-peer -ENV SERVER_NAME_GW=deploy-gateway - -COPY script/* /export/jdchain/ -RUN dos2unix /export/jdchain/*.sh -RUN chmod +x /export/jdchain/*.sh - -# ports -EXPOSE 8080 -#EXPOSE 16000 -#EXPOSE 16010 -#EXPOSE 16020 -#EXPOSE 16030 - -#ENTRYPOINT ["/bin/sh","-c","/export/jdchain/start.sh"] -ENTRYPOINT sh /export/jdchain/start.sh - diff --git a/deploy/docker/docker-demo/src/main/docker/script/shutdown.sh b/deploy/docker/docker-demo/src/main/docker/script/shutdown.sh deleted file mode 100644 index 8cf6d207..00000000 --- a/deploy/docker/docker-demo/src/main/docker/script/shutdown.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -ps -ef|grep 'jdchain'|grep -v grep|cut -c 9-15|xargs kill -9 - diff --git a/deploy/docker/docker-demo/src/main/docker/script/start.sh b/deploy/docker/docker-demo/src/main/docker/script/start.sh deleted file mode 100644 index c55380b6..00000000 --- a/deploy/docker/docker-demo/src/main/docker/script/start.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash - -cd $RELEASE_DIR - -# check the files; -peer_file="./jdchain-peer-$RELEASE_VERSION.RELEASE.zip" -gw_file="./jdchain-gateway-$RELEASE_VERSION.RELEASE.zip" -sdk_file="./docker-sdk-$RELEASE_VERSION.RELEASE.jar" -if [[ ! -f $peer_file ]] || [[ ! -f $gw_file ]] || [[ ! -f $sdk_file ]] ; then -echo "not find $peer_file or $gw_file or $sdk_file in the $RELEASE_DIR, please check the image of jdchain-demo:$RELEASE_VERSION." -exit 1 -fi - - unzip -o conf.zip - - for i in `seq 0 3` - do - unzip -n -d ./peer$i jdchain-peer-$RELEASE_VERSION.RELEASE.zip - chmod +x ./peer$i/bin/* - done - - unzip -n -d ./gw jdchain-gateway-$RELEASE_VERSION.RELEASE.zip - chmod +x ./gw/bin/* - -sh ./peer0/bin/peer-startup.sh -sh ./peer1/bin/peer-startup.sh -sh ./peer2/bin/peer-startup.sh -sh ./peer3/bin/peer-startup.sh -sleep 30 -sh ./gw/bin/startup.sh -sleep 10 -java -jar docker-sdk-1.3.0.RELEASE.jar > sdk.log - -tail -f /dev/null diff --git a/deploy/docker/docker-demo/src/main/docker/zip/conf.zip b/deploy/docker/docker-demo/src/main/docker/zip/conf.zip deleted file mode 100644 index dc8dffb12ecef03f0a8dfecc4da7f9fb539a07a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 91179 zcma&N19TY}@H19ox2T+qP}ncG4Z&wr$(C|MWTUo^#KA_l@z_7;9IJJ?5IT zYEOOjt+iz(fPj$!{vK%_8gl=*_&*;&-)9pS8rlD+0N6hT^v$gQB?Jy|?5`pBYfFP8 z01yB``Ue02!oP(a9QEuSoouNcOqJr-tk-CvgRdTtyKoA$Lc}-ywWvh>^ewiNzQhqSh0i9@pNPMP*Vx=$rOfSXB^#tsnCx>A zyi{ibYlJ%byQkz-m)+&0G#z#MOL`D_3I17t{E}t=6Q@vNH6*bzE$#dsxbdFq^TO5k z$)3Y)F}ELv5MUDe^f{i+pLC*-do0DAUtTMkjbN!LACR#E0%g%!&c%unGJX?5&HeoB zyc|I0f`$E6kwp&^fAh6>0Ju4)=-0*}xa|Xq6#-&FP7jUFXm?>LyeVKVLVv{zH_Gx^ zzevPIP)522PC!ecTZ$&Wl$Bpc z;;JyDWI9=KPJ7pE4MZQFpBndwj6A4p71gjVe%;`>oxPsi>s>QhCrwaT_1zej@7j#(@Iab#{Y>R2?=7xLfc-P`&SoRbpos{c9rET4_`lsMqNe#D zeuw<0#|&(&jm=E{n`ixnl;yC@|KtXK2S(|)?@|9l&P31A$VJbM`tP&}MGKo*S@h4z zD%zd8q+Jlk6Rz{HO6Yai)pJ~~1 zw`M*)b11Uw#W%^RBoi&1S8v=$ZZy(+b6SaO^vTv)5J9T#@`6E3^|z2*qt;6)ICMgB zrK#X3gF+Xc(H+-=4lqfhIPFBEkb6#UPoQTD{@PhRP)!q0nC9!Nl~eigi{sK7hm$yO zp{(V;atFEnWORc&wuBYJgw+7U#(J}<e z&K5X|8?EnZ=njvDo+sJ)oZYy`8l@lA1a``;LEINBf>RIi)Nin zK)AtCq?Kn8K@8jNa z!i$ahhS@`ae-@?Re^ruf$R&JgF`Za5Jvk_(&tZ)n8e{Ne3&2spmeBkGP6#ZFGB%?7 zdCj|7Nh8!x#MG~_6NnxrqMu1?$piu1M+@`YR?#mR;lP5*eO1K@tPpO2l6-Q{#f>xX zBmT}XycGZp$SfALZGBZHNu6W(oj@!k!I6pjPje>ICZ`ojrm_IurOqjj%xt*IS*bRm zX9a=X?sVRnzvEuN6NM7XaK&aSA69~n4ab3#zjKQwQQYA;JK4{~in9fM1{Fr`->hA_ zG{X?yYRrp@Wv;hyH*4`NX}=(Pl9&w2d~@LYN+vQ;5_5Xtly4R}0SnAgXqY2S@US^c zY!ji8)c`Q4`HBvJ>)=4p!~FJTY7A{#!%!z;B8p$3540Jt)RLl$?L{l%tgr3hu^%uW zKn$FE7t54d?Px*Ca0Oc&`SS`LQ_s`-Y*PIVa$U8>i;#UnWum~kTm15Pd2~0t z{dlZ4DfHVK=kq#|kT8MQ^ftJOMT`h|hrBt%_f^VO}#W zD!mvNK;@7QHcuALt;K6&3BW3Te=_yETnPjz6-vb=aN)hWu+r8KF`&ED?aVA=Y#$Z; zk|}mC=uy?tg@(p%i9ZSQ8uFm`DxnDy$fselPwQMA!b9ofH>5l+2^g)>4K`}=enD*0 zD4n3!lXy<}f&=!o6-;y+Ji`mu!2k~yrpd%T9nowD7szkga@k4r!H%heh#*;>qFOz8!W1VAk5L(EJQi_H&O$}yHA;4^ffkJGj z2hm_3mt5&miBEU!$j24sV!ugt_J(R><**lTz%gVc?nM1SX0b|MLxXi(OxtH zDlVp9**RjI`91O*?!XyCVxD989lD#op7~4zvMQ{&O*Pim;?^#XO%?V-(Jz$UaL#)I zPGr!(uXq`n%2y- z0H`Ch;IZ7Qc{X(+lxZ*#}U|0@*0G1PWCz z=v4vyEm>D`txod;?%IS8<+nDw(E~~->Dx$kg7pIR}ycQyru=~Q5 zsi;m`k6O+nOv&}GnWE+4#6Y;dnM7ARer(n8fc|dv@p=H>^ce&;LlwKZOq({+CHNhF zV}SSkIWOTP$!z!8exO}G{O%>!7jOXdxhd{8+dY9bv~Xr1(@n-91V$EqyJ!@ofJWHi zykDUk8=gvMGwD+y0!OZL*8V*fxX&eS`R&x2yB_I|1d9uXceESY)V&hv-5<(VuLJB$ zgomx>TgvfNl$jUN$#$84`s{cdn6p!R9WW@?Oi=dHU4?Q}r8$9X>ia<+m7?Aj+WfH1Y?Ht7`Wu`HRDr1aM&Eq0}|K*8e^wPQCz)m~w;8CMisH!LsSJ$B%5^etb%Ti8yc`>EY?QVe<-15?IYGcN(Lb)Al z_bjrxZ^yzqMO5wU+1z05Q|rJOCpG6!uN%X_i<#r-f>XM_dad6@{8DU@sJC$U zj=tpblm$O)Y4}WO?hkTo@9visvu&Xxv~Xruh7Mur6wW-}haNd|hv>*E~F3Y&>0{ zhe++@EI$qHFnY}_o`rd<0w~CLcq*0m{uCQl1yCK0zuXE>3{c-xC;I44Nb+_N z8FaYnBf~vnL3Jz;JF{22`dr~MaB`@eDgUp)6u zng2xUXWvMj8JtEe`5UP_e+HL(2j5MzYdIc9%u zuZL0vv18eE?S}meDmCPtYmG9cDLW2UFXmUJ1I)s6#B4mDwD2i4y&&`L`Niv5)xt!R z(`&j^ul7$~#gp_|UDjK*;32DcGo?`axWp9gfwr+A^1aSevH+R7sXAXNvNFcrBdq26 zLePu_raOuajF8aq8Kvu95CbZ9*qM^|9p5{iLC6LC%rB0usNIfqQd^f0i2N;q=!;|~ z6f#g~N!{|vVK!T{up^vfBF#PONTN${HK7%PXoGS{(o;U`HYmr|pfX1a2kgMa>u~fS zNIfkiu+JYf3)TGhsGwkwJqkySQ0;UzHy4AWJH-$B2_r|PUi62)iWDiZZBVrdba0HALKn|rxqOttZc=#2mjB-OU=N> z9Kavt-Wsk+9}XG&vNfhW5L#+NJvf-1F;rVT)V@EM^IkQsopOJ;GzwcrCtyNN#X->9 zkG>*m2Iej*btqP{5n%%1SUxDHofEmThU^h)LJFgRmW4*Ajj|sk(&#^+Pq$jZ&@y0sr#|5~G{pH|-D+uMXkuhf{Wm0{ z;++53bR8ad2yx?@mmocX~q6?qXYAC_pSoSQJ5PHp)P+mE;$ zvI}eUX5vv=bt&x$gCPQ}%H<+g(be)$o64E>kF^^>5&W!>^-9%@8Hgs(&7DO> zvHOaR8^HOc)?UXYN0QmZ1XTh{+$)$&dqNH4NZgnh{2&;?8Ry!8E9T-T|xt zFc~F4B!tNRSPx_?6dwpR9wAU*r&~P`&GtzNFFC#XIG{_^5C_e3ln6XI>}M}_5vi0c z(=BUcqs+20G39DE(Ap0|>b7G&&u){Y40i3Sm&?Moj8CqHK|LszNwWxoOFvkDTzN|l zJzO7cG@a>}#pom!!4>kjDMhPXj4HA#@0Ed`HUa!ePT5Jv!6uStaksij9us07cj0(p z4RaQWfiVs{Vp#IrNAQLLb+!nJX{3D2>bv7FmAgXg9|^`QGtn{PXP>q_$$i~+(s4RT zg8Knp=}vB_jyfJTB1{_uz9=X=Ga2R|k~^n`h%}k~9P0l0?6(W?=zenLNF4vs$#R-J zXFwU^phqFcg4i=A_~(e3u#SWt4Q%-dRoB|8wg&e8}O`**DNcB;| z5kXc(f{{*GYus?VWZ`0K6!?Om=(nN6zC)UD&$x;V?VQ|!T$a@3jp^D@G+_Ib!h!KA zThl52JE`wE;94Z|7Fxevq5d6JzTd@Sm@7*E_B*N)fdK%p|M#e}G0?O8N2TymkWg5a zMc%xlV0SWL5xYil3=Se)ojJ{YJ}(9$;_r@+1%}5lnU?DrEfX>;dp<7h{?QX;5SQXZ z!U93@$xj1Z)Obo1;o;E9wzZe(=qrwW9?a*cKq}bZpXJ#65KMS_0N+lX5SV0$Fe-MV zPl%sr9n_l?lAB2iv-KW%=1L3us!!{q z%!~7E=0%vtekxw{1V&Vz28_=etTlEN;>5B*d(%sNOg@`2J-xTWTkU}|}|+%0&qrTy-7fG$6USphXiu8R5AOE{)SH{I9-rV2c6 z?noBgbEgGKP+CtF-<>*<8vn;2nX!+bD?BnktzuT#PY z2`_Sbf#yGz`9zDV@Lg2bU@Vf=Q4zID01 z;w-i16PT6Txgk%aPJ&P3&I$)=_K~o4->t+WVh|U}lLamjf?t+vKwXq%gBUdvGG(a} zg1?vRbhV`lcF0Ay5pCr`_?fbO+LkEwn_YEvcN_ffC+~0anWeFiid%s2N;;Cw#?5E0 zKOVNYGUlW$z0;I2u=LQyd>HYDGsXx6p*}{w_U~i40o{Fqf7JY0rWK}Oq zCL$^Q+yGy6jNxkbHUb#EY|3D9x%*cI{{PX4EsWe8{?Um4LO1|`zquuNgrF?)ef@X* z8?gPK9{&${b3Lna%2GXpmTAvy&|W(8+iSv`4sCE353{cr1)3+~O#-ZySHd|J#;}A*+zNg{rcGqN|j!xq!2@w1c7zt31Dy;P;PZ zWx-;iW}(8UARy@EZ0Nu*^6xqoicTqY@G7zlRrhma`qRrv9|FfkxA#>L}r>1M-JabB-h%}C$e5=uM9Tn1;7oZ zi`u127hM)VO(w_*7JSUZr1Z=1cti}V@eo;F(zR~jkhDZrM7@y~bCtZ>Ja^GRIezj! zg*D4gi6QFRd*w)IJ7E?gP>oc3Lm=tectlUC5e15$YInVN)TGx}JrSPkq&DNo=0dPw zUCi~iLOc~ya1UM1wOLva?tGL)R_cTwBJNJ&r4}uLWuk&O)k(`gyJOsv^Br( zK5(oSkQY!^I4DDO;T=XUm{7E$_ds|Y;`q)!+pMYDeB1nI>uL0P0(C1|A_38U>u#ou zcwdJV65$+QwtJ%KI8XJ??Gv_;)k6|MT?vm48jw^rU3Mw&5zfp2^XRtU0dF5CbnTTX z+VfYj(y%}DZ54u5o(n4L&aE<|h11+tH+} zON>-Iek`HUYVPjguLdSbq?SPq0{G7?Iv^9F(9o=D-ML##-+-#T=_bJ7W&X; zmHEXuW|=jt_uqr zOK5+<&Rm*tGY8zl6-|L2CNipXr|aKQ|Mx8+|1yPX<-0TU!~HMa`TwwifAjhue4vVk z%r_tC&2q!@%?GNUas|sm^OuomLRZiYqSrM_sQrTviFmEC&x9#H|Zt zRpVo9(&)8I<7#Z;;pguk7KX`UX7Y62;>mEtVPcX_4IN1As3XfH7q_uK5{!h6xbKUI zd=Q(_cKF0T<$#tRgKNfq=otSSL^GAXE`qgHe;?KMv?b0bNF*PhV1zDHiYcGE3XSQf zO+;4_jRQ~K*^sHyJ}%vS)h+vW z4^4a#%hkk5N&4j3$Eve;z3MmfNtv-HAZOhSmU;~^j{OQYGWg?Hwn(;Jyr!BbZ!%^OY=I-n|8rMuWA=eF^-I~m_GM_-~KmE~E|jxx}jhZ5hJ zQem!2qV7#B%G^YleM~O_hi`_3nbm{lLqNCvprKXvBvz(HOc?ETxi$ctTguRjqSt;| zJ1pRVv$yQYk{q6gy+ic&^ubuvj%`6P)3ax%-o9;i122IH?=F+o@jY8?!%q<~dqNRa zC^k?|AXqoBc#@Q?Fs&9r%Cv5KM2|2eGha15LD}XxJaQW6cIz6E5%qA-?7q7Q)+};k z;qahP5^m(3DiG`)1^bSH8k*-pDYYu}gI*0vUI_5}E}(J;Yw?42E< zm!6&Zjjd`J(6OD_1PpW7x(&!|v0p<#e>>@^*IfcziTfCb3NyH)(PgfB$8Sk7E zB_ixR$ZlFAtY{~r*7X#@J7VaKH;ozUW+napa4G1`C9w!qEJ7j^WL9 zjzN#B9P2Lc@iZHrEtg4`&O5EHqrt3sC13!-Ee5=IxKs~T)z$GF78$St0o%m^_i?OO zgrnfJ5>~Gd+`~7M1}40=#tz8$kEl1}35+(WWa-hY$LsCZS7XPam66a-uS zATVMkv}#d0Gr$t}k6;wDF`uaA$C;xMHv?4995xQK>w^|IX2l&*CE{(7PR6iCE7G6D zU4@rrLqbd3f4b-fl#E><$SeD`#SwTm0JK_-6Y?{I4-wGFBOX&@&9)d>&yq&}{DVMX zues{TZ*}5KT}x#OVJSfv;0h)}mz^m;nXD2~`| z`rg)H4u}HBF-E+NsJyPIU!%VVw5w+)&R%@EKFjoSfcLSq(fqg{2lcg{?Ozl?IEe+1 zfJ=giX$W1{U}{xO^M+cda5-jT*7tj5dm6)vS!YcoFsOHViFmlh z3iK$HtmJViq|%`+0pqMf@5Emc8vB~sB$6Tm7OMnjtZjRD7akn2$ZVr_%^Io*xmI*s z7YI66EuO;2X)){eT5Fw7YHmq>B=*&tpD)x*|Fo(Qj zKQwcY(FcfsW{$6~5{VDA52p~JU$y}C0#Rq?9m+Mpg zTf4WOMy#%}IVhQkksn-Fb3cMrONTG>IqRNjPcYGsQD-W$_MIY*#bY%ur)-sJ8y4e) zR=m!=oZ0KgUOsDVTM1wUKU&HKrhG_;9$U}ni zCIs-DyoEP;UJvi;QLvTUzivc)HYZaCkr(lSFLn{YwRKNN$88{IT9TciX@>{y&KaRE zHA-z{J!|W{y^?mAEkiDPAuWryGr;Dg8dE*4Y+$I5;~Ga6eqN`2U`6<^bk&GP6OJ+j zM9BcIRzF6{3A&k7-=OejS{^e$$b*IVn*%9)E>W?&Ad@nUX6*xg9paWa%rku zp8dkB8W_3JFSnfwhSe+lrKVdf#6p#Bj;0TyWC9EN%V|QHo3=QD{CnA-Z6x3FhR7^2AVjowab}#GXguhS}ly4i| zm$;8ZO$)78*hb}+NNSHR5oA>_XxL%esDVqN3YhZUs zWl|9`K@eZQA_)N;B3F;tI%Ed3rpVKn==$eWc%j=C|HbghC@gTkLUT&ofITN2`z+He z#L+Q2Mo^EOtsk|Zhm63XJ&+G0u5@Vi4=LpLs@;f1W?nPDuW5tod=a%)y3}OvU{h#7 z8=Q)!KuZKmU8#G+V2o+4ih>zo+aDn)-W}!d%hmRakM%duPcg+bH3O<5z%)H-a-rIe zEUi%uooowKjI(1P(yTT9wJ9imS;~UN6?HswQ;S(Ac%nnEd(g>y$<6E@Lh@;&nL zgRS`#3A3juvgmJ1atnFsvnO*A`TX^Jy<~Iky(4*fEe_{9r*ct=*dg%z_2WYk66zVJ zF;_9!6$aiq&!hMVlY$`urf1`S4h~1k@~SwW#?0V@X7PsMgUONhbm*H|-1`WWOy3Y7 zlao4IER9 z^WXG^JV)yP@?A8Z@E^5gZssfO#uwpaN)?f@h~~sLo>ae2DL|Hx1HVc{WJ3201SGqFWR?3yRk5(~-+@(bYs6O_)$4Espo^fIc+`gnbx?$IV{(q@lSAb0 zD$_Q2(RdwTOrk_5)HS=;gZwJHe2lvBVKliT(Qk|p6w(#Yci2BMg6O3~(2h^x;c~xg z>C7bXrZ2H8ljxNQvSL@*?ZD7x8E87nq<~D$hL--sY9b67O)Y)X&{(YvKs@V@a5%H? zjhq}e6(7|Du53+#^FL2+p?1=<*9E1aCuF;@^S<}Ufs1ES@(G?1$l_mpEW8ke_6k-m z8`K&M8vJ09HWq-w+^a<3--{+SjihAjsknwo&Sd2UGf{Yy@3p@2?LTB8or%nLJ^G=P z@xg@^8+`kE;RsU`mX!ty`4H_~E%ECF9%iY%bTSG;xzK&LF zo$HHPzzA9<)#ZEf?Xdcs+Wr|M@8WxmCw*P5iK%N;+c9X=n~kD^a5|!OAWV8ZkmGdX zAZq%;m}GQ&jp!sCjLg;S+dJx;Np{?td5c*w7E;XuDKC_>T{@7+7X}0@w$?!3Iv~_u zwsAQYu4}3Y06YXjB8oT9p-bARj~*JYFZj_D_5*<_hjrW(e3ub@i4{c8$i#gy!^86L zEDPo1(@^bCK*+t;lWkbK8#5@tJ#T1wi6f!v6f%)=Akzf5o6ZZ9r6KhF#sR{sHk~kf zj>;x^qEdoCtIH{kvvqZj)9Ddx+5_8mwm-<^1Huysagwu>p_En|Rb%yepmehkdvy+~ z>{6-RY}$Br9JG51M)a7b(#%v6NjH6FA3t{}mv@^Q?YrVnGcPu6>JfY?ETMc; z2q_WrEM5bIuXmThtJAjm@)^5@A<>BEgUkGR4PbrEuLlY|tcSCxFMn z1(aqE+pWTinAKEB%Z>ycg%`cYf^S9nqJ}5xBxTUi>5+)-K@7udO`=e+;S3C7+Sc0V zGpT8|-0`#FaRqLNPxG&GKdH;#naPRCQKW5oXYiqw(w0T*)sJiRd2|n16^)!jER(I% zMAsSMj$q1MeCSN!w%bG`Yb#UTwhn{B(m@Z5pR+vyryKOPU>`kj9Ot01mk!fMSeN=y zCnj>@T{s!RY@M7D4&;X$#zY}7Y_O!m@NTRPbK^w3yJJlH`s0PH*~wLPPTO&Wa=@XIw#;#yh3+EH4Z<3Y(Y6uY2%W z!w;HX8@r0Oe0;Ys4{Fsr=|Fmfbq1^5c#^`qc$|(P#%>D9ryIXeV!>%c@|wP$7Hu-w zuD5oOBl(QL&NR|tMx2!{tZ(k}QT%$1)zG>@VRIS--5C?NbqvU-lWclM3L0nV z{?i}VT66QmwY6uff^x$hBt*JqCy;STQZgjuJ`~Iy!1kb!sX~S%&hZUHUnkpxc!6q! zf7UTu9NlG%%+yKUY1=%(&l%2*HQZq$u+B<-bMKGRaUq=fgh@G?_e5N$RSJ11CPiRC8J8YT zG#qF+z1p8n{>-&cg(cY%hFXwh3dzE!ReF^(y^Ow`J4F`BFhf|BR4UY6X6oe}ZW_D! z_=qF?m_Ba;njD4M-`q>zs&`9xLUL5EalBNUbqnHYm{ok&87VC!VZ738u^$#wwc!<# zzSijPK8@3#*qR7kJ!?CBH*a}fu)irg*i{)Ds7j8|p+fu8{=TXl{i-RmKbNc{e2dR^ z8?e^9cyXe0b3O?=ddlj*tV5`EhRvqH`*_4hFfN7=GA4vwrmwvUY&;yIT&o!N+kg5O7CvA zUv?AwJd`!v$q>=InhZPYH#L>q+L~SowvQNm7bkUMT3~V&93|s)vMg@O=C!*Tp}J4u z&I+z7c(RJu1`)9LAG!>_ft|{F1)8Pr0v+ahgG+lV*_(0dWIjcPMdMij2ZiB%+zB;4 z))@m}GHj&eJpz49fhUeo9!Vxpx`)h+nmdl`ezfs!K4RFKO;*h0Mf~(jhCbqr+8Z^~ zvCmoFC+wAq$U^RxOcFnZ*pD5!1(~JSa9#L@8?FzP3NLG=-g-p!iz;^7m}wKAI}_S~(2L zr_vA?+oT{7$&I#t^=$t5>7QU#>cgH()4e^;wj{|)v1Fui&e6fPuIRP2|D&ob7*APi zp{sPUh|{Y8XyAU%Y=wA!W`yMafG5)sDSS|{5OcbTEfe)FZxv;SC}G`Bckh`K9Jn6< z@wL`BNw3fOojVSVV&pO<$X?>_EM+Y6S<&aSXEeFLc@ ze&#Ky5;?{ol%70G8}$?Su*hdw5yQ=?KlO7tNm18}MUm5YxXyClpWG>#N6p%L26Mpf zwW3ILyuTW4Jc_x+-6y3)GvcmodUhImn_1Uuq8Qmc-K5qwX{XmW0Xx{6-=!H3$ z0-tnYI0sG?{+mZd2+{!%9DuJ5LwfoTB5)^zS9@E}EhD{+8nwI>otmw6bsB8LS9^z2 zccV=3pIT&!+%o|NM&_Bb+r^Zomu_F~<;^Jg@iv#G+eYg9!&}f+Cx5k2G-c z6NlPH58{B$H0qWXDLz+V)u11cN0!(&Q{P5bv=YVd2tUofYk7@4kvt19P)^I!Y4uez z3MOQ@9CRvW#s@}h?Vrd8_L`VWxqvHkZK2Y8DB$ffrC-cRz=XqD%vq#+y9p;jY!wd< zqz;b$bHKYKwmom3Yf6cE+U7y6i^Itt^$8Fe{CXLKR;1>j&~<4v??pUVG52d~GKGx6 ziUGVTCf2Ltoat~r45mNu87cD!P`0Y22SVqP)wZpekN6^VW(4ql5b_tBZ$2G@*JG3R z#&Z<^IvKINYNmX3x9w`Ak`N?>X~r&j*uxAB%Wt!o7Ii+BgC5O_++oSUV63q4Fe4jJ zlt1bcF`~^%ha1%Dcj7wFxA_ZD%;7}yX>Bn~9xBD$s@iCBil@(tZ*x`*0k0Sul^c%q zWEZROZ-oT~ZdEjyE8p!LL8uw^4lQNtZqCVK9w|rYH@R96-Fz|w5kpx$1Ao1FdJ20~ zk;X6z_h*5Wk`=`3!Jkp$42=cIXu$&5X6AOjdiATZgv-DJEaa%L=HE}Yq4t~TJL+=| z9bPY$TJ)l$Wr;}&m&4r0BYSz@yAwSI7w#Y)O2l@M<64aACnJDLm1p@PXXaqF7X2A6 zWElsuG@v*>~EX#=?K|`hN$?|HbbMD9OtUNh?b6 zNs9>!DJW9?gTtqT{P%8zzbYEY&xwe?r~Vy(|M>LJoe%#-`-=&FD-csuW2U2H(l<0< zqcSvNVxeMWq-CXIrDbKMGB#w=GiIPSU^Ap+`yYzGb^Yu3_fE|JQYzFtk5^@`fp(w9Ugo7Fh%(73$h zjz@u~0+$$O^BSFP=lr68X6BZdl-l;U%a%*WI$EfUi#;gWH&A~?c&Uy;>t{uIq6y6` zUMqUhnA`@WVV;K@acVt$vWQPnpoGrBx@AeNI{*#8i#<`1O zE-rMEg3nF~aIOLop*^<4WKzbvyH zj%tL3DQLU99Ex8;NB+eW_fao_+LtpwHb*Kitt0Vi@$4a97tA!mJa~cydH+J{4wE&f ziw0ndrUh$-Q810;$rC;+ViTkTsEV5;ReitT9)UgNeQ1XxKe;;V$L;$bddUk@QNnH2 z0fAxLF`wFy{EF>w{JZG~SZ#RYz~CKH_3(D*2SAkq;yGmdxYv9qkZgffX7}kJrT$OL zS;S}D7TYlUFu0=YibXpTZ@AB65p1=ghOw}zhI8)|8oq`fJj=Eq0mBq*q3# zj1^V~;TN@P#zjwAsv_u9MR@B~ca9l>{MoiyR&4i*28&dXU0RwRMi_1&FtdUcunBSw z>p+Len-QweJ(m+ji0=4V0^J=*BlhIu7||-5{me&?{j$|U0-{enB0Ckl1_j9X7~s>t zFiFuI=9jN7)StTn7{CHz2LUDu;8E(isQsh5ApvQcZpdnAH-!7$9bm1C>LkJb5;LFMHMMqDAw4FjY%Xt` zkybPwsR#)qrw1T z8dvp$@{%Dw6UqVv2~$#JWe~2ppiL4pSpUkPm4@B;ZKzDX>Inx(b&R=G3I!Oe(Y!YP z4WCyo(TPprQkO{KJVbZyOZ?U8(zqWBYxchxP?$$g(pTipf3VI2<_zaO=%03S|KNsG zM%@=kmt}ixM&K!BDJ{%BH8@VN%YK8CwJF_Qx6KInbM7Qh95|^6lnzCRpFgD|e+{39 zXwM7_5huXs5Ze?h>_}8)%g7&pOIHE>dPTJnz1_0ss>|F|&0fv#UnI^66LmetXxzh^ zY+oc^{~K3q5Hc*QilA@r>@-l&h=QtkY9j$Rq<#UQ zL>snhcmAN7R0v0O8sz%o92TIKSq%55l>fCHfqkk6naL@=0#>@lAmTIBN# zJBZR&KL}YP#-fdBbL`KD!i%;yo-6LokJ#iZ)m;&)b*@x$%s{Ik0o(K^^$!o9qkiH9 z+I`*x?&*h|M;VnNp7DEt$s5u-?$LuB%DU(wJFxPotGu#@L0uNscbk zk%fzee)BE3`1yAt-;e#t!q^Y0p()&mx?@hwTN`V?Nq=3vl|eN7AP^PX{=!Ob1O<7B zps_cm#VOA|bW6K1SLXG{p!M)$bOIGOOqOQTyL$7yBNjBg&91kd%RMY0o?=V(fFFlP z#j5RlNcIyn`%+H|SkOO+kqAe}1lRevLmUrm3U0^vrR9uoLK;a!mXQ%-L%De|8#vp5 zIiA1=YRW#?*y3hve-*!^+uRVuvX9k(9dP{KnHNj9BwfJ)13N>=wZQSqQa>&4keSpA#_Q1eOX zjScDbq?-PS$W#4x8GOa2o)~rymOl|=jCakYaH3%r{iZErt_TsjDIq#`d29=v^~<@x zB?N)^o`R#`u#urRlPP;uGk=+yOwRklX-29jHaj)^3P)ch2P9bqYpi)rD*Vy>A(`dJ zyce*kMss7(y#M+*hw^!zRgQ)mY)8h+;5!h@(u}l}g+YxMe1RErwdC5Y2lUH|TXR6< zK`P%J!UV<}bsn&9!8`*%GEJ7oyHZ;wwyJ&rr|(#XtJD((rYxN_v#Qs!G$JhG55q&& zgEesdY<)=e_I0-};*qTY*mTV>j0Ir)^|nYV-9?J3`v?TL&PfC@p8UkN{bDU`knU*X zwh>%&2niqNC9#O6BH7xHWgH>3w7#$*DM&&`85fkvPgqBtGOdYhVM{?Vn#uHBl$1yX z5f{X_B9#YO2!bkd|97(x)7_Ex&;Kb`nWN{>+O($q9SWth5*%&%G-GtWcg@Zt7`hIwx4w zxN8K`faOB|5V-byf|WQ`vQY!FB73HHgtGSpe*{}batoy0RG!<{+tyc?O58+@rv>N6 zp7V88o5aYvOb2v^{>?o`7+lA4FkPwqg%X+z&%GJmgAtUl6h1ikX0d8C7ufW(l9!n! z)`RG`{Q5;@vx`bCmG=(yjoq)P9CVceZKz;e9(L#uPhcK<#14O768iJz5TpB*=X~|K z-yc6r6=1l$0PBl_vpWFX^MjqW0j$dMHqdxwlBOBU+l%s~N@nD4Bqy>9_+5+>5@%m{Xo9Hq|M0INn69W&HEBD5uR@Rd_qO}EL>Mh3i+PXNW zLopKLA14Zn%~9?(MM&k9r?!&K&dd@YmoPSr&ti`maW1Q&eS8}5TKnQG<`K!*E|@b3 z_$UPdTdB8_*ZQc|YbYYIHDp`D@SU|{I$F?{H$Tr{j9O`((H~6DneZA&Q<7iC zP*Sq|L0(Sd0^9OA-CbK;&z{1z;7&ra&lU^48;R*bu1qzh9b{JqD2Cn>2*+-jV^ErvTIDcSBWmQYjD9*-^k)6Q*RD_4m$QBJ>rEAfl z1GvmQ1KZ$MW*nU%O5CpABe9QMCa3GV|Gb*VS&uK`4S@1WDM+Dyp+$aldCc_xv3D0h zeQaB!;0f;T4#C|$xVyW%yK8U@?ry9as3xYnfAAp>U9c)d8M2mk9p zhIDkOTW?&f(0k^7>~f>*a)f!v3|R*|<`Ar+%<0BG!8vHt?B*Qf%L$~XieG3*v=I6b z<_m&iBE;LMmyhXD5UAA=K0K?_8eJ0PRHv&K^scju;9DSsGsmR6uduWnNE`7r<2i{S zaglP|dbzAyq6ac)1H$Z+R) zjZez zWYiW4GDSe?2zs}2p@6X_?Fpq}w&u(c2U0r&Hb;yF%iIB|rqj#PVLy%;7`%J2(-HMn zU!gkg{ZtBYvh8(ssH<34qP8}!vvJwBXdANhQW3`!hwq7vM5O|J_48+v#* z8aA+*bQjy*MkyYx4w8M^I;*D<32ulFEkAabh0fzT=nqx0pf$~2j3yYJjj}U!yG~Mn zeey`_$t>-s`(g|?6rvpq`?D%k0iK!F2jG|XK-bglv6p=Z{^JJv4ZJPvr%=`2EZRPV z$8tDOps<`LokhN_rl9u^YCqnURw4oFU-_gQHj2KeJ3)KJ9aNr8T~fCiK_`PI0efIk zD_=VLB;0!0BLp={i)fEAnpAhLE-!syO47l)pYNV7s<80L@M2rICDohKP;S92nz(Sz znESrVihy8bu-k$7h{F-JsT6I^M>EBE#j-CQERZYlIYrt5BFMRQ+E#aeQcD$KL#m>N zpIlop4yVmFOM``!vxiyM;)%kS`1Cd*JU|GI#aHuz>k9$Th6UQ;O4QI!q%GcJH9e;C zewx7(N0v?G73G+d=I0Lfxyrk{UDem0uYZyy=naHf^v0X4f@ers))`!VE??~o22>8P zd3dcm+O5|+Pu{&jWRQi$BxZC{H*7@TNGl_K0sL!XKmud%>uan+_1 zrUJJIr=ohZ9I=cSFcYls!~6L%18v&7U4_9&N-0zjz2_?C2r@Z-mN|}M+*J>_?gwAB zf1+{~UW91?1fVwZL1#AgCnL_75egWFrA`;NwL95!jRp$_B43Ov(cb(I0D} z)ua^FE_#UXU&avPxy{g0+Q?KU$&^xwd4Rlj=4xu1D=Ai&+^PZ*%u~bwm?{iq299D! zcGivE*wu5wlgv0P;oFgIi)+>GOSq0{EKg{NYA+NUH@Y%~Y!AVm<=-#L!+p(MN2>9A z0**t;d8u&rMqGiyn!E>RO_(m8H2Dzc%bPN~5YiMdCk}=(R;rjV%6h?rZZx?TFP^!T zHXpjAs2P%t2)28=d(8`oiYEvgTZ+ZmITKNq0;Z%;i0$Mbxn(sqqrALRo~cCU+k}yV zKl(ss_QGCB05|B9^Qo5BT4*!1-=x8&u%>#G<;VH8A*fgS+P$YAZBjd~_MrCuVzxfB zi%M7Y`}7F8H8UMRFRG+z^!@i%LJGMNuH;bcrroJ|0lnL%rn+N+h|WD57rwUpJg!@1)jkeo*$YiL?2u3mF*ybl1Q+|ZN_+mevh zx3Q*QrP-a*w~Uvc3pxn(#p8@-jJV}RKfJ;;gijxDU42$>LdeCf#!n!Iyf+#;JWl4G ztIC0^S8>^cqy6DXyJ5d9R$}jZ6ggmf`ooA5Tk^Poa7#+>#QS|w^SjR$5QJ*1vVvR2 zrS+d|)&>u7=rm88g^77&=LThRDsch{k@ojX$itQnrkumNU7AH3eRBpZBM_SndrDzVDL(~mn2IOpg*QuE-{rDg+ma-+p<;8U? zU|O!3Wf;O@f`UHa@yYw#ShAJmp=grKAo}DqONo2bbvNU-6cMcT6t3Z=1TBk*yZsLQ z+rd_?Hw5H(F?hDDR)?Sa&K2|ZgbEOQYHGozLWY`SP88sH&oeItPy-YT2l5SJ)&sBl z)NQn_6l=ogJ&=;qk!o1A6$PYt^KaV6Dl}CNg9fu*&q#JxBKYeiBZ?w*0W-i-fg8J! zYj@1KgehVZ1AGRkLZ3G!g0E$)aA}Q~`<&}Dt&&v8u0@$z>>D&a`e#bN0Zlr8+Q72R zM*RRSfDMz&)-3*Rzc#&z4;!}}E$zx3tGoefxT4;h3YT$F;UKGU|L78_9LZJ5_{GG% zUTq6hy>bo~_eHlcqb504BGAGzpnGq6(hdmSeW{RLk|5_2Fn35uacD|sZZD~^XQucR zn?QC%5h}9QV=kf0hd6cS;pyPUClp}GOKX1=kz@(?l+&I)+4u3L=v!fL=CctDV0dT6Bs$jnzJt>C9;9DuDSx4c4)gON^GgIV4}7hb5E0q09h9=FQX zy94+c?fEF_k4)|D7TI{KbNwMA&5ilhg=4Xdiyh`|o*_xk8T|_89bLDrK$xr}W6qp9 zRA2B%uV3rbVt}acI05IsUggOP|X?gOd`YWO**=7_9lH?xRze>gA0metJq4N_8kq54IQ_+JvHv9CM{XqEE60 zX!h3U4SSfB;k?#|#JjY10)Bm^dib^?Bi2 zJ`WtkVV`He@8^?k)8SZm-?S{H1Mhl?5oSHV*Wy0OXv`uzdHko^gny@f(Em}B@b9$` zdfLBgA4jj+N89|7<*(YuZ*f0&5&mb|hyMSv_OWBVals~yN(Xo6(V#|S^Wi;CH?EB4 z9%jiIT0XIV`lqj2h9Q>Qi_2H@I;CkghnLK00d2w`i>8_LyKJ{>p`(_GW=i0V2+3*N z!d#M|l={796+p9ScqRZ@^2?^dvut!{Vlge1=SM3YO|S_mgjKqr&_31gvk7LM2Lp}2 zpi?UP+diLovWDH5=XS2*kc4={u{UVUDSk$$qw}ric)!<~Pn_wJ9cLTSLKoZk-Vnto zm@TrJBD)Bz>4;|XJ8H#r`KTSdd^e>&GDU!s6!tAtt6~r2lmwhQ3)05{vUYk1wE`0*lR@2&rG~h6TED_8rSyJ`U1o9Y4AjLEWC9h zBhZZ0!Nqjepa+5QZZ7oZ&WaN&gM3ecDnOlYHGy)LK`%;cG5$mCgP!(3);|6S{SUQ| zzsA35AO8)t4?bcpmS44xzaG~Ale7=hH|^v9742i;sT%U~7wzMhQT4C1kLKz$;5zhI z?E`SGI1r(}odpLU0Wj4t7J$1S!%?y7++1#nf}4Y)ir02Sb$cgkNUA%mV-kw5xSV5X zb1mIw{DzHmH9Hlc1p$5;IZTe;jTFbajo?g}7`ieX_?SpF;IpW6p6MUv0p9aIjslD{~B;k9CTT~9*@*ZL)9!T|IGat2116 z?#Wh>-AYu!KH;jCm-G-|MuzWpFQ9kVX8JY#&g!-J=Ac-MoOlkW@L$Q6$S zp%DwdSRNu^AkIF22~mD+gs{;KIkm)nYAYtRgS-3K^IfQS+{k5@S)FSeX8Dl$!EEHx zy|5}{Tg?v_nzPb5`pP-oLeSl@y!IyY@-#e*w9853Oy*{)uM3p?+oSBrfI6d)qrO0%1mcM=1Syd+K)$FA$4ShfXA4Oq|{A%@?^gS#GINt;6P| zpwl7>%noD09LC*@*Q78%Y2%H(L{FofXyjK*Gs-B>6=T z>+n5Swd42#CS-i~>njM-72_E8KkzNl_+-2zs#&xq@t{`h2@UzmeE=3&{p3E*Ubzq2 zpWMfXpWFx3EB9dy@VVxd`%nceB!*v+t4EuYmS+=VM_3v;L;&`Rr^VF_kKrOCPK`a5 z>D?!8yrBeAEKiq*y*qxa;B{+9#?i5_GAhbo}iKfDXw zELdOuNb~&N`}qA)l)s)WR(5~P?!W<*e8A*tRPu7$UkmoVUQY)TJ%plFNju2ZW zN$T@KMqKC}OxBBax)?ucku6{5^BU9KtX=SoJgqvEH?-s;vwRJ_IXGZi8pTyPw1&M4cvLUD4I%DPLV()Eec zk#lctr-Rz9BEeHic%)h4klp2(4>O8S0b-niAAq&D3m>#ATNaC`=@Y#QJ zL&|_kB8JK8iymwk5e8;r8B1&pJypaa#PDvg+9zMqzKirB$Hc_gp|2&&WSr#{*8ZGE z7o!K+@nn?~(r|}Jf1UeO$W@(Ds;b_zO7wBs6 zadza1)iF40s(tJrl4he{@!beW*>{LxNt)H`vKl{Z1R8JxBdaG(V$qvSW;}d$WH)Lj zOM-+&Pf2DIk-!cY1KggMLbjiWDnTxo8XzW)ONctJHxvi@oLWMgP_Iuv6*4H)8vtI2 zi-(F18{U`K131#RD#>ICQG@y-8xKMMPCZ0*oF!OC zMsqi$SOyCeHHQtCJ2%}6ln`{4#ogX62jOPxb$KytXmMM zr+clc&Z}j>@4U)KWAa#6G9$9KO|2@+w7GdV)QHsqJiQV6^y-j}diZP5HRyw87g|J9 zu8>QUcEsi5#~)|$(fA%l$m%P{y;WX!|4A>UbKVoF?8;HVxU6}Or>N;1!bhbcz6bh& z)uM|B&h10SbrC)Bz8wvsbl{BWG5PvlKSmahRF#~nefyfsZagCo6Qc!3}D%u$eR;RnMu31|!1y3vAV5)K3xzmcZ2r>fL zNJW-7v_`0o*;n48{(#lUxTzE=#sJZ7d~8O5uuwq=Jtu6kI5%IRBxFQhs7_u;LP`-q zMI2>H90kYIF}z0HJ`(@x@YP7DbuzrWSMlnW8kr}g%#h&@r+|umFWM5@` zW!&IO^GRmli6G=+AoA9p|@W1cfUl*?sZtYk@P)xSP)Tw~gSF57~H^A5NfLJ!Nh?)1q^<_ z=n~lu{$rP6Ug>vm9efI4g$<|Xm53N;i@5EVouxblCi{4s;js45-!fxEaXhCw_zi&Z zsC|@4lu_8{FQmIp&|)m~9RTjBzuG*SA&L9^xRVAayyv-ZWCVWV8kDv0)#`Bv!X#n; zKJB}^2#L}A1nlgg%}_+Hh12v!0~y+J#K7LTXFEPrcji0-G<9?^RA|Kkef5qOd**UP70AhIexdD8Ih;Y~^l^xRUUEimOe* zyh!HdtUr-;+`gV$3Rnb@rj&ong9>*t^}**Z%jb`T$#1tyKPhEYy4U$=*6WbL{dYsg z%I+VclbMNEtd`7@Ec(F79WseFoEg*(@pq8(T!Z)ifDT&~HV9<0+0>N;#l{l0aZRDX z!joe{d$CO$v3*6lw+!it+Gi-r1KQ!SadGLSdv4wnFOONZjq7lc!C>d529qCv9;OYe zs5|gJc8wcOzMc=mU%1&^)tsoPm5#wmBE{Dca4Rrx5ozhj4Z?I-jfI2_KXJv4FtSsL zL>p>O#u6-;GnqlztIASZ(oxZTJOo`RiR$o)Zz;?xec!YSz-gmsCScV8R6n<$Tj^~o z16n^Ngwwi~Sx~HHhWXsSnrcO5o5E&<9F{EGdUH{Bl7ES%+d-T)za4?wxTMhj<7xSf z20x{DaQle&?np)HK+@NNfR(=BsHwRbk&Pqeo`7}2STq$rQosJ@?2N&mk0j0vSHwD_ zbS!6=u!vP;-pR_({-oBv=3$^o%|485S~8YQ@09w-S-6zZg?!?@PySOXlZ|9J`Dhs} zK%2zamRZWQ8)#-$Ny|Xq#zw?V{oL*8%nS07zoB5PkvMu>0nj55uu)SiD_E0WEwSJN z5(^P+c8Y1hHO&JXUi&BHjK@^Gd`S9qNhA)`&n9Oy;Zd-SEAPHsh<%1f;Rn~b{9Kcn zoMfK@MW4I9-LsKQ9mb%kIN*0x7GMI>z*qXLL=t45W6Ayy4YN#rY`e50Vi(2fM{0^# zAX^&pzA-XzU(GDj*EEbd{B?H#c01$QwfhR4Z?aI-Ekc`gtZ6-rdT;{*igG>ZkIU2F zXLG+ly!*%0>|f|@l#Nawf7RRm8vhr)P0B@Go<+!1NZdqF+R2HYSx8QZfmu|KQP^I} z#?95m#LCfKPQr;D2uzz3uOP{Xf;)l3(>UQx`4xFTdz*fAsKwt+xSFz3Of24XN^?jlED(c&N@mBZ(3& zXo{K9;C_&Gvw+p6<`hXRE0&moyibgrwIfJAIZ(gwZED;1S$r3tIG6Wc6m^SfBwNXj zK#Bo<)UaUMtDny4lmVKsqQzk+qyq=L!{OL=yU2^f!F2^Y1uQ(pmzr{v@IxG~@}`<9 zNE4kXy?MH1)eAKcoECma@MSHD2SCI*B-zupqDKi;XeSL4TiztO9=8+{yV2UhB4Hd? zuoI|z27qNTXx4v#kiPpYGeQNE?X9`hv!WdjF4`wg+W8XM$ocL&w5aAqIniL>qBZKS z%>W?WmgXaB{L(rX$&@~N;LIf#K0&-bI5`E$bZeh2RIP<=zu0``1eUbynAo-cgrc8= zs+nBW{Ua!Cz1|rp5x^qisCESOB^pBFBYers!;jF+KaYmIXqaiRyu|$G3Y}=HTEV0q z0l99emveVFd-jY^-wb%IaAPXYUsM z&~;te&uvfLu)O7cjk_8mp}G$a+Z)XMy$hAC>w__;oO!`t5Qwl}=(4R8BD z6mO%a{r$nIKbMpL4sZK2_6=`)!`t5Qwl}=(4R3qH+urcDH@xi)Z+pYr-te|JyzLEd zd&Aq_@U}O+?G0~x!`t5Qwl}=(4R3qH+urcD|DAZ-pE>*g2ycTt0X_N^Z~L=}-|;qP z21Y$rdL}k%7Djq{Y9>Z@R%$(VqgTs~oza+{-hfe$fywwkz}x<8`0scdzp=Zx=`VQO zKgYk~ZE6n7Eby;*8whSjq`}4XoiV3WT5*PRnW4KO19n&d(bv}Do`(Cw7N}@Ka!7nb zR=AAnf_gIeM^w!pF!+gj7-$7{dTKTi-&gE*`?o#y$NC!tNGM5@)t?8`(LtLe9v>c8 zQQWfC#$#Qj`}eNzZUwJz9ap>_MU36{PJ6<)X*C(lp7~GlDbDf(TqAF=2Lwp zI)xPXK7-O{?`0TG!Pfl>@{v{5v%RgY^+Dizuy-l1N!62(5IvNp&B|$0y7ZiBm?DX}t8^VtUdBa|$^onX9tol6eIqRWoZ{Kadt+yB7eh zek|19?Sp{Q^bzoVPhq+|`BQGY{VBIiJapNMi6DK8o~e$^Rfy9)qp<5S)S7}I@#S$! zul9>9&z@NqUW*Gy+#w;4@O`kZ_Jx3mRx)63aOmay8h_qR$?KR5w@^qA^=GbX_Ca>#@mUOk_nZ2wU{f zsQh?B(}?wo(7EABL~M66r|a~jG==cP`OoBz@P2m*fTzxGpXMG1$frgnkC!ZxSNoA+9&a14K zVxvh58V-HDd{Lbht2@6wvLbTC3BXL1hi=TOPoPk1##AH$wnnC!tGOvb?s+IGk#N5r zr9Bwqq}h_*m~V6gWdl*NbN+#*ikHyWFjT=3QOdok=HA=46|Rlrxe4Mq*P>-Z=Ev6=MNt1|;h zjPaFjYj~yG=uIN61{WFT;HR}tV_GOD7wF!3fdXEJ-j@tv!(l#TXhGqk>l*9A$AV|O zA?F6*qIL-@1(pXq>euHkX20OVVLf0r`$N^Pmz&MOj(!Tw&z?cK;+FBu+HGplv-8C^ zLmmtSr=W~q`L6y9%2*IZGw*7>X%^Jb7%{q8rwDds5L(2_R3cUu1n0hF)M~~yGi69c znvJyd1t%QM_4M$arH|Sq>FfgOePX*?O75%P1`}8s*DlHOhu)@PyI~#Hn$vE--NVqd z$HP#i9-U;fN(S-qFcDjQesdZfq;ME&g%D^9DpQ&L;LL84T*7Qn9}_%KiEjd1YM()x z0p5{%^w^fT3ylq^W0c_)Z<`AFFTAbY_9RDAz?mA|P#o-m~1mu-0y zOvpsX1Uq<6Fj%KfD~!vplc$_umk6hBRScwfamm9|U|JJ;ZG6NaN_hNVg58ck@V4cI zIruXGDJ^~{0?)x-n)qx(C&0fQJp;W>olT&$hq68!bQU)78F9Zu zLLGM9Oq4Ep4AB_OShPwpv=D*8O$d?R2YrNzsp?iT$xd==%T3oINv}KNy&`W%fLP6TbY30-C&7zoWEnvwSUmZ+c%f*nesS(g z;(D95Mu@%fyM0|FbMp{P)A%ptPf_9CS-Nso3@NGw*o)KfT-eqz$o#YijmnXxuVg<()DG*jss!$!}se$zapgY|Uo7+tK4mQB0 zW5{P*O@n+_>20g^iENrhWg8gC4`da72(+4{*#~K4q;4ttPeGs#?9-W4J}xhNn=>s@ zVrQ9s+6(8&j5_ESoP|5=@s}Bc{7Tyy>q!+-RYy1@V@N5=U zedGYfjVe~n&KX^tSF+HL{O{MM*-wDqJZNbLuU6}=>a)Pa%)Jpiy;H@1#9E<06l{f$ zemg2>UCJJuH2Ru8~|?` zjxFjEN-I=?YHMtJyxn+$3zxBhc}%X^P<O@cWwQ_ItXiRXNU}+8*(0SParmzK^iM`?w)Q?2F(#)sR z+5aHi!2gSI``YIP3-way(i!`0@EE1r5`eC9MmX)Miyn{C)1Kvyc9MT}EB}pZeVVoMA0^PkqN1^191Q zKj;lkDnrm!du4y>tz<*!JaRc;Vfuup=Hars5S~3Uwz_BW*yK{1>1x_v3@`?ODzF9s z>&?MZ(29|0q1mpzwdn`5;^^vtf;S5ndO*Cwe5)l32CY(R1W0GZ-*(&P&dt{zi|PVx z;Ir?&Q)((ZE4bl}Vl1E~=~#07Rc5Xfq2F@Fy4$Y@2uM7=2L^|CfRJ?*M6}xPPr0Z8;}S67fpCs}&!IHT0_Gg7r*3GwWf%7me09bu zu+{@`b>3W~wh#eJZaRFL#I%om>+4j8p!SBcM^Qku3JTwIgBR18vG(9x^5byCsG^w- zTv@290eF+}xG7cN3KT*Mgg6*NfI}!pStkQnq9Z3QjUN&D!usY5&7v42*^{7{wj;(v zl0J$Nwq38b5m|ZeYWaEcCK8he=#Bn`lTMB zh9c)BS{{T7z-;N+zbGH}<92>(-tr;rR$W zJt;v=kYTkcG1E8%JFwFRJUKTZ4IO}*MAFInyp~XIjwKuVXczi%D?XoL^_Vjq9u)tM zxIrRnnav9Zq1aR!|Mno7V}z5b#sph5f5`Rn90RJMq@=^n_W z0rO=$%E?LKXP=~3#7*-z#LdQr=%EVMVj_4ymsO~sin&y4z5Bpn?#G!1`$aXV zqhpPkx@rfsUQpUv)z}EQ8XjcZTI~_)t1$oIv)=Tvtkm|`>(5`{)aPe)zkv3Hg`;KW zt<2TaD8PR30AJp0JZ=dcF7Te6;4meCEvpq=aw9Ah zreAg7-0TxY9=!30H)xkK{1^o5=e1OO@x1?1+yoetY`l%(EDr2z4E~|Gu`#B7AZ^;s z!6W)jaXU<}1o*L$@N^t2lDa`C|4xL`H8fqH84@}Q+2>YK%b}AR<^vwZrP)X2I`tOC zh*g#=mfsw=N|z!)@l#_C3_Gp*(lEz;l(-NX>z3FGl5K3EeO(N7Uv5p?m3RyE4lp0v z6$-kiJU3>9E;vmma6Kjdz;Zj5N{_~X6DF`g)P6b@eVKYq$^$uIsv;Qm&UQ8=U?KgmtEKk^4ZbGkGSC3rK$HN&Om~X>2jgb1pBaNJtIA*lPrQ z(mbv2`?0u<2~A5(BVbJCILop~Jcla1#*LX12^R=)PDOmtK0TzV+xAx7S$rgm&Z`ks2Fn>^NN zPjWTU5IkI-q5~{uT$fdhuR~Jtynu!dplGi+rRn#$twPKja=J`}EHKe{BHW!FNNTQV zYi(PZ+wzx7Kz><(*k@s};ogGLwF~0yvOSF(=*NEEYL9+o>d2t&4f)Bq30$3j+l{bU z_FtZIpL+PoxM}>#xEcHxoma>eKhD%B-|R1s5K-HV>Pv&}xxZD+G_O9A5awErKC=4hWM2%*Jbeak5}c^}!s{|;gV-XNy1}GH zSGScA1SA!l*&PP{ZToe0u3wn?Lw7*SdU53k(>!naT`6#4=b;J>B8k=lu>IEAO3-7# z0L@$qT1+YR26v5t0aMvzslgLVmde0=L_Y`BbY@|(1gL7&ya&e0I`JzLw=9k33K*N4{Vw#iz)KJ()WKLQapti0BlcHnze zntb|6k)^N#L98=*RQt&QW)X8{JJ!4I9)jJ)tJxd|n=@yHt*mx`K?yt+le;gq|ZvXKXp(tFod^qaLt^ZFLZ^2=am zk$`xd@l7z#eCg&6nFsONGTd68>h0ePe$eNmkU+j1iJf1i_bSs9z%#Er9>>#O_^#c( z(-5b)bGn2RwXwc9;>?!3A|=$H*1h&5Eb9a$I*%w=W1St+Kc;Mf@vSvxmQ%NJ-6=xc zJG(3;8n!8gVD*D1q&^$Iz=YqRmVm7{0Ix6?cIDiRX{{P{^N8}JDIJ|C z?>KVYXT5fB%(K@ zSyTmbVH^7z9T_&g4krnWLlSF|+*qYoygz+60?pYc%Dgl58GI-nYWJ%<&n8W(-P)W! zfr`^7VeI8iP===XSwzz~W^jRlnGVAom3KG!u?z#@uJ^8|!ISDu+(YnI;}mp}H|S5> zW67>k*K#>>{rKNDcuGB!&qg`VRES|+H}EW9lxVrdJUuTE+Ght$o)D4K6%crHn!Q1; zh7T=%WL1LNGcil`6>`+>^W%Y}2S52Lf?A?kI+7g=ao_mX<(?62q*_ymO+YDbBpKk; zG!~MO&9=9#RBGz(MGuttKap-8MDcbiB^5`SLKH%zLiUWIv|d;U3NgfGM)~(sg^Bhh z!5(ETacPe>gddrISf;6z{Sjm8ykpsP{i#r;11$Rpy@z#ym)adYhz(QF&M^US(uCc? zpN&hMmgVq@McoQ5Ud!T1mCLxM@GFnZ)be*yb<)$4(dUUv^V)uxI_+{CuIDaWP9u8a zB*^&%@W6@uoGnng%Q_i{WPbkdAcAq>MX`AuWfQb^!G#JN?EKk@#i-aOH)WJ+FXBvv zHxIKXFNp8z9-1dph$ZVjth;R6(*fNb0kO^)^nJ(K0DeyIimRbS8x9_H!vpa#Iz&lc2#S>H~7FM`WGv7uG$MW$BA8@k>vRwaIy}yW>JxX%Aj+M&edoQSY7*u1+b*>g!Xq9TA z6h@JtsQCJRAIgA|*qj4$f4C&PJ%ThjZ41E{8{OJ4bxUQ|(Mn4bY<&t+l?_HT>H1qX z;jHrkpm84zO2u%~=RHr>P&o74-lq?My*vZh%hVPY^3dohld2hU4(rp1wC-bobBj&;w7N zP};tMoco$`^a#y1aGK+EMk(1i^MhmZ0Eku1>U7jCtQvnU?g2@;$h;YKi(Lv5fyzva zy%>yO%fV&|?E<$(vBF9a21XuzyNenbu4xpUHV*KbJn#i5*e+q5eoM;b4@J zsy<1jp^CA`Ub61>td=osgq^$R8b7kx0G}t>}l@G)W zl=jE{j_;d5909H(s5&zqTJUsF8w8Y6ap;}2=~g*|#eOu|P|TB7yh<@d$|ufmf|$!n zeIQKenIc;0l(GNE%?BL7_jLgb?Y^1$tKs&`sQSZL@ludbSdv9vyP)LwhvA0#YPj8E zmqPn(8q7e5^}<8Z10&;@Ov-f)mkODc-tHH50(J!cEUutHEi^V0(7HyjX!dpNYS zf7?p0^S6io5y)#OF&Nx;+vZsJ6iH%{kKalo_c>w{eu?iwe}xwLB;b*G!?BUOnr&bP zTnf@H>qkf6y7^gfdK+?>9y8e;uenbBHqj~ZHbUW{VzD+pAZN!o|2n>ikZ+Cm^H9Cpp{veNtwl%Z7*;NBu8cFGOgGMuR&&hzOPe`c!)>s}fg5p;E zvFhXi_AQfvieVCaY4y`<4(gHDU)`9|jcZ%Lf=+$!j6NM{(|eSoo9S{&Jv~_fUrddt z&AD4*3xArRHBSlL!EG`2(`JcCKDw8K<6%B9f?k`#sNI4gus;BV^rhu;`FM%}4JAku zut_Y~$<{YY3lH_t6F#V9Kuht9G8e6{{Pu@d|LsKARQPiOc8!?f#rvdA8Tc_@TC=+@ z+Weah*Aj@V^pyaA_#SfjMxYq1CPX04mw;COgE|3c<2n6I^$hd|I9F)U`FI73X&Go( zH4+}iWUI)OJ6&9KM-R~?Z{VdEn>c+!AV5;5wl20o&^}&d!Jg?Z3@+XyD@c9{Z;-{j z18HzbMdtz-)I-cikne0z!@(ua^y!HLaygIqIG^)3AqH1RIvlcVU|_@s?g~p~Tjlu` z?;Xit*Pp37zlVBoM~hP81bV(pNBQWZGOMXdd4z8k3}e^qhU1qlbKcj3MIR3msH!#H zQ;gmYIR7nQQhs)v9im@IJqtiwDYRXg!#wdp)-j{+t}o*LL~b$o!c|$ISZQwP%#>%O zq@GMd4tt4=@H2{4?$=Z3MY34uw*FbNxfE09Je!Q2^heNUX?2aChFiu@!>ypQ5b989 zw#2#rqB-3CsJx$Aw3N1>zm80%>DKtC;kIkK`ciR3R2?z{tLX1Ya1it`E;4Te&Aa%d ze9>?H+v^08|8b`_lUM%BUHf;#?XPFce`L7*?IZtm<$g2V-VC=l!|ly*`~R!q_V;Vw zAM=Yh!|ly*`&WkB--gVa;r3>@{eNV*{V~>lG2DKxl_vEF1b#K#{u=*JhMT#QfRLVm zx*e-6vx|b8KC7X$ErUECpSyvDgqyvxJBx`utAV7tDVv3{9g8`=l$o8ph=Mu4sI#M@ zsGEhfo2ZI{^`BdnKYNE8A{G7BaQl1j|7^G!$nq=6si{k=8pw+av&*u(Daq&Yuh3d-w=NE#?xn45_IwXc6=xc$Aa{}sbcL{WI9@fXAGj~@PKhMUwQ91%Af zVL5NwDZn7HoEV5-^H0MKszt-PB^**l6qpZW$=p|!i8(<6%bEp-F#9R2?~#}0apAhAF?-43mZ|2FxaI4m zfkRP<1UdKif((2sm5IXekOa^W^pn= zok=(MI$?AkolY8?u^wz-^{uuu05=b{2Dim%-tkVQg<$W_(2KsoxiZg*!Dv&x)#fEy*!vrk0N|FCf2f z2Y~-0>zjL=#JuA#QJo+y9}68^iw% z#O=@6H^l7?aeG7D-VnDp#O)1ndqdpb5VtqP?G15zL)_jFw>QM?4RL!z+};qkH^l7? zaeG7D-VnDp#O)1ndqdp*cOq_o=Is9?#O?BimhxA`?awBDN8IS>jrEP$nT)CR>GYYX znV5}@so9y>e{yat`pj%>Y>e#ejQ;`R_GiO?N8D_y)C+z^-2OTK6>-yWNE1eWMcn$h zNpJ>sNun?ROL6;Q=sskK4oqogVGUxa@pSdU1PvJcsHP?@cwBHs-PHGl%4EU|t{QrT zjWxny#N9x0FOre9m2o09Wd`Cbk;y(I6vr)y&${-s-P1!1*Ho^CDg)_el-ogu=!-_= zRL7Hua*RDrG@xE?V8=og+qp{Fxt=3UaP>W%idt-Kax7U9*&T6N$PWAnor(QNGdzII z_2ZebvA!d`X^mF3m&BHqC_@NAiEep&>8$gzqVbooZl}&^2c$;b^u2i0IkzWEBK$qVhI%o{o$Xo8?`GgGGA)-Aly7MTW&z+cRoBr z>3aZZH@^n~juc5!ZL+=bQMNE?0l)l_(WqJ_DbiHI4MS88NDeQjm<^JyxG2OO6Lu-z z2k-hIkkDwQ3hZU>O$mae|+55O=l9 z53Pu7y)tfVMC4`G-TExu1#(&(r-D;}JBWDV#~M}+nhS7#JfQRpy2>~&rNXPUqpPbGsaW<=W%^NTS8heJOi zyKT_sy_NdB#d@feO8vio=XLqmlz(H8$Xn&%P#l0Ox(9E&CwT4$=@7J4&uTZCVt@tN zV7l<2bt-o+F}c77^bf`@sHCq>c*nYXWWAXyGyGxL*Z9ee}gdH7B& zYQ#&T!1B`M2Tj#LU7>JqT~xCDOX`VDWZ2@2Rabg!D^{>-KXw{kA`({~Z1slA&KZYUplea1iAaVh`KYTp;rg$mF z?gAr+$0ezBNKP%>t+b4r+Q+hAF3>704t%N)MYA#y!^)&Mwt|~9^x;MB)dz&WeCj z413jSMSf}cl^y%5NatSHaJuK9JcS6}4IJ1NXacgL7xXITp4%zFRS?H`1Pe&fur#R> zKbsv>b`w4z;r$~B2pj@A0qIsWJbQc{fLpk!q{w+#C}*U~2jhC)m$sZV1TGtFxY1ou zWiaJ`AZ|GbdnyRLsiK;-eey zmDebJkIvLiKqh^R!sT~JxL^pD{C@;KBC*#5vEj%T*tZqu{*X&yb{81JRHwD>ZgSw} zRy|`9gkhpsV0j)8>5!=^+I7n`8jdN%^b4LTuMywxGu7l|9zY1Ze!z#qz_zUj0SGjT zfrC^qtcs_z67WRs6;|lAX0Hkmk~$B1McgVNSEgpL-f3Axao=yFUo!KW^j))6PnCZ9 zAMKq5R8()*_J>C4Mj9jqq(M-S4hbcsyFsM8L>f^*2^HxUkdj7HQj{(UX;6@sP>F9C z9D!lb|MNc2v%d9yAGmM}Yuwk__r1@3uDNHVUr=2~RA zU6)k&84lR!?uis$ta#lnAicVEO?}zQ@_t*LHTu;uon}0I&04JRv>&1}QZ%@8fAl?JITq_6QMX^t7mWFlf(9^h2UC_ez<{_F5cy99S%ge@eA9(GFKg9?NxINPbsl> z+GMImEZLDmZGODaIpu`$9eZ-OH6f)_;u@ux;}oSh#$!Y8HWInzF%?66&$tGr;bQyI zab6*JH>8|-70+EQwAD;zNyG@E+?&W)>z>WTUvMnzdPoR96KX%ynz-)m<1=7VwbQPr8kolF(KNm3z{1>b zI;YSn*io%V3P3{e;lT@L!=F7j_?(Ar431zDkIPbVA;`eL_=Z=|#(h~T&(!CDMFGWo z2}avpgIDL%gJl}z;%Ql6k7+9-m@Gd~FqH*>omw@F!jZ^?e z|BAd_c!qYJnom4}LydLOCt83aZBKCgoj#l?#(C=|){S(szN!b}4zjpy{d(Lz4fSH{ z&u7|oYDa+~>z?jpe)L69&9V=$F2qbQsgI7x^c$*&W~^3`XNodJI zMq*2wm;EDC6PxrIdZscP`@Hq}eIAIXVn^M0rrUvZGb9B+-8VG=9*}jL&_2OlDzQ>1 zf$fqi2@fEyX73Ws@~}L)rz%wktj`m#^WFrQ#1mFB5^}Rf`wTZRAj56CEH0)?iSCKg zV+KYh;aA0%sumiuB*rJ7h*jWfNj10w8E)@xd^M`+&QtPhOfimiHE zMi0MEPg;3}d3$*7%dCq!?KqiLPagV^QA%}RObo?~@BE=8@}-QOBSWDmVg22O3hvUy zD1K2l#!rqEPoMcPCx|##xdqUymzQMw72~>JC`aHGfd2_+p+QFZaPwZV3t{OqIh@pL zFKSe8jCM{6ZDnh5gC#w$Si#Kp)o0^hfe5$s-g^jC^uD1b`qtv(Q$lgKjW|$^LOw0M{h{xJtm_ZD9=cPyUr*-BYI){Bi2(bD40hC1$!T`8tX zA8YQ4W7n9V)p~GlOLrsb0Svy!oY3`)^(@7VbmQ+Nb@L4)e5ZL8+h1H*d~Y+w`NNxK zfoSdfB9PyP4dl0JalCw~?)NfkK2Y@C9MX$R^Vc4A?G84|qYU5S8`i(DyKBy4A8@fh zgT~f&lAZBlpdUSj)7vhc1a54R4UAyNU~{VUV!BF^j_JP}mQDeqSZbJ5OE|Ae`HcM` z1|Bw#2V$)pkl$94@`=9BbOROm!Qx~@-jAWgE=lC8N;q=wec)M$u)H0jE-3>^-k9% z1zsnWT)pG~rqvANYLN@K_~PWmhm{uQs7PJKK6@{!KkaPp?Yy+`6`7Ptq_vZ75}|3` z7-0=#45Lj0A#GIWBPP~%jaC4mBTdA>q+Ifz?YRShTb`2ska`Z@Iz0@2PYfQ+c=T;^ z3kj487lO#FYG=yZX!GW^b*Lg$W}KN#}o{d^MSp4K%+Mn{98ZoT++_ zkGO?c%!vTy>W1aF52VC|thc#o2P}u42_}npoNG6grqI+k6HV^^G#~F^_9E}evvlgz zn?8C!BkDV*ZdAh##(CWK`qsm(|dXZ(+$2PvfEw*KZ+(ly# z)B7y?7ll4~mVaO02_SD7X$lT?`jCM;9Q(+7sDsIFb!T%s{(|5L<>04lN4-_jgD&6A$lukB?cA5!-Er{uEyA-Zu86 zGuA|Mrdyg1*yFqD-W@v}(ia@J$(l0LoupYPlpYqUtaeMm*;8z64c$jDv3;ANYH7Lq?YzmO{s(XZ7jUI* z?0t5>&Gp}0XwG7Nr5zp|5J?CmxQ(W7Z8Gl@+;)KkH`@{^D{&IuQFO=>*3Qe;bXEvzeETta)Xqumt zUXfM56dp`p=fJmW9>*JuusGURnOeC$*inriCGMcrKmWC^w(t76;7|F3>w!^8Ttk8qn1 zL;Ki?;#6kjR=_h1aOlg$SA`ALsgP8)*TZRcsIenn#i?@4zvS(!X?bq*F+Xv#o?jJj zU+&uX8P)2p{lkA@uoE3`6|xSDGpZPP!f7EUU-x{N-U=fZuDMe>Rg^fs1)Nu9t6G4Q z79Q;PZ!u6FZf!1&;u1HWaA>w>tK(6-xeIz%^}(mxp9XO|d|Lj`A#SU0(VZb7Ziji! zAZ}+6xBop5H?=c}+Zn{|4B~bMaXW*!{aX;XTD9ZU55!-rE{@+Eozo$191swQnB*g9DaZn&`-3Jgixh_+_CA$WjEpIOLH_lK^jfPUsLXEt&P~Q z!wx}#xV8S6XAjn%J_&K-f`GXB9f!DeZ}u+CI&8%J1Blz^Q}R##7UC|iuy!i$f4F3x zuQtM3T@Y&%_%cS*Z?DWcv!RLYN5pk9>)meEM0L@%(b)D{#oU1t}`PM5JP* zTz9!0qO$N#iBEYl3vsRlkQw!KJerh*T7k#)?n+dv825XptKY8#IZA}083)aB^t(@C zd7>dY-U$dTX4;5-yC$3ggt*nW_>*Uu=oks}by$2!_%Y_7Yi@~=QLv+<|$O;6+P4pb!TVeOYWzZ~*Oob6NyO>&n$>>v=S>o*P2WS`_C9S}x*>1lbO=;8~?^M}`~u-Xv)Y#udg= z@YGr#6|^qe68WGl*^oyT(I;env!sS^1z`%tgl*2g9rONz%=*wY4%4hn$COF1yl!KV z(Zjk%69M~bCn7rdsKsKm$;Qx&a2FNRJS9{JEMLUpJsn21iq~}`p{+>n-ed}TGU!EJ zqyU(5Oh8G+RcYfkPaR1wyf*!6jfnOA^u{AQJLO5_$}4a5i(WlTdukfqIy;}tz%ol^ zQsLf;DmSrMYiVrZX(?8ZCfCV9_2koYqoL1)Mm&nKBDu)&inF(n-N(z|d~fQhQrKR` zW1)a;WH9l(uL_v*VO3(h95!P7JsbYU}9Scoy0%hSASq6{A;1VVy3S5Xx2gwWM#pF6w<5VxHm zk}7zW(MTDE{J%iQfLEDfI*g>;t(ElU@5U##)ps^CzSg>2dzqLLYT$ykFnOi& z!nEWQ31Tr{>jc>dvN%d&sati@nve8jgC_c1Re0m@`a5pdbxv%f7<{hBt4=?uN_%BVUHIfmdD%5mD+VixDLna z-Z%L#?g7B}-~#}sG2D)yEdPk%cH+MrS-8&_Zf6X)Gltt4!|nf7hTF-t@3*};W4N6$ z+@LYsPPEJ!!|jaW_FrPS{q8jghTHyArRaWXEl7sj@y8!!xH;dlHWucPzH|46tFXK) zw~EajZWCu`p*v=d)+R=3oX$>`dPaKYVqBJTrpB)JdTa*v4$}5Uj;c=bvT|n1SIpSO z54mdl>L!n#>OeBwPO2YexT(r;Ng8k{*je4-lE0zHp)M^dt;8*&&&jDLucl^iV{U1y ztYF2f&#kP_?tEMe8pG|R*1yAWqcp5Rhh(_@R)ETIlf6kKMCi4&&v1)Fkry?^K~G3v z@C&5BB#Y6yc)mhIU|37}-MJu&3_%TM%;2=N)lPqw7l>H5_TING+D@?8>jlZW*6Gf$ zugR^ptqE`}$*je;Cpb37FQD9epjkyFH;g%rxRPzHw&+xyA1llfgYbnIGjzR8uA#Xn z7=wO7SgH(Xjp|vyn3)aU^gzc4qDLXAq&{z}ygtn9xLhNYog-PcFumKG_HCSq5x+(z zB$WEvx;|^#_Zwo|7Y#J3Q|w0hnOoc*zb24fbtYL_BR%VSx_j!zWdy4U z>Lm9^FlQpAQ_=75k=i)HVoPCG;?J>#h2*`$eOg9}eC^@#V~sE?ueCgx01E0G^5I@v zlusI`!XhMp;tMg;zkUPD(KhXX=-5d+kk)wjMHXWSMT;rseEG)|SJ;`3@b?oq-476M zw(<6)ksx`oEUZ`ImcEA>GJ7DsF;1dnS0_t4ymT{UoKE`(<=`t)a`^hB1jnJJ#&t}; zOip*6xA#Wpop7j7=*3HeuQ|C>I3f>)tXDKy@E|3vXkV#~k1CtPE3F!J4e>7IFfT&E$k{4czkxIEg43lTvPOKh{tY8x4;JC1Q=*1ltrm-~aH-IA zQE7*sR6q zMH1W>Jv6sGboAD`w=g;a`Wn630!+A@rS*>q4X-j>z74xVE6L;g69<#|nopvZXcpU8 zecDJdJ}2=CLZg0SpL_J}N|#RE5?AD+JHZ3bUNbWx68d(r?DT)=B?*iu%SF4a2DcC{ z)QLf$nF+{Lt5Pu3sHTGvjadZ*nMsk$+zMPP5>6EK@g$dG6yFQ33r$KhVyx#LWL z!;bb)YIIo#{QU8BhTCD+pE2Cd7;f!LUrI+DKIkB-oH5+a7;gWcGTb=+1%}((K%foEQ!_CgjX>7>N z%*)Qk$;`&ZZOm=R!Ku%o$Mz2xZik6aGTabJpPHHjKYpu60RX`IeKVJ>mBAf5Lwy#R zn?Qz}n%xjLCXnHVKo%Qfu=Ygq%Lm73ZpEt~23o!~8(zUZCzat6nPQPuT~z^KAj1}H zetylKHo7lKa7@EGp8&sA)!#_-S)-p(CHI@M$@gfR!$q=o=9o@qS8IDxaQa!BweSTZ zta6$!GT(?wwPWWmX-}tnG9JaXfU)=dc8Vrair_QTaTac-kZrsychgvDy|1-QIj^Zr z*l6T$$**dtVzp96>9tJ(!UX`=YIo9yhFUAGr;4OG-fQ03ktl#qqS0e0OK5jdRwB43 zG0vy^#qXRZ>t)*;C_j@txv}#n9=gBJq@Z2mX^9~;(cg|~HbYPQ?p8Yn!_v^M>MyK{m(HF`pNFj%~LMlD^vG0$vG zrIE@J+I^@bIq<)aN5e`)bcB$76Pig0tqC(Bv|=Gh<9852aH5syG?UD;fBxO3SeVcK zO(Si4JPND=^Gi*Rq0avP3Tg;=f#@hOKRJz77WmpJ zCa;is5m`OKRp!xSOP({yFN#aOd09O{b3%l{eO^X7a-%lh``N|$Qb*pEASeL0_;-m8 zJ&AXw-+f5ZGEUu$b&()!7sC|Deqa$UE*xhao66nI{msx(VtliexH?n6Iz=za)*(zJ zGL(t&#wI^_o*v01LZWD~Z z5AaB%WJ)7(?HO45s{XAR@EYI~Mg#bej7jsYG~ep>?SUHuI!sRs$1LP+-8IXJKa*X~ z7S2l&V`0@$1#Z&4+Vv`hXb^6bi^PYlU32zz7a^S$e*vpxlpcPGGNzoULlYAQ%VXEuJ0LJ3 z?6r+#Aju7NuQy_xX>%TalSGrwy^6{x->wsvU#RU4(`9`@tV*g%S*t)3#}z`Lq*f=4 z#`I`eI{O=~EQ9MCfW;zJTd9rjNC>tjbIQVmt|wj;5eFU3A`s;!K!2ah*A!E5FP2&N zF%xf!6Q?1|RJ`P4el#1t1es}9MX`^HnG-%dMA=>sZE@OS*E(w-OqvA}nJVSo-3|@) zjMr88;Gos2L~#e1Os?eJr9=Yv#v8_0CIf|kdW89*UcRCt>Qz_JjO53_j*EQ-8Iznh zf5lLShg7vLiPl9V;A+Ur^TWNrP;TW;s6U7jUhBK1416{+c4hofa)5F(COh{@Jdx^7 zrk4OMx5V>nu&$ikR^PSr^7s(TS+?Lx`);i8%jfxDPcG+t@ikT`5vf6u6}vtQRmmeZ zB4v2$S#@HkW(a3kF*(_8`QFxyhjOKn8K#r9lWwM|wf;cB7-tjT+x&ah6HlZFZ4#JlN8H3OwHVqOC12nzGZXn352@b7vjhmj)*2C$p z#>fhV)WS{Jf%Z>I=Xp6j;yt3KR>TU&Mu%Zz1ICAKAF@ml;pB`3N`-q+r7_`{Yb;_u zBPaA>&cSTIucy--8yJFRt7a$_9L#-_k-*i)?fdS?Q2gTcD(+>2cWDL~TH~|aYHy}- zR^PKsIqY>4qQWxZP<6G6KBS1hSdPkVvu<=bmA+`}XK{wt04_HLn}_e+o{8=AUx#t1 zn{u2sQ$#lkR)2c>;xl^I2MDfTmO$qTQ+hex^N|xK#)E_;TOu$Ahz~+#{)Acb5r}cy z#9*q1DcMoxY<^6a{9+Vl6|ZqeMMx?+)LG=g*Jik9WK=EivAn}$wYdXoTQ2yot%tfi zf;Zt;fQYahjP2baqNzezN%hBf-|$h2PW{vym5g7(Oq(ecY+BaO4&H-DN7s+Y&P!fjK>CGnB6cee4tKgbw5B?^^?V(9Xbm0jSu2nIDqa;Ax7 zwJx1U{sGqqV||O&Suvl`z@|#*suLZedjiKshRq9$nFf6cF7(c*UZ$5WL z(`u~nsqiGC?wFog?B?1f8MeSDi?(49t z47-;VQNtzSv85Lzh=O_p_7bwTAW2{~Hz`AGr#C05xjPnx2|AZXPRx^6I8vF-5qPwT z${IAIKe{||1um-<4katsD+h^J2nW%~2sfbJ9;t$j^CXZ#|4^;EK?`#Nl?K8^2JS*FXiapT8i$ z)T*Hs9T)pG%a4Gu?_WsMdgccU@COx^jdXgZw=lmh=?!%u3tf~QTzp&8VE=%A2f?C_ zV#`_{;lB zzhACL$CiJFT|1GKAotDL*3?94sKQ>I6tBG}j`xy%6A6FuJbA0|1(|#4XoNUT`OLfs zD#Z&7uW87-QcGb*UsN{hdx>poW|;VHHN?G>a%RVV#&m&3uWXEUxN9WqmDR0XLDeFeq*^HDFS`Ka=Z)_r6n*D;3hI`}Rp({wTdNp3{DA6;gWMcv-ECohY2>$}r4X#V{`hO?G+Rhc4M+X&LP2 z^@=|bV19+~jIoxUD`H4Dtn|WeObIX~jxKXwbJ}1{zf3o8`itb&7ZXv=vI9tLvGH|A z)R^BnUqi;Vy>l`2O^ry(dF@wY47LwG(z9N(byp!EzY#V`;fv})mWcPN{c@yTKhwZx z%3u#AyHAOz^rRvt62pcIbJReNTLzfpHt4#9Oe!P7*zu~_jjaaq}f;S#2Dg; z-T2!ziUEXjzd3HXoJfB7jX;jud?i|tYXktryzPOP^@mGb#aVtMZ-U?T>bwcd^j|O3 z)$^yUsinE@cdmq}14jgQU$Ma+&>JSSA&ew(aZP`A7FWM*Lt@HxT3YxV0(L^hc!S zXzJD`8W7`_ySI;VtKP@B5r8mmt~5D7j9Y$xg6ZH+q*h_vooXYVU{c*32VueNT^2i1 zf-e{j4ING|ck#((N9&{;z05jjKo~cZ3hjx_79-qoKGKgbmSP%UHv=333P-F^cdGI@ zGd;C(U!x#t>`^nhHktRMeelrE=`I`DrKL);_^{36T`e&yALsuOaSEHid(3OmqFPER zsIc3t0!VS=KtqZ~e^mDnnZ-7e=0?7vh-2ZTrPaqzU%7Y$W7o3~qG~4lFJt3t4Zz#t zELWm^N6@KI#S;?FDfa!@9QUX*Pk5AczT~^RQ}(E7&Lq{&NWN!lyHnE-V06k3z@{X_ zeS-!>58_a6y#Fq|=w!9v4?~@&?>>YS(MoiY!{(}CDdG5wY`Q&| z!}}FZqqrSDk^kouH)6R&W=M+LVV*OJ+Zn~}e-FjY<&5HXMsYi%xSdhl&M0pGBZ?ag z>~+9CCg&uh4GusJ`aJ-E`74?@v784`fPR1h5CZZ3!G8Aytw8`Ze#0|Nla0l_|4zZ35tp!i{8 zJ9|A_dk5>wcBV&`(VoqPZADS!nX<~Gr@H*(T*xx6oR zv<5&@w>2toydmRZ4F|X5!Nb!|Jw*Tj-s1oO1jlp+fWJM|u{gLh&&27n^IeOhLg0cR zUx$)P#tKhd1vZl_JN7R?fbZgZ}?S>G-YtN0ow4RdO=?=Sm0UI6r4)dB%}j+=sXDA5~g_ zRDyCt|53%qGO|HJz>^Fd0oz=5`%QA#6wq#;KB_o*0b&$?E!q2L0py|$UN#`(p&9_7 z>!l$NJa>P8q5nRm_gAG2GO!x>wg02lEU0ou@&H%8&<8%C2wubw^CJWHH!Z&N$jEyV z0064mz(wMBehOfIDsssDhxaxfRq`^5wcQ^ltuA(ul5W;PJlcKxN>#RVVf)9u|BZJ{ zsSntc2P*}*l3(x0fF|=+nhhkAOnnd`Wr{9+r9OP0#?k4yzym1*e%A&x9Tbd%GQbg1 z2Ux}Lufz460{A=I6FU2E;((^1AIJ)-1sowKr&se9uHNsrdK|4K35tdUG>JKS7LY^_ z%t3@KL;6DV9`O1D*#9OyHY@DFRq6PwKx|<_lLNOMI6_uLF(BW4xVLq*BG(gg$9KA* zsU<~nfQkdhL$$v-o+mdCieo$Gc$o7OL2ETQHq!f@xRyV6JAP6gcRaDI~1|&@$Ju$&X_0@Vf zXxhN>=(OFwha_L1lAKfp4*+-ommP{@t4_peGN%n=D&^xa3*fUXhzO}j)bT2H189ve z0-w>5A5#niUinTo3a~H>=pzXs1Qv!AJkAh1y3)PXJw@<0QS8`|{#Ea9XB&$Fgx;xM zm7gaXG;QE`P}8ATWh`~@g6>syplbXZUX`2iQxVV>0QSF+`;w7UrT)`iRmu?B!0i|H zuY1*JK(A`*q=i`mJeBu=&+CtOeHNtBU%cv+l4iobR|Us^-K!>;K-BgJuL@r9A>(1o z|IMquwg9Vv9p2Fv7wYE)UB)Bx54xPV=gT zhzGTRBjn@=8@t{({Ag+FUwRb4r~^doE(UiCB1L6Z-DJ=$^yd%J($ol}lIP-8!KsYT*$0j<^G`0ha_=;y;K z{{YGe!NUV(cdqGTDQI^N>{VySb0I4E+to7~GX1Gu6}hFK>D2We4-&eJQRQiioRqIm*c~x-y*S)Gy5kzf&@T%YiA2J@E)qnG< z&gEb=XkIlDR~Y0`G6oL3D){nynE!8HHRVM9KYP{lqyiwt#dUsAJlcJK_NuB~kd^$w zt13rb1EmK?NSSY&ER4{->WdRHfAXq@Y6oS+utP8Bw^0TDW)5F0q=IVavw;PVH`xr;X2)`R!l%j`~$BVef6O2!0iWF z(eY1yJ?g&UCL!hi?o|cG4qA@8>>wVh0RSgIPY$fMze_|sf1t*3%&U^Ufi>8F>j5}k zEbBet!v4Ikdi{4^NUy3hfe!Mj#CZq%s^$G3A#x)FPJV73_?Rhx$82&e_aD6K9b^KR zeXk0RVCkd7^JlNhxd0v>C|(uQ_Q0!l2p{aL7MFd3sN|&YIXpx5M_&gr{i$Acrg0pU z8ypWy_?uS^g{tu%@T$i@S?uTp881PM+P~{n_g}a^{*hBh<=(77m4oh8|N1id$vHdj zRlj_Lrgn-~?fSU@nl^AeI&FXUswC^D$o$v5D&ddQ6rqpgU-vl=?D*;Xs?yu12>zG6 zYRN8y-l<--ko5;>+Q9K(+77(xelH$p`XBAZS*RM&yy_nt@lfhG!2hU}(H^vN_N5@a z>OWLE-K%z zS2aD6|Ic1kB}EdX2#!Y^{m)*NgaWdXKX}z#Q3;R?I6}%CXEhvc2%Qr$fAXqC$Oq+s zBcu-SlKcDX@JdVp{9Wk@ozuLkd4@Qs7I1`|oa4BMqqTg7q5;*b(oc(lB)}1}jMKg9 zeOAa>`3GM0%cX<11GgV!MaS=NIa*Q63Aw*})i@I=P;uaROB!MSBaQ*)z(1Uf0KoVa J;H!Rs{{u>5mjnO+ diff --git a/deploy/docker/docker-demo/src/main/resources/docker-compose-all.yaml b/deploy/docker/docker-demo/src/main/resources/docker-compose-all.yaml deleted file mode 100644 index b42268f9..00000000 --- a/deploy/docker/docker-demo/src/main/resources/docker-compose-all.yaml +++ /dev/null @@ -1,36 +0,0 @@ -version: '2' - -services: - demo: - image: "jdchain-demo:1.3.0" - container_name: jdchain-demo - networks: - jdchain_default: - aliases: - - demo - hostname: demo - restart: always - ports: - - "11010:11010" - - "7080:7080" - - "10080:10080" - - "10081:10081" - - "11011:11011" - - "7081:7081" - - "10082:10082" - - "10083:10083" - - "11012:11012" - - "7082:7082" - - "10084:10084" - - "10085:10085" - - "11013:11013" - - "7083:7083" - - "10086:10086" - - "10087:10087" - - "8080:8080" -# volumes: -# - "./logs:/export/jdchain/peer0/logs" - -networks: - jdchain_default: - driver: bridge diff --git a/deploy/docker/docker-demo/src/main/resources/start-net.sh b/deploy/docker/docker-demo/src/main/resources/start-net.sh deleted file mode 100644 index 7eafc55d..00000000 --- a/deploy/docker/docker-demo/src/main/resources/start-net.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -echo "停止network" -docker-compose -f docker-compose-all.yaml down -echo "启动jdchain" -docker-compose -f docker-compose-all.yaml up -d diff --git a/deploy/docker/docker-demo/src/main/resources/zip.sh b/deploy/docker/docker-demo/src/main/resources/zip.sh deleted file mode 100644 index 3305c646..00000000 --- a/deploy/docker/docker-demo/src/main/resources/zip.sh +++ /dev/null @@ -1,9 +0,0 @@ -#/bin/bash - -# all in one; -allIn1_file="./jdchain-demo_1.3.0.tar.gz" -if [ -f $allIn1_file ] ; then - rm -rf $allIn1_file -fi -docker save jdchain-demo:1.3.0 -o jdchain-demo_1.3.0.tar -gzip jdchain-demo_1.3.0.tar diff --git a/deploy/docker/docker-sdk/pom.xml b/deploy/docker/docker-sdk/pom.xml deleted file mode 100644 index 974edb25..00000000 --- a/deploy/docker/docker-sdk/pom.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - - com.jd.blockchain - docker - 1.5.0.RELEASE - - 4.0.0 - - docker-sdk - - - - com.jd.blockchain - crypto-classic - ${framework.version} - - - com.jd.blockchain - crypto-sm - ${framework.version} - - - com.jd.blockchain - ledger-model - ${framework.version} - - - com.jd.blockchain - sdk-client - ${framework.version} - - - - - - - maven-assembly-plugin - - false - - jar-with-dependencies - - - - - com.jd.blockchain.SDKDemo - - - - - - make-assembly - package - - single - - - - - - - - \ No newline at end of file diff --git a/deploy/docker/docker-sdk/src/main/java/com/jd/blockchain/ContractParams.java b/deploy/docker/docker-sdk/src/main/java/com/jd/blockchain/ContractParams.java deleted file mode 100644 index b1464b4e..00000000 --- a/deploy/docker/docker-sdk/src/main/java/com/jd/blockchain/ContractParams.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.jd.blockchain; - -import com.jd.blockchain.ledger.BlockchainIdentity; -import com.jd.blockchain.ledger.BlockchainKeypair; - -public class ContractParams { - String contractZipName; - BlockchainKeypair signAdminKey; - BlockchainIdentity contractIdentity; - boolean isDeploy; - boolean isExecute; - boolean hasVersion; //contract's version; - long version; - BlockchainIdentity dataAccount; - String key; - String value; - - public String getContractZipName() { - return contractZipName; - } - - public ContractParams setContractZipName(String contractZipName) { - this.contractZipName = contractZipName; - return this; - } - - public BlockchainKeypair getSignAdminKey() { - return signAdminKey; - } - - public ContractParams setSignAdminKey(BlockchainKeypair signAdminKey) { - this.signAdminKey = signAdminKey; - return this; - } - - public BlockchainIdentity getContractIdentity() { - return contractIdentity; - } - - public ContractParams setContractIdentity(BlockchainIdentity contractIdentity) { - this.contractIdentity = contractIdentity; - return this; - } - - public boolean isDeploy() { - return isDeploy; - } - - public ContractParams setDeploy(boolean deploy) { - isDeploy = deploy; - return this; - } - - public boolean isExecute() { - return isExecute; - } - - public ContractParams setExecute(boolean execute) { - isExecute = execute; - return this; - } - - public boolean isHasVersion() { - return hasVersion; - } - - public ContractParams setHasVersion(boolean hasVersion) { - this.hasVersion = hasVersion; - return this; - } - - public long getVersion() { - return version; - } - - public ContractParams setVersion(long version) { - this.version = version; - return this; - } - - public BlockchainIdentity getDataAccount() { - return dataAccount; - } - - public ContractParams setDataAccount(BlockchainIdentity dataAccount) { - this.dataAccount = dataAccount; - return this; - } - - public String getKey() { - return key; - } - - public ContractParams setKey(String key) { - this.key = key; - return this; - } - - public String getValue() { - return value; - } - - public ContractParams setValue(String value) { - this.value = value; - return this; - } -} diff --git a/deploy/docker/docker-sdk/src/main/java/com/jd/blockchain/SDKDemo.java b/deploy/docker/docker-sdk/src/main/java/com/jd/blockchain/SDKDemo.java deleted file mode 100644 index 5e02ccf1..00000000 --- a/deploy/docker/docker-sdk/src/main/java/com/jd/blockchain/SDKDemo.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.jd.blockchain; - -import com.jd.blockchain.ledger.*; -import org.apache.commons.codec.binary.Base64; - -import java.util.Random; -import java.util.UUID; - -public class SDKDemo extends SDK_Base_Demo{ - public static void main(String[] args) { - SDKDemo sdkDemo = new SDKDemo(); - //注册用户; - sdkDemo.registerUsers(); - //构建数据账户; - sdkDemo.genDataAccount(); - //发布和执行合约; - sdkDemo.deployContract(); - } - - //注册用户; - public void registerUsers(){ - this.registerUser(); - } - - //构建数据账户; - public void genDataAccount(){ - byte[] arr = new byte[1024]; - new Random().nextBytes(arr); - String value = Base64.encodeBase64String(arr); - this.insertData(null,null,"key1",value,-1); - } - - public BlockchainKeypair insertData(BlockchainKeypair dataAccount, BlockchainKeypair signAdminKey, - String key, String value, long version) { - // 在本地定义注册账号的 TX; - TransactionTemplate txTemp = blockchainService.newTransaction(ledgerHash); - //采用KeyGenerator来生成BlockchainKeypair; - if(dataAccount == null){ - dataAccount = BlockchainKeyGenerator.getInstance().generate(); - txTemp.dataAccounts().register(dataAccount.getIdentity()); - } - - System.out.println("current dataAccount=" + dataAccount.getAddress()); - txTemp.dataAccount(dataAccount.getAddress()).setText(key, value, version); - txTemp.dataAccount(dataAccount.getAddress()).setTimestamp(UUID.randomUUID().toString(),System.currentTimeMillis(),-1); - - // TX 准备就绪 - commit(txTemp,signAdminKey); - - //get the version - TypedKVEntry[] kvData = blockchainService.getDataEntries(ledgerHash, - dataAccount.getAddress().toBase58(), key); - System.out.println(String.format("key1 info:key=%s,value=%s,version=%d", - kvData[0].getKey(),kvData[0].getValue().toString(),kvData[0].getVersion())); - - return dataAccount; - } - - public void deployContract(){ - ContractParams contractParams = new ContractParams(); - contractParams.setContractZipName("contract-compile-1.3.0.RELEASE.car").setDeploy(true).setExecute(false); - BlockchainIdentity contractAddress = - this.contractHandle(contractParams); - contractParams.setContractIdentity(contractAddress); - this.contractHandle(contractParams); - this.contractHandle(contractParams.setExecute(true)); - } -} diff --git a/deploy/docker/docker-sdk/src/main/java/com/jd/blockchain/SDKDemo_Constant.java b/deploy/docker/docker-sdk/src/main/java/com/jd/blockchain/SDKDemo_Constant.java deleted file mode 100644 index cfdd0140..00000000 --- a/deploy/docker/docker-sdk/src/main/java/com/jd/blockchain/SDKDemo_Constant.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.jd.blockchain; - -import com.jd.blockchain.crypto.KeyGenUtils; -import com.jd.blockchain.crypto.PrivKey; -import com.jd.blockchain.crypto.PubKey; -import com.jd.blockchain.ledger.BlockchainKeypair; -import org.apache.commons.io.FileUtils; -import org.springframework.core.io.ClassPathResource; - -import java.io.File; -import java.io.InputStream; - -public class SDKDemo_Constant { - - public static String GW_IPADDR = "localhost"; - public static int GW_PORT = 8080; - public static String GW_PUB_KEY = "7VeRL1kWpYpvawkgFbM9N9ao1YiAE9HW65QpwLvpw6oPjCnZ"; - public static String GW_PRIV_KEY = "177gk2PbxhHeEdfAAqGfShJQyeV4XvGsJ9CvJFUbToBqwW1YJd5obicySE1St6SvPPaRrUP"; - public static String GW_PASSWORD = "8EjkXVSTxMFjCvNNsTo8RBMDEVQmk7gYkW4SCDuvdsBG"; - - public static PrivKey gwPrivkey0 = KeyGenUtils.decodePrivKey(GW_PRIV_KEY, GW_PASSWORD); - public static PubKey gwPubKey0 = KeyGenUtils.decodePubKey(GW_PUB_KEY); - public static BlockchainKeypair adminKey = new BlockchainKeypair(gwPubKey0, gwPrivkey0); - - public static final byte[] readChainCodes(String contractZip) { - // 构建合约的字节数组; - try { - ClassPathResource contractPath = new ClassPathResource(contractZip); -// File contractFile = new File(contractPath.getURI()); - - InputStream in = contractPath.getInputStream(); - // 将文件写入至config目录下 - File directory = new File("."); - String configPath = directory.getAbsolutePath() + File.separator + "contract.jar"; - File targetFile = new File(configPath); - // 先将原来文件删除再Copy - if (targetFile.exists()) { - FileUtils.forceDelete(targetFile); - } - FileUtils.copyInputStreamToFile(in, targetFile); - return FileUtils.readFileToByteArray(targetFile); - } catch (Exception e) { - throw new IllegalStateException(e); - } - } -} diff --git a/deploy/docker/docker-sdk/src/main/java/com/jd/blockchain/SDK_Base_Demo.java b/deploy/docker/docker-sdk/src/main/java/com/jd/blockchain/SDK_Base_Demo.java deleted file mode 100644 index 4f321b13..00000000 --- a/deploy/docker/docker-sdk/src/main/java/com/jd/blockchain/SDK_Base_Demo.java +++ /dev/null @@ -1,192 +0,0 @@ -package com.jd.blockchain; - -import static com.jd.blockchain.SDKDemo_Constant.readChainCodes; -import static com.jd.blockchain.transaction.ContractReturnValue.decode; - -import com.jd.blockchain.crypto.HashDigest; -import com.jd.blockchain.ledger.BlockchainIdentity; -import com.jd.blockchain.ledger.BlockchainKeyGenerator; -import com.jd.blockchain.ledger.BlockchainKeypair; -import com.jd.blockchain.ledger.PreparedTransaction; -import com.jd.blockchain.ledger.TransactionResponse; -import com.jd.blockchain.ledger.TransactionTemplate; -import com.jd.blockchain.sdk.BlockchainService; -import com.jd.blockchain.sdk.client.GatewayServiceFactory; -import com.jd.blockchain.transaction.GenericValueHolder; -import com.jd.chain.contract.TransferContract; - -import utils.Bytes; - -public abstract class SDK_Base_Demo { - protected BlockchainKeypair adminKey; - - protected HashDigest ledgerHash; - - protected BlockchainService blockchainService; - - public SDK_Base_Demo() { - init(); - } - - public void init() { - // 生成连接网关的账号 - adminKey = SDKDemo_Constant.adminKey; - - // 连接网关 - GatewayServiceFactory serviceFactory = GatewayServiceFactory.connect(SDKDemo_Constant.GW_IPADDR, - SDKDemo_Constant.GW_PORT, false, adminKey); - - // 获取网关对应的Service处理类 - blockchainService = serviceFactory.getBlockchainService(); - - HashDigest[] ledgerHashs = blockchainService.getLedgerHashs(); - // 获取当前账本Hash - ledgerHash = ledgerHashs[0]; - } - - public TransactionResponse commit(TransactionTemplate txTpl){ - return this.commitA(txTpl,null); - } - - /** - * 默认使用A方式commit; - * @param txTpl - * @param signAdminKey - * @return - */ - public TransactionResponse commit(TransactionTemplate txTpl, BlockchainKeypair signAdminKey){ - return commitA(txTpl, signAdminKey); - } - - /** - * 采用A方式提交; - * @param txTpl - * @param signAdminKey - * @return - */ - public TransactionResponse commitA(TransactionTemplate txTpl, BlockchainKeypair signAdminKey) { - PreparedTransaction ptx = txTpl.prepare(); - - if(signAdminKey != null){ - System.out.println("signAdminKey's pubKey = "+signAdminKey.getIdentity().getPubKey()); - ptx.sign(signAdminKey); - }else { - System.out.println("adminKey's pubKey = "+adminKey.getIdentity().getPubKey()); - ptx.sign(adminKey); - } - TransactionResponse transactionResponse = ptx.commit(); - - if (transactionResponse.isSuccess()) { - System.out.println(String.format("height=%d, ###OK#, contentHash=%s, executionState=%s", - transactionResponse.getBlockHeight(), - transactionResponse.getContentHash(), transactionResponse.getExecutionState().toString())); - } else { - System.out.println(String.format("height=%d, ###exception#, contentHash=%s, executionState=%s", - transactionResponse.getBlockHeight(), - transactionResponse.getContentHash(), transactionResponse.getExecutionState().toString())); - } - return transactionResponse; - } - - /** - * 生成一个区块链用户,并注册到区块链; - */ - public BlockchainKeypair registerUser() { - return this.registerUser(null,null,null); - } - - public BlockchainKeypair registerUser(String cryptoType, BlockchainKeypair signAdminKey, BlockchainKeypair userKeypair) { - // 在本地定义注册账号的 TX; - TransactionTemplate txTemp = blockchainService.newTransaction(ledgerHash); - if(userKeypair == null){ - if("SM2".equals(cryptoType)){ - userKeypair = BlockchainKeyGenerator.getInstance().generate(cryptoType); - }else { - userKeypair = BlockchainKeyGenerator.getInstance().generate(); - } - } - System.out.println("user'address="+userKeypair.getAddress()); - txTemp.users().register(userKeypair.getIdentity()); - // TX 准备就绪; - commit(txTemp,signAdminKey); - return userKeypair; - } - - public BlockchainKeypair registerUser(BlockchainKeypair signAdminKey, BlockchainKeypair userKeypair) { - return registerUser(null,signAdminKey,userKeypair); - } - - /** - * 生成一个区块链用户,并注册到区块链; - */ - public BlockchainKeypair registerUserByNewSigner(BlockchainKeypair signer) { - return this.registerUser(signer,null); - } - - public BlockchainIdentity createDataAccount() { - // 首先注册一个数据账户 - BlockchainKeypair newDataAccount = BlockchainKeyGenerator.getInstance().generate(); - - TransactionTemplate txTpl = blockchainService.newTransaction(ledgerHash); - txTpl.dataAccounts().register(newDataAccount.getIdentity()); - commit(txTpl); - return newDataAccount.getIdentity(); - } - - public String create1(Bytes contractAddress, String address, String account, String content) { - System.out.println(String.format("params,String address=%s, String account=%s, String content=%s, Bytes contractAddress=%s", - address,account,content,contractAddress.toBase58())); - TransactionTemplate txTpl = blockchainService.newTransaction(ledgerHash); - // 使用合约创建 - TransferContract guanghu = txTpl.contract(contractAddress, TransferContract.class); - GenericValueHolder result = decode(guanghu.putval(address, account, content, System.currentTimeMillis())); - commit(txTpl); - return result.get(); - } - - public BlockchainIdentity contractHandle(ContractParams contractParams) { - if(contractParams.getContractZipName() == null){ - contractParams.setContractZipName("contract-JDChain-Contract.jar"); - } - // 发布jar包 - // 定义交易模板 - TransactionTemplate txTpl = blockchainService.newTransaction(ledgerHash); - Bytes contractAddress = null; - if(contractParams.getContractIdentity() != null){ - contractAddress = contractParams.getContractIdentity().getAddress(); - } - - if(contractParams.isDeploy){ - // 将jar包转换为二进制数据 - byte[] contractCode = readChainCodes(contractParams.getContractZipName()); - - // 生成一个合约账号 - if(contractParams.getContractIdentity() == null){ - contractParams.setContractIdentity(BlockchainKeyGenerator.getInstance().generate().getIdentity()); - } - contractAddress = contractParams.getContractIdentity().getAddress(); - System.out.println("contract's address=" + contractAddress); - - // 生成发布合约操作 - txTpl.contracts().deploy(contractParams.contractIdentity, contractCode); - - // 生成预发布交易; - commit(txTpl,contractParams.getSignAdminKey()); - } - - if(contractParams.isExecute){ - // 注册一个数据账户 - if(contractParams.dataAccount == null){ - contractParams.dataAccount = createDataAccount(); - contractParams.key = "jd_zhangsan"; - contractParams.value = "{\"dest\":\"KA006\",\"id\":\"cc-fin08-01\",\"items\":\"FIN001|3030\",\"source\":\"FIN001\"}"; - } - // 获取数据账户地址x - String dataAddress = contractParams.dataAccount.getAddress().toBase58(); - // 打印数据账户地址 - System.out.printf("DataAccountAddress = %s \r\n", dataAddress); - System.out.println("return value = "+create1(contractAddress, dataAddress, contractParams.key, contractParams.value)); - } - return contractParams.contractIdentity; - } -} diff --git a/deploy/docker/docker-sdk/src/main/java/com/jd/chain/contract/TransferContract.java b/deploy/docker/docker-sdk/src/main/java/com/jd/chain/contract/TransferContract.java deleted file mode 100644 index a3a41529..00000000 --- a/deploy/docker/docker-sdk/src/main/java/com/jd/chain/contract/TransferContract.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.jd.chain.contract; - -import com.jd.blockchain.contract.Contract; -import com.jd.blockchain.contract.ContractEvent; - -@Contract -public interface TransferContract { - - @ContractEvent(name = "create") - String create(String address, String account, long money); - - @ContractEvent(name = "transfer") - String transfer(String address, String from, String to, long money); - - @ContractEvent(name = "read") - long read(String address, String account); - - @ContractEvent(name = "readAll") - String readAll(String address, String account); - - @ContractEvent(name = "putval1") - String putval(String address, String account, String content, Long time); - - @ContractEvent(name = "putvalBif") - String putvalBifurcation(String address, String account, String content, String isHalf); - - @ContractEvent(name = "getTxSigners") - String getTxSigners(String input); - - @ContractEvent(name = "test") - String test(String input); -} diff --git a/deploy/docker/docker-sdk/src/main/resources/contract-compile-1.3.0.RELEASE.car b/deploy/docker/docker-sdk/src/main/resources/contract-compile-1.3.0.RELEASE.car deleted file mode 100644 index cd7c3c825ef6c2d305a0a7e3c1ea974825205a86..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5129 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 - - - com.jd.blockchain - deploy-root - 1.5.0.RELEASE - - 4.0.0 - pom - - docker - - - 1.4.3.RELEASE - - - - docker-sdk - docker-demo - - - \ No newline at end of file diff --git a/deploy/docker/readme.md b/deploy/docker/readme.md deleted file mode 100644 index 72f9c804..00000000 --- a/deploy/docker/readme.md +++ /dev/null @@ -1,44 +0,0 @@ -# jdchain-demo镜像使用说明 -本镜像主要为快速构建JDChain测试环境使用,内嵌固定的公私钥,不可用于生产正式环境。 -JDChain在docker中的安装路径:/export/jdchain,网关对外端口为:8080。可通过docker-compose-all文件来修改端口。 -demo环境构建完成后执行sdk加载部分测试数据,区块高度:7,交易总数:8,用户总数:5,数据账户总数:2,合约总数:1。 - -## 如何生成镜像 -1. 如果构建的docker镜像为当前开发版本,将docker模块中的跟主版本对齐,然后在deploy模块执行:mvn clean package即可。 -如果镜像版本与所在开发版本不一致(举例说明:构建1.3.0的镜像版本,但当前开发版本是1.4.0),需要预先在deploy-peer和deploy-gateway的 -target文件夹下放置相应版本zip安装包(jdchain-peer-xxx.zip,jdchain-gateway-xxx.zip),然后在docker模块执行:mvn clean package。 -2. 在maven构建过程中,两个zip安装包和docker-sdk-xxx.jar,会放至docker-demo模块src/main/docker/zip文件夹下。 -3. maven构建完成后,控制台执行:docker images,可看到构建的jdchain-peer镜像。 -4. 生成镜像文件。执行docker-demo模块中src/main/resources/zip.sh,可生成镜像的tar.gz压缩包; - -## 镜像快速使用 -1.在已经安装docker工具的环境中,装入jdchain-demo镜像: -```` -docker load -i jdchain-demo_1.3.0.tar.gz -```` -2.启动脚本 -每次执行启动脚本时,会删除原有的容器,然后重新构建全新的容器。 -所以每次执行之后,会清除原先链上新增的区块。 -```` -sh start-net.sh -```` -3.卸载容器 -如果不再使用容器,在start-net.sh脚本所在路径下执行: -```` -docker-compose -f docker-compose-all.yaml down -```` - -## SDK连接网关参数 -```` -ip=localhost -port=8080 -#默认公钥的内容(Base58编码数据); -keys.default.pubkey=7VeRL1kWpYpvawkgFbM9N9ao1YiAE9HW65QpwLvpw6oPjCnZ -#默认私钥的内容(加密的Base58编码数据);在 pk-path 和 pk 之间必须设置其一; -keys.default.privkey=177gk2PbxhHeEdfAAqGfShJQyeV4XvGsJ9CvJFUbToBqwW1YJd5obicySE1St6SvPPaRrUP -#默认私钥的解码密码; -keys.default.privkey-password=8EjkXVSTxMFjCvNNsTo8RBMDEVQmk7gYkW4SCDuvdsBG -```` - - - diff --git a/deploy/pom.xml b/deploy/pom.xml index f5491a8e..92566af0 100644 --- a/deploy/pom.xml +++ b/deploy/pom.xml @@ -5,22 +5,21 @@ com.jd.blockchain jdchain-parent - 1.1.5.RELEASE + 1.1.6-SNAPSHOT ../project/parent deploy-root - 1.5.0.RELEASE + 1.6.0-SNAPSHOT pom - 1.5.0.RELEASE + 1.6.0-SNAPSHOT ../core deploy-gateway deploy-peer - docker diff --git a/docs/ca.md b/docs/ca.md new file mode 100644 index 00000000..75ac8f97 --- /dev/null +++ b/docs/ca.md @@ -0,0 +1,150 @@ +## 证书 + +`JD Chain`身份认证支持两种模式:`KEYPAIR`(默认)/`CA`,即公私钥对和证书。 + +证书模式采用`X.509`标准的数字证书作为用户标识,证书字段中,附加组织和角色等信息。 + +`JD Chain`使用`jdchain-cli`/`openssl`生成的自签名证书,也支持使用`CFCA`等国家认可的第三方`CA`颁发的外部证书。 + +`JD Chain` `CA`支持`RSA 2048`/`ECDSA P-256`/`SM2 SM3WithSM2`/`ED25519`四种签名算法。 + + +### 类别 + +`JD Chain`证书体系分`ROOT`,`CA`,`PEER`,`GW`,`USER`几个类别。 + +使用证书`Subject`中`OU`字段区分。 + +#### ROOT + +根证书,可用于签发证书及账本初始化时作为账本证书。 + +#### CA + +中间证书,可用于签发证书及账本初始化时作为账本证书。 + +#### PEER + +共识节点证书,注册参与方时需要提供`PEER`类型证书。 + +#### GW + +网关证书,网关所配置公私钥对应账户信息在链上必须存储有`GW`类型的证书。 + +#### USER + +普通用户证书 + +### 实现 + +`JD Chain`证书使用链上存储方式。主要存储于两个地方: +- 元数据区,存储账本初始化时配置的根证书列表 +- 用户账户头部,存储用户注册时提供的证书 + +> 根证书支持列表,即支持多个参与机构使用不同的证书链,且根证书可使用`ROOT`证书,也可使用`CA`(中间)证书,但节点/网关/用户证书必须由配置在`JD Chain`根证书列表中的证书直接签出。 + +#### 账本初始化 + +*`ledger.init`* +较`KEYPAIR`模式有如下修改: + +1. `identity-mode` +```properties +identity-mode=CA +``` +`identity-mode`身份认证模式,`KEYPAIR`(默认)/`CA` + +2. `root-ca-path` +```properties +root-ca-path=/**/ledger1.crt,/**/ledger2.crt +``` +`root-ca-path`根证书列表,使用`ROOT`或者`CA`类型证书,多个根证书使用半角逗号分割。初始化完成后,证书信息会上链存储,通过[2.7 获取账本初始化配置信息](api.md#27-获取账本初始化配置信息)可查。 + +3. `cons_parti.*.ca-path` + +**CA 模式参与方需要增加配置网关信息,网关节点IP和端口不需要填写** + +节点公钥配置改为证书地址: + +```properties +// KEYPAIR +// cons_parti.0.pubkey-path= +// cons_parti.0.pubkey= + +// CA +cons_parti.0.ca-path=/**/peer0.crt +``` + +*`local.conf`* +较`KEYPAIR`模式有如下修改: +```properties +#当前参与方的公钥,用于非证书模式 +# local.parti.pubkey= +#当前参与方的证书信息,用于证书模式 +local.parti.ca-path= + +#当前参与方的私钥文件,PEM格式,用于证书模式 +local.parti.privkey-path= +``` + +#### 节点运行 + +节点启动和运行时会校验证书类型,时间有效性以及是否由某个根证书签出等,一旦校验失败会阻止网关接入,不再对外服务。 + +#### 网关接入 + +*gateway.conf* +```properties +#默认公钥的内容(Base58编码数据),非CA模式下必填; +keys.default.pubkey= +#默认网关证书路径(X509,PEM),CA模式下必填; +keys.default.ca-path=/home/imuge/jd/nodes/peer0/config/keys/gw1.crt +#默认私钥的路径;在 pk-path 和 pk 之间必须设置其一; +keys.default.privkey-path=/home/imuge/jd/nodes/peer0/config/keys/gw1.key +#默认私钥的内容(加密的Base58编码数据);在 pk-path 和 pk 之间必须设置其一; +keys.default.privkey= +``` + +网关接入网络需要配置`GW`类型证书及对应的私钥信息,证书类型必须是`GW`。 + +网关接入时会做如下认证: +- 证书类型包含`GW` +- 根证书列表存在类型正确且有效证书 +- 网关证书由根证书列表中某个证书签出(此证书类型正确且有效) + +#### 交易认证 + +交易时使用证书持有者私钥签名,交易内容不包含签名用户证书信息。 +交易执行前会校验所有账本根证书,签名终端用户和节点用户的证书类型及有效性。 + +> 请务必在证书到期前更新证书有效期 + +#### 证书更新 + +1. 根证书 + +`SDK`方式: +```java +TransactionTemplate txTemp = blockchainService.newTransaction(ledger); +txTemp.metaInfo().ca(X509Utils.resolveCertificate("*.crt")); +``` + +命令行方式:[更新账本证书](tx.md#更新账本证书) + +2. 节点/网关/普通用户证书 + +> 在`JD Chain`中,共识节点,网关配置的接入账户和普通用户本质都是用户账户类型,它们对应的证书管理方式一致。 + +`SDK`方式: + +```java +txTemp.user("user address").ca(X509Utils.resolveCertificate("*.crt")); +``` + +命令行方式:[更新用户证书](tx.md#更新用户证书) + +### 证书生成 + +使用`jdchain-cli`提供的[keys](cli/keys.md)和[ca](cli/ca.md)指令工具创建公私钥对以及签发证书。 + +其中[ca-test](cli/ca.md#生成测试证书)可一键生成账本初始化所需的所有证书外加可用的普通用户证书。 \ No newline at end of file diff --git a/docs/cli/ca.md b/docs/cli/ca.md new file mode 100644 index 00000000..a8a8699e --- /dev/null +++ b/docs/cli/ca.md @@ -0,0 +1,248 @@ +### 证书管理 + +`jdchain-cli`提供**`ED25519`,`RSA`,`ECDSA`,`SM2`**密钥算法的证书签发工具:[证书列表](#证书列表),[显示证书](#显示证书),[CSR](#CSR),[CRT](#CRT),[更新证书](#更新证书),[生成测试证书](#生成测试证书) + +> 目前支持创建`ED25519`,RSA`,`ECDSA`,`SM2`四种签名算法,请使用对应算法的公私钥 + +```bash +:bin$ ./jdchain-cli.sh ca -h +Usage: jdchain-cli ca [-hV] [--pretty] [--home=] [COMMAND] +List, create, update certificates. + -h, --help Show this help message and exit. + --home= Set the home directory. + Default: ../ + --pretty Pretty json print + -V, --version Print version information and exit. +Commands: + list List all the certificates. + show Show certificate. + csr Create certificate signing request. + crt Create new certificate. + renew Update validity period. + test Create certificates for a testnet. + help Displays help information about the specified command +``` +- `home`,指定密钥和证书存储相关目录,`${home}/config/keys` + +#### 证书列表 +```bash +:bin$ ./jdchain-cli.sh ca list -h +List all the certificates. +Usage: jdchain-cli ca list [-hV] [--pretty] [--home=] + -h, --help Show this help message and exit. + --home= Set the home directory. + --pretty Pretty json print + -V, --version Print version information and exit. +``` + +如: +```bash +:bin$ ./jdchain-cli.sh keys list +NAME ALGORITHM ADDRESS PUBKEY +``` +- `NAME`,名称 +- `ALGORITHM`,算法 +- `ADDRESS`,地址 +- `PUBKEY`,公钥 + +#### 显示证书 +```bash +:bin$ ./jdchain-cli.sh ca show -h +Show certificate. +Usage: jdchain-cli ca show [-hV] [--pretty] [--home=] -n= + -h, --help Show this help message and exit. + --home= Set the home directory. + -n, --name= Name of the certificate + --pretty Pretty json print + -V, --version Print version information and exit. +``` +- `name`,证书名称 + +如显示`${home}/config/keys`下名为`G1`的证书信息: +```bash +:bin$ ./jdchain-cli.sh ca show -n G1 +./jdchain-cli.sh ca show -n G1 +NAME ALGORITHM TYPE ROLE PUBKEY +G1 SM2 ROLE-TODO [GW] SFZ6LjGKVz6wdU4G9PAraojyzCYPJ1BXAg1XBwSPCMC6Ug6u5oom5zcLPUzWtz42aCp9PLGXpHweBjSu3EW2aDzsa4JoT + [0] Version: 3 + SerialNumber: 440724497 + IssuerDN: O=JDT,OU=ROOT,C=CN,ST=BJ,L=BJ,CN=ROOT,E=imuge@jd.com + Start Date: Fri Sep 03 16:43:01 GMT+08:00 2021 + Final Date: Thu May 30 16:43:01 GMT+08:00 2024 + SubjectDN: O=JDT,OU=GW,C=CN,ST=BJ,L=BJ,CN=G1,E=imuge@jd.com + Public Key: EC Public Key [c0:b9:58:d1:35:3d:a9:bc:1d:85:2a:ea:bf:57:80:39:e9:f6:57:6d] + X: 67e4a4afe0a5beb1e5fb6e915314a9ed94b74f449cc4f50314ff78ecf62ba786 + Y: 2d5c233bfcd582f0c1098dbe4f1319db074fcf00023fdc9f3461a8d01488d9f2 + + Signature Algorithm: SM3WITHSM2 + Signature: 3046022100b70107554a723ec96569bbb23c65cb + ac6d7934f47722aa50f18a5e9ca3a978b9022100 + 9b68e5f3bd14bf103248c8516c493e5e1d9a872c + 39841c3704686ca85311bac0 +``` + +#### CSR + +生成证书请求文件 +```bash +:bin$ ./jdchain-cli.sh ca csr -h +Create certificate signing request. +Usage: jdchain-cli ca csr [-hV] [--pretty] [--home=] [-n=] + [--priv=] [--pub=] + -h, --help Show this help message and exit. + --home= Set the home directory. + -n, --name= Name of the key + --pretty Pretty json print + --priv= Path of the private key file + --pub= Path of the public key file + -V, --version Print version information and exit. +``` + +- `name`,密钥对名称,创建公私钥请参照[keys](keys.md)文档说明 + +如使用`${home}/config/keys`下名为`ROOT`的公私钥信息创建`CSR`: +```bash +:bin$ ./jdchain-cli.sh ca csr -n ROOT +// 选择证书角色,输入对应数字即可,多个角色使用半角逗号相隔 +input certificate roles (0 for ROOT, 1 for CA, 2 for PEER, 3 for GW, 4 for USER. multi values use ',' split): +> 1 +input country: +> CN +input locality: +> BJ +input province: +> BJ +input organization name: +> JDT +input email address: +> imuge@jd.com +// 输入ROOT私钥密码 +input password of the key: +> 1 +create [${home}/config/keys/ROOT.csr] success +``` +成功后会创建`${home}/config/keys/ROOT.csr`文件。 + +#### CRT + +签发证书: +```bash +:bin$ ./jdchain-cli.sh ca crt -h +Create new certificate. +Usage: jdchain-cli ca crt [-hV] [--pretty] [--csr=] --days= + [--home=] [--issuer-crt=] + [--issuer-name=] + [--issuer-priv=] [-n=] + --csr= Path of the certificate signing request file + --days= Days of certificate validity + -h, --help Show this help message and exit. + --home= Set the home directory. + --issuer-crt= + Path of the issuer certificate file + --issuer-name= + Name of the issuer key + --issuer-priv= + Path of the issuer private key file + -n, --name= Name of the certificate signing request file + --pretty Pretty json print + -V, --version Print version information and exit. +``` + +- `name`,`CSR`文件名,不为空时要求在`${home}/config/keys`目录下存在`${name.csr}`文件 +- `csr`,`CSR`文件路径,与`name`二选一,优先使用`name`参数 +- `days`,证书有效天数,当前签发时间开始计算 +- `issuer-name`,签发者公私钥对名称,不为空时需要`${home}/config/keys`目录下至少存在`${issuer-name}.priv`,`${issuer-name}.crt` +- `issuer-crt`,签发者证书文件 +- `issuer-priv`,签发者私钥文件 +> `issuer-name`为空时,`issuer-crt`和`issuer-priv`必须同时提供 + + +如使用`${home}/config/keys`下名为`ROOT`签发自签名证书: +```bash +./jdchain-cli.sh ca crt -n CA --issuer-name ROOT --days 1000 +// 输入签发者私钥密码 +input password of the issuer: +> 1 +create [${home}/config/keys/ROOT.crt] success +``` + +#### 更新证书 + +仅可更新证书有效天数 +```bash +Update validity period. +Usage: jdchain-cli ca renew [-hV] [--pretty] [--crt=] --days= + [--home=] [--issuer-crt=] + [--issuer-name=] + [--issuer-priv=] [-n=] + --crt= File of the certificate + --days= Days of certificate validity + -h, --help Show this help message and exit. + --home= Set the home directory. + --issuer-crt= + Path of the issuer certificate file + --issuer-name= + Name of the issuer key + --issuer-priv= + Path of the issuer private key file + -n, --name= Name of the certificate + --pretty Pretty json print + -V, --version Print version information and exit. +``` +- `name`,`CRT`文件名,不为空时要求在`${home}/config/keys`目录下存在`${name.crt}`文件 +- `crt`,`CRT`文件路径,与`name`二选一,优先使用`name`参数 +- `days`,证书有效天数,当前签发时间开始计算 +- `issuer-name`,签发者公私钥对名称,不为空时需要`${home}/config/keys`目录下至少存在`${issuer-name}.priv`,`${issuer-name}.crt` +- `issuer-crt`,签发者证书文件 +- `issuer-priv`,签发者私钥文件 +> `issuer-name`为空时,`issuer-crt`和`issuer-priv`必须同时提供 + +如更新`${home}/config/keys`下名为`ROOT`证书有效期: +```bash +./jdchain-cli.sh ca crt -n ROOT --issuer-name ROOT --days 2000 +input password of the issuer: +> 1 +renew [${home}/config/keys/ROOT.crt] success success +``` + +#### 生成测试证书 + +一键生成可用于初始化`JD Chain`网络及使用需要的证书 +```bash +:bin$ ./jdchain-cli.sh ca test -h +Create certificates for a testnet. +Usage: jdchain-cli ca test [-hV] [--pretty] [-a=] + --country= --email= [--gws=] + [--home=] --locality= + [--nodes=] --org= + [-p=] --province= + [--users=] + -a, --algorithm= + Crypto algorithm + --country= Country + --email= Email address + --gws= Gateway size + -h, --help Show this help message and exit. + --home= Set the home directory. + --locality= Locality + --nodes= Node size + --org= Organization name + -p, --password= Password of the key + --pretty Pretty json print + --province= Province + --users= Available user size + -V, --version Print version information and exit. +``` +- `algorithm`,签名算法,默认`ED25519`,仅支持传入`ED25519`, `RSA`,`ECDSA`,`SM2`之一 +- `nodes`,共识节点个数,生成`nodes`个`PEER`类型的证书,可用于节点使用。默认:`4` +- `gws`,网关节点个数,生成`gws`个`GW`类型的证书,可用于网关使用。默认:`1` +- `users`,用户个数,生成`users`可个可用于普通用户使用的证书。默认:`10` + +如创建基于`SM2`签名算法的一个`ROOT`类型证书,`4`个节点证书,`1`个网关证书,`10`个用户证书: +```bash +:bin$ ./jdchain-cli.sh ca test --org JDT --country CN --locality BJ --province BJ --email jdchain@jd.com +input private key password: +// 输入操作过程中生成的私钥加密密码 +> 1 +create test certificates in [${home}/config/keys] success +``` \ No newline at end of file diff --git a/docs/cli/keys.md b/docs/cli/keys.md index 29ca2b49..64305d1c 100644 --- a/docs/cli/keys.md +++ b/docs/cli/keys.md @@ -77,6 +77,8 @@ Usage: jdchain-cli keys add [-hV] [--pretty] [-a=] [--home=] --home= Set the home directory. -n, --name= Name of the key --pretty Pretty json print + -p, --password= + Password of the key -V, --version Print version information and exit. ``` diff --git a/docs/cli/participant.md b/docs/cli/participant.md index ad4a0986..7f6eb7c7 100644 --- a/docs/cli/participant.md +++ b/docs/cli/participant.md @@ -27,35 +27,39 @@ Commands: ```bash :bin$ ./jdchain-cli.sh participant register -h Register new participant. -Usage: jdchain-cli participant register [-hV] [--pretty] [--gw-host=] +Usage: jdchain-cli participant register [-hV] [--ca-mode] [--pretty] + [--crt=] [--gw-host=] [--gw-port=] [--home=] - --name= + [-n=] + --participant-name= + [--pubkey=] + --ca-mode Register with CA + --crt= File of the X509 certificate --gw-host= Set the gateway host. Default: 127.0.0.1 --gw-port= Set the gateway port. Default: 8080 -h, --help Show this help message and exit. --home= Set the home directory. - --name= Name of the participant + -n, --name= Name of the key + --participant-name= + Name of the participant --pretty Pretty json print + --pubkey= Pubkey of the user -V, --version Print version information and exit. ``` -- `name`,新节点名称 +- `participant-name`,新节点名称 +- `ca-mode`,身份认证模式是否为证书(`CA`)模式,默认`false` +- `name`,当`ca-mode`为`true`时会读取本地`${home}/config/keys/${name}.crt`文件,反之读取`${home}/config/keys/${name}.pub` +- `crt`,证书文件路径 +- `pubkey`,`Base58`编码公钥信息,仅在非`ca-mode`情况下使用 注册新节点: ```bash -:bin$ ./jdchain-cli.sh participant register --name node4 +:bin$ ./jdchain-cli.sh participant register --participant-name node4 --name node4 select ledger, input the index: INDEX LEDGER 0 j5sB3sVTFgTqTYzo7KtQjBLSy8YQGPpJpvQZaW9Eqk46dg // 选择账本 > 0 -// 选择待注册节点公私钥(链上必须不存在此公私钥对应的用户) -select keypair to register, input the index: -0 k1 LdeNq3862vtUCeptww1T5mVvLbAeppYqVNdqD -1 1627618939 LdeNyibeafrAQXgHjBxgQxoLbna6hL4BcXZiw -2 node4 LdeNwG6ECEGz57o2ufhwSbnW4C35TvPqANK7T -2 -input password of the key: -> 1 // 选择此交易签名用户(必须是链上存在的用户,且有相应操作权限) select keypair to sign tx, input the index: 0 k1 LdeNq3862vtUCeptww1T5mVvLbAeppYqVNdqD @@ -199,20 +203,16 @@ Usage: jdchain-cli participant inactive [-hV] [--pretty] --address=
--ledger= Set the ledger. --port= Set the participant service port. --pretty Pretty json print - --syn-host= Set synchronization participant host. - --syn-port= Set synchronization participant port. -V, --version Print version information and exit. ``` - `ledger`,账本哈希 - `address`,待移除节点共识端口 - `host`,待移除节点服务地址 - `port`,待移除节点服务端口 -- `syn-host`,数据同步节点地址 -- `syn-port`,数据同步节点服务端口 如移除`node4`: ```bash -:bin$ ./jdchain-cli.sh participant inactive --ledger j5sB3sVTFgTqTYzo7KtQjBLSy8YQGPpJpvQZaW9Eqk46dg --address LdeNwG6ECEGz57o2ufhwSbnW4C35TvPqANK7T --host 127.0.0.1 --port 7084 --syn-host 127.0.0.1 --syn-port 7080 +:bin$ ./jdchain-cli.sh participant inactive --ledger j5sB3sVTFgTqTYzo7KtQjBLSy8YQGPpJpvQZaW9Eqk46dg --address LdeNwG6ECEGz57o2ufhwSbnW4C35TvPqANK7T --host 127.0.0.1 --port 7084 participant inactivated ``` diff --git a/docs/cli/tx.md b/docs/cli/tx.md index 0d67aa4a..0cc2bffe 100644 --- a/docs/cli/tx.md +++ b/docs/cli/tx.md @@ -2,7 +2,8 @@ ```bash :bin$ ./jdchain-cli.sh tx -h -Usage: git status [...] [--] [...] +Usage: jdchain-cli tx [-hV] [--pretty] [--export=] [--gw-host=] + [--gw-port=] [--home=] [COMMAND] Build, sign or send transaction. --export= Transaction export directory --gw-host= Set the gateway host. Default: 127.0.0.1 @@ -15,14 +16,19 @@ Build, sign or send transaction. --pretty Pretty json print -V, --version Print version information and exit. Commands: + ledger-ca-update Update ledger certificates. user-register Register new user. + user-ca-update Update user certificate. + user-state-update Update user(certificate) state. role Create or config role. authorization User role authorization. data-account-register Register new data account. kv Set key-value. event Publish event. + event-listen Subscribe event. contract-deploy Deploy or update contract. contract Call contract method. + contract-state-update Update contract state. event-account-register Register event account. sign Sign transaction. send Send transaction. @@ -36,53 +42,102 @@ Commands: - `home`,指定密钥存储相关目录,`${home}/config/keys` 命令: +- `ledger-ca-update`,[更新账本证书](#更新账本证书) - `user-register`,[注册用户](#注册用户) +- `user-ca-update`,[更新用户证书](#更新用户证书) +- `user-state-update`,[更新用户(证书)状态](#更新用户(证书)状态) - `role`,[角色管理](#角色管理) - `authorization`,[权限配置](#权限配置) - `data-account-register`,[注册数据账户](#注册数据账户) - `kv`,[KV设值](#KV设值) -- `event-account-register`,[注册事件账户](#注册事件账户) +- `event-account-register`,[注册事件账户](#注册事件账户) - `event`,[发布事件](#发布事件) +- `event-listen`,[监听事件](#监听事件) - `contract-deploy`,[部署合约](#部署合约) - `contract`,[合约调用](#合约调用) +- `contract-state-update`,[更新合约状态](#更新合约状态) - `sign`,[离线交易签名](#离线交易签名) - `send`,[离线交易发送](#离线交易发送) +#### 更新账本证书 + +```bash +:bin$ ./jdchain-cli.sh tx ledger-ca-update -h +Update ledger certificates. +Usage: jdchain-cli tx ledger-ca-update [-hV] [--pretty] --crt= + [--export=] [--gw-host=] + [--gw-port=] [--home=] + --crt= File of the X509 certificate + --operation Operation for this certificate. Optional values: ADD,UPDATE,REMOVE + --export= Transaction export directory + --gw-host= Set the gateway host. Default: 127.0.0.1 + --gw-port= Set the gateway port. Default: 8080 + -h, --help Show this help message and exit. + --home= Set the home directory. + --pretty Pretty json print + -V, --version Print version information and exit. +``` +- `crt`,证书文件路径 +- `operation`,操作类型:`ADD`,`UPDATE`,`REMOVE` + +如: +```bash +:bin$ $ ./jdchain-cli.sh tx ledger-ca-update --crt /home/imuge/jd/nodes/peer0/config/keys/ledger.crt --operation UPDATE +select ledger, input the index: +INDEX LEDGER +0 j5pFrMigE47t6TobQJXsztnoeA29H31v1vHHF1wqCp4rzi +// 选择账本,当前网关服务只有上面一个可用账本 +> 0 +select keypair to sign tx: +INDEX KEY ADDRESS +0 peer0 LdeNpEmyh5DMwbAwamxNaiJgMVGn6aTtQDA5W +// 选择链上已存在且有注册用户权限的用户所对应的公私钥对,用于交易签名 +> 0 +input password of the key: +// 输入签名私钥密码 +> 1 +ledger ca: [7VeRBQ9jpsgNXje2NYXU5MhyGKVRj462RtkJ8f6FNL1oxYbX](pubkey) updated +``` +会更新链上公钥为`7VeRBQ9jpsgNXje2NYXU5MhyGKVRj462RtkJ8f6FNL1oxYbX`的账本证书信息。 + + #### 注册用户 ```bash :bin$ ./jdchain-cli.sh tx user-register -h Register new user. -Usage: jdchain-cli tx user-register [-hV] [--pretty] [--export=] +Usage: jdchain-cli tx user-register [-hV] [--ca-mode] [--pretty] + [--crt=] [--export=] [--gw-host=] [--gw-port=] - [--home=] + [--home=] [-n=] + [--pubkey=] + --ca-mode Register with CA + --crt= File of the X509 certificate --export= Transaction export directory --gw-host= Set the gateway host. Default: 127.0.0.1 --gw-port= Set the gateway port. Default: 8080 -h, --help Show this help message and exit. --home= Set the home directory. + -n, --name= Name of the key --pretty Pretty json print + --pubkey= Pubkey of the user -V, --version Print version information and exit. ``` +- `ca-mode`,身份认证模式是否为证书(`CA`)模式,默认`false` +- `name`,当`ca-mode`为`true`时会读取本地`${home}/config/keys/${name}.crt`文件,反之读取`${home}/config/keys/${name}.pub` +- `crt`,证书文件路径 +- `pubkey`,`Base58`编码公钥信息,仅在非`ca-mode`情况下使用 + 从`${home}/config/keys`目录下密钥对选择密钥注册到网关服务对应的区块链网络。 如: ```bash -:bin$ ./jdchain-cli.sh tx user-register +:bin$ ./jdchain-cli.sh tx user-register -name k1 select ledger, input the index: INDEX LEDGER 0 j5sB3sVTFgTqTYzo7KtQjBLSy8YQGPpJpvQZaW9Eqk46dg // 选择账本,当前网关服务只有上面一个可用账本 > 0 -select keypair to register: -INDEX KEY ADDRESS -0 peer0 LdeNyibeafrAQXgHjBxgQxoLbna6hL4BcXZiw -1 k1 LdeNwQWabrf6WSjZ35saFo52MfQFhVKvm11aC -// 选择公私钥对用于注册用户 -> 1 -input password of the key: -// 输入所选择公私钥对密钥密码 -> 1 select keypair to sign tx: INDEX KEY ADDRESS 0 peer0 LdeNyibeafrAQXgHjBxgQxoLbna6hL4BcXZiw @@ -96,6 +151,87 @@ register user: [LdeNwQWabrf6WSjZ35saFo52MfQFhVKvm11aC] ``` 会在链上注册地址为`LdeNwQWabrf6WSjZ35saFo52MfQFhVKvm11aC`的用户账户信息。 +#### 更新用户证书 + +```bash +:bin$ ./jdchain-cli.sh tx ledger-ca-update -h +Update user certificate. +Usage: jdchain-cli tx user-ca-update [-hV] [--pretty] [--crt=] + [--export=] [--gw-host=] + [--gw-port=] [--home=] + --crt= File of the X509 certificate + --export= Transaction export directory + --gw-host= Set the gateway host. Default: 127.0.0.1 + --gw-port= Set the gateway port. Default: 8080 + -h, --help Show this help message and exit. + --home= Set the home directory. + --pretty Pretty json print + -V, --version Print version information and exit. +``` +- `crt`,证书文件路径 + +如: +```bash +:bin$ $ ./jdchain-cli.sh tx user-ca-update --crt /home/imuge/jd/nodes/peer0/config/keys/peer0.crt +select ledger, input the index: +INDEX LEDGER +0 j5pFrMigE47t6TobQJXsztnoeA29H31v1vHHF1wqCp4rzi +// 选择账本,当前网关服务只有上面一个可用账本 +> 0 +select keypair to sign tx: +INDEX KEY ADDRESS +0 peer0 LdeNpEmyh5DMwbAwamxNaiJgMVGn6aTtQDA5W +// 选择链上已存在且有注册用户权限的用户所对应的公私钥对,用于交易签名 +> 0 +input password of the key: +// 输入签名私钥密码 +> 1 +user: [LdeNpEmyh5DMwbAwamxNaiJgMVGn6aTtQDA5W] ca updated +``` +会更新链上地址为`LdeNpEmyh5DMwbAwamxNaiJgMVGn6aTtQDA5W`的用户证书信息。 + +#### 更新用户(证书)状态 + +```bash +:bin$ ./jdchain-cli.sh tx user-state-update -h +Update user(certificate) state. +Usage: jdchain-cli tx user-state-update [-hV] [--pretty] --address=
+ [--export=] + [--gw-host=] + [--gw-port=] [--home=] + --state= + --address=
User address + --export= Transaction export directory + --gw-host= Set the gateway host. Default: 127.0.0.1 + --gw-port= Set the gateway port. Default: 8080 + -h, --help Show this help message and exit. + --home= Set the home directory. + --pretty Pretty json print + --state= User state,Optional values: FREEZE,NORMAL,REVOKE + -V, --version Print version information and exit. +``` +- `address`,用户地址 +- `state`,用户状态,可选值:FREEZE,NORMAL,REVOKE + +如冻结用户`LdeNpEmyh5DMwbAwamxNaiJgMVGn6aTtQDA5W`: +```bash +:bin$ $ ./jdchain-cli.sh tx user-state-update --address LdeNpEmyh5DMwbAwamxNaiJgMVGn6aTtQDA5W --state FREEZE +select ledger, input the index: +INDEX LEDGER +0 j5pFrMigE47t6TobQJXsztnoeA29H31v1vHHF1wqCp4rzi +// 选择账本,当前网关服务只有上面一个可用账本 +> 0 +select keypair to sign tx: +INDEX KEY ADDRESS +0 peer0 LdeNpEmyh5DMwbAwamxNaiJgMVGn6aTtQDA5W +// 选择链上已存在且有注册用户权限的用户所对应的公私钥对,用于交易签名 +> 0 +input password of the key: +// 输入签名私钥密码 +> 1 +user: [LdeNpEmyh5DMwbAwamxNaiJgMVGn6aTtQDA5W] revoked +``` +会冻结链上地址为`LdeNpEmyh5DMwbAwamxNaiJgMVGn6aTtQDA5W`的用户(证书),此用户无法再接入使用此网络。 #### 角色管理 ```bash @@ -366,6 +502,42 @@ input password of the key: event publish success ``` +#### 监听事件 +```bash +:bin$ ./jdchain-cli.sh tx event-listen -h +Subscribe event. +Usage: jdchain-cli tx event-listen [-hV] [--pretty] [--address=
] + [--export=] [--gw-host=] + [--gw-port=] [--home=] + --name= [--sequence=] + --address=
Event address + --export= Transaction export directory + --gw-host= Set the gateway host. Default: 127.0.0.1 + --gw-port= Set the gateway port. Default: 8080 + -h, --help Show this help message and exit. + --home= Set the home directory. + --name= Event name + --pretty Pretty json print + --sequence= Sequence of the event + -V, --version Print version information and exit. +``` +- `address`,事件账户地址,不传则表示监听系统事件 +- `name`,事件名,系统事件目前仅支持:`new_block_created` +- `sequence`,起始监听序号 + +如监听系统新区块事件: +```bash +:bin$ ./jdchain-cli.sh tx event-listen --name new_block_created --sequence 0 +select ledger, input the index: +INDEX LEDGER +0 j5mXXoNsmh6qadnWLjxFMXobyNGsXT1PmTNzXiHyiYMxoP +> 0 +# 会打印新区块事件:区块高度:最新区块高度 +New block:0:12 +New block:1:12 +New block:2:12 +``` + #### 部署合约 ```bash @@ -450,6 +622,48 @@ return string: LdeNqvSjL4izfpMNsGpQiBpTBse4g6qLxZ6j5 ``` 调用成功并返回了字符串:`LdeNqvSjL4izfpMNsGpQiBpTBse4g6qLxZ6j5` +#### 更新合约状态 + +```bash +:bin$ ./jdchain-cli.sh tx contract-state-update -h +Update contract state. +Usage: jdchain-cli tx contract-state-update [-hV] [--pretty] + --address=
[--export=] [--gw-host=] + [--gw-port=] [--home=] --state= + --address=
Contract address + --export= Transaction export directory + --gw-host= Set the gateway host. Default: 127.0.0.1 + --gw-port= Set the gateway port. Default: 8080 + -h, --help Show this help message and exit. + --home= Set the home directory. + --pretty Pretty json print + --state= Contract state,Optional values: FREEZE,NORMAL, + REVOKE + -V, --version Print version information and exit. +``` +- `address`,合约地址 +- `state`,合约状态,可选值:FREEZE,NORMAL,REVOKE + +如冻结合约`LdeNpEmyh5DMwbAwamxNaiJgMVGn6aTtQDA5W`: +```bash +:bin$ $ ./jdchain-cli.sh tx contract-state-update --address LdeNpEmyh5DMwbAwamxNaiJgMVGn6aTtQDA5W --state FREEZE +select ledger, input the index: +INDEX LEDGER +0 j5pFrMigE47t6TobQJXsztnoeA29H31v1vHHF1wqCp4rzi +// 选择账本,当前网关服务只有上面一个可用账本 +> 0 +select keypair to sign tx: +INDEX KEY ADDRESS +0 peer0 LdeNpEmyh5DMwbAwamxNaiJgMVGn6aTtQDA5W +// 选择链上已存在且有注册用户权限的用户所对应的公私钥对,用于交易签名 +> 0 +input password of the key: +// 输入签名私钥密码 +> 1 +contract: [LdeNpEmyh5DMwbAwamxNaiJgMVGn6aTtQDA5W] revoked +``` +会冻结链上地址为`LdeNpEmyh5DMwbAwamxNaiJgMVGn6aTtQDA5W`的合约,此合约不能再被调用。 + #### 离线交易签名 diff --git a/docs/jdchain_cli.md b/docs/jdchain_cli.md index dcab9053..74bcc153 100644 --- a/docs/jdchain_cli.md +++ b/docs/jdchain_cli.md @@ -14,8 +14,9 @@ transactions to jdchain network, query data from jdchain network. Commands: -The most commonly used git commands are: +The most commonly used commands are: keys List, create, update or delete keypairs. + ca List, create, update certificates. tx Build, sign or send transaction. query Query commands. participant Add, update or delete participant. @@ -26,5 +27,6 @@ See 'jdchain-cli help ' to read about a specific subcommand or concept. - `keys` [密钥管理](cli/keys.md) - `tx` [交易](cli/tx.md) +- `ca` [证书](cli/ca.md) - `query` [链上信息查询](cli/query.md) - `participant` [共识节点变更](cli/participant.md) \ No newline at end of file diff --git a/explorer b/explorer index 5907ebd0..ecbc5194 160000 --- a/explorer +++ b/explorer @@ -1 +1 @@ -Subproject commit 5907ebd035217371050e101196cb947b85fa2dd7 +Subproject commit ecbc519401ad29863a50c4d6cab1ff8604f4cab4 diff --git a/feature/ledger-database/.gitkeep b/feature/ledger-database/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/framework b/framework index a5dfbdfa..2cf36760 160000 --- a/framework +++ b/framework @@ -1 +1 @@ -Subproject commit a5dfbdfac61d2afac37b8b8bebf98be7c8f0e5c4 +Subproject commit 2cf367601c10676d9cb7bc7088ff579562760788 diff --git a/libs/bft-smart b/libs/bft-smart index 90e9666e..972c19f4 160000 --- a/libs/bft-smart +++ b/libs/bft-smart @@ -1 +1 @@ -Subproject commit 90e9666e469010979f2bfceed396265642ec5678 +Subproject commit 972c19f4eab34c59361522abe2ea98ae043d042f diff --git a/libs/binary-proto b/libs/binary-proto index cd0f9eb9..d9666111 160000 --- a/libs/binary-proto +++ b/libs/binary-proto @@ -1 +1 @@ -Subproject commit cd0f9eb99a6441874f8ddba7048d00ac6f8abeeb +Subproject commit d9666111bbb33cbbb0a34ff96ffcefba34f46c72 diff --git a/libs/httpservice b/libs/httpservice index c4265ebf..a6cb4583 160000 --- a/libs/httpservice +++ b/libs/httpservice @@ -1 +1 @@ -Subproject commit c4265ebf3a30a1dbe518a40d5b0584ca760fb35a +Subproject commit a6cb4583fc6271ce62e2b8b1d6273c3bdd03b810 diff --git a/libs/kvdb b/libs/kvdb index c3de2e7d..9a7235f1 160000 --- a/libs/kvdb +++ b/libs/kvdb @@ -1 +1 @@ -Subproject commit c3de2e7da6e33ec502e1ba285ee042e467b06f84 +Subproject commit 9a7235f1eb63f7f22bc1f5995778633c9a086c89 diff --git a/libs/utils b/libs/utils index dedda124..d2807e16 160000 --- a/libs/utils +++ b/libs/utils @@ -1 +1 @@ -Subproject commit dedda124dfeb4488ece714d1e6af24b8d5f0a17a +Subproject commit d2807e16e2c9a1104a712242733e3b04f3044ea8 diff --git a/pom.xml b/pom.xml index d080517b..e8583531 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain jdchain-root - 1.5.0.RELEASE + 1.6.0-SNAPSHOT pom jdchain root project diff --git a/project b/project index e6a91274..731ab7ec 160000 --- a/project +++ b/project @@ -1 +1 @@ -Subproject commit e6a91274cc35748ad903e432aaf34e41b3803702 +Subproject commit 731ab7ec8602655d7b050516f05c194eb07f13d1 diff --git a/samples/contract-samples/pom.xml b/samples/contract-samples/pom.xml index 67c844ee..79a7b2ad 100644 --- a/samples/contract-samples/pom.xml +++ b/samples/contract-samples/pom.xml @@ -6,7 +6,7 @@ com.jd.blockchain jdchain-samples - 1.5.0.RELEASE + 1.6.0-SNAPSHOT diff --git a/samples/pom.xml b/samples/pom.xml index 43f31d16..7b89fee3 100644 --- a/samples/pom.xml +++ b/samples/pom.xml @@ -4,12 +4,12 @@ 4.0.0 com.jd.blockchain jdchain-samples - 1.5.0.RELEASE + 1.6.0-SNAPSHOT pom - 1.5.0.RELEASE - 1.5.0.RELEASE + 1.6.0-SNAPSHOT + 1.6.0-SNAPSHOT diff --git a/samples/sdk-samples/pom.xml b/samples/sdk-samples/pom.xml index 099cbb16..670da363 100644 --- a/samples/sdk-samples/pom.xml +++ b/samples/sdk-samples/pom.xml @@ -5,7 +5,7 @@ com.jd.blockchain jdchain-samples - 1.5.0.RELEASE + 1.6.0-SNAPSHOT sdk-samples diff --git a/test b/test index 068665c9..d51c0368 160000 --- a/test +++ b/test @@ -1 +1 @@ -Subproject commit 068665c9955ff2f577f0e48fd50cf25ed2767e3b +Subproject commit d51c036823918daef9199810817853b84836ac60