基于Debian搭建Hyperledger Fabric 2.4开发环境及运行简单案例
<h2 id="前言">前言</h2><p>在基于truffle框架实现以太坊公开拍卖智能合约中我们已经实现了以太坊智能合约的编写及部署,但其工作方式注定其只能应用于有限的业务场景中。相比之下,基于超级账本的<code>Fabric</code>具有高可扩展性和高可定制性,能够应用在更为复杂的商业场景中,但<code>Fabric</code>技术涉及很多新的概念,源代码跟新速度快且各版本间兼容性差,对初学者很不友好。为了使能够快速掌握<code>Fabric</code>,本文基于其目前最新的2.4版本搭建了一套区块链运行环境,并在此之上部署了官方示例<code>chaincode</code>并对其进行交互调试,最终整个环境及示例代码能够正常运行且得出预期结果。</p>
<h2 id="环境搭建">环境搭建</h2>
<p>网上几乎所有的<code>Fabric</code>教程都是基于<code>Ubuntu</code>环境而不是<code>Windows</code>,其原因主要是<code>Fabric</code>的运行需要的<code>Docker</code>环境在<code>Windows</code>下表现不佳,此外<code>Fabric</code>许多官方文档也是基于<code>Ubuntu</code>纂写,在<code>windows</code>下运行可能会遇到难以预估的<code>bug</code>。原本为了方便后期部署至公网服务器想在CentOS上搭建环境,但由于CentOS8停止维护,且CentOS Stream使用体验颇差,于是最终选择了Debian系统。<br>
本环境各系统、软件版本如下:</p>
<table>
<thead>
<tr>
<th style="text-align: center">系统、软件</th>
<th style="text-align: center">版本</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">VMware Pro</td>
<td style="text-align: center">16.0.0</td>
</tr>
<tr>
<td style="text-align: center">Debian</td>
<td style="text-align: center">debian-11.2.0-amd64-DVD-1.iso</td>
</tr>
<tr>
<td style="text-align: center">git</td>
<td style="text-align: center">2.30.2</td>
</tr>
<tr>
<td style="text-align: center">curl</td>
<td style="text-align: center">7.74.0</td>
</tr>
<tr>
<td style="text-align: center">docker</td>
<td style="text-align: center">20.10</td>
</tr>
<tr>
<td style="text-align: center">golang</td>
<td style="text-align: center">go1.17.8</td>
</tr>
<tr>
<td style="text-align: center">jq</td>
<td style="text-align: center">jq-1.6</td>
</tr>
<tr>
<td style="text-align: center">fabric</td>
<td style="text-align: center">2.4.0</td>
</tr>
<tr>
<td style="text-align: center">fabric-ca</td>
<td style="text-align: center">1.5.2</td>
</tr>
<tr>
<td style="text-align: center">fabric-samples</td>
<td style="text-align: center">v2.3.0</td>
</tr>
</tbody>
</table>
<p>本环境各Docker镜像版本如下:</p>
<table>
<thead>
<tr>
<th style="text-align: center">镜像</th>
<th style="text-align: center">版本</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">hyperledger/fabric-tools</td>
<td style="text-align: center">2.4</td>
</tr>
<tr>
<td style="text-align: center">hyperledger/fabric-peer</td>
<td style="text-align: center">2.4</td>
</tr>
<tr>
<td style="text-align: center">hyperledger/fabric-orderer</td>
<td style="text-align: center">2.4</td>
</tr>
<tr>
<td style="text-align: center">hyperledger/fabric-ccenv</td>
<td style="text-align: center">2.4</td>
</tr>
<tr>
<td style="text-align: center">hyperledger/fabric-baseos</td>
<td style="text-align: center">2.4</td>
</tr>
<tr>
<td style="text-align: center">hyperledger/fabric-ca</td>
<td style="text-align: center">1.5</td>
</tr>
</tbody>
</table>
<blockquote>
<p>警告:建议Fabric所有实验过程皆在root权限下进行,否则在sudo权限切换的过程中会出现很多环境变量的问题。</p>
</blockquote>
<h3 id="杂项安装">杂项安装</h3>
<ol>
<li>安装最新版本<code>Git</code><pre><code class="language-Shell">apt install git
</code></pre>
</li>
<li>安装最新版本<code>cURL</code><pre><code class="language-Shell">apt install curl
</code></pre>
</li>
<li>安装<code>Golang</code></li>
<li>安装jq<pre><code class="language-Shell">apt install jq
</code></pre>
</li>
</ol>
<h3 id="安装fabric">安装Fabric</h3>
<h4 id="官方脚本安装">官方脚本安装</h4>
<p>为了帮助开发者快速搭建<code>Fabric</code>环境,官方创建了一个<code>Fabric</code>环境搭建的批处理工具<code>bootstrap.sh</code>,可以通过该工具直接安装环境:</p>
<pre><code class="language-Shell">wget https://raw.githubusercontent.com/hyperledger/fabric/master/scripts/bootstrap.sh
chmod +x bootstrap.sh
./bootstrap.sh
</code></pre>
<p>不出意外的话会看见脚本顺利的环境安装过程:<br>
<img src="https://cdn.jsdelivr.net/gh/wefantasy/FileCloud/img/202108131408966.png" alt="./bootstrap.sh" title="./bootstrap.sh" loading="lazy"></p>
<h4 id="手动安装">手动安装</h4>
<p>当然,直接使用官方脚本不出意外的话肯定会出意外(网络原因),在此我们可以通过手动安装需要的各项环境。</p>
<ol>
<li>安装fabric-samples<br>
<code>fabric-samples</code>是<code>Fabric</code>的官方Demo集合,其内部包含多个示例,每个示例有<code>Golang</code>、<code>JavaScript</code>、<code>typescript</code>、<code>Java</code>的链码实现,并且这些链码可以直接部署到对应的<code>Fabric</code>上,对初学者很有帮助。<code>fabric-samples</code>安装非常简单,使用<code>git clone git@github.com:hyperledger/fabric-samples.git</code>将项目源码克隆到本地即可,若一直失败也可以直接在release中下载对应版本的压缩包。</li>
<li>安装Fabric<br>
Fabric是联盟链的核心开发工具,包含了我们开发、编译、部署过程中的所有命令。</li>
<li>下载fabric 2.4.0并解压</li>
</ol>
<pre><code class="language-Shell">wget https://github.com/hyperledger/fabric/releases/download/v2.4.0/hyperledger-fabric-linux-amd64-2.4.0.tar.gz
mkdir /usr/local/fabric
tar -xzvf hyperledger-fabric-linux-amd64-2.3.2.tar.gz -C /usr/local/fabric
</code></pre>
<ol start="4">
<li>下载fabric-ca 1.5.2并解压</li>
</ol>
<pre><code class="language-Shell">wget https://github.com/hyperledger/fabric-ca/releases/download/v1.5.2/hyperledger-fabric-ca-linux-amd64-1.5.2.tar.gz
tar -xzvf hyperledger-fabric-ca-linux-amd64-1.5.2.tar.gz
mv bin/* /usr/local/fabric/bin
</code></pre>
<ol start="5">
<li>设置环境变量,在<code>/etc/profile</code>末尾添加</li>
</ol>
<pre><code class="language-Shell">#Fabric
export FABRIC=/usr/local/fabric
export PATH=$PATH:$FABRIC/bin
</code></pre>
<ol start="6">
<li>更新环境变量<code>source /etc/profile</code></li>
</ol>
<h3 id="安装docker">安装Docker</h3>
<ol>
<li>如果存在则移除旧的版本</li>
</ol>
<pre><code class="language-Shell">apt remove docker docker-engine docker.io containerd runc
</code></pre>
<ol start="2">
<li>更新<code>apt</code>索引包并允许其使用<code>HTTPS</code>安装</li>
</ol>
<pre><code class="language-Shell">apt update
apt install \
apt-transport-https \
ca-certificates \
curl \
gnupg \
lsb-release
</code></pre>
<ol start="3">
<li>添加<code>Docker</code>官方<code>GPG</code>密钥</li>
</ol>
<pre><code class="language-Shell">curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
</code></pre>
<ol start="4">
<li>添加<code>Docker</code>仓库</li>
</ol>
<pre><code class="language-Shell">echo \
"deb https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
</code></pre>
<ol start="5">
<li>安装<code>Docker</code>引擎</li>
</ol>
<pre><code class="language-Shell">apt update
apt install docker-ce docker-ce-cli containerd.io
</code></pre>
<ol start="6">
<li>安装<code>docker-compose</code></li>
</ol>
<pre><code class="language-Shell">apt install docker-compose
</code></pre>
<h3 id="安装docker镜像依赖">安装Docker镜像依赖</h3>
<p><code>Fabric</code>相关镜像均可以在DockerHub官方镜像网站进行下载,搜索需要的镜像则可获取安装方法,本试验用到的所有镜像为:</p>
<pre><code class="language-Shell">docker pull hyperledger/fabric-tools:2.4
docker pull hyperledger/fabric-peer:2.4
docker pull hyperledger/fabric-orderer:2.4
docker pull hyperledger/fabric-ccenv:2.4
docker pull hyperledger/fabric-baseos:2.4
docker pull hyperledger/fabric-ca:1.5
</code></pre>
<p>使用<code>docker images</code>命令查看安装完成后镜像:</p>
<pre><code class="language-Shell">hyperledger/fabric-tools 2.4 625237d887db 4 weeks ago 473MB
hyperledger/fabric-peer 2.4 ee643d889779 4 weeks ago 62.3MB
hyperledger/fabric-orderer 2.4 df64446ac2df 4 weeks ago 37.3MB
hyperledger/fabric-ccenv 2.4 da4f00cb576a 4 weeks ago 517MB
hyperledger/fabric-baseos 2.4 0287ebf8aaf3 4 weeks ago 6.94MB
hyperledger/fabric-ca 1.5 4ea287b75c63 6 months ago 69.8MB
</code></pre>
<p>示例代码中使用的镜像标签都为<code>latest</code>,但如果在<code>pull</code>时直接选择<code>latest</code>可能会报错,因此我们在上面镜像拉取完成后手动使用以下命令为镜像打上<code>latest</code>标签:</p>
<pre><code class="language-Shell"># docker tag IMAGEID(镜像id) REPOSITORY:TAG(仓库:标签)
docker tag 625237d887db hyperledger/fabric-tools:latest
docker tag ee643d889779 hyperledger/fabric-peer:latest
docker tag df64446ac2df hyperledger/fabric-orderer:latest
docker tag da4f00cb576a hyperledger/fabric-ccenv:latest
docker tag 0287ebf8aaf3 hyperledger/fabric-baseos:latest
docker tag 4ea287b75c63 hyperledger/fabric-ca:latest
</code></pre>
<p>最终的镜像为:<br>
<img src="https://cdn.jsdelivr.net/gh/wefantasy/FileCloud/img/202203301519799.png" alt="最终的镜像" title="最终的镜像" loading="lazy"></p>
<h2 id="运行测试">运行测试</h2>
<h3 id="启动fabric网络">启动fabric网络</h3>
<ol>
<li>进入fabric-sample的test-network目录</li>
</ol>
<pre><code class="language-Shell">cd fabric-samples/test-network
</code></pre>
<ol start="2">
<li>运行<code>./network.sh up </code>启动网络</li>
</ol>
<pre><code class="language-Shell">Creating network "fabric_test" with the default driver
Creating volume "docker_orderer.example.com" with default driver
Creating volume "docker_peer0.org1.example.com" with default driver
Creating volume "docker_peer0.org2.example.com" with default driver
Creating peer0.org1.example.com ... done
Creating orderer.example.com ... done
Creating peer0.org2.example.com ... done
Creating cli ... done
CONTAINER ID IMAGE COMMAND CREATED STATUS
PORTS
NAMES
7738c1e84751 hyperledger/fabric-tools:latest "/bin/bash" Less than a second ago Up Less than a second cli
1f24de2c6cd5 hyperledger/fabric-peer:latest "peer node start" 2 seconds ago Up Less than a second 0.0.0.0:9051->9051/tcp, :::9051->9051/tcp, 0.0.0.0:19051->19051/tcp, :::19051->19051/tcp peer0.org2.example.com
bfc48b20360c hyperledger/fabric-orderer:latest "orderer" 2 seconds ago Up Less than a second 0.0.0.0:7050->7050/tcp, :::7050->7050/tcp, 0.0.0.0:7053->7053/tcp, :::7053->7053/tcp, 0.0.0.0:17050->17050/tcp, :::17050->17050/tcp orderer.example.com
b9a61fdaf47a hyperledger/fabric-peer:latest "peer node start" 2 seconds ago Up Less than a second 0.0.0.0:7051->7051/tcp, :::7051->7051/tcp, 0.0.0.0:17051->17051/tcp, :::17051->17051/tcp peer0.org1.example.com
</code></pre>
<p>最终出现以上输出日志则表示网络启动成功,每个加入Fabric网络的Node和User都需要隶属于某个组织,以上网络中包含了两个平行组织————<code>peer0.org1.example.com</code>和<code>peer0.org2.example.com</code>,它还包括一个作为ordering service维护网络的<code>orderer.example.com</code>。</p>
<h3 id="创建channel">创建channel</h3>
<p>上节已经在机器上运行了peer节点和orderer节点,现在可以使用network.sh为Org1和Org2之间创建channel。channel是特定网络成员之间的私有通道,只能被属于该通道的组织使用,并且对网络的其他成员是不可见的。每个channel都有一个单独的区块链账本,属于该通道的组织可以让其下peer加入该通道,以让peer能够存储channel上的帐本并验证账本上的交易。<br>
使用以下命令创建自定义通道testchannel:</p>
<pre><code class="language-Shell">./network.sh createChannel -c testchannel
</code></pre>
<p><img src="https://cdn.jsdelivr.net/gh/wefantasy/FileCloud/img/202108151123057.png" alt="./network.sh createChannel -c testchannel" title="./network.sh createChannel -c testchannel" loading="lazy"></p>
<h3 id="部署chaincode">部署chaincode</h3>
<blockquote>
<p>建议部署操作全部在<code>root</code>账户下进行,否则可能发生未知错误,以下流程为笔者在非<code>root</code>用户下所遇问题,最终重建虚拟机全部指令在<code>root</code>账户下才完成部署。</p>
</blockquote>
<p>创建通道后,您可以开始使用智能合约与通道账本交互。智能合约包含管理区块链账本上资产的业务逻辑,由成员运行的应用程序网络可以在账本上调用智能合约创建,更改和转让这些资产。可以通过<code>./network.sh deployCC</code>命令部署智能合约,但本过程可能会出现很多问题。<br>
使用以下命令部署chaincode:</p>
<pre><code class="language-Shell">./network.sh deployCC -c testchannel -ccn basic -ccp ../asset-transfer-basic/chaincode-go -ccl go
</code></pre>
<p>此命令执行后可能会出现错误:<code>scripts/deployCC.sh: line 114: log.txt: Permission denied</code>,很明显这是权限不足所致,加上sudo试试:</p>
<pre><code class="language-Shell">./network.sh deployCC -c testchannel -ccn basic -ccp ../asset-transfer-basic/chaincode-go -ccl go
</code></pre>
<p>加上sudo后出现新的错误:<code>deployCC.sh: line 59: go: command not found</code>。检查本用户<code>go</code>命令可用,检查<code>root</code>用户<code>go</code>命令可用,单单<code>sudo</code>后不能用。查阅资料后发现这是因为<code>linux</code>系统为了安全,限制在使用<code>sudo</code>时会清空自定义的环境变量,最简单的解决方法是在<code>/etc/sudoers</code>文件中直接将该限制注释<sup class="footnote-ref"></sup>:<br>
<img src="https://cdn.jsdelivr.net/gh/wefantasy/FileCloud/img/202108131722335.png" alt="vim /etc/sudoers" title="vim /etc/sudoers" loading="lazy"><br>
加上注释后重新执行上条命令,又出现了新的错误:</p>
<pre><code class="language-Shell">go: github.com/golang/protobuf@v1.3.2: Get "https://proxy.golang.org/github.com/golang/protobuf/@v/v1.3.2.mod": dial tcp 172.217.160.81:443: i/o timeout
</code></pre>
<p>很明显这是因为本地网络无法访问proxy.golang.org所致,在命令行输入<code>go env -w GO111MODULE=on && go env -w GOPROXY=https://goproxy.cn,direct</code>命令配置国内代理<sup class="footnote-ref"></sup>后再次执行。令人意外的是错误不变,设置的代理没有生效?手动使用<code>go get github.com/golang/protobuf</code>手动下载安装后再次运行错误还是不变,此时检查本地<code>GOPATH</code>目录下已有<code>github.com/golang/protobuf</code>包,为什么没有识别到?此时灵机一动,使用<code>go env</code>查看<code>GOPATH</code>环境变量,发现与本地用户不一致,原来<code>sudo</code>命令会使用<code>root</code>的<code>go</code>环境变量,而之前设置的代理、下载的包都只能在本地用户下生效,因此这个问题最终的解决方案是直接切换到<code>root</code>用户下重新配置<code>go</code>代理并运行。成功运行后可看见如下结果:</p>
<pre><code class="language-Shell">2021-08-15 00:45:54.064 PDT ClientWait -> INFO 001 txid committed with status (VALID) at localhost:7051
2021-08-15 00:45:54.144 PDT ClientWait -> INFO 002 txid committed with status (VALID) at localhost:9051
Chaincode definition committed on channel 'testchannel'
Using organization 1
Querying chaincode definition on peer0.org1 on channel 'testchannel'...
Attempting to Query committed status on peer0.org1, Retry after 3 seconds.
+ peer lifecycle chaincode querycommitted --channelID testchannel --name basic
+ res=0
Committed chaincode definition for chaincode 'basic' on channel 'testchannel':
Version: 1.0, Sequence: 1, Endorsement Plugin: escc, Validation Plugin: vscc, Approvals:
Query chaincode definition successful on peer0.org1 on channel 'testchannel'
Using organization 2
Querying chaincode definition on peer0.org2 on channel 'testchannel'...
Attempting to Query committed status on peer0.org2, Retry after 3 seconds.
+ peer lifecycle chaincode querycommitted --channelID testchannel --name basic
+ res=0
Committed chaincode definition for chaincode 'basic' on channel 'testchannel':
Version: 1.0, Sequence: 1, Endorsement Plugin: escc, Validation Plugin: vscc, Approvals:
Query chaincode definition successful on peer0.org2 on channel 'testchannel'
Chaincode initialization is not required
</code></pre>
<h3 id="合约交互">合约交互</h3>
<p>在安装fabric中我们已经设置了<code>fabric</code>可执行文件的环境变量,需保证可以成功在<code>test-network</code>目录下使用<code>peer</code>命令。</p>
<ol>
<li>设置FABRIC_CFG_PATH变量,其下需包含core.yaml文件</li>
</ol>
<pre><code class="language-Shell">export FABRIC_CFG_PATH=$PWD/../config/
# export FABRIC_CFG_PATH=/usr/local/fabric/config/
</code></pre>
<ol start="2">
<li>设置其它<code>Org1</code>组织的变量依赖</li>
</ol>
<pre><code class="language-Shell"># Environment variables for Org1
# CORE_PEER_TLS_ROOTCERT_FILE和CORE_PEER_MSPCONFIGPATH环境变量指向Org1的organizations文件夹中的身份证书。
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051
</code></pre>
<ol start="3">
<li>初始化chaincode</li>
</ol>
<pre><code class="language-Shell">peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C testchannel -n basic --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"function":"InitLedger","Args":[]}'
</code></pre>
<p><img src="https://cdn.jsdelivr.net/gh/wefantasy/FileCloud/img/202108151634613.png" alt="初始化chaincode" title="初始化chaincode" loading="lazy"><br>
4. 查询账本资产列表</p>
<pre><code class="language-Shell">peer chaincode query -C testchannel -n basic -c '{"Args":["GetAllAssets"]}'
</code></pre>
<p><img src="https://cdn.jsdelivr.net/gh/wefantasy/FileCloud/img/202108151637440.png" alt="查询账本资产列表" title="查询账本资产列表" loading="lazy"><br>
5. 修改账本资产</p>
<pre><code class="language-Shell">peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C testchannel -n basic --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"function":"TransferAsset","Args":["asset6","Christopher"]}'
</code></pre>
<p><img src="https://cdn.jsdelivr.net/gh/wefantasy/FileCloud/img/202108151646082.png" alt="修改账本资产" title="修改账本资产" loading="lazy"><br>
6. 关闭网络</p>
<pre><code class="language-Shell">./network.sh down
</code></pre>
<p>该命令将停止并删除节点和链码容器、组织加密材料、删除之前运行的通道项目和docker卷,并从Docker Registry移除链码镜像。</p>
<blockquote>
<p>因为<code>asset-transfer (basic)</code>链码的背书策略需要交易同时被<code>Org1</code>和<code>Org2</code>签名,所以链码调用指令需要使用<code>--peerAddresses</code>标签来指向<code>peer0.org1.example.com</code>和<code>peer0.org2.example.com</code>;因为网络的<code>TLS</code>被开启,指令也需要用<code>--tlsRootCertFiles</code>标签指向每个<code>peer</code>节点的<code>TLS</code>证书。</p>
</blockquote>
<p>相关实验源码已上传:https://github.com/wefantasy/FabricLearn</p>
<h2 id="参考">参考</h2>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>qq_JWang_03215367. 解决command not found 报错. 慕课. ↩︎</p>
</li>
<li id="fn2" class="footnote-item"><p>沐沐子枫. failed to normalize chaincode path: 'go list' failed with: go. 博客园. ↩︎</p>
</li>
</ol>
</section><br><br>
来源:https://www.cnblogs.com/ifantasy/p/16106803.html
頁:
[1]