前提条件
启动测试网络
创建通道
1
| ./network-myself.sh createChannel
|
智能合约(链码)
pom.xml文件
配置远程仓库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <repositories> <repository> <id>central</id> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </repository> <repository> <id>jitpack.io</id> <url>https://www.jitpack.io</url> </repository> <repository> <id>artifactory</id> <url>https://hyperledger.jfrog.io/hyperledger/fabric-maven</url> </repository> </repositories>
|
依赖合约sdk
1 2 3 4 5
| <dependency> <groupId>org.hyperledger.fabric-chaincode-java</groupId> <artifactId>fabric-chaincode-shim</artifactId> <version>${fabric-chaincode-java.version}</version> </dependency>
|
通过插件 maven-shade-plugin 指定 mainClass 为 org.hyperledger.fabric.contract.ContractRouter
新版本所有合约的 mainClass 都为 org.hyperledger.fabric.contract.ContractRouter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| <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>org.hyperledger.fabric.contract.ContractRouter</mainClass> </transformer> </transformers> <filters> <filter> <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>
|
model
创建合约的数据对象 User 使用 @DataType 注解标识,定义三个字段 userId、name、money 使用 @Property 注解标识:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| @DataType public class User { @Property private final String userId;
@Property private final String name;
@Property private final double money;
public User(final String userId, final String name, final double money) { this.userId = userId; this.name = name; this.money = money; }
@Override public boolean equals(final Object obj) { if (this == obj) { return true; } if ((obj == null) || (getClass() != obj.getClass())) { return false; } User other = (User) obj; return Objects.deepEquals( new String[] {getUserId(), getName()}, new String[] {other.getUserId(), other.getName()}) && Objects.deepEquals( new double[] {getMoney()}, new double[] {other.getMoney()}); }
@Override public int hashCode() { return Objects.hash(getUserId(), getName(), getMoney()); }
@Override public String toString() { return JSON.toJSONString(this); }
public String getUserId() { return userId; }
public String getName() { return name; }
public double getMoney() { return money; } }
|
合约逻辑
- 合约类使用 @Contract 与 @Default 注解标识,并实现 ContractInterface 接口
- 合约方法使用 @Transaction 注解标识
Transaction.TYPE.SUBMIT 为 **「写入交易」
**Transaction.TYPE.EVALUATE 为 「查询」
- 包含3个交易方法:init、addUser、transfer
- 包含2个查询方法:getUser、queryAll
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| @Contract(name = "mycc") @Default public class MyAssetChaincode implements ContractInterface { public MyAssetChaincode() {}
@Transaction(intent = Transaction.TYPE.SUBMIT) public void init(final Context ctx) { addUser(ctx, "1", "zlt",100D); addUser(ctx, "2", "admin",200D); addUser(ctx, "3", "guest",300D); }
@Transaction(intent = Transaction.TYPE.SUBMIT) public User addUser(final Context ctx, final String userId, final String name, final double money) { ChaincodeStub stub = ctx.getStub(); User user = new User(userId, name, money); String userJson = JSON.toJSONString(user); stub.putStringState(userId, userJson); return user; }
@Transaction(intent = Transaction.TYPE.EVALUATE) public User getUser(final Context ctx, final String userId) { ChaincodeStub stub = ctx.getStub(); String userJSON = stub.getStringState(userId); if (userJSON == null || userJSON.isEmpty()) { String errorMessage = String.format("User %s does not exist", userId); throw new ChaincodeException(errorMessage); } User user = JSON.parseObject(userJSON, User.class); return user; }
@Transaction(intent = Transaction.TYPE.EVALUATE) public String queryAll(final Context ctx) { ChaincodeStub stub = ctx.getStub(); List<User> userList = new ArrayList<>(); QueryResultsIterator<KeyValue> results = stub.getStateByRange("", ""); for (KeyValue result: results) { User user = JSON.parseObject(result.getStringValue(), User.class); System.out.println(user); userList.add(user); } return JSON.toJSONString(userList); }
@Transaction(intent = Transaction.TYPE.SUBMIT) public void transfer(final Context ctx, final String sourceId, final String targetId, final double money) { ChaincodeStub stub = ctx.getStub(); User sourceUser = getUser(ctx, sourceId); User targetUser = getUser(ctx, targetId); if (sourceUser.getMoney() < money) { String errorMessage = String.format("The balance of user %s is insufficient", sourceId); throw new ChaincodeException(errorMessage); } User newSourceUser = new User(sourceUser.getUserId(), sourceUser.getName(), sourceUser.getMoney() - money); User newTargetUser = new User(targetUser.getUserId(), targetUser.getName(), targetUser.getMoney() + money); stub.putStringState(sourceId, JSON.toJSONString(newSourceUser)); stub.putStringState(targetId, JSON.toJSONString(newTargetUser)); } }
|
链码安装及部署
模拟参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| export CHANNEL_NAME="mychannel" export CC_NAME=mycc export CC_SRC_PATH=../chaincode export CC_SRC_LANGUAGE=java export CC_VERSION="1.0" export CC_SEQUENCE="1" export CC_INIT_FCN="" export CC_END_POLICY="" export CC_COLL_CONFIG="" export DELAY="3" export MAX_RETRY="5" export VERBOSE="false" export INIT_REQUIRED="" export CC_RUNTIME_LANGUAGE=java
|
部分字段解释:
CHANNEL_NAME=${1:-“mychannel”} # 通道名称,默认mychannel
CC_NAME=${2} # 链码名称
CC_SRC_PATH=${3} # 智能合约代码所在的目录
CC_SRC_LANGUAGE=${4} # 智能合约语言(当前支持Go、Java、Javascript、Typescript)
CC_VERSION=${5:-“1.0”} # 链码版本,默认1.0
CC_SEQUENCE=${6:-“1”} # 链码被定义或者更新多少次的指数
CC_INIT_FCN=${7:-“NA”} # 链码初始化调用的函数名
CC_END_POLICY=${8:-“NA”} # 背书策略
DELAY=${10:-“3”} # 延迟执行时间(单位:秒)
MAX_RETRY=${11:-“5”} # 尝试最多失败次数
INIT_REQUIRED # 请求执行 Init 函数来初始化链码
设置环境变量
1 2 3 4 5 6 7 8 9
| export FABRIC_CFG_PATH=/root/go/src/github.com/hyperledger/fabric-samples/config export CORE_PEER_TLS_ENABLED=true export ORDERER_CA=${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem export PEER0_ORG1_CA=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt export PEER1_ORG1_CA=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/tls/ca.crt export PEER0_ORG2_CA=${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt export PEER0_ORG3_CA=${PWD}/organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/ca.crt export ORDERER_ADMIN_TLS_SIGN_CERT=${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.crt export ORDERER_ADMIN_TLS_PRIVATE_KEY=${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.key
|
打包链码
1 2
| rm -rf mycc.tar.gz peer lifecycle chaincode package ${CC_NAME}.tar.gz --path ${CC_SRC_PATH} --lang ${CC_RUNTIME_LANGUAGE} --label ${CC_NAME}_${CC_VERSION}
|
安装链码
在组织1中peer节点安装链码
1 2 3 4 5 6 7 8 9 10
| export CORE_PEER_LOCALMSPID="Org1MSP" export CORE_PEER_TLS_ROOTCERT_FILE=$PEER0_ORG1_CA export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp export CORE_PEER_ADDRESS=localhost:7051 peer lifecycle chaincode install ${CC_NAME}.tar.gz
export CORE_PEER_TLS_ROOTCERT_FILE=$PEER1_ORG1_CA export CORE_PEER_ADDRESS=localhost:8051 peer lifecycle chaincode install ${CC_NAME}.tar.gz
|
在组织2中peer节点安装链码
1 2 3 4 5 6
| export CORE_PEER_LOCALMSPID="Org2MSP" export CORE_PEER_TLS_ROOTCERT_FILE=$PEER0_ORG2_CA export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp export CORE_PEER_ADDRESS=localhost:9051 peer lifecycle chaincode install ${CC_NAME}.tar.gz
|
查询组织2中peer节点安装链码的链码包ID
在组织1与组织2中安装链码的链码包ID是一样的,因此在一个组织中查看即可
1 2 3 4
| peer lifecycle chaincode queryinstalled >&log.txt export PACKAGE_ID=$(sed -n "/${CC_NAME}_${CC_VERSION}/{s/^Package ID: //; s/, Label:.*$//; p;}" log.txt) echo $PACKAGE_ID
|
批准智能合约
组织1身份批准智能合约
1 2 3 4 5 6
| export CORE_PEER_LOCALMSPID="Org1MSP" export CORE_PEER_TLS_ROOTCERT_FILE=$PEER0_ORG1_CA export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp export CORE_PEER_ADDRESS=localhost:7051 peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "$ORDERER_CA" --channelID $CHANNEL_NAME --name ${CC_NAME} --version ${CC_VERSION} --package-id ${PACKAGE_ID} --sequence ${CC_SEQUENCE} ${INIT_REQUIRED} ${CC_END_POLICY} ${CC_COLL_CONFIG}
|
组织2身份批准智能合约
1 2 3 4 5 6
| export CORE_PEER_LOCALMSPID="Org2MSP" export CORE_PEER_TLS_ROOTCERT_FILE=$PEER0_ORG2_CA export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp export CORE_PEER_ADDRESS=localhost:9051 peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "$ORDERER_CA" --channelID $CHANNEL_NAME --name ${CC_NAME} --version ${CC_VERSION} --package-id ${PACKAGE_ID} --sequence ${CC_SEQUENCE} ${INIT_REQUIRED} ${CC_END_POLICY} ${CC_COLL_CONFIG}
|
检查通道成员是否已批准相同的链码定义
1
| peer lifecycle chaincode checkcommitreadiness --channelID $CHANNEL_NAME --name ${CC_NAME} --version ${CC_VERSION} --sequence ${CC_SEQUENCE} --tls --cafile "$ORDERER_CA" --output json
|
将链码提交至通道
1 2 3
| export PEER_CONN_PARMS=(--peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:8051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt) peer lifecycle chaincode commit -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "$ORDERER_CA" --channelID $CHANNEL_NAME --name ${CC_NAME} "${PEER_CONN_PARMS[@]}" --version ${CC_VERSION} --sequence ${CC_SEQUENCE} ${INIT_REQUIRED} ${CC_END_POLICY} ${CC_COLL_CONFIG}
|
若需要私有数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| export CORE_PEER_LOCALMSPID="Org1MSP" export CORE_PEER_TLS_ROOTCERT_FILE=$PEER0_ORG1_CA export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp export CORE_PEER_ADDRESS=localhost:7051 peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "$ORDERER_CA" --channelID $CHANNEL_NAME --name ${CC_NAME} --version ${CC_VERSION} --package-id ${PACKAGE_ID} --sequence ${CC_SEQUENCE} --collections-config /home/user/chaincode/collections_config.json --signature-policy "OR('Org1MSP.member','Org2MSP.member')"
export CORE_PEER_LOCALMSPID="Org2MSP" export CORE_PEER_TLS_ROOTCERT_FILE=$PEER0_ORG2_CA export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp export CORE_PEER_ADDRESS=localhost:9051 peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "$ORDERER_CA" --channelID $CHANNEL_NAME --name ${CC_NAME} --version ${CC_VERSION} --package-id ${PACKAGE_ID} --sequence ${CC_SEQUENCE} --collections-config /home/user/chaincode/collections_config.json --signature-policy "OR('Org1MSP.member','Org2MSP.member')"
export PEER_CONN_PARMS=(--peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:8051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt) peer lifecycle chaincode commit -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "$ORDERER_CA" --channelID $CHANNEL_NAME --name ${CC_NAME} "${PEER_CONN_PARMS[@]}" --version ${CC_VERSION} --sequence ${CC_SEQUENCE} --collections-config /home/user/chaincode/collections_config.json --signature-policy "OR('Org1MSP.member','Org2MSP.member')"
|
查询提交的链码
查询组织1提交的链码
1 2 3 4 5 6
| export CORE_PEER_LOCALMSPID="Org1MSP" export CORE_PEER_TLS_ROOTCERT_FILE=$PEER0_ORG1_CA export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp export CORE_PEER_ADDRESS=localhost:7051 peer lifecycle chaincode querycommitted --channelID $CHANNEL_NAME --name ${CC_NAME}
|
查询组织2提交的链码
1 2 3 4 5 6
| export CORE_PEER_LOCALMSPID="Org2MSP" export CORE_PEER_TLS_ROOTCERT_FILE=$PEER0_ORG2_CA export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp export CORE_PEER_ADDRESS=localhost:9051
peer lifecycle chaincode querycommitted --channelID $CHANNEL_NAME --name ${CC_NAME}
|
调用链码函数
当前是调用的是组织2中的peer节点
初始化链码
1
| 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 mychannel -n mycc ${PEER_CONN_PARMS[@]} -c '{"function":"init","Args":[]}'
|
查询所有资产
1
| peer chaincode query -C mychannel -n mycc -c '{"Args":["queryAll"]}'
|
附加:
1 2 3 4 5 6
| docker-compose down -v docker volume ls docker volume rm explorer_pgdata docker volume rm explorer_walletstore docker-compose up -d docker ps -a
|
1
| cd ~/.local/share/Trash/files/
|