Hyperledger Fabric Gateway最新版1.3.1开发总结
  iwbGD3gmtxyT 2023年11月02日 29 0

简介

Fabric网关是Hyperledger Fabric区块链网络的核心组件,用于代表客户应用程序来协调提交交易和查询账本状态所需的操作。通过使用网关,客户端应用程序只需要连接到Fabric网络中的单个端点即可达到上述目的。

Hyperledger Fabric Gateway最新版1.3.1开发总结_Hyperledger Fabric

Fabric网关客户端API允许应用程序与Hyperledger Fabric区块链网络进行交互。它实现了Fabric编程模型,提供了通过一个简单的API实现向分类帐提交交易或用最少的代码查询分类帐的内容。

安装

开发过程中,需要使用以下命令将包依赖项添加到项目中:

go get github.com/hyperledger/fabric-gateway

兼容性

此API要求Fabric v2.4(或更高版本)具有启用网关的Peer节点。如下文档中提供了其他兼容性信息:

https://hyperledger.github.io/fabric-gateway/

当前最新版本详细要求见下表:

Hyperledger Fabric Gateway最新版1.3.1开发总结_Hyperledger Fabric_02

新应用概述¶

Gateway组件的客户端使Go开发人员能够使用Hyperledger Fabric编程模型构建客户端应用程序。

客户端应用程序使用Fabric Gateway与区块链网络进行交互。客户端通过调用client.Connect(),其中传入带有客户端标识、客户端签名实现和客户端连接详细信息,来建立与Fabric网关的连接。返回的网关可用于与部署到可通过Fabric网关访问的网络的智能合约进行交易。

注意:在旧版本中,Gateway对象是使用Connect()函数创建的,用于使用存储在钱包(Wallet)中的身份连接到网络配置文件中指定的“网关”Peer节点。然后在此网关连接的上下文中调用与智能合约的交互。

最新应用代码举例

package main

import (
	"fmt"
	"os"

	"github.com/hyperledger/fabric-gateway/pkg/client"
	"github.com/hyperledger/fabric-gateway/pkg/identity"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

func main() {
	// Create gRPC client connection, which should be shared by all gateway connections to this endpoint.
	clientConnection, err := grpc.Dial("gateway.example.org:1337", grpc.WithTransportCredentials(insecure.NewCredentials()))
	panicOnError(err)
	defer clientConnection.Close()

	// Create client identity and signing implementation based on X.509 certificate and private key.
	id := NewIdentity()
	sign := NewSign()

	// Create a Gateway connection for a specific client identity.
	gateway, err := client.Connect(id, client.WithSign(sign), client.WithClientConnection(clientConnection))
	panicOnError(err)
	defer gateway.Close()

	// Obtain smart contract deployed on the network.
	network := gateway.GetNetwork("channelName")
	contract := network.GetContract("chaincodeName")

	// Submit transactions that store state to the ledger.
	submitResult, err := contract.SubmitTransaction("transactionName", "arg1", "arg2")
	panicOnError(err)
	fmt.Printf("Submit result: %s", string(submitResult))

	// Evaluate transactions that query state from the ledger.
	evaluateResult, err := contract.EvaluateTransaction("transactionName", "arg1", "arg2")
	panicOnError(err)
	fmt.Printf("Evaluate result: %s", string(evaluateResult))
}

// NewIdentity creates a client identity for this Gateway connection using an X.509 certificate.
func NewIdentity() *identity.X509Identity {
	certificatePEM, err := os.ReadFile("certificate.pem")
	panicOnError(err)

	certificate, err := identity.CertificateFromPEM(certificatePEM)
	panicOnError(err)

	id, err := identity.NewX509Identity("mspID", certificate)
	panicOnError(err)

	return id
}

// NewSign creates a function that generates a digital signature from a message digest using a private key.
func NewSign() identity.Sign {
	privateKeyPEM, err := os.ReadFile("privateKey.pem")
	panicOnError(err)

	privateKey, err := identity.PrivateKeyFromPEM(privateKeyPEM)
	panicOnError(err)

	sign, err := identity.NewPrivateKeySign(privateKey)
	panicOnError(err)

	return sign
}

func panicOnError(err error) {
	if err != nil {
  panic(err)
	}
}

旧版本中网关与Wallet结合使用举例

/*
Copyright 2020 IBM All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package main

import (
	"fmt"
	"log"
	"os"
	"path/filepath"

	"github.com/hyperledger/fabric-sdk-go/pkg/core/config"
	"github.com/hyperledger/fabric-sdk-go/pkg/gateway"
)

func main() {
	log.Println("============ application-golang starts ============")

	err := os.Setenv("DISCOVERY_AS_LOCALHOST", "true")
	if err != nil {
  log.Fatalf("Error setting DISCOVERY_AS_LOCALHOST environment variable: %v", err)
	}

	walletPath := "wallet"
	// remove any existing wallet from prior runs
	os.RemoveAll(walletPath)
	wallet, err := gateway.NewFileSystemWallet(walletPath)
	if err != nil {
  log.Fatalf("Failed to create wallet: %v", err)
	}

	if !wallet.Exists("appUser") {
  err = populateWallet(wallet)
  if err != nil {
  	log.Fatalf("Failed to populate wallet contents: %v", err)
  }
	}

	ccpPath := filepath.Join(
  "..",
  "..",
  "test-network",
  "organizations",
  "peerOrganizations",
  "org1.example.com",
  "connection-org1.yaml",
	)

	gw, err := gateway.Connect(
  gateway.WithConfig(config.FromFile(filepath.Clean(ccpPath))),
  gateway.WithIdentity(wallet, "appUser"),
	)
	if err != nil {
  log.Fatalf("Failed to connect to gateway: %v", err)
	}
	defer gw.Close()

	channelName := "mychannel"
	if cname := os.Getenv("CHANNEL_NAME"); cname != "" {
  channelName = cname
	}

	log.Println("--> Connecting to channel", channelName)
	network, err := gw.GetNetwork(channelName)
	if err != nil {
  log.Fatalf("Failed to get network: %v", err)
	}

	chaincodeName := "basic"
	if ccname := os.Getenv("CHAINCODE_NAME"); ccname != "" {
  chaincodeName = ccname
	}

	log.Println("--> Using chaincode", chaincodeName)
	contract := network.GetContract(chaincodeName)

	log.Println("--> Submit Transaction: InitLedger, function creates the initial set of assets on the ledger")
	result, err := contract.SubmitTransaction("InitLedger")
	if err != nil {
  log.Fatalf("Failed to Submit transaction: %v", err)
	}
	log.Println(string(result))

	log.Println("--> Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger")
	result, err = contract.EvaluateTransaction("GetAllAssets")
	if err != nil {
  log.Fatalf("Failed to evaluate transaction: %v", err)
	}
	log.Println(string(result))

	log.Println("--> Submit Transaction: CreateAsset, creates new asset with ID, color, owner, size, and appraisedValue arguments")
	result, err = contract.SubmitTransaction("CreateAsset", "asset113", "yellow", "5", "Tom", "1300")
	if err != nil {
  log.Fatalf("Failed to Submit transaction: %v", err)
	}
	log.Println(string(result))

	log.Println("--> Evaluate Transaction: ReadAsset, function returns an asset with a given assetID")
	result, err = contract.EvaluateTransaction("ReadAsset", "asset113")
	if err != nil {
  log.Fatalf("Failed to evaluate transaction: %v\n", err)
	}
	log.Println(string(result))

	log.Println("--> Evaluate Transaction: AssetExists, function returns 'true' if an asset with given assetID exist")
	result, err = contract.EvaluateTransaction("AssetExists", "asset1")
	if err != nil {
  log.Fatalf("Failed to evaluate transaction: %v\n", err)
	}
	log.Println(string(result))

	log.Println("--> Submit Transaction: TransferAsset asset1, transfer to new owner of Tom")
	_, err = contract.SubmitTransaction("TransferAsset", "asset1", "Tom")
	if err != nil {
  log.Fatalf("Failed to Submit transaction: %v", err)
	}

	log.Println("--> Evaluate Transaction: ReadAsset, function returns 'asset1' attributes")
	result, err = contract.EvaluateTransaction("ReadAsset", "asset1")
	if err != nil {
  log.Fatalf("Failed to evaluate transaction: %v", err)
	}
	log.Println(string(result))
	log.Println("============ application-golang ends ============")
}

func populateWallet(wallet *gateway.Wallet) error {
	log.Println("============ Populating wallet ============")
	credPath := filepath.Join(
  "..",
  "..",
  "test-network",
  "organizations",
  "peerOrganizations",
  "org1.example.com",
  "users",
  "User1@org1.example.com",
  "msp",
	)

	certPath := filepath.Join(credPath, "signcerts", "cert.pem")
	// read the certificate pem
	cert, err := os.ReadFile(filepath.Clean(certPath))
	if err != nil {
  return err
	}

	keyDir := filepath.Join(credPath, "keystore")
	// there's a single file in this dir containing the private key
	files, err := os.ReadDir(keyDir)
	if err != nil {
  return err
	}
	if len(files) != 1 {
  return fmt.Errorf("keystore folder should have contain one file")
	}
	keyPath := filepath.Join(keyDir, files[0].Name())
	key, err := os.ReadFile(filepath.Clean(keyPath))
	if err != nil {
  return err
	}

	identity := gateway.NewX509Identity("Org1MSP", string(cert), string(key))

	return wallet.Put("appUser", identity)
}

上述代码来自地址:https://github.com/hyperledger/fabric-samples/blob/main/asset-transfer-basic/application-go/assetTransfer.go

旧版本与新版本对照(迁移指南)

接下来,我们来详细介绍旧版本Gateway组件与最新版本Gateway组件的应用区别,以及在迁移时的注意事项。

Fabric编程模型

Fabric网关客户端API是遗留SDK和Fabric编程模型的演变结果。总体来看,最新的API结构和功能与传统SDK大致相同。

相似之处

相似之处包括:


  • 网关:连接到Fabric Peer节点,提供对区块链网络的访问。
  • 网络:托管共享账本(类似于通道)的节点的区块链网络。
  • 合约:部署到区块链网络的智能合约。
  • 提交交易:调用智能合约交易功能来更新分类帐状态。
  • 评估交易:调用智能合约交易函数来查询分类帐状态。
  • Chaincode事件:接收已提交事务发出的事件,以触发业务流程。
  • 阻止事件:接收提交到分类帐的阻止。
  • 事件检查点:保持当前事件位置以支持事件恢复。

用于连接网关实例、提交或评估事务的高级API部分保持与原来的部分几乎相同。

对于更高级的事务调用,例如那些涉及瞬态数据的调用,遗留SDK在Contract对象上提供了createTransaction()方法,该方法允许客户端应用程序指定其他调用参数(请参阅GoNodeJava文档)。Fabric Gateway客户端API在Contract对象上提供了一个newProposal()方法来执行相同的功能(请参阅相关Go、Node和Java文档)。

主要差异

从传统SDK切换到现在的Fabric网关客户端API时,需要考虑的关键API和行为差异如下:


  • gRPC连接由应用程序管理,并且可以由网关实例共享。
  • 不需要连接配置文件。
  • 不再需要钱包(Wallet)机制,由客户端应用程序自身来选择如何管理凭据存储。
  • 背书要求通常不再需要具体说明。
  • 事件重新连接由客户端应用程序来控制。

接下来,对这些主要差异点作更多细致介绍,并提供一些开发建议。

gRPC连接

在旧版SDK中,每个网关实例都维护到网络节点的内部gRPC连接,用于评估和提交事务以及获取事件。可以为每个网关实例创建许多gRPC连接,并且这些连接不会与其他网关实例共享。由于创建gRPC连接会产生大量开销,因此这可能会导致资源问题

在现在的Fabric网关客户端API中,每个网关实例对所有操作都使用到Fabric网关服务的单个gRPC连接。网关实例的gRPC连接由客户端应用程序提供,并且可以由多个网关实例共享。这允许客户端应用程序完全控制gRPC连接配置和资源分配。

API文档中已经包含创建gRPC连接并使用此连接来连接Go、Node和Java的网关实例的相应示例。

连接配置文件

Fabric网关客户端API不再使用通用连接配置文件。相反,只需要Fabric网关服务的端点地址就可以建立gRPC连接,该连接将在连接网关实例时使用。由于Fabric网关服务是由Fabric Peer提供的,因此端点地址可以是将在连接配置文件中定义的Peer地址之一。它也可以是负载均衡器或入口控制器的地址,用于将连接转发到网络Peer,从而提供高可用性。

钱包(Wallet)

传统SDK提供用于凭据管理的钱包。钱包有两个功能:


  1. 永久凭据存储。
  2. 基于凭据类型的网关客户端配置(例如,由硬件安全模块管理的标识)。

使用现在的Fabric网关客户端API,存储凭据的机制是客户端应用程序的一种选择。应用程序可以继续使用遗留SDK来访问存储在钱包中的凭证,或者可以使用不同的机制来存储和访问凭证。

要连接Gateway实例,应用程序只需提供一个Identity对象和一个签名实现。API中提供了帮助程序函数,用于从X.509证书创建Identity对象,还用于从私钥或HSM管理的身份创建签名实现。为了使用替代签名机制,应用程序可以提供自己的签名实现。

背书要求

在更复杂的场景中使用遗留SDK时,例如涉及私有数据收集、链代码到链代码调用或基于密钥的背书策略的场景,客户端应用程序通常需要明确指定事务调用的背书要求。这可以采用指定链代码兴趣、认可组织或认可Peer节点的形式。

使用现在的Fabric网关客户端API,客户端应用程序通常不需要指定认可要求。Fabric Gateway服务动态地确定给定事务调用的认可要求,并使用最合适的Peer节点来获得认可。

对于包含瞬态数据的交易方案,有两种显著的场景确实需要应用程序明确指定可用于背书的组织:


  1. Fabric Gateway服务的组织无法认可事务建议。
  2. 对没有读取权限的私有数据集合执行盲写操作的事务。

这些限制是为了确保私人数据不会分发给不应该访问它的组织。

建议仅在特别需要的情况下指定认可组织。

事件重连

在事件侦听期间发生Peer节点或网络故障的情况下,遗留SDK会透明地尝试重新建立连接,并在成功重新连接后继续传递事件。现在的Fabric网关客户端API则在请求下一个事件时向客户端应用程序显示事件错误。要重新建立事件,应用程序必须启动一个具有适当起始位置的新事件侦听会话。

事件检查点跟踪当前事件位置,可用于在重新连接时在正确的开始位置恢复事件。


参考




【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  W38TVusHpjX0   2023年11月26日   21   0   0 重启服务端客户端
iwbGD3gmtxyT