可能很多朋友,用SDK调通了Fabric、不过E2E默认自带的智能合约是转账。
除了转账、还有很多可以玩的东西。
那么在学习更换智能合约链码的过程中,这次我们玩一个可以核验的智能合约。
- MD5加密技术
- Key/Value智能合约链码
- 修改script.sh启动脚本
- 通过cli终端去安装这份链码
- 底层封装
- service封装
- Test中调用
MD5加密是一种很常见的加密方式。如果自定义了比较长的加密字符串,解密起来是非常困难的。
public class MD5Util {
private static String byteArrayToHexString(byte b[]) {
StringBuffer resultSb = new StringBuffer();
for (int i = 0; i < b.length; i++)
resultSb.append(byteToHexString(b[i]));
return resultSb.toString();
}
private static String byteToHexString(byte b) {
int n = b;
if (n < 0)
n += 256;
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] + hexDigits[d2];
}
/**
* 返回大写MD5
*
* @param origin
* @param charsetname
* @return
*/
private static String MD5Encode(String origin, String charsetname) {
String resultString = null;
try {
resultString = new String(origin);
MessageDigest md = MessageDigest.getInstance("MD5");
if (charsetname == null || "".equals(charsetname))
resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
else
resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));
} catch (Exception exception) {
}
return resultString.toUpperCase();
}
public static String MD5EncodeUtf8(String origin) {
origin = origin + "这里可以放入你的加密字符串");
return MD5Encode(origin, "utf-8");
}
private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
@Test
public void fun1() {
System.out.println(MD5EncodeUtf8("LANTIAN"));
}
智能合约是用来满足Fabric实际运用需求的一种约束,诸如此类的chaincode有很多。比如小汽车、转账、核验等等。其实也就是一个类,然后有很多的属性,和一些操作的方法。
package main
import (
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/protos/peer"
)
// SimpleAsset implements a simple chaincode to manage an asset
type SimpleAsset struct {
}
// Init is called during chaincode instantiation to initialize any
// data. Note that chaincode upgrade also calls this function to reset
// or to migrate data.
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {
// Get the args from the transaction proposal
args := stub.GetStringArgs()
if len(args) != 2 {
return shim.Error("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
err := stub.PutState(args[0], []byte(args[1]))
if err != nil {
return shim.Error(fmt.Sprintf("Failed to create asset: %s", args[0]))
}
return shim.Success(nil)
}
// 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.
func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
// Extract the function and args from the transaction proposal
fn, args := stub.GetFunctionAndParameters()
var result string
var err error
if fn == "set" {
result, err = set(stub, args)
} else { // assume 'get' even if fn is nil
result, err = get(stub, args)
}
if err != nil {
return shim.Error(err.Error())
}
// Return the result as success payload
return shim.Success([]byte(result))
}
// Set stores the asset (both key and value) on the ledger. If the key exists,
// it will override the value with the new one
func set(stub shim.ChaincodeStubInterface, args []string) (string, error) {
if len(args) != 2 {
return "", fmt.Errorf("Incorrect arguments. Expecting a key and a value")
}
err := stub.PutState(args[0], []byte(args[1]))
if err != nil {
return "", fmt.Errorf("Failed to set asset: %s", args[0])
}
return args[1], nil
}
// Get returns the value of the specified asset key
func get(stub shim.ChaincodeStubInterface, args []string) (string, error) {
if len(args) != 1 {
return "", fmt.Errorf("Incorrect arguments. Expecting a key")
}
value, err := stub.GetState(args[0])
if err != nil {
return "", fmt.Errorf("Failed to get asset: %s with error: %s", args[0], err)
}
if value == nil {
return "", fmt.Errorf("Asset not found: %s", args[0])
}
return string(value), nil
}
// main function starts up the chaincode in the container during instantiate
func main() {
if err := shim.Start(new(SimpleAsset)); err != nil {
fmt.Printf("Error starting SimpleAsset chaincode: %s", err)
}
}
script.sh是一套区块链生成的脚本,从用户核验、到节点加入组织、按照智能合约、实例化智能合约、执行智能合约一套的脚本。
那么。根据需求。我们想修改智能合约的代码,我们通过修改script.sh的方式去解决
那么我们开始修改链码吧。其实很简单,直接把原本的关于链码的部分都去掉就好了。这样执行之后的区块链网络中,就没有自带的链码了。
#!/bin/bash
# Copyright London Stock Exchange Group All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0
#
echo
echo " ____ _____ _ ____ _____ _____ ____ _____ "
echo "/ ___| |_ _| / \ | _ \ |_ _| | ____| |___ \ | ____|"
echo "\___ \ | | / _ \ | |_) | | | _____ | _| __) | | _| "
echo " ___) | | | / ___ \ | _ < | | |_____| | |___ / __/ | |___ "
echo "|____/ |_| /_/ \_\ |_| \_\ |_| |_____| |_____| |_____|"
echo
CHANNEL_NAME="$1"
: ${CHANNEL_NAME:="mychannel"}
: ${TIMEOUT:="60"}
COUNTER=1
MAX_RETRY=5
ORDERER_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
echo "Channel name : "$CHANNEL_NAME
verifyResult () {
if [ $1 -ne 0 ] ; then
echo "!!!!!!!!!!!!!!! "$2" !!!!!!!!!!!!!!!!"
echo "================== ERROR !!! FAILED to execute End-2-End Scenario =================="
echo
exit 1
fi
}
setGlobals () {
if [ $1 -eq 0 -o $1 -eq 1 ] ; then
CORE_PEER_LOCALMSPID="Org1MSP"
CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/[email protected]/msp
if [ $1 -eq 0 ]; then
CORE_PEER_ADDRESS=peer0.org1.example.com:7051
else
CORE_PEER_ADDRESS=peer1.org1.example.com:7051
fi
else
CORE_PEER_LOCALMSPID="Org2MSP"
CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/[email protected]/msp
if [ $1 -eq 2 ]; then
CORE_PEER_ADDRESS=peer0.org2.example.com:7051
else
CORE_PEER_ADDRESS=peer1.org2.example.com:7051
fi
fi
env |grep CORE
}
createChannel() {
setGlobals 0
if [ -z "$CORE_PEER_TLS_ENABLED" -o "$CORE_PEER_TLS_ENABLED" = "false" ]; then
peer channel create -o orderer.example.com:7050 -c $CHANNEL_NAME -f ./channel-artifacts/channel.tx >&log.txt
else
peer channel create -o orderer.example.com:7050 -c $CHANNEL_NAME -f ./channel-artifacts/channel.tx --tls $CORE_PEER_TLS_ENABLED --cafile $ORDERER_CA >&log.txt
fi
res=$?
cat log.txt
verifyResult $res "Channel creation failed"
echo "===================== Channel \"$CHANNEL_NAME\" is created successfully ===================== "
echo
}
updateAnchorPeers() {
PEER=$1
setGlobals $PEER
if [ -z "$CORE_PEER_TLS_ENABLED" -o "$CORE_PEER_TLS_ENABLED" = "false" ]; then
peer channel update -o orderer.example.com:7050 -c $CHANNEL_NAME -f ./channel-artifacts/${CORE_PEER_LOCALMSPID}anchors.tx >&log.txt
else
peer channel update -o orderer.example.com:7050 -c $CHANNEL_NAME -f ./channel-artifacts/${CORE_PEER_LOCALMSPID}anchors.tx --tls $CORE_PEER_TLS_ENABLED --cafile $ORDERER_CA >&log.txt
fi
res=$?
cat log.txt
verifyResult $res "Anchor peer update failed"
echo "===================== Anchor peers for org \"$CORE_PEER_LOCALMSPID\" on \"$CHANNEL_NAME\" is updated successfully ===================== "
sleep 5
echo
}
## Sometimes Join takes time hence RETRY atleast for 5 times
joinWithRetry () {
peer channel join -b $CHANNEL_NAME.block >&log.txt
res=$?
cat log.txt
if [ $res -ne 0 -a $COUNTER -lt $MAX_RETRY ]; then
COUNTER=` expr $COUNTER + 1`
echo "PEER$1 failed to join the channel, Retry after 2 seconds"
sleep 2
joinWithRetry $1
else
COUNTER=1
fi
verifyResult $res "After $MAX_RETRY attempts, PEER$ch has failed to Join the Channel"
}
joinChannel () {
for ch in 0 1 2 3; do
setGlobals $ch
joinWithRetry $ch
echo "===================== PEER$ch joined on the channel \"$CHANNEL_NAME\" ===================== "
sleep 2
echo
done
}
## Create channel
echo "Creating channel..."
createChannel
## Join all the peers to the channel
echo "Having all peers join the channel..."
joinChannel
## Set the anchor peers for each org in the channel
echo "Updating anchor peers for org1..."
updateAnchorPeers 0
echo "Updating anchor peers for org2..."
updateAnchorPeers 2
echo
echo "===================== All GOOD, End-2-End execution completed ===================== "
echo
echo
echo " _____ _ _ ____ _____ ____ _____ "
echo "| ____| | \ | | | _ \ | ____| |___ \ | ____|"
echo "| _| | \| | | | | | _____ | _| __) | | _| "
echo "| |___ | |\ | | |_| | |_____| | |___ / __/ | |___ "
echo "|_____| |_| \_| |____/ |_____| |_____| |_____|"
echo
exit 0
首先,我们还是把区块链网络中开启起来。
cd $GOPATH/src/github.com/hyperledger/fabric/examples/e2e_cli/
./network_setup.sh down(已经启动的话、或者不知道有没有启动)
./network_setup.sh up
会发现,到安装链码之前的步骤就停下来了。
===================== PEER3 joined on the channel "mychannel" =====================
===================== Anchor peers for org "Org1MSP" on "mychannel" is updated successfully =====================
===================== Anchor peers for org "Org2MSP" on "mychannel" is updated successfully =====================
===================== All GOOD, End-2-End execution completed =====================
_____ _ _ ____ _____ ____ _____
| ____| | \ | | | _ \ | ____| |___ \ | ____|
| _| | \| | | | | | _____ | _| __) | | _|
| |___ | |\ | | |_| | |_____| | |___ / __/ | |___
|_____| |_| \_| |____/ |_____| |_____| |_____|
接下里、我们开始进行安装新链码的操作。需要把链码放到指定的位置里面去。
服务器端
使用cli安装链码
进入cli
docker exec -it cli bash
安装链码
peer chaincode install -n mycc -v 0 -p github.com/hyperledger/fabric/examples/chaincode/go/sacc
实例化链码、这边通常会久一些、耐心等待一下。
peer chaincode instantiate -n mycc -v 0 -c '{"Args":["a","10"]}' -C mychannel
执行智能合约-修改a的值
peer chaincode invoke -n mycc -c '{"Args":["set", "a", "20"]}' -C mychannel
查询智能合约-查询a的值
peer chaincode query -n mycc -c '{"Args":["query","a"]}' -C mychannel
到这里、我们就完成了链码更新的所有操作了~!!!
好了、我们终于可以开始修改SDK了
为了方便理解、先给出接口。
public interface ChaincodeService {
/**
* 执行智能合约
*
* @param key 键
* @param Value 值
*/
public void invoke(String key, String Value);
/**
* 查询智能合约
*
* @param key 键
* @return 智能合约结果
*/
public Map query(String key);
}
实现
@Service
public class ChaincodeServiceImpl implements ChaincodeService {
@Autowired
private CheckService checkService;
@Autowired
private OCRService ocrService;
/**
* 执行智能合约
*
* @param key 键
* @param Value 值
*/
@Override
public void invoke(String key, String Value) {
try {
ChaincodeManager manager = FabricManager.obtain().getManager();
String[] str = {key, Value};
Map result = manager.invoke("set", str);
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 查询智能合约
*
* @param key 键
* @return 智能合约结果
*/
@Override
public Map query(String key) {
try {
ChaincodeManager manager = FabricManager.obtain().getManager();
String[] str = {key};
Map query = manager.query("query", str);
return query;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
底层的封装只是简单的Key、Value交互、为了满足核验逻辑需求、我们进一步封装。
接口
public interface CheckService {
/**
* 增加税票信息
*
* @param information 图片参数
*/
public void create(String information);
/**
* 检验税票信息
*
* @param information 税票信息
* @return true代表检验通过 false代表验证不通过
*/
public boolean check(String information);
}
实现,把传入的信息,进行MD5加密后存入区块链,也从fabric网络中取出MD5信息进行匹配。
@Service
public class CheckServiceImpl implements CheckService {
@Autowired
private OCRService ocrService;
@Autowired
private ChaincodeService chaincodeService;
/**
* 增加税票信息
*
* @param information 税票信息
*/
@Override
public void create(String information) {
//MD5算法加密
String MD5 = MD5Util.MD5EncodeUtf8(information);
chaincodeService.invoke(MD5, MD5);
}
/**
* 检验税票信息
*
* @param information 税票信息
* @return true代表检验通过 false代表验证不通过
*/
@Override
public boolean check(String information) {
Boolean flag = false;
try {
//MD5算法加密
String MD5 = MD5Util.MD5EncodeUtf8(information);
Map mapResult = chaincodeService.query(MD5);
System.out.println(mapResult);
flag = mapResult.get("code").contains("success");
System.out.println(flag);
return flag;
} catch (Exception e) {
return flag;
}
}
}
好了、期待已久了吧。别着急,我们这就开始(๑′ᴗ‵๑)I玩
如果没有做封装的话
@Test
public void ChaincodeManagerTest() {
try {
ChaincodeManager manager = FabricManager.obtain().getManager();
//String[] str = {"a"};
//Map query = manager.query("query", str);
//System.out.println(query);
//Boolean flag = query.get("code").contains("success");
//System.out.println(flag);
//String[] str = {"a", "111"};
//Map result = manager.invoke("set", str);
//System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
}
如果、你封装之后的话。这里为什么要一个睡眠呢。因为执行的时候是多线程的。会导致MD5还未存入的时候、就进行读取。就会发生读不到的异常。
@Test
public void TestCreateAndCheck() throws InterruptedException {
checkService.create("发票识别码开票日期发票代码");
Thread.sleep(5000);
checkService.check("发票识别码开票日期发票代码");
}
这里的话、就完成了一个简易的发票核验区块链系统、是不是很简单~加油��