超级账本

/ 区块链 / 没有评论 / 33浏览

介绍 hyperledger fabric 联盟链环境搭建、链码开发、sdk 调用。

介绍

Hyperledger Fabric 是一个开源的企业级许可分布式账本技术(Distributed Ledger Technology,DLT)平台,专为在企业环境中使用而设计,与其他流行的分布式账本或区块链平台相比,它有一些主要的区别。

一个主要区别是 Hyperledger 是在 Linux 基金会下建立的,该基金会本身在开放式治理的模式下培育开源项目的历史悠久且非常成功,发展了强大的可持续社区和繁荣的生态系统。Hyperledger 由多元化的技术指导委员会进行管理,Hyperledger Fabric 项目由多个组织的不同的维护人员管理。从第一次提交以来,它的开发社区已经发展到超过35个组织和近200个开发人员。

Fabric 具有高度模块化可配置的架构,可为各行各业的业务提供创新性、多样性和优化,其中包括银行、金融、保险、医疗保健、人力资源、供应链甚至数字音乐分发。

Fabric 是第一个支持通用编程语言编写智能合约(如 Java、Go 和 Node.js)的分布式账本平台,不受限于特定领域语言(Domain-Specific Languages,DSL)。这意味着大多数企业已经拥有开发智能合约所需的技能,并且不需要额外的培训来学习新的语言或特定领域语言。

Fabric 平台也是许可的,这意味着它与公共非许可网络不同,参与者彼此了解而不是匿名的或完全不信任的。也就是说,尽管参与者可能不会完全信任彼此(例如,在同行业竞争对手),但网络可以在一个治理模式下运行,这个治理模式是建立在参与者之间确实存在的信任之上的,如处理纠纷的法律协议或框架。

该平台最重要的区别之一是它支持可插拔的共识协议,使得平台能够更有效地进行定制,以适应特定的业务场景和信任模型。例如,当部署在单个企业内或由可信任的权威机构管理时,完全拜占庭容错的共识可能是不必要的,并且大大降低了性能和吞吐量。在这种的情况下,崩溃容错(Crash Fault-Tolerant,CFT)共识协议可能就够了,而在去中心化的场景中,可能需要更传统的拜占庭容错(Byzantine Fault Tolerant,BFT)共识协议。

Fabric 可以利用不需要原生加密货币的共识协议来激励昂贵的挖矿或推动智能合约执行。不使用加密货币会降低系统的风险,并且没有挖矿操作意味着可以使用与任何其他分布式系统大致相同的运营成本来部署平台。

这些差异化设计特性的结合使 Fabric 成为当今交易处理和交易确认延迟方面性能较好的平台之一,并且它实现了交易的隐私和保密以及智能合约(Fabric 称之为“链码”)。

架构

10

基本架构:

主要特性:

自身特性:

性能:

以太坊与超级账本

以太坊

以太坊优势:

以太坊劣势:

超级账本

超级账本相比以太坊完善了上述内容,但架构上有个缺陷:

实施超级账本困难

快速开始

环境

阅读 入门 文档,准备好环境

准备

构建网络

阅读 构建你的第一个网络 文档,构建第一个网络。

在构建你的第一个网络(BYFN)场景中,提供了一个包含两个组织的 Hyperledger Fabric 网络,每个组织包含两个 Peer 节点,一个 "Solo" 模式的排序服务。

准备

生成网络构件

执行命令:

./byfn.sh generate

这一步为我们的各种网络实体生成证书和秘钥。创世区块 genesis block 用于引导排序服务,也包含了一组配置 Channel 所需要的配置交易集合。

启动网络

启动网络:

./byfn.sh up

上面的命令会编译 Golang 智能合约的镜像并且启动相应的容器。Go 语言是默认的链码语言,但是它也支持 Node.jsJava 的链码。如果你想要在这个教程里运行 node 链码,你可以使用下面的命令:

./byfn.sh up -l node

关于:

要让示例运行 Java 链码:

./byfn.sh up -l java

注意:不要同时运行这两个命令。除非你停止并重新创建了网络,否则只能尝试一种语言。

最终启动成功,能看到输出 END 标志:

up

关闭网络

接下来的命令会结束掉你所有的容器,移除加密的材料和四个构件,并且从 Docker 仓库删除链码镜像。

./byfn.sh down

错误排查

链码

类似以太坊的智能合约

阅读 链码开发者教程 文档

开发链码

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)
	}
}

部署链码

每个 peer 节点上安装:

peer chaincode install -n sacc -v 1.0 -l golang -p apps/sacc

注意:采用 fabric-samples/first-network 方式,则只需要在 cli 上部署、初始化、调用

初始化

某个 peer 节点:

peer chaincode instantiate -n sacc -v 1.0 -c '{"Args":["a","10"]}' -C mychannel -P "AND ('Org1MSP.peer','Org2MSP.peer')" --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem

注意:采用 fabric-samples/first-network 方式,则只需要在 cli 上部署、初始化、调用

调用

某个 peer 节点:

# 调用
peer chaincode invoke -n sacc -c '{"Args":["get", "b"]}' -C mychannel --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem

peer chaincode invoke -n sacc -c '{"Args":["set", "b", "100"]}' -C mychannel --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem

注意:采用 fabric-samples/first-network 方式,则只需要在 cli 上部署、初始化、调用

invoke

java集成

基于 fabric-gateway-java 官方 sdk

依赖

依赖 fabric-gateway-java 即可,不在需要 fabric-java-sdk 低等级 api 。

<dependency>
    <groupId>org.hyperledger.fabric</groupId>
    <artifactId>fabric-gateway-java</artifactId>
    <version>1.4.2</version>
</dependency>

配置

将 crypto-config 文件夹放入工程,并创建 config 文件夹。

配置

其中 connection.yaml 如下:

---
name: first-network-org1
version: 1.0.0
client:
  organization: Org1
  connection:
    timeout:
      peer:
        endorser: '300'
organizations:
  Org1:
    mspid: Org1MSP
    peers:
      - peer0.org1.example.com
      - peer1.org1.example.com
    certificateAuthorities:
      - ca.org1.example.com
    adminPrivateKey:
      path: crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore/a1f06c22739534812d5e216e187dea158227d74fa1984de25c14dc34a3f7dc70_sk
    signedCert:
      path: crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/signcerts/Admin@org1.example.com-cert.pem

peers:
  peer0.org1.example.com:
    url: grpcs://47.101.136.24:7051
    tlsCACerts:
      path: crypto-config/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem
    grpcOptions:
      ssl-target-name-override: peer0.org1.example.com
      hostnameOverride: peer0.org1.example.com
  peer1.org1.example.com:
    url: grpcs://47.101.136.24:8051
    tlsCACerts:
      path: crypto-config/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem
    grpcOptions:
      ssl-target-name-override: peer1.org1.example.com
      hostnameOverride: peer1.org1.example.com

certificateAuthorities:
  ca.org1.example.com:
    url: https://47.101.136.24:7054
    caName: ca-org1
    tlsCACerts:
      path: crypto-config/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem
    httpOptions:
      verify: false

注意:以上开启了 tls 认证。

示例代码

如下是访问 Channel 为 mychannel 下部署的 mycc 链码:

package tk.fishfish.fabric;

import org.hyperledger.fabric.gateway.Contract;
import org.hyperledger.fabric.gateway.ContractException;
import org.hyperledger.fabric.gateway.Gateway;
import org.hyperledger.fabric.gateway.Network;
import org.hyperledger.fabric.gateway.Wallet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tk.fishfish.fabric.utils.GateWayUtils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.TimeoutException;

/**
 * Java调用链码
 *
 * @author 奔波儿灞
 * @since 1.0
 */
public class Sample {

    private static final Logger LOG = LoggerFactory.getLogger(Sample.class);

    public static void main(String[] args) {
        // Load a file system based wallet for managing identities.
        Wallet wallet;
        try {
            wallet = GateWayUtils.getWallet("Org1MSP", "user1", "config/wallet/user.pem", "config/wallet/user.priv");
        } catch (IOException e) {
            throw new RuntimeException("read wallet failed", e);
        }

        // Path to a common connection profile describing the network.
        Path networkConfigFile = Paths.get("config/connection.yaml");

        // Configure the gateway connection used to access the network.
        Gateway.Builder builder;
        try {
            builder = Gateway.createBuilder()
                    .identity(wallet, "user1")
                    .networkConfig(networkConfigFile);
        } catch (IOException e) {
            throw new RuntimeException("identity error", e);
        }

        // Create a gateway connection
        try (Gateway gateway = builder.connect()) {
            // Obtain a smart contract deployed on the network.
            Network network = gateway.getNetwork("mychannel");

            Contract contract = network.getContract("mycc");

            // Submit transactions that store state to the ledger.
            byte[] createCarResult = contract.submitTransaction("query", "a");

            LOG.info("result: {}", new String(createCarResult, StandardCharsets.UTF_8));
        } catch (ContractException | TimeoutException | InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

}

注意:

运行

这里相当于调用了链码 mycc 的 query 方法:

invoke

java部署链码

当采用 go 开发完链码后,通过 java sdk 部署到联盟链。

配置

6

示例

package tk.fishfish.fabric;

import org.hyperledger.fabric.gateway.Gateway;
import org.hyperledger.fabric.gateway.Network;
import org.hyperledger.fabric.gateway.Wallet;
import org.hyperledger.fabric.gateway.impl.GatewayImpl;
import org.hyperledger.fabric.sdk.ChaincodeResponse;
import org.hyperledger.fabric.sdk.HFClient;
import org.hyperledger.fabric.sdk.InstallProposalRequest;
import org.hyperledger.fabric.sdk.Peer;
import org.hyperledger.fabric.sdk.ProposalResponse;
import org.hyperledger.fabric.sdk.TransactionRequest;
import org.hyperledger.fabric.sdk.exception.InvalidArgumentException;
import org.hyperledger.fabric.sdk.exception.ProposalException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tk.fishfish.fabric.utils.GateWayUtils;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.stream.Collectors;

/**
 * 部署链码
 *
 * @author 奔波儿灞
 * @since 1.0
 */
public class InstallSample {

    private static final Logger LOG = LoggerFactory.getLogger(InstallSample.class);

    public static void main(String[] args) {
        // Load a file system based wallet for managing identities.
        Wallet wallet;
        try {
            wallet = GateWayUtils.getWallet("Org1MSP", "admin", "config/wallet/admin.pem", "config/wallet/admin.priv");
        } catch (IOException e) {
            throw new RuntimeException("read wallet failed", e);
        }

        // Path to a common connection profile describing the network.
        Path networkConfigFile = Paths.get("config/connection.yaml");

        // Configure the gateway connection used to access the network.
        Gateway.Builder builder;
        try {
            builder = Gateway.createBuilder()
                    .identity(wallet, "admin")
                    .networkConfig(networkConfigFile);
        } catch (IOException e) {
            throw new RuntimeException("identity error", e);
        }

        // Create a gateway connection
        try (Gateway gateway = builder.connect()) {
            Network network = gateway.getNetwork("mychannel");

            // 部署 org1 节点
            Collection<Peer> peers = network.getChannel().getPeersForOrganization("Org1MSP")
                    .stream()
                    // 过滤掉 name 带端口的 peer
                    .filter(peer -> !peer.getName().contains(":")).collect(Collectors.toList());
            LOG.info("install peers: {}", peers);

            // Send install chaincode request
            Collection<ProposalResponse> responses = sendInstallProposal(((GatewayImpl) gateway).getClient(), peers);
            for (ProposalResponse resp : responses) {
                String transactionId = resp.getTransactionID();
                Peer peer = resp.getPeer();
                String message = resp.getMessage();
                if (resp.getStatus() == ChaincodeResponse.Status.SUCCESS) {
                    LOG.info("install chaincode success, transactionId: {}, peer: {}, message: {}", transactionId, peer, message);
                } else {
                    LOG.warn("install chaincode failed, transactionId: {}, peer: {}, message: {}", transactionId, peer, message);
                }
            }
        } catch (InvalidArgumentException | ProposalException e) {
            throw new RuntimeException("install chaincode failed", e);
        }
    }

    private static Collection<ProposalResponse> sendInstallProposal(HFClient client, Collection<Peer> peers) throws InvalidArgumentException, ProposalException {
        InstallProposalRequest request = client.newInstallProposalRequest();
        request.setChaincodeName("bo");
        request.setChaincodeVersion("2.0");
        request.setChaincodeLanguage(TransactionRequest.Type.GO_LANG);
        // 必须指定,{chaincodeSourceLoc}/src/{chaincodePath}/ 下存在 go 源文件
        request.setChaincodeSourceLocation(new File("chaincode"));
        request.setChaincodePath("go/bo");
        return client.sendInstallProposal(request, peers);
    }

}

注意:

运行

可以看到 org1 下的 2 个 peer 节点均正确安装了链码 bo 的 2.0 版本:

7

注意

java初始化链码

当部署完链码后,进行初始化

配置

同部署链码

示例

代码大致同部署,支持使用 InstantiationProposalRequest 接口

package tk.fishfish.fabric;

import org.hyperledger.fabric.gateway.Gateway;
import org.hyperledger.fabric.gateway.Network;
import org.hyperledger.fabric.gateway.Wallet;
import org.hyperledger.fabric.gateway.impl.GatewayImpl;
import org.hyperledger.fabric.sdk.ChaincodeResponse;
import org.hyperledger.fabric.sdk.Channel;
import org.hyperledger.fabric.sdk.HFClient;
import org.hyperledger.fabric.sdk.InstantiateProposalRequest;
import org.hyperledger.fabric.sdk.Peer;
import org.hyperledger.fabric.sdk.ProposalResponse;
import org.hyperledger.fabric.sdk.exception.InvalidArgumentException;
import org.hyperledger.fabric.sdk.exception.ProposalException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tk.fishfish.fabric.utils.GateWayUtils;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.stream.Collectors;

/**
 * 初始化链码
 *
 * @author 奔波儿灞
 * @since 1.0
 */
public class InstantiationSample {

    private static final Logger LOG = LoggerFactory.getLogger(InstantiationSample.class);

    public static void main(String[] args) {
        // Load a file system based wallet for managing identities.
        Wallet wallet;
        try {
            wallet = GateWayUtils.getWallet("Org1MSP", "admin", "config/wallet/admin.pem", "config/wallet/admin.priv");
        } catch (IOException e) {
            throw new RuntimeException("read wallet failed", e);
        }

        // Path to a common connection profile describing the network.
        Path networkConfigFile = Paths.get("config/connection.yaml");

        // Configure the gateway connection used to access the network.
        Gateway.Builder builder;
        try {
            builder = Gateway.createBuilder()
                    .identity(wallet, "admin")
                    .networkConfig(networkConfigFile);
        } catch (IOException e) {
            throw new RuntimeException("identity error", e);
        }

        // Create a gateway connection
        try (Gateway gateway = builder.connect()) {
            Network network = gateway.getNetwork("mychannel");

            // 部署 org1 节点
            Collection<Peer> peers = network.getChannel().getPeersForOrganization("Org1MSP")
                    .stream()
                    // 过滤掉 name 带端口的 peer
                    .filter(peer -> !peer.getName().contains(":")).collect(Collectors.toList());
            LOG.info("install peers: {}", peers);

            // Send init chaincode request
            Collection<ProposalResponse> responses = sendInstantiationProposal(network.getChannel(), ((GatewayImpl) gateway).getClient(), peers);
            for (ProposalResponse resp : responses) {
                String transactionId = resp.getTransactionID();
                Peer peer = resp.getPeer();
                String message = resp.getMessage();
                if (resp.getStatus() == ChaincodeResponse.Status.SUCCESS) {
                    LOG.info("init chaincode success, transactionId: {}, peer: {}, message: {}", transactionId, peer, message);
                } else {
                    LOG.warn("init chaincode failed, transactionId: {}, peer: {}, message: {}", transactionId, peer, message);
                }
            }
            // 注意,一定要提交事务,否则初始化了链码,仍然调用不了链码
            network.getChannel().sendTransaction(responses);
        } catch (InvalidArgumentException | ProposalException e) {
            throw new RuntimeException("init chaincode failed", e);
        }
    }

    private static Collection<ProposalResponse> sendInstantiationProposal(Channel channel, HFClient client, Collection<Peer> peers) throws InvalidArgumentException, ProposalException {
        InstantiateProposalRequest request = client.newInstantiationProposalRequest();
        request.setChaincodeName("bo");
        request.setChaincodeVersion("4.0");
        request.setArgs("a", "100");
        return channel.sendInstantiationProposal(request, peers);
    }

}

运行

初始化完成后,会启动 docker 容器。

8

注意

java调用链码

当链码初始化之后,可以调用链码。当然也可以参考 java集成章节 的示例代码。

配置

同上

示例

package tk.fishfish.fabric;

import org.hyperledger.fabric.gateway.Gateway;
import org.hyperledger.fabric.gateway.Network;
import org.hyperledger.fabric.gateway.Wallet;
import org.hyperledger.fabric.gateway.impl.GatewayImpl;
import org.hyperledger.fabric.sdk.ChaincodeID;
import org.hyperledger.fabric.sdk.ChaincodeResponse;
import org.hyperledger.fabric.sdk.Channel;
import org.hyperledger.fabric.sdk.HFClient;
import org.hyperledger.fabric.sdk.Peer;
import org.hyperledger.fabric.sdk.ProposalResponse;
import org.hyperledger.fabric.sdk.TransactionProposalRequest;
import org.hyperledger.fabric.sdk.TransactionRequest;
import org.hyperledger.fabric.sdk.exception.InvalidArgumentException;
import org.hyperledger.fabric.sdk.exception.ProposalException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tk.fishfish.fabric.utils.GateWayUtils;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.stream.Collectors;

/**
 * 调用链码
 *
 * @author 奔波儿灞
 * @since 1.0
 */
public class InvokeSample {

    private static final Logger LOG = LoggerFactory.getLogger(InvokeSample.class);

    public static void main(String[] args) {
        // Load a file system based wallet for managing identities.
        Wallet wallet;
        try {
            wallet = GateWayUtils.getWallet("Org1MSP", "admin", "config/wallet/admin.pem", "config/wallet/admin.priv");
        } catch (IOException e) {
            throw new RuntimeException("read wallet failed", e);
        }

        // Path to a common connection profile describing the network.
        Path networkConfigFile = Paths.get("config/connection.yaml");

        // Configure the gateway connection used to access the network.
        Gateway.Builder builder;
        try {
            builder = Gateway.createBuilder()
                    .identity(wallet, "admin")
                    .networkConfig(networkConfigFile);
        } catch (IOException e) {
            throw new RuntimeException("identity error", e);
        }

        // Create a gateway connection
        try (Gateway gateway = builder.connect()) {
            Network network = gateway.getNetwork("mychannel");

            // 部署 org1 节点
            Collection<Peer> peers = network.getChannel().getPeersForOrganization("Org1MSP")
                    .stream()
                    // 过滤掉 name 带端口的 peer
                    .filter(peer -> !peer.getName().contains(":")).collect(Collectors.toList());
            LOG.info("query peers: {}", peers);

            // Send invoke chaincode request
            Collection<ProposalResponse> responses = sendTransactionProposal(network.getChannel(), ((GatewayImpl) gateway).getClient(), peers);
            for (ProposalResponse resp : responses) {
                String transactionId = resp.getTransactionID();
                Peer peer = resp.getPeer();
                String message = resp.getMessage();
                byte[] payload = resp.getChaincodeActionResponsePayload();
                if (resp.getStatus() == ChaincodeResponse.Status.SUCCESS) {
                    LOG.info("query chaincode success: {}, transactionId: {}, peer: {}", new String(payload), transactionId, peer);
                } else {
                    LOG.warn("query chaincode failed, transactionId: {}, peer: {}, message: {}", transactionId, peer, message);
                }
            }
            // 注意,一定要提交事务
            network.getChannel().sendTransaction(responses);
        } catch (InvalidArgumentException | ProposalException e) {
            throw new RuntimeException("query chaincode failed", e);
        }
    }

    private static Collection<ProposalResponse> sendTransactionProposal(Channel channel, HFClient client, Collection<Peer> peers) throws InvalidArgumentException, ProposalException {
        TransactionProposalRequest request = client.newTransactionProposalRequest();
        ChaincodeID id = ChaincodeID.newBuilder()
                .setName("demo")
                .build();
        request.setChaincodeID(id);
        request.setChaincodeLanguage(TransactionRequest.Type.GO_LANG);
        request.setFcn("query");
        request.setArgs("a");
        return channel.sendTransactionProposal(request, peers);
    }

}

注意:

运行

9

注意

建议使用 java集成 章节调用链码的写法!