Hyperledger fabric入门 day1

Hyperledger fabric入门 day1

Fabric技术特性

在Fabric之前的区块链平台如比特币或者以太坊,一般都只有一个账本,所有的记录都在一个账本中。这样导致这个账本非常大,比如现在比特币的账本已经有160G左右。

多账本特性

在Fabric中有一个被称为通道(Channel)的概念。一个通道包含若干个成员,这些成员共享账本数据并且共同维护账本。通道内所有成员共享账本数据并且共同维护账本。一个通道可以包含多个会员,一个会员也可以在权限允许的情况下加入多个通道。同时不同的通道中账本数据的格式也是不一样的,Fabric中账本的存储方式被设计成插件的形式。不同的会员可以根据自己的实际情况选择不同的数据存储方式

Fabric的账本具有以下特点

  • 使用基于Key的查询、范围查询、复合键查询来查询或更新账本。
  • 只读查询支持丰富的查询语句(CouchDB)
  • 只读的历史查询——实现数据追溯场景。
  • 交易包含读取链码键值对(读集),以及写入链码键值对(写集)的版本。
  • 交易包含所有背书节点提交至排序服务的签名
  • 交易被打包排序成区块,并通过通道从共识节点传至对等节点。
  • 对等节点通过背书政策标准来验证交易
  • 在增加区块前,需要执行版本检查以确保数据在链码执行时间段没有被篡改
  • 当交易被验证并承诺后,便不可改变
  • 每个通道的账本都包含配置区块,它定义了政策标准、访问控制列表与其他相关信息。
  • 通道包含了会员服务提供商实例,因此加密证书能传递到不同的证书颁发机构。

Fabric的智能合约

Fabric中的智能合约成为链码(Chaincode),Chaincode是一段用计算机语言编写的程序。Chaincode运行在容器中,Fabric通过Chaincode可以读取和修改账本数据,同时会把交易的日志保存在状态数据库中。Chaincode可以通过多种编程语言来开发,目前支持Go、Java、Node.js等语言。

Fabric的权限系统

Fabric和其他区块链的最大区别是:Fabric的网络是不公开的,如想进入网络中必须获取授权,因此Fabric可以说是联盟链。

在Fabric中没有采用类似POW这样的共识算法,因此成员如果想加入网络必须获取授权,否则不论算力多大都无法进入网络。为了解决成员授权加入的问题,Fabric中有一个会员服务系统(Membership Service Provider,MSP)。MSP是基于PKI规范而建立的一个用户证书和私钥体系。

Fabric的共识算法

Fabric支持Solo(单节点共识)、Kafka(分布式队列)和SBFT(简单拜占庭容错)三种共识方式。

Solo方法

指在单个节点中完成排序的方法,这种模式安全性和稳定性都比较差,如果单个节点出现问题,那么整个区块链系统都无法运行。因此Solo模式通常只是用在演示系统和本机开发环境中。

Kafka

是一种高吞吐量的分布式发布订阅消息系统。在Fabric的Kafka模式中,排序节点从Kafka集群里获取响应topic(Kafka的分区,用于在队列里隔离出多个数据域)的数据,以保证交易数据有序,这里借助了Kafka的分布式一致机制实现对交易的排序。同时借助Kafka,排序节点还可以进行集群,这样能有效地避免单点故障而导致整个网络崩溃的问题。

Fabric模块在系统中的作用

peer模块在Fabric系统中的作用

1.提交节点(Committer Peer)

Committer节点主要负责维护区块链的账本结构,该节点会定期从Orderer节点获取包含交易的区块,在对这些区块进行合法发行校验之后,会把这些区块加入到区块链中。Committer节点无法通过配置文件配置,当前客户端或者命令行发起交易请求的时候需要指定相关的Committer节点。

2.背书节点(Endorse Peer)

Endorse节点主要负责对交易进行校验。当Endorse节点接收到客户端发送的交易请求之后会对交易的合法性进行校验,校验成功之后会将结果反馈给客户端。**Endorse节点也是无法通过配置文件指定的,而是由发起交易请求的客户端指定的。**Endorse节点在组织中可以有多个。

3.Leader节点(Leader Peer)

Leader节点是负责代表组织从Orderer节点中获取区块信息。Leader节点在整个组织中只有一个。**Endorse节点的产生方式是通过peer模块的配置文件core.yaml进行配置的。**配置文件有两种方式制定Leader节点,分别是:自主选举和强制指定。Leader节点相关的配置信息如下所示:

Copy# core.yaml
peer:
gossip:
userLeaderElection: true	# 是否自动选举Leader节点
orgLeader: false	# 是否代表组织从Orderer获取区块链数据
# 自主选举配置如下:
peer:
gossip:
useLeaderElection: true
orgLeader: false

自主选举的模式,系统会根据gossip协议自动在组织中选择某一个Peer节点为Leader节点。

强制指定的配置模式如下:

Copy# core.yaml
peer:
gossip:
useLeaderElection: true
orgLeader: false

选择强制指定的模式,当前Peer节点会自动被设定为Leader节点。在强制指定方式设定Leader节点的模式中,一个组织内只能有一个固定节点作为Leader节点存在。

在实际的项目中,我们建议采用自主选举的模式。因为在该模式下,如果被选举的Leader节点发生故障,系统会自动选取另外一个节点作为Leader节点。

4.锚节点(Anchor Peer)

**锚节点主要负责代表节点和其他组织进行信息交换。**每个组织都有一个锚节点,锚对于组织来说非常重要,如果锚节点出现问题,那么组织和其他组织会失去联系。锚节点也是通过配置的方式指定。**锚节点的配置信息在configtxgen模块的配置文件configtx.yaml中配置的。**configtx.yaml文件中关于锚节点的配置信息如下:

Copyorganization:
- &Org1
AnchorPeers:
- Host: peer0.org1.example.com
Port: 7051
  • Host属性:Host属性表示本组织锚节点的访问地址,可以是IP地址或者域名,建议采用域名
  • Port属性:Port属性表示本组织锚节点的访问端口。

任何组织都必须保证本组织内锚节点服务器处于可访问的状态。

orderer模块在Fabric系统中的作用

**在Fabric中,orderer模块负责对不同客户就客户端发送的交易进行排序和打包。**目前Orderer组件提供了两种打包模式:Solo模式和Kafka模式。其中Solo模式太简单仅用于开发模式,在生产系统中推荐用Kafka模式。orderer模块的工作原理如下:

  • 客户端向Oderer模块发送交易
  • Orderer节点对交易进行检查,如果符合条件,则将交易发送到排序队列(Solo模式在本地,Kafka模式会提交给Kafka)。
  • Orderer节点从消息队列中对取出交易并进行打包。打包之后会将相关的消息存储到本地存储中。
  • Orderer节点根据客户端代码请求,将区块链发送给客户端。

Fabric数据安全传输的方式

为了保证数据传输中的安全性,Fabric提供了一系列相关的组件和配置,这些组建和配置在Fabric中被统称为Transport Layer Security(TLS)。TLS不是Fabric中的必选项,可以通过相关的配置参数激活或者关闭。

安全传输层协议(TLS)用于在两个通信应用程序之间提供保密性和数据完整性

Fabric中orderer模块TLS设置

orderer模块中TLS相关的设置主要包括配置文件和环境变量两种方式。

  1. orderer模块在配置文件中设置TLS的样例如下所示:

    CopyGeneral:
    TLS:
    Enabled: true
    PrivateKey: /opt/hyperledger/.../orderers/orderer.example.com/tls/server.key
    Certificate: /opt/hyperledger/.../orderers/orderer.example.com/tls/server.crt
    RootCAs:
    - /opt/hyperledger/.../orderers/orderer.example.com/tls/ca.crt
    ClientAuthEnabled: false
    ClientRootCAs:
    
  2. orderer模块在环境变量中设置TLS的样例如下所示:

    CopyORDERER_GENERAL_TLS_ENABLED = true
    ORDERER_GENERAL_TLS_PRIVATEKEY = /opt/hyperledger/.../orderers/orderer.example.com/tls/server.key
    ORDERER_GENERAL_TLS_CERTIFICATE = /opt/hyperledger/.../orderer/orderer.example.com/tls/server.crt
    
  3. orderer模块TLS相关配置的详细解释如下:

    • General.TLS.Enabled: TLS激活标记,true标识激活,false标识关闭。
    • General.TLS.PrivateKey: 服务器私钥文件路径。
    • General.TLS.Certificate: 服务器数字证书文件路径。
    • General.TLS.RootCAs: 根CA服务器证书文件的路径。

Fabric中peer模块TLS设置

peer模块中TLS相关的设置主要包括配置文件和环境变量两种方式。但是由于peer模块有服务器节点(peer node命令)和命令行接口两种运行模式,这两种的运行模式有写区别。

  1. peer模块作为服务器节点运行时TLS的设置

    peer模块作为服务器节点运行时,可以通过配置文件和环境变量这两种方式设置TLS相关的配置信息,这两种方式分别如下所示:

    1. 配置文件中TLS属性的设置方式

      Copypeer:
      tls:
      enabled: true
      cert:
      file: /opt/hyperledger/.../peers/peer0.org1.example.com/tls/server.crt
      key: 
      file: /opt/hyperledger/.../peers/peer0.org1.example.com/tls/server.key
      rootcert:
      file: /opt/hyperledger/.../peers/peer0.org1.example.com/tls/ca.crt
      serverhostoverride:
      
    2. 环境变量中TLS属性的设置方式

      CopyCORE_PEER_TLS_ENABLED=false
      CORE_PEER_TLS_CERT_FILE=/opt/hyperledger/.../peers/peer0.org1.example.com/tls/server.crt
      CORE_PEER_TLS_KEY_FILE=/opt/hyperledger/.../peers/peer0.org1.example.com/tls/server.key
      CORE_PEER_TLS_ROOTCERT_FILE=/opt/hyperledger/.../peers/peer0.org1.example.com/tls/ca.crt
      

    相关配置选项的详细解释如下所示:

    • peer.tls.enabled: TLS激活标记,true表示激活,false表示关闭。
    • peer.tls.privatekey: 服务器私钥文件路径。
    • peer.tls.certificate: 服务器数字证书文件路径。
    • peer.tls.rootCAs: 根CA服务器证书文件的路径。
  2. peer模块采用命令行工具的方式运行时TLS属性的设置

    peer模块的命令行选项中有一些操作需要和远程的peer模块服务器节点或者orderer模块进行通信,这个时候可以选择采用TLS的方式以提高安全性。以peer模块的Chaincode选项为例,采用TLS方式实例化Chaincode的命令格式如下所示:

    Copypeer chaincode instantiate ...... --tls --cafile /opt/hyperledger/.../orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
    

Fabric账号

Fabric账号是什么

Fabric中的账号实际上是根据KPI规范生成的一组证书和密钥文件。我们通常接触到的账号系统一般是由账号和密码两个属性组成的,比如常用的电子邮箱系统。在这些系统中账号和密码只是获取操作权限的工具,一旦账号和密码验证成功,后面的操作基本上就和账号密码没有什么关系了。

但是区块链系统的一个非常重要的特点是:记录在区块链中的数据具有不可逆、不可篡改的特性。在Fabric中每条交易都会加上发起者的标签(签名证书),同时用发起人的私钥进行加密。如果交易需要其他组织的节点提供背书功能,那么背书节点也会在交易中加入自己的签名。这样每一笔交易的操作过程会非常清晰并且不可篡改。鉴于传统系统中基于账号和密码的验证体系已经无法完成这样的工作,因此Fabric设计了基于PKI规范的账号系统满足这样的要求。

一个完整的Fabric账号中包括的内容如下所示:

Copy|--- msp
|   |--- admincerts
|   |--- cacerts
|   |--- keystore
|   |--- signcerts
|   |--- tlscacerts
|--- tls
    |--- ca.crt
    |--- server.crt
    |--- server.key

每个Fabric账号包含若干证书文件和密钥文件

1.msp文件夹中的内容

msp中主要存放签名用的证书文件和加密用的私钥文件。msp中包含以下5个部分:

  • admincerts:管理员证书
  • cacerts:根CA服务器的证书
  • keystore:节点或账号的私钥
  • signcerts:符合X.509的节点或者用户证书文件
  • tlscacerts:TLS根CA的证书
2.tls文件夹中的内容

tls文件夹中存放加密通信相关的证书文件。这一组文件实际上起到了账号的作用。我们把这样一组文件成为Fabric的账号。

Fabric的官方文档把这些证书称为Membership Service Providers,简称为MSP

什么地方需要使用Fabric的账号

Fabric中Orderer、Peer、客户端SDK、CLI接口等所有操作都需要账号。Fabric中每个具体的动作,创建通道、部署Chaincode、调用Chaincode等都需要指定的账号。每个Peer向Orderer发送请求的时候也需要Peer的账号。在Fabric中如果需要新增加一个Peer节点,首先的事情是给这个Peer创建账号。

1.启动Orderer需要的账号

启动Orderer的时候我们需要通过环境变量或者配置文件给当前启动的Orderer设定相应的账号。

环境变量设置账号:

CopyORDERER_GENERAL_LOCALMSPDIR=/opt/hyperledger/.../orderers/orderer.example.com/msp

配置文件为General节点下面的LocalMSPDir子节点,如下所示:

CopyGeneral:
LocalMSPDir: /opt/hyperledger/.../orderers/orderer.example.com/msp
2.启动Peer需要的账号

启动Peer的时候我们需要通过环境变量或者配置文件给当前启动的Peer设定相应的账号。

环境变量设置账号:

Copyexport set CORE_PEER_MSPCONFIGPATH=/opt/hyperledger/.../peers/peer0.org1.example.com/msp

配置文件为Peer节点下面的mspConfigPath子节点,如下所示:

Copypeer:
mspConfigPath:/opt/hyperledger/.../peers/peer0.org1.example.com/msp
3.创建Channel需要用到的账号

Channel是Fabric中非常重要的组成部分,创建Channel的时候也是需要账号的。

环境变量设置账号:

Copyexport set CORE_PEER_MSPCONFIGPATH=/opt/hyperledger/.../users/Admin@org1.example.com/msp

Orderer、Peer、Channel启动账号的路径对比:

Copy# Orderer启动的账号路径
ordererOrganizations/example.com/orderers/orderer.example.com/msp
# Peer启动的账号路径
peerOrgnizations/org1.example.com/peers/peer0.org1.example.com/msp
# 创建Channel的账号路径
peerOrgnizations/org1.example.com/users/Admin@org1.example.com/msp

我们发现Peer和Orderer都有属于自己的账号,创建Channel使用的是用户账号。其中Peer和创建Channel的用户账号属于某个组织,而Orderer的启动账号只属于他自己。

增加新账号和用户

通过使用cryptogen模块生成账号的默认模板如下:

CopyOrdererOrgs:
- Name: Orderer
Domain: example.com
Specs:
- Hostname: orderer
PeerOrgs:
- Name: Org1
Domain: org1.example.com
Template:
Count: 1
Users:
Count: 1
- Name: Org2
Domain: org2.example.com
Template:
Count: 1
Users: 
Count: 1

在配置文件中PeerOrgs节点Template子节点中有个属性Count,Count表示当前组织包含Peer节点的数目,同时也会生成相应数目的配置文件。

如果需要增加新的节点,可以给Start属性赋值上一次的Count。

如果要给默认模板的Org1新增加三个Peer节点,可以使用下面的配置:

CopyPeerOrgs:
- Name: Org1
Domain: org1.example.com
Template:
Count: 3
Start: 1

通过上面的配置文件,cryptogen模块可以给组织Org1新增加三个Peer节点的配置文件。

Fabric账号服务器:Fabric-ca

Fabric-ca项目是专门为了解决Fabric账号问题而发起的一个开源项目,它非常完美地解决了Fabric账号生成的问题。Fabric-ca项目由Fabric-ca-server和Fabric-ca-client这两个模块组成。

Fabric-ca的编译和安装

Copy# 安装组件包
sudo apt install libtool libltdl-dev
# 下载源码并编译
cd $GOPATH/src/github.com/hyperledger
git clone http://gerrit.hyperledger.org/r/fabric-ca
cd $GOPATH/src/github.com/hyperledger/fabric-ca
make fabric-ca-server
make fabric-ca-client
# 安装编译好的可执行文件
cd $GOPATH/src/github.com/hyperledger/fabric-ca/bin
cp $GOPATH/src/github.com/hyperledger/fabric-ca/bin/* /usr/local/bin
chmod -R 775 /usr/local/bin/fabric-ca-server
chmod -R 775 /usr/local/bin/fabric-ca-client
# 检查是否安装正确
fabric-ca-server version
fabric-ca-client version
# 显示版本信息为安装正确

fabric-ca-server的启动和配置

fabric-ca-server启动之后是以守护进程方式存在,可以通过fabric-ca-client或者实现其通信协议的客户端发起请求。fabric-ca-server有三种方式设置配置信息,分别是启动函数、环境变量和配置文件。

fabric-ca-server的命令行选项

fabric-ca-server模块有三个子命令,这三个子命令分别是:

  • init:初始化fabric-ca服务器
  • start:启动fabric-ca服务器
  • version:显示版本
fabric-ca-server的选项
  • –address:Fabric-ca服务器的监听地址(默认为“0.0.0.0”)
  • -b,–boot:系统启动对应的管理员账号和密码
  • 。。。
fabric-ca-server的初始化

除了命令行参数,fabric-ca-server还可以通过配置文件进行参数设置。执行fabric-ca-server的命令init,可以初始化生成相关的文件夹,例子:

Copymkdir -p /opt/hyperledger/fabric-ca-server
# fabric-ca-server服务器初始化的命令
fabric-ca-server init -b admin:adminpw

-b 参数后面的是fabric-ca-server服务器管理账号的用户名和密码

fabric-ca-server初始化命令和执行完成之后会在当前的目录下面生成相应的配置文件,这些文件的名字及其作用如下所示:

  • fabric-ca-server-config.yaml:配置文件
  • fabric-ca-server.db:数据库文件(数据库选择sqlite3时有效)
  • ca-cert.pem:证书文件
  • msp:私钥文件夹
fabric-ca-server的配置文件

fabric-ca-server的配置文件一共分为11个部分

通用配置部分

通用配置部分包含了一些公用属性,比如端口、运行模式等,具体配置信息如下所示:

Copyport: 7054	# 监听端口号
debug: false	#是否调试
crlsizelimit: 512000
cacount:	# 支持的CA数目
cafiles:	# 相关CA配置文件
crl:
expiry: 24h	# 授权证书的有效期
tls部分

tls部分主要包含了TLS通信相关的配置,包括是否需要打开TLS通信,TLS通信的证书和私钥文件的路径等,具体配置文件如下所示:

Copytls:
enabled: false	# 是否启用TLS
certfile: tls-cert.pem	# TLS证书文件
keyfile:
clientauth:
type: noclientcert	# 客户端类型
certfiles:	# 客户端证书类型
ca部分

ca服务器属性的配置,包含发布证书的组织机构的名称和相关的证书文件路径等,具体配置信息如下所示:

Copyca:
name:	# CA的名字,如果存在多个CA服务器,不能重复
keyfile: ca-key.pem	# 私钥文件
certfile: ca-cert.pem	# 证书文件
chainfile: ca-chain.pem	# 证书链文件
registry部分

registry节点包含了客户端注册相关的信息,具体配置信息如下所示:

Copyregistry:
maxenrollments: -1
identities:	# 注册实体信息,可以有多个
- name: admin
pass: adminpw
type: client
affiliation: ""
maxenrollments: -1
attrs:
hf.Registrar.Roles: "client, user, peer, validator, auditor"
hf.Registrar.DelegateRoles: "client, user, validator, auditor"
hf.Revoker: true
hf.IntermediateCA: true
hf.GenCRL: true
hf.Registrar.Attributes: "*"

其中,maxenrollments是同一个用户名和密码允许执行enrollment的次数,-1为不限制,0表示不支持登记。

db部分

db部分包含了Fabric-ca服务器存储账号文件的数据类型的配置,Fabric-ca服务器目前支持sqlite3、postgres和mysql三种数据库,选择任何一种数据库在启动Fabric-ca服务器之前都需要安装,Fabric-ca本身不会自动安装这些数据库引擎。db部分配置示例如下:

Copydb:
type: sqlite3	# 数据库类型
datasource: fabric-ca-server.db	# 数据库连接方式,不同数据库是不同的
tls:	# TLS加密通信配置属性
enabled: false
certfiles:
- db-server-cert.pem
client:
certfile: db-client-cert.pem
keyfile: db-client-key.pem

db部分的配置中选择不同的数据库服务器配置格式是不一样的,不同的数据库对应的配置格式是不一样的,具体配置信息如下所示:

选择sqlite3存储账号:

Copytype: sqlite3
datasource: fabric-ca-server.db

系统启动会在启动目录下面生成数据库文件fabric-ca-server.db,数据库文件名由datasource节点设置,该文件为sqlite3的数据库文件。

选择mysql存储账号:

Copydb:
type: mysql
datasource: root:rootpw@tcp(localhost:3306)/fabric_ca? parseTime=true&tls=custom

选择postgres存储账号:

Copydb:
type: postgres
datasource: host=localhost port=5432 user=Username password=Password dbname=fabric_ca sslmode=verify-full

如果使用mysql或者postgres存储账号,可以选择配置采用TLS加密的方式和服务器进行通信。具体配置信息如下所示:

Copytls:
enabled: true	# 是否采用加密的方式和服务器进行通信
certifiles:	# TLS加密通信的证书文件
- db-server-cert.pem
client:
certfile: db-client-cert.pem	# 客户端TLS通信证书文件
keyfile: db-client-key.pem	# 客户端TLS通信私钥文件
Idap部分

Fabric-ca可以配置使用远端LDAP服务器来进行注册管理并且保存注册相关的数据,LDAP服务相关的配置信息包含在Idap节点中,具体配置信息如下:

Copyldap:
ldap:
enbaled: true
url: ldap://cn-admin, dc=example, dc=org:admin@localhost:10389/dc=example, dc=org
userfilter: (uid=%s)
tls:
certfiles:
- ldap-server-cert.pem
client:
certfile: ldap-client-cert.pem
keyfile: ldap-client-key.pem
affiliations部分

affiliations节点包含了组织中的部门的相关配置信息,这些配置信息在客户端SDK调用时相关的参数必须保持一致。affiliations配置信息如下所示:

Copyaffiliations:
org1: 	# 组织org1中的部门
- department1
- department2
org2:	# 组织org2中的部门
- department1

在通过Fabric的客户端SDK访问Fabric-ca服务器的时候,相关的方法需要设置affiliations的参数时,需要跟这里的配置保持一致,否则无法正确访问。

signing节点

signing节点包含了证书签发相关的配置,包括证书的到期时间等属性。signing相关的配置信息如下:

Copysigning:
default:	# 默认的签发Ecert
usage: 	# 证书签发的作用域
- digital signature
expiry: 8760h	# 证书有效时间
profiles:
ca:		# 本节点作为父节点给下层节点签发时的模板
usage:
- cert sign
expiry: 43800h
caconstraint:
isca: true
maxpathlen: 0	# 先在中间层继续下发节点
tls:	# TLS通信相关配置
usage:
- signing
- key encipherment
- server auth
- client auth
- key agreement
expiry: 8760h
csr部分

csr节点包含了证书申请请求时需要使用的配置信息。如果当前CA服务器是作为根CA服务器存在的,那么需要设置这些属性。csr相关的配置信息如下:

Copycsr:
cn: fabric-ca-server	# 服务器名称
names:		# 证书签发单位的基本信息
- C: US
ST: "North Carolina"
L:
O: Hyperledger
OU: Fabric
hosts:
- roberfeng-All-Series
- localhost
ca:
expiry: 13140h	# 证书有效时间
pathlength: 1	# 下一级服务器是否可以继续其下级服务器签发证书,-1为不可,取值大于0时为允许的层级数
bccsp节点

bccsp节点包含了加密算法相关的配置,在bccsp节点中可以选择相关的加密算法以及相关加密算法的证书文件。bccsp节点的配置信息如下:

Copybccsp:
default: SW		# 加密方式,硬件加密和程序加密
sw:
hash: SHA2
security: 256
filekeystore:
keystore: msp/keysore
intermediate部分

当前CA作为中间层时相关的配置。如果当前CA服务器需要从上级CA服务器获取授权才能工作,需要配置intermediate节点的相关属性。

Copyintermediate:
parentserver:		# 上级CA服务器相关信息
url:
cname:
enrollment:		# 需要在上级CA服务器进行登记的信息
hosts:		# 上级CA服务器地址列表,用逗号分隔
profile: 	# 签发用的profile
label:		# 用于HSM操作的标签信息
tls: 	# TLS通信相关的属性信息
certfiles: 	# 根证书文件
client: 	# 如果启用客户端文件需要的使用到的部分
certfile: 	# 客户端证书文件
keyfile: 	# 客户端私钥文件
fabric-ca-server的启动
Copyfabric-ca-server start -H /opt/hyperledger/fabric-ca --boot admin:adminpw

启动完成后可以通过客户端或者通过fabric-ca-client模块访问fabric-ca-server服务器

fabric-ca-client的使用

fabric-ca-server提供了一组RESTAPI接口供第三方应用程序调用。fabric-ca-client对这些RESTAPI接口进行了封装,只需设置简单的参数便可以完成账号注册、账号授权等操作。fabric-ca-client模块由一组子命令和相关的参数选项组成

fabric-ca-client模块的子命令
  • enroll:登记账号
  • gencrl:撤销证书
  • gencsr:创建证书签名
  • getcacert:获取CA链证书
  • reenroll:重新登记账号
  • register:注册一个新账号
  • revoke:撤销一个账号
  • version:显示版本信息
fabric-ca-client模块的参数选项
  • –caname:CA服务器名称
  • -H,–home:客户端的目录,用来存放客户端相关的文件(默认值为“/root/.fabric-ca-client")
  • 。。。
fabric-ca-client常用命令

注册新账号

Copyfabric-ca-client register --id.name peer2 --id.type peer --id.affiliation org1.department1 --id.secret peer2wd -u http://localhost:7054

载入账号信息

Copyfabric-ca-client enroll -M /usr/fabric-test1/msp -u http://peer1:peer1pw@localhost:7054

获取CA服务器的证书

Copyfabric-ca-client getcacert -u http://localhost:7054 -M /usr/fabric-test1/msp

将fabric-ca-server绑定到现有项目中

绑定fabric-ca-server到现有组织

首先打开fabric-ca-server的配置文件fabric-ca-server-config.yaml,在配置文件中找到以下内容:

Copyca:
name: ca-org1
keyfile:
certfile:
chainfile: ca-chain.pem

进入生成证书的文件夹,进入存放组织org1相关证书的文件夹

将ca文件夹绑定到fabric-ca-server的配置文件中

Copyca:
name: ca-org1
keyfile: /opt/hyperledger/.../peerOrganizations/org1.example.com/ca/......_sk
certfile: /opt/hyperledger/.../peerOrganizations/org1.example.com/ca/ca.org1.example.com-cert.pem
chainfile: ca-chian.pem
  • keyfile:对应ca文件夹中文件名后缀为_sk的文件
  • certfile:对应ca文件夹中文件名为ca.org1.example.com-cert.pem的文件
通过客户端从已经绑定到fabric-ca-server中生成账号

设置fabric-ca-client环境变量

fabric-ca-client在使用前需要创建一个目录,该目录存放fabric-ca-client的账号(msp)文件,示例:

Copymkdir -p /opt/hyperledger/fabric-client

目录创建完成之后,利用管理员账号和密码注册,获取管理员账号(msp)的证书文件。

Copyexport FABRIC_CA_CLIENT_HOME=/opt/hyperledger/fabric-client
fabric-ca-client enroll -u http://admin:adminpw@localhost:7054 -M /opt/hyperledger/fabric-client

注册账号

注册一个用户名为usertest,密码为user2wd的账号,注册命令如下:

Copyfabric-ca-client register --id.name usertest --id.type user --id.affiliation org1.department1 --id.secret user2wd -u http://localhost:7054

载入账号

将上一步注册的账号usertest加载到本地,首先需要在本地创建要给目录用来存放从服务器下载的证书,目录可以时任何目录,示例:

Copymkdir -p /opt/hyperledger/exampleuser

在上述命令创建的目中,登记账号usertest,并将登记成功的账号的相关文件保存到指定目录中。登记账号:

Copyfabric-ca-client enroll -u http://usertest:user2wd@localhost:7054 -M /opt/hyperledger/exampleuser/msp

复制管理员签名和公用TLS证书文件

Copy# 复制管理账号的签名的命令
mkdir /opt/hyperledger/exampleuser/msp/admincerts
cp /opt/hyperledger/.../peerOrgnizations/org1.example.com/users/Admin@org1.example.com/msp/signcerts/* /opt/hyperledger/exampleuser/msp/admincerts

# 复制公用TLS签名证书的命令
mkdir /opt/hyperledger/exampleuser/tls
cp /opt/hyperledger/.../peerOrgnizations/org1.example.com/peers/peer0.org1.example.com/tls/* /opt/hyperledger/exampleuser/tls

Hyperledger fabric入门 day2

Hyperledger fabric入门 day2

Fabric链码规范

Copy/* 
	simplechaincode可以自己进行命名
	chaincode结构体是chaincode的主体结构。chaincode结构体需要实现Fabric提供	的接口"github.com/hyperledger/fabric/protos/peer",其中必须实现以下的	Init和Invoke方法
*/
type simplechiancode struct {
}

/*
系统初始化方法,在部署chaincode的过程中当执行命令的时候会调用该方法
peer chaincode instantiate -o orderer.example.com:7050 -C <通道名> -n <链码名称> -v <版本号> -c <传入参数> -P <策略>
*/
func (t *simplechaincode) Init(stub shim.ChaincodeStubInterface) peer.Response {
    // 获取参数
    _, args := stub.GetFunctionAndParameters()
}

/*
主业务逻辑,在执行命令的时候会调用该方法并传入相关的参数,注意“invoke”之后的参数是需要传入的参数
peer chaincode invoke -o 192.168.23.212:7050 -C <通道名> -n <链码名称> -c <传入参数>
*/
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    
}

func main() {
    err := shim.Start(new(simplechiancode))
    if err != nil {
        fmt.Printf("error: %s", err)
    }
}

链码的安装和调用

在Chaincode安装之前首先要确保已经成功编译安装了Fabric的相关子模块,并且要保证已经正确启动Orderer节点和Peer节点

在部署前查看当前Peer节点已经加入了哪些Channe

Copyexport set FABRIC_CFG_PATH=/opt/hyperledger/peer
export set CORE_PEER_LOCALMSPID=Org1MSP
export set CORE_PEER_ADDRESS=peer0.org1.example.com:7051
export set CORE_PEER_MSPCONFIGPATH=/opt/hyperledger/.../peerOrgnizations/org1.example.com/users/Admin@org1.example.com/msp

peer channel list

部署

Copyexport set FABRIC_CFG_PATH=/opt/hyperledger/peer
export set CORE_PEER_LOCALMSPID=Org1MSP
export set CORE_PEER_ADDRESS=peer0.org1.example.com:7051
export set CORE_PEER_MSPCONFIGPATH=/opt/hyperledger/.../peerOrgnizations/org1.example.com/users/Admin@org1.example.com/msp

peer chaincode install -n <链码名称> -v <版本号> -p <链码所在目录>

实例化

Copyexport set FABRIC_CFG_PATH=/opt/hyperledger/peer
export set CORE_PEER_LOCALMSPID=Org1MSP
export set CORE_PEER_ADDRESS=peer0.org1.example.com:7051
export set CORE_PEER_MSPCONFIGPATH=/opt/hyperledger/.../peerOrgnizations/org1.example.com/users/Admin@org1.example.com/msp

peer chaincode instantiate -o orderer.example.com:7050 -C <通道名称> -n <链码名称> -v <版本号> -c <输入参数> -P <背书策略>

Chaincode实例化成功之后运行Chaincode的Docker容器会被启动,这个时候执行命令docker ps,会发现一个包含chaincode名字的进程以及被运行。Chaincode如果被成功实例化,在当前的Peer节点被重新启动之后,已经被实例化的Chaincode不会自动重新启动,这个时候如果客户端对Chaincode发起请求(比如请求query方法),系统会自动运行Chaincode的Docker进程。

还有一点需要注意,如果Chaincode某个方法发生异常导致Docker容器关闭,若此时客户端重新对Chaincode发起访问请求,只要请求的方法没有异常,系统会自动启动Chaincode的容器。

关于Chaincode的示例还有一点需要注意,如果在一个channel中已经加入了多个peer节点,并且这些peer节点需要安装相同的chaincode。这个时候只需要在第一个部署Chaincode的Peer节点中执行peer chaincode instantiate,其余的Peer节点部署Chaincode的时候只需要执行peer chaincode install命令,然后执行invoke或者query命令,系统会自动启动Chaincode相关的Docker镜像。当需要在多个Peer节点部署同一个Chaincode的时候instantiate命令只需要执行一次。

调用

Copyexport set FABRIC_CFG_PATH=/opt/hyperledger/peer
export set CORE_PEER_LOCALMSPID=Org1MSP
export set CORE_PEER_ADDRESS=peer0.org1.example.com:7051
export set CORE_PEER_MSPCONFIGPATH=/opt/hyperledger/.../peerOrgnizations/org1.example.com/users/Admin@org1.example.com/msp

peer chaincode invoke -o orderer.example.com:7050 -C <通道名> -n <链码名> -c <输入参数>

shim包的核心方法

shim包主要负责和客户端进行通信。shim提供了一组核心方法和客户端进行交互,这些方法如下所示

Success

Success方法负责将正确的消息返回给调用Chaincode的客户端,Success方法的定义和调用示例如下:

Copy// 方法定义
func Success(payload []byte) peer.Response
// 代码示例
func (t *simplechaincode) Invoke(stub shim.ChiancodeStubInterface) peer.Response {
    return shim.Success([]byte("success invoke"))
}

Error

Error方法负责将错误的信息返回给调用Chaincode的客户端,Error方法的定义和调用示例如下:

Copy// 方法定义
func Error(msg string) peer.Response
// 代码示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    shim.Error(error_str)
}

LogLevel

LogLevel方法负责修改Chaincode中运行日志的级别,LogLevel方法的定义个调用示例如下:

Copy// 方法定义
func LogLevel(levelString string) (LoggingLevel, error)
// 代码示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    loglevel, _ := shim.Loglevel("debug")
    shim.SetLoggingLevel(loglevel)
    return shim.Success([]byte("success invoke"))
}

shim包中ChaincodeStubInterface接口的核心方法

ChaincodeStubInterface接口的核心方法大概可以分四个大类:系统管理、存储管理、交易管理、调用外部chaincode。

ChaincodeStubInterface接口的系统管理相关方法

**GetFunctionAndParameters:**该方法负责接收调用Chaincode的客户端传递过来的参数,它的定义和调用示例:

Copy// 方法定义
GetFunctionAndParameters() (string, []string)
// 代码示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    _, args := stub.GetFunctionAndParameters()
    var a_parm = args[0]
    var b_parm = args[1]
    var c_parm = args[2]
}
-c '{"Args":["invoke", "set", "abc", "def"]}'

根据Invoke子命令的格式定义,Args不是参数而是格式关键字,后面的参数数组中第一个是方法名,后面三个是参数

ChaincodeStubInterface接口的存储管理相关方法

PutState:PutState方法负责把客户端传递过来的数据保存到Fabric中,它的定义和调用示例如下:

Copy// 方法定义
PutState(key string, value []byte) error
// 代码示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    stub.PutState(b_parm, []byte("putvalue"))
    return shim.Success([]byte("Success invoke"))
}

GetState:GetState方法负责从Fabric中取出数据,然后把这些数据交给Chiancode处理,它的定义和调用示例如下:

Copy// 方法定义
GetState(key string) ([]byte, error)
// 代码示例
func (t *simplechaincode) Invoke(stub *shim.ChaincodeStubInterface) peer.Response {
    var keyvalue []byte
    var err error
    keyvalue, err = stub.GetState("getkey")
    if (err != nil) {
        return shim.Error("error")
    }
    return shim.Success(keyvalue)
}

GetStateByRange:GetStateByRange方法根据key的范围来查询相关的数据。它的定义和调用示例如下:

Copy// 方法定义
GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error)
// 代码示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    startkey := "startkey"
    endkey := "endkey"
    keysIter, err := stub.GetStateByRange(startkey, endkey)
    if err != nil {
        return shim.Error(fmt.Sprintf("GetStateByRange find err: %S", err))
    }
    defer keyIter.Close()
    var keys []string
    for keysIter.HasNext(){
        response, iterErr := keyIter.Next()
        if iterErr != nil {
            return shim.Error(fmt.Sprintf("find an error %s", iterErr))
        }
        keys = append(keys, response.Key)
    }
    for key, value := range keys {
        fmt.Printf("key %d contains %s\n", key, value)
    }
    jsonKeys, err := json.Marshal(keys)
    if err != nil {
        return shim.Error(fmt.Sprintf("find error on Marshaling JSON: %S", err))
    }
    return shim.Success(jsonKeys)
}

GetHistoryForKey:GetHistoryForKey方法负责查询某个键的历史记录,它的定义和调用示例如下:

Copy// 方法定义
GetHistoryForKey(key string) (HistoryQueryIteratorInterface, error)
// 代码示例
func (t *simplechiancode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    KeysIter, err := stub.GetHistoryForKey(b_parm)
    if err != nil {
        return shim.Error(fmt.Sprintf("GetHistoryForKey failed. Error accessing state: %s", err))
    }
    defer keysIter.Close()
    var keys []string
    for keysIter.HasNext() {
        response, iterErr := keysIter.Next()
        if iterErr != nil {
            return shim.Error(fmt.Sprintf("GetHsitoryForKey operation failed. Error accessing state: %s", err))
        }
        // 交易编号
        txid := response.TxId
        // 交易的值
        txvalue := response.Value
        // 当前交易的状态
        txstatus := response.IsDelete
        // 交易发生的时间戳
        txtimestamp := response.Timestamp
        tm := time.Unix(txtimestamp.Seconds, 0)
        datestr := tm.Format("2006-01-02 03:04:05 PM")
        fmt.Printf("Tx info - txid : %s  value : %s if delete: %t  datatime: %s\n", txid, string(txvalue), txstatus, datestr)
        keys = qppend(keys, rxid)
    }
    jsonKeys, err := json.Marshal(keys)
    if err != nil {
        return shim.Error(fmt.Sprintf("Query operation failed. Error marshaling JSON: %s", err))
    }
    return shim.Success(jsonKeys)
}

DelState:DelState方法用来删除一个key,它的定义和调用示例如下:

Copy// 方法定义
DelState(key string) error
// 代码示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    err := DelState("deletekey")
    if err != nil {
        return shim.Error("delete error")
    }
    return shim.Success([]byte("delete success"))
}

CreateCompositeKey:CreateCompositeKey方法负责创建一个组合键,它的定义和调用示例如下:

Copy// 方法定义
CreateCompositeKey(objectType string, attributes []string) (string, error)
// 代码示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    parms := []string{"c_1", "d_1", "e_1", "f_1", "g_1", "h_1"}
    ckey, _ := stub.CreateCompositeKey("testkey", parms)
    err := stub.PutState(ckey, []byte(c_parm))
    if err := nil {
        fmt.Println("find error %s", err)
    }
    return shim.Success([]byte(ckey))
}

GetStateByPartialCompositeKey:GetStateByPartialCompositeKey方法用来查询复合键的值,它的定义和调用示例如下:

Copy// 方法定义
GetStateByPartialCompositeKey(objectType string, key []string) (StateQueryIteratorInterface, error)
//代码示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    parms := []string{"c_1"}
    rs, err := stub.GetStateByPartialCompositeKey("testkey", parms)
    if err := nil {
        error_str := fmt.Sprintf("find error: %s", err)
        return shim.Error(error_str)
    }
    defer rs.Close()
    var i int
    var tlist []string
    for i = 0; rs.HasNext(); i++ {
        responseRange, err := rs.Next()
        if err != nil {
            error_str := fmt.Sprintf("find error: %s", err)
            fmt.Printfln(error_str)
            return shim.Error(error_str)
        }
        value1, compositeKeyParts, _ := stub.SplitCompositeKey(responseRange.Key)
        value2 := compositeKeyParts[0]
        value3 := compositeKeyParts[1]
        fmt.Printf("find value v1:%s v2:%s v3:%s\n", value1, value2, value3)
    }
    return shim.Success("success")
}

SplitCompositeKey:SplitCompositeKey方法用来拆分复合键的属性,它的定义示例如下:

Copy// 方法定义
SplitCompositeKey(compositeKey string) (string, []string, error)
ChaincodeStubInterface接口中交易管理相关的方法

GetTxTimestamp:该方法负责获取当前客户端发送的交易时间戳,它的定义和调用示例如下:

Copy// 方法定义
GetTxTimestamp() (*timestamp.Timestamp, error)
// 代码示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    txtime, err := stub.GetTxTimestamp()
    if err != nil {
        fmt.Printf("Error getting transaction timestamp: %s", err)
        return shim.Error(fmt.Sprintf("Error getting transaction timsstamp:%s", err))
    }
    tm := time.Unix(txtime.Seconds, 0)
    fmt.Printf("Transaction Time: %v\n", tm.Format(2006-01-02 03:04:05 PM))
    return shim.Success([]byte(fmt.Sprint("time is: %s", tm.Format("2006-01-02 15:04:05"))))
}
调用其他Chaincode的方法
InvokeChaincode

InvokeChaincode方法的定义和调用示例如下:

Copy// 方法定义
InvokeChaincode(chaincodeName string, args [][]byte, channel string) peer.Response
// 代码示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    // 设置参数
    parms1 := []string{"query", "a"}
    queryArgs := make([][]byte, len(parms1))
    for i, arg := range parms1 {
        queryArgs[i] = []byte(arg)
    }
    // 调用Chaincode
    response := stub.InvokeChaincode("cc_endfinlshed", queryArgs, "roberttestchannel12")
    if response.Status != shim.OK {
        errStr := fmt.Sprintf("Failed to query chaincode. Got error: %s", reponse.Payload)
        fmt.Printf(errStr)
        return shim.Error(errStr)
    }
    result := string(response.Payload)
    fmt.Printf("invoke chaincode %s", result)
    return shim.Success([]byte("Success InvokeChaincode and Not opter" + result))
}
GetTxID

该方法可以获取客户端发送的交易的编号,GetTxID方法的定义和调用示例如下:

Copy// 方法定义
GetTxID() string
// 代码示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.response {
    txid := stub.GetTxID()
    fmt.Println("===== GetTxID ===== %s", txid)
    return shim.Success([]byte(txid))
}

如何通过Chaincode进行交易的endorse

在Fabric中有一个非常重要的概念称为Endorsement,中文名为背书。背书的过程是一笔交易被确认的过程。而背书策略被用来指示对相关的参与方如何对交易进行确认。当一个节点接收到一个交易请求的时候,会调用VSCC(系统Chaincode,专门负责处理背书相关的操作)与交易的Chaincode共同来验证交易的合法性。在VSCC和交易的Chaincode共同对交易的确认中,通常会做以下的校验

  • 所有的背书是否有效(参与的背书的签名是否有效)。
  • 参与背书的数量是否满足要求。
  • 所有背书参与方是否满足要求。

背书策略是指定第二和第三点的一种方式。

背书策略的设置通过Chaincode部署时instantiate命令中-P参数来设置的。

Copypeer chiancode instantiate -o oerderer.example.com:7050 -C <链码名称> -n <链码名> -v <版本号> -c <输入参数> -P "AND ('Org1MSP.member', 'Org2MSP.member')"

"AND ('Org1MSP.member', 'Org2MSP.member')"这个参数包说明的是当前Chaincode发起的交易,需要组织编号为Org1MSP和组织编号为Org2MSP的组织中的任何一个用户共同参与交易的确认并且同意,这样交易才能生效并被记录到区块中。

在cryptogen的配置文件中有一个节点Orgnizations->ID,该节点的值就是该组织的编号,也是在配置背书策略时需要用到的组织的编号。

OR('Org1MSP.member', AND('Org2MSP.member','Org3MSP.member'))此背书策略有两种方法使它生效:

  1. 组织Org1MSP中的某个用户对交易进行验证
  2. 组织Org2MSP和Org3MSP中的成员共同对交易进行验证

有一点需要注意的是,背书规则只针对Chiancode中写入数据的操作进行校验(PutState、DelState),对于查询类的操作不需要背书

Fabric中的背书是发生在客户端的,需要进行相关的代码的编写才能完成整个背书的操作。

Chaincode的调试方式

Chaincode在Docker容器之外的运行

注册需要调试的chaincode

如果需要在Docker容器之外运行Chaincode,首先需要把需要运行的Chaincode注册到Fabric中,注册完成后就可以借用已注册成功的chaincode的名字和版本号在Docker容器之外运行Chaincode。有一点需要强调,在注册的时候只需要向Fabric提交Chaincode的名字和版本号,实际注册的Chaincode的代码并不重要

设置Peer节点的运行模式

可以通过修改配置文件或者添加环境变量的方式修改Peer节点的启动模式。配置文件可以通过修改core.yaml中的chaincode节点的mode子节点的值,将mode的值修改为net,修改后如下:

Copychaincode:
mode: net

或者通过环境变量修改Peer运行模式

Copyexport set CORE_CHAINCODE_MODE=net
注册Chaincode
Copyexport set FABRIC_CFG_PATH=/opt/hyperledger/peer
export set CORE_PEER_LOCALMSPID=Org1MSP
export set CORE_PEER_ADDRESS=peer0.org1.example.com:7051
export set CORE_PEER_MSPCONFIGPATH=/opt/hyperledger/.../peerOrgnizations/org1.example.com/users/Admin@org1.example.com/msp

peer chaincode install -n <链码名称> -v <版本号> -p <链码所在目录>
peer chaincode instantiate -o orderer.example.com:7050 -C <通道名称> -n <链码名称> -v <版本号> -c <输入参数> -P <背书策略>

注册成功之后一定要记住install命令执行时参数-n和-v的值,这一点非常重要,后面的配置中需要用到这两个参数的值。

将Peer节点设置为调试模式

要想在Docker外面运行Chaincode,首先需要调整Peer节点的运行模式。可以通过修改配置文件或者添加环境变量的方式修改Peer节点的启动模式。配置文件可以通过修改配置文件core.yaml中的chaincode节点的mode子节点的值,将mode的值修改为dev,修改后如下所示:

Copychiancode:
mode: dev

mode子节点有两个属性dev和net,net表示chaincode运行在Docker容器中,dev表示Chaincode运行在容器之外,通常用于开发模式。

设置环境变量的方式让Chaincode可以运行在Docker容器之外:

Copyexport set CORE_CHAINCODE_MODE=dev

如果设置的dev模式,那么当前Peer模块不能执行peer chaincode instantiate命令

编译chaincode

进入链码所在目录进行go build

运行Chaincode

要运行Chaincode需要设置相应的环境变量和系统参数

Copyexport set CORE_PEER_ADDRESS=192.168.23.212:7051
export set CORE_CHAINCODE_ID_NAME=mycc:1.1
export set CORE_CHIANCODE_LOGGING_LEVEL=debug
export set CORE_CHAINCODE_LOGGING_SHIM=debug
./simplechaincode -peer.address=192.168.23.212:7052

上述的环境变量和命令选项作用如下:

  • CORE_PEER_ADDRESS:Peer节点的IP地址
  • CORE_CHAINCODE_ID_NAME:chaincode的名字和版本号,冒号前面的是chaincode的名字,后面是chaincode的版本。这两个值必须和第一步install命令中-n和-v的参数完全一致,否则Chaincode无法执行
  • CORE_CHAINCODE_LOGGING_LEVEL:chaincode系统的日志级别
  • CORE_CHAINCODE_LOGGING_SHIM:shim的日志级别
  • -peer.address=:Peer节点中的Chiancode的监听端口,该端口在core.yaml文件中的peer节点下面的chaincodeListenAddress子节点
调动Chaincode

上面启动的Chaincode通过下面的命令调用:

Copyexport set FABRIC_CFG_PATH=/opt/hyperledger/peer
export set CORE_PEER_LOCALMSPID=Org1MSP
export set CORE_PEER_ADDRESS=peer0.org1.example.com:7051
export set CORE_PEER_MSPCONFIGPATH=/opt/hyperledger/.../peerOrgnizations/org1.example.com/users/Admin@org1.example.com/msp

peer chaincode invoke -o 192.168.23.212:7050 -C <通道名> -n <链码名> -c <输入参数>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值