一、准备
1、在项目中引入maven依赖
org.hyperledger.fabric-sdk-java
fabric-sdk-java
1.3.0
2、编写测试代码(包含Fabcar.java、CertUtils.java两个类)
Fabcar.java
import org.hyperledger.fabric.sdk.ChaincodeID;
import org.hyperledger.fabric.sdk.Channel;
import org.hyperledger.fabric.sdk.Enrollment;
import org.hyperledger.fabric.sdk.HFClient;
import org.hyperledger.fabric.sdk.ProposalResponse;
import org.hyperledger.fabric.sdk.QueryByChaincodeRequest;
import org.hyperledger.fabric.sdk.TransactionProposalRequest;
import org.hyperledger.fabric.sdk.User;
import org.hyperledger.fabric.sdk.security.CryptoSuite;
import org.hyperledger.fabric_ca.sdk.HFCAClient;
import java.util.Collection;
import java.util.Collections;
import java.util.Properties;
import java.util.Set;
public class Fabcar{
public static void main(String[] args) throws Exception {
enroll("admin", "adminpw", "cert");
queryCar();
updateCar();
Thread.sleep(5000);
queryCar();
}
/**
* 更新账本
* @throws Exception
*/
private static void updateCar() throws Exception {
HFClient client = HFClient.createNewInstance();
Channel channel = initChannel(client);
// 构建proposal
TransactionProposalRequest req = client.newTransactionProposalRequest();
// 指定要调用的chaincode
ChaincodeID cid = ChaincodeID.newBuilder().setName("fabcar").build();
req.setChaincodeID(cid);
req.setFcn("changeCarOwner");
req.setArgs(new String[]{"CAR1", "Marry"});
System.out.println("Executing for " + "CAR1");
// 发送proprosal
Collection resps = channel.sendTransactionProposal(req);
// 提交给orderer节点
channel.sendTransaction(resps);
}
/**
* 查询账本
* @throws Exception
*/
private static void queryCar() throws Exception {
HFClient client = HFClient.createNewInstance();
Channel channel = initChannel(client);
String key = "CAR1";
// 构建proposal
QueryByChaincodeRequest req = client.newQueryProposalRequest();
// 指定要调用的chaincode
ChaincodeID cid = ChaincodeID.newBuilder().setName("fabcar").build();
req.setChaincodeID(cid);
req.setFcn("queryCar");
req.setArgs(new String[] { key });
System.out.println("Querying for " + key);
Collection resps = channel.queryByChaincode(req);
for (ProposalResponse resp : resps) {
String payload = new String(resp.getChaincodeActionResponsePayload());
System.out.println("response: " + payload);
}
}
/**
* 用户注册, 保存证书和私钥
*
* @param username Fabric CA Admin用户的用户名
* @param password Fabric CA Admin用户的密码
* @param certDir 目录名, 用来保存证书和私钥
* @throws Exception
*/
private static void enroll(String username, String password, String certDir) throws Exception {
HFClient client = HFClient.createNewInstance();
CryptoSuite cs = CryptoSuite.Factory.getCryptoSuite();
client.setCryptoSuite(cs);
Properties prop = new Properties();
prop.put("verify", false);
HFCAClient caClient = HFCAClient.createNewInstance("http://192.168.80.134:7054", prop);
caClient.setCryptoSuite(cs);
// enrollment保存了证书和私钥
Enrollment enrollment = caClient.enroll(username, password);
System.out.println(enrollment.getCert());
// 保存到本地文件
CertUtils.saveEnrollment(enrollment, certDir, username);
}
private static Channel initChannel(HFClient client) throws Exception {
CryptoSuite cs = CryptoSuite.Factory.getCryptoSuite();
client.setCryptoSuite(cs);
client.setUserContext(
new CarUser(
"admin",
CertUtils.loadEnrollment("cert", "admin")
)
);
// 初始化channel
Channel channel = client.newChannel("mychannel");
channel.addPeer(client.newPeer("peer", "grpc://192.168.80.134:7051"));
// 指定排序节点地址, 无论是后面执行查询还是更新都必须指定排序节点
channel.addOrderer(client.newOrderer("orderer", "grpc://192.168.80.134:7050"));
channel.initialize();
return channel;
}
}
/**
* User接口实现类
*/
class CarUser implements User {
private String name;
private Enrollment enrollment;
public CarUser(String name, Enrollment enrollment) {
this.name = name;
this.enrollment = enrollment;
}
@Override
public String getName() {
return this.name;
}
@Override
public Set getRoles() {
return Collections.emptySet();
}
@Override
public String getAccount() {
return "";
}
@Override
public String getAffiliation() {
return "";
}
@Override
public Enrollment getEnrollment() {
return this.enrollment;
}
@Override
public String getMspId() {
return "Org1MSP";
}
}
CertUtils.java
import org.hyperledger.fabric.sdk.Enrollment;
import javax.xml.bind.DatatypeConverter;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
public class CertUtils {
private CertUtils() {}
/**
* 保存cert, key到文件
* @param enrollment Client返回的Enrollment对象
* @param dir 要保存的目录
* @param name 用户名
* @throws IOException
*/
public static void saveEnrollment(Enrollment enrollment, String dir, String name) throws IOException {
if (null == enrollment) {
throw new IllegalStateException("enrollment cannot be null");
}
// 保存cert
String certFileName = String.join("", dir, File.separator, name, ".cert");
try (FileOutputStream certOut = new FileOutputStream(certFileName)) {
certOut.write(enrollment.getCert().getBytes());
} catch (IOException ex) {
throw ex;
}
// 保存private key
String keyFileName = String.join("", dir, File.separator, name, ".priv");
try (FileOutputStream keyOut = new FileOutputStream(keyFileName)) {
StringBuilder sb = new StringBuilder(300);
sb.append("-----BEGIN PRIVATE KEY-----\n");
String priKey = DatatypeConverter.printBase64Binary(enrollment.getKey().getEncoded());
// 每64个字符输出一个换行
int LEN = priKey.length();
for (int ix = 0; ix < LEN; ++ix) {
sb.append(priKey.charAt(ix));
if ((ix + 1) % 64 == 0) {
sb.append('\n');
}
}
sb.append("\n-----END PRIVATE KEY-----\n");
keyOut.write(sb.toString().getBytes());
} catch (Exception e) {
throw e;
}
}
/**
* 从文件中读取身份信息
* @param dir
* @param name
* @return
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
*/
public static Enrollment loadEnrollment(String dir, String name)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, GeneralSecurityException {
byte[] certBuf = Files.readAllBytes(Paths.get(dir, name + ".cert"));
String cert = new String(certBuf);
// 读取文件, 构造PrivateKey对象
PrivateKey key = loadPrivateKey(Paths.get(dir, name + ".priv"));
return new MyEnrollment(key, cert);
}
/***
* loading private key from .pem-formatted file, ECDSA algorithm
* (from some example on StackOverflow, slightly changed)
* @param fileName - file with the key
* @return Private Key usable
* @throws IOException
* @throws GeneralSecurityException
*/
private static PrivateKey loadPrivateKey(Path fileName) throws IOException, GeneralSecurityException {
PrivateKey key = null;
try (FileInputStream is = new FileInputStream(fileName.toString())) {
BufferedReader br = new BufferedReader(new InputStreamReader(is));
StringBuilder builder = new StringBuilder();
boolean inKey = false;
for (String line = br.readLine(); line != null; line = br.readLine()) {
if (!inKey) {
if (line.startsWith("-----BEGIN ") && line.endsWith(" PRIVATE KEY-----")) {
inKey = true;
}
continue;
} else {
if (line.startsWith("-----END ") && line.endsWith(" PRIVATE KEY-----")) {
inKey = false;
break;
}
builder.append(line);
}
}
byte[] encoded = DatatypeConverter.parseBase64Binary(builder.toString());
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
// KeyFactory kf = KeyFactory.getInstance("ECDSA");
KeyFactory kf = KeyFactory.getInstance("EC");
key = kf.generatePrivate(keySpec);
} catch (Exception e) {
throw e;
}
return key;
}
}
class MyEnrollment implements Enrollment {
private PrivateKey privateKey;
private String cert;
public MyEnrollment(PrivateKey privateKey, String cert) {
this.privateKey = privateKey;
this.cert = cert;
}
@Override
public PrivateKey getKey() {
return this.privateKey;
}
@Override
public String getCert() {
return this.cert;
}
}
3、新建一个cert文件夹,用于保存cert和key
二、执行步骤
1、启动fabcar网络。参考之前的笔记(不需要用node): http://note.youdao.com/noteshare?id=08cba7c7957649ad31049483b42f3067&sub=F7517602136F4A2F9785CBB21110D1B2
root@ubuntu:~/demo# cd fabric-samples/fabcar #进入项目目录
root@ubuntu:~/demo/fabric-samples/fabcar# ./startFabric.sh #启动fabcar
2、执行Fabcar.java中的main()方法,先执行enroll()注册用户,然后可进行查询和更新操作(由于延迟原因,更新之后立马查询的话值可能还没来得及更新)。查询更新后的值如下:
三、上传自己编写的chaincode,并进行交互
1、上传chaincode
分析fabcar的源码发现此项目中fabric调用链码的路径为 github.com/fabcar/go, 其实前面省略了$GOPATH/src/,完整路径应该为 $GOPATH/src/github.com/fabcar/go,将自己编写的链码放到此目录下(可以利用docker的volume映射,此项目中映射为路径fabric-samples1/chaincode/fabcar/go),注意最好只放一个链码,将原来的链码删除,这里为了区分是不同的链码,将fabcar.go改为test01.go,除了下面的初始化数据改了之外(将Ford0改为了Ford),其它内容均一样。
2、修改脚本文件( startFabric.sh)关于链码操作的命令(主要是将原来的fabcar改为test01,这里的名称不一定要跟链码文件名称一样,也可以改为test02等其他名称。若需要修改其他参数则根据具体链码的情况而定)
3、关闭之前的网络
docker rm -f $(docker ps -aq) #关闭所有正在运行的容器
docker network prune #移除网络
docker volume prune #移除映射
4、启动网络并查询(参考步骤二,但注意在程序中将查询的链码名称改为test01),结果如下
四、问题
1、若将fabcar关闭,修改链码文件初始化数据,将脚本命令中的链码名称改为test02,然后再启动网络查询数据会更新(这是正常的)。但是若将脚本中的链码名称改回test01,启动网络查询,发现查询到的数据是原来test01中的数据(此时链码中的初始化数据已经改为了test02的数据,应该为test02的数据)。也就是说如果一个链码名称查询到的数据只是第一次安装此链码的内容数据,后面尽管链码内容更改了,但只要名称不变,查询结果还是不变。
个人猜想是网络关闭时数据并没有完全清空。请知道的同学给个意见。。。
已经找到答案。原因是产生了镜像没有删除,如下图所示:
将类似这样的镜像删除即可:
docker rmi [IMAGE _ID]
参考:
1、https://github.com/wanghongfei/hyperledger-fabric-java-SDK-demo
2、https://www.jianshu.com/p/3d61e8c46f43