Hyperledger Fabric - 链码部署

前提条件

启动测试网络

./network-myself.sh up

创建通道

./network-myself.sh createChannel

智能合约(链码)

pom.xml文件

配置远程仓库

<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

<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

<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>
                <!-- 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>

model

创建合约的数据对象 User 使用 @DataType 注解标识,定义三个字段 userId、name、money 使用 @Property 注解标识:

@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;
    }
}

合约逻辑

  1. 合约类使用 @Contract 与 @Default 注解标识,并实现 ContractInterface 接口

  2. 合约方法使用 @Transaction 注解标识

    Transaction.TYPE.SUBMIT 为「写入交易」
    Transaction.TYPE.EVALUATE 为 「查询」

  3. 包含3个交易方法:init、addUser、transfer

  4. 包含2个查询方法:getUser、queryAll

@Contract(name = "mycc")
@Default
public class MyAssetChaincode implements ContractInterface {
    public  MyAssetChaincode() {}

    /**
     * 初始化3条记录
     */
    @Transaction(intent = Transaction.TYPE.SUBMIT)
    public void init(final Context ctx) {
        addUser(ctx, "1", "chenyi",100D);
        addUser(ctx, "2", "admin",200D);
        addUser(ctx, "3", "chenyi666",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);
    }

    /**
     * 转账
     * @param sourceId 源用户id
     * @param targetId 目标用户id
     * @param money 金额
     */
    @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));
    }
}

链码安装及部署

模拟参数

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 函数来初始化链码

设置环境变量

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

打包链码

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节点安装链码

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节点安装链码

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是一样的,因此在一个组织中查看即可

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身份批准智能合约

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身份批准智能合约

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} 

检查通道成员是否已批准相同的链码定义

peer lifecycle chaincode checkcommitreadiness --channelID $CHANNEL_NAME --name ${CC_NAME}  --version  ${CC_VERSION} --sequence ${CC_SEQUENCE} --tls --cafile "$ORDERER_CA"  --output json

将链码提交至通道

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} 

若需要私有数据

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提交的链码

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提交的链码

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节点

初始化链码

 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":[]}'

查询所有资产

 peer chaincode query -C mychannel -n mycc -c '{"Args":["queryAll"]}'

附加:

docker-compose down -v
docker volume ls
docker volume rm explorer_pgdata
docker volume rm explorer_walletstore
docker-compose up -d
docker ps -a
cd ~/.local/share/Trash/files/
posted @ 2025-04-02 21:37  终须有  阅读(132)  评论(0)    收藏  举报