本教程将讲解如何编写基于Java
的Hyperledger Fabric
链代码。有关链码的一般说明,如何编写和操作,请访问Chaincode教程。
JDK8+
Gradle 4.8+
Hyperledger Fabric 1.3+
这里主要是配置 Gradle
的环境变量。
export path=path:/opt/gradle-4.8
编写自己的链代码需要了解Fabric
平台,Java
和Gradle
。应用程序是一个基本的示例链代码,用于在分类帐上创建资产(键值对)。
$ git clone https://github.com/hyperledger/fabric-chaincode-java.git
在开发工具eclipse
中导入工程代码。
文件夹结构:
-
fabric-chaincode-protos
文件夹包含Java shim
用于与Fabric
对等方通信的protobuf
定义文件。 -
fabric-chaincode-shim
文件夹包含定义Java
链代码API
的java shim
类以及与Fabric
对等方通信的方式。 -
fabric-chaincode-docker
文件夹包含构建docker
镜像的说明hyperledger/fabric-javaenv
。 -
fabric-chaincode-example-gradle
包含一个示例java chaincode gradle
项目,其中包含示例链代码和基本gradle
构建指令。
可以使用fabric-chaincode-example-gradle
作为起始点。或者在fabric-chaincode-example-gradle
的同级新建一个gradle
项目fabric-chaincode-asset-gradle
。确保项目构建创建一个可运行的jar
,其中包含名为chaincode.jar
的所有依赖项。
plugins {
id 'com.github.johnrengelman.shadow' version '2.0.3'
id 'java'
}
group 'org.hyperledger.fabric-chaincode-java'
version '1.3.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
compile group: 'org.hyperledger.fabric-chaincode-java', name: 'fabric-chaincode-shim', version: '1.3.+'
testCompile group: 'junit', name: 'junit', version: '4.12'
}
shadowJar {
baseName = 'chaincode'
version = null
classifier = null
manifest {
attributes 'Main-Class': 'com.github.hooj0.chaincode.SimpleAssetChaincode'
}
}
新建完成后,可以在项目上右键,选择Gradle -> Refresh Gradle Project
加载项目依赖的Jar
包。
可以在fabric-chaincode-example-gradle
的同级新建一个maven
项目fabric-chaincode-asset-maven
。确保项目构建创建一个可运行的jar
,其中包含名为chaincode.jar
的所有依赖项。在maven
的配置文件pom.xml
中添加如下配置:
<properties>
<!-- Generic properties -->
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- fabric-chaincode-java dev -->
<fabric-chaincode-java.version>1.3.1-SNAPSHOT</fabric-chaincode-java.version>
<!-- prod env
<fabric-chaincode-java.version>1.3.0</fabric-chaincode-java.version>
-->
<!-- Logging -->
<logback.version>1.0.13</logback.version>
<slf4j.version>1.7.5</slf4j.version>
<!-- Test -->
<junit.version>4.11</junit.version>
</properties>
<dependencies>
<!-- fabric-chaincode-java -->
<dependency>
<groupId>org.hyperledger.fabric-chaincode-java</groupId>
<artifactId>fabric-chaincode-shim</artifactId>
<version>${fabric-chaincode-java.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hyperledger.fabric-chaincode-java</groupId>
<artifactId>fabric-chaincode-protos</artifactId>
<version>${fabric-chaincode-java.version}</version>
<scope>compile</scope>
</dependency>
<!-- fabric-sdk-java -->
<!-- Logging with SLF4J & LogBack -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<scope>runtime</scope>
</dependency>
<!-- Test Artifacts -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>chaincode</finalName>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.github.hooj0.chaincode.SimpleAssetChaincode</mainClass>
</transformer>
</transformers>
<filters>
<filter>
<!-- filter out signature files from signed dependencies, else repackaging fails with security ex -->
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
最终目录结构如下:
$ ll
total 65
-rw-r--r-- 1 Administrator 197121 687 十二 3 16:30 build.gradle
drwxr-xr-x 1 Administrator 197121 0 十二 3 17:04 fabric-chaincode-asset-gradle/
drwxr-xr-x 1 Administrator 197121 0 十二 3 16:39 fabric-chaincode-asset-maven/
drwxr-xr-x 1 Administrator 197121 0 十一 30 16:55 fabric-chaincode-docker/
drwxr-xr-x 1 Administrator 197121 0 十一 30 16:55 fabric-chaincode-protos/
drwxr-xr-x 1 Administrator 197121 0 十一 30 16:56 fabric-chaincode-shim/
-rwxr-xr-x 1 Administrator 197121 5296 十一 1 17:31 gradlew*
-rw-r--r-- 1 Administrator 197121 2260 十一 1 17:31 gradlew.bat
-rw-r--r-- 1 Administrator 197121 1062 十二 3 15:54 LICENSE
-rw-r--r-- 1 Administrator 197121 22743 十二 3 17:04 README.md
-rw-r--r-- 1 Administrator 197121 146 十二 3 16:30 settings.gradle
使用Java
版的Simple Asset Chaincode
作为示例。这个链代码是Simple Asset Chaincode
的Go to Java
翻译,将对此进行解释。
ChaincodeBase
类是一个抽象类,它继承了Chaincode
形式,它包含用于启动chaincode
的start
方法。因此,将通过扩展ChaincodeBase
而不是实现Chaincode
来创建我们的链代码。
首先,从一些基本的开始,创建一个class
文件SimpleAssetChaincode
。与每个链代码一样,它继承了ChaincodeBase
抽象类,特别是实现了init
和invoke
函数。
package com.github.hooj0.chaincode;
import org.hyperledger.fabric.shim.ChaincodeBase;
import org.hyperledger.fabric.shim.ChaincodeStub;
/**
* simple asset chaincode
* @author hoojo
* @createDate 2018年11月30日 下午4:13:27
* @file SimpleAssetChaincode.java
* @package com.github.hooj0.chaincode
* @project fabric-chaincode-asset-gradle
* @blog http://hoojo.cnblogs.com
* @email [email protected]
* @version 1.0
*/
public class SimpleAssetChaincode extends ChaincodeBase {
}
接下来,将实现init
函数。
/**
* Init is called during chaincode instantiation to initialize any
* data. Note that chaincode upgrade also calls this function to reset
* or to migrate data.
*
* @param stub {@link ChaincodeStub} to operate proposal and ledger
* @return response
*/
@Override
public Response init(ChaincodeStub stub) {
}
注意:链码升级也会调用此函数。在编写将升级现有链代码的链代码时,请确保正确修改
Init
函数。特别是,如果没有“迁移”或者在升级过程中没有任何内容要初始化,请提供一个空的Init
方法。
接下来,将使用ChaincodeStub -> stub.getStringArgs
函数检索Init
调用的参数并检查其有效性。在例子中,将使用一个键值对。Chaincode
初始化在Response init(ChaincodeStub stub)
方法内完成。首先,使用ChaincodeStub.getStringArgs()
方法获取参数。
/**
* Init is called during chaincode instantiation to initialize any
* data. Note that chaincode upgrade also calls this function to reset
* or to migrate data.
*
* @param stub {@link ChaincodeStub} to operate proposal and ledger
* @return response
*/
@Override
public Response init(ChaincodeStub stub) {
// Get the args from the transaction proposal
List<String> args = stub.getStringArgs();
if (args.size() != 2) {
newErrorResponse("Incorrect arguments. Expecting a key and a value");
}
return newSuccessResponse();
}
接下来,既然已经确定调用有效,将把初始状态存储在分类帐中。为此,将调用stub.putStringState
作为参数传入的键和值。假设一切顺利,返回一个Response
对象,表明初始化成功。
/**
* Init is called during chaincode instantiation to initialize any
* data. Note that chaincode upgrade also calls this function to reset
* or to migrate data.
*
* @param stub {@link ChaincodeStub} to operate proposal and ledger
* @return response
*/
@Override
public Response init(ChaincodeStub stub) {
try {
// Get the args from the transaction proposal
List<String> args = stub.getStringArgs();
if (args.size() != 2) {
newErrorResponse("Incorrect arguments. Expecting a key and a value");
}
// Set up any variables or assets here by calling stub.putState()
// We store the key and the value on the ledger
stub.putStringState(args.get(0), args.get(1));
return newSuccessResponse();
} catch (Throwable e) {
return newErrorResponse("Failed to create asset");
}
}
首先,添加invoke
函数的签名。Chaincode
调用在Response invoke(ChaincodeStub stub)
方法内完成。
/**
* Invoke is called per transaction on the chaincode. Each transaction is
* either a 'get' or a 'set' on the asset created by Init function. The Set
* method may create a new asset by specifying a new key-value pair.
*
* @param stub {@link ChaincodeStub} to operate proposal and ledger
* @return response
*/
@Override
public Response invoke(ChaincodeStub stub) {
return newSuccessResponse();
}
与上面的init
函数一样,需要从ChaincodeStub
中提取参数。invoke
函数的参数将是要调用的链代码应用程序函数的名称。在例子中,应用程序将只有两个函数:set
和get
,它们允许设置资产的值或检索其当前状态。使用ChaincodeStub.getFunction()
和ChaincodeStub.getParameters()
方法提取函数名称和参数。验证函数名称并调用相应的链代码方法。链代码方法接收的值应作为成功响应有效负载返回。如果出现异常或不正确的函数值,则返回错误响应。
public Response invoke(ChaincodeStub stub) {
try {
// Extract the function and args from the transaction proposal
String func = stub.getFunction();
List<String> params = stub.getParameters();
}
}
接下来,将函数名称验证为set
或get
,并调用这些链代码应用程序函数,通过调用父类的newSuccessResponse
或newErrorResponse
函数返回适当的响应,这些函数将响应序列化为gRPC protobuf
消息。
public Response invoke(ChaincodeStub stub) {
try {
// Extract the function and args from the transaction proposal
String func = stub.getFunction();
List<String> params = stub.getParameters();
if (func.equals("set")) {
// Return result as success payload
return newSuccessResponse(set(stub, params));
} else if (func.equals("get")) {
// Return result as success payload
return newSuccessResponse(get(stub, params));
}
return newErrorResponse("Invalid invoke function name. Expecting one of: [\"set\", \"get\"");
} catch (Throwable e) {
return newErrorResponse(e.getMessage());
}
}
如上所述,链码应用程序实现了两个可以通过invoke
函数调用的函数,现在实现这些功能。请注意,正如上面提到的,为了访问分类帐的状态,使用ChaincodeStub.putStringState(key,value)
和ChaincodeStub.getStringState(key)
实现方法set()
和get()
。
/**
* get returns the value of the specified asset key
*
* @param stub {@link ChaincodeStub} to operate proposal and ledger
* @param args key
* @return value
*/
private String get(ChaincodeStub stub, List<String> args) {
if (args.size() != 1) {
throw new RuntimeException("Incorrect arguments. Expecting a key");
}
String value = stub.getStringState(args.get(0));
if (value == null || value.isEmpty()) {
throw new RuntimeException("Asset not found: " + args.get(0));
}
return value;
}
/**
* set stores the asset (both key and value) on the ledger. If the key exists,
* it will override the value with the new one
*
* @param stub {@link ChaincodeStub} to operate proposal and ledger
* @param args key and value
* @return value
*/
private String set(ChaincodeStub stub, List<String> args) {
if (args.size() != 2) {
throw new RuntimeException("Incorrect arguments. Expecting a key and a value");
}
stub.putStringState(args.get(0), args.get(1));
return args.get(1);
}
最后,需要添加main
函数,它将调用shim.Start
函数。这是整个链码程序的完整源代码文件。
package com.github.hooj0.chaincode;
import java.util.List;
import org.hyperledger.fabric.shim.ChaincodeBase;
import org.hyperledger.fabric.shim.ChaincodeStub;
/**
* simple asset chaincode
* @author hoojo
* @createDate 2018年11月30日 下午4:13:27
* @file SimpleAssetChaincode.java
* @package com.github.hooj0.chaincode
* @project fabric-chaincode-asset-gradle
* @blog http://hoojo.cnblogs.com
* @email [email protected]
* @version 1.0
*/
public class SimpleAssetChaincode extends ChaincodeBase {
/**
* Init is called during chaincode instantiation to initialize any
* data. Note that chaincode upgrade also calls this function to reset
* or to migrate data.
*
* @param stub {@link ChaincodeStub} to operate proposal and ledger
* @return response
*/
@Override
public Response init(ChaincodeStub stub) {
try {
// Get the args from the transaction proposal
List<String> args = stub.getStringArgs();
if (args.size() != 2) {
newErrorResponse("Incorrect arguments. Expecting a key and a value");
}
// Set up any variables or assets here by calling stub.putState()
// We store the key and the value on the ledger
stub.putStringState(args.get(0), args.get(1));
return newSuccessResponse();
} catch (Throwable e) {
return newErrorResponse("Failed to create asset");
}
}
/**
* Invoke is called per transaction on the chaincode. Each transaction is
* either a 'get' or a 'set' on the asset created by Init function. The Set
* method may create a new asset by specifying a new key-value pair.
*
* @param stub {@link ChaincodeStub} to operate proposal and ledger
* @return response
*/
@Override
public Response invoke(ChaincodeStub stub) {
try {
// Extract the function and args from the transaction proposal
String func = stub.getFunction();
List<String> params = stub.getParameters();
if (func.equals("set")) {
// Return result as success payload
return newSuccessResponse(set(stub, params));
} else if (func.equals("get")) {
// Return result as success payload
return newSuccessResponse(get(stub, params));
}
return newErrorResponse("Invalid invoke function name. Expecting one of: [\"set\", \"get\"");
} catch (Throwable e) {
return newErrorResponse(e.getMessage());
}
}
/**
* get returns the value of the specified asset key
*
* @param stub {@link ChaincodeStub} to operate proposal and ledger
* @param args key
* @return value
*/
private String get(ChaincodeStub stub, List<String> args) {
if (args.size() != 1) {
throw new RuntimeException("Incorrect arguments. Expecting a key");
}
String value = stub.getStringState(args.get(0));
if (value == null || value.isEmpty()) {
throw new RuntimeException("Asset not found: " + args.get(0));
}
return value;
}
/**
* set stores the asset (both key and value) on the ledger. If the key exists,
* it will override the value with the new one
*
* @param stub {@link ChaincodeStub} to operate proposal and ledger
* @param args key and value
* @return value
*/
private String set(ChaincodeStub stub, List<String> args) {
if (args.size() != 2) {
throw new RuntimeException("Incorrect arguments. Expecting a key and a value");
}
stub.putStringState(args.get(0), args.get(1));
return args.get(1);
}
public static void main(String[] args) {
new SimpleAssetChaincode().start(args);
}
}
现在编译构建链码。
$ cd fabric-chaincode-asset/fabric-chaincode-asset-gradle
$ gradle clean build shadowJar
# 如果是Maven项目
$ mvn clean install
假设没有错误,会生成jar
文件,现在可以继续下一步,测试链代码。
$ ll build/libs/
total 16584
-rw-r--r-- 1 Administrator 197121 16974993 十一 30 17:56 chaincode.jar
-rw-r--r-- 1 Administrator 197121 2371 十一 30 17:56 fabric-chaincode-asset-gradle-1.3.1-SNAPSHOT.jar
通常,链码由对等体启动和维护。然而,在“开发模式”中,链码由用户构建和启动。在链码开发阶段,此模式非常有用,可用于快速代码/构建/运行/调试周期周转。
通过为示例开发网络,利用预先生成的定序者和通道工件来启动“开发模式(dev mode
)”。这样,用户可以立即进入编译链码和操作调用的过程。
如果还不会开发模式,可以参考开发模式使用方式文档。
如果还没有这样做,请安装样本,二进制文件和Docker镜像。
进入到示例目录下的 chaincode
位置。
$ cd fabric-samples/chaincode
创建链码文件夹
$ mkdir -p asset/java
将项目的**源代码和gradle
**的配置文件,拷贝到上面的文件夹中
$ cp xxx/xxx/src/*.java asset/java/src
$ cp xxx/xxx/*.gradle asset/java/
如果还没有这样做,请安装样本,二进制文件和Docker镜像。
进入到示例目录下的 chaincode
位置。
$ cd fabric-samples/chaincode
创建链码文件夹
$ mkdir -p asset/java
将项目的**源代码和maven
**的配置文件,拷贝到上面的文件夹中
$ cp -r xxx/xxx/src asset/java/
$ cp xxx/xxx/*.pom asset/java/
进入到fabric-samples
项目的chaincode-docker-devmode
目录:
$ cd chaincode-docker-devmode
现在打开三个终端并导航到每个终端中的chaincode-docker-devmode
目录。
$ docker-compose -f docker-compose-simple.yaml up
以上内容使用SingleSampleMSPSolo
定序者配置文件启动网络,并以“开发模式”启动对等体。它还启动了两个额外的容器,一个用于链码环境,另一个用于与链代码交互。创建和加入通道的命令嵌入在CLI
容器中,因此可以立即跳转到链代码调用。
即使处于--peer-chaincodedev
模式,仍然必须安装链代码,以便生命周期系统链码可以正常进行检查。在--pere-chaincodedev
模式下,将来可能会删除此要求。
$ docker exec -it cli bash
在 cli
容器中运行:
$ CORE_LOGGING_PEER=info CORE_CHAINCODE_LOGGING_SHIM=info CORE_CHAINCODE_LOGGING_LEVEL=info peer chaincode install -n asset -v v0 -l java -p /opt/gopath/src/chaincodedev/chaincode/asset/java/
$ CORE_LOGGING_PEER=info CORE_CHAINCODE_LOGGING_SHIM=info CORE_CHAINCODE_LOGGING_LEVEL=info peer chaincode instantiate -n asset -v v0 -c '{"Args":["a", "5"]}' -C myc -l java
现在发出一个invoke
调用,将a
的值更改为“20
”。
$ peer chaincode invoke -n asset -c '{"Args":["set", "a", "20"]}' -C myc
最后,查询a
。应该看到20
的值。
$ peer chaincode query -n asset -c '{"Args":["get","a"]}' -C myc
默认情况下,只挂载sacc
。但是,可以通过将不同的链码添加到chaincode
子目录并重新启动网络来轻松地测试它们。此时,可以在chaincode
容器中访问它们。
链码容器的日志查看,需要在链码安装并实例化后才能通过 docker
容器的方式进行查看容器日志。一般链码容器是以dev-peer-xxx-xxx
格式的名称的容器。
查看日志具体操作如下:
# 找到链码容器
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
32ef0a73a344 dev-peer-asset-v0-ab37288c7dfae60b51cf93c7fade76b6d55b2225c1d00a81a627037628408dc7 "/root/chaincode-jav…" 15 minutes ago Up 15 minutes dev-peer-asset-v0
299103c48df3 hyperledger/fabric-ccenv:latest "/bin/bash -c 'sleep…" 17 minutes ago Up 17 minutes chaincode
0e81e8e846d2 hyperledger/fabric-tools:latest "/bin/bash -c ./scri…" 17 minutes ago Up 17 minutes cli
2ef83f03cce6 hyperledger/fabric-peer:latest "peer node start --p…" 17 minutes ago Up 17 minutes 0.0.0.0:7051->7051/tcp, 0.0.0.0:7053->7053/tcp peer
296f09b716af hyperledger/fabric-orderer:latest "orderer" 17 minutes ago Up 17 minutes 0.0.0.0:7050->7050/tcp orderer
# 通过名称查看容器日志
$ docker logs dev-peer-asset-v0
# 通过id查看容器日志
$ docker logs -f 32ef0a73a344
设置环境变量覆盖掉core.yaml
中的配置,在执行命令的时候进行环境变量设置如下:
CORE_LOGGING_PEER=info CORE_CHAINCODE_LOGGING_SHIM=info CORE_CHAINCODE_LOGGING_LEVEL=info peer chaincode install -n asset -v v0 -l java -p xxxx
链码日志需要使用官方指定的日志记录器,目前官方支持的链码容器日志输出是 apache.commons.logging
。如果采用其他的日志记录器,可能存在安装实例化链码的时候找不到相关JAR
文件;而且日志的输出级别不受环境变量控制,尝试使用logback
输出日志,但无法通过配置文件或环境变量设置日志格式和级别。
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
private static final Log log = LogFactory.getLog(Xxx.class);