Hyperledger 账本包含两部分:
清单 6 展示了如何创建自定义数据模型 / 模式。它定义了住房贷款申请所需的数据模型。主要模型称为 LoanApplication,它拥有原始数据类型和复杂数据类型,也就是 Personal 和 Financial Info。
因为我们的键值存储将数据存储为 JSON,所以这些数据模型最终需要转换为 JSON 字符串。每个字段(如 json:"id")的注释的作用类似于编组 (marshal) / 解组 (unmarshal) API 的元数据,该 API 将使用这些注释将每个字段映射到相应的 JSON 字符串等效表示。
- //custom data models
- type PersonalInfo struct {
- Firstname string `json:"firstname"`
- Lastname string `json:"lastname"`
- DOB string `json:"DOB"`
- Email string `json:"email"`
- Mobile string `json:"mobile"`
- }
- type FinancialInfo struct {
- MonthlySalary int `json:"monthlySalary"`
- MonthlyRent int `json:"monthlyRent"`
- OtherExpenditure int `json:"otherExpenditure"`
- MonthlyLoanPayment int `json:"monthlyLoanPayment"`
- }
- type LoanApplication struct {
- ID string `json:"id"`
- PropertyId string `json:"propertyId"`
- LandId string `json:"landId"`
- PermitId string `json:"permitId"`
- BuyerId string `json:"buyerId"`
- SalesContractId string `json:"salesContractId"`
- PersonalInfo PersonalInfo `json:"personalInfo"`
- FinancialInfo FinancialInfo `json:"financialInfo"`
- Status string `json:"status"`
- RequestedAmount int `json:"requestedAmount"`
- FairMarketValue int `json:"fairMarketValue"`
- ApprovedAmount int `json:"approvedAmount"`
- ReviewerId string `json:"reviewerId"`
- LastModifiedDate string `json:"lastModifiedDate"`
- }
清单 7 和清单 8 中的代码展示了如何将数据存储到账本中以及如何从账本中获取数据。
清单 7 中第 1 行 上的 CreateLoanApplication 接受两个参数。第一个参数是 ChaincodeStubInterface,它拥有实用的 API 来与区块链账本、交易上下文、调用方证书等交互。第二个参数是一个字符串数组,该方法的调用方可以使用该数组传入所需的参数。
第 2-8 行记录和验证了输入参数。
在第 9 行上,检索了贷款申请的 Id 值,该值将用作存储实际贷款申请对象的键。
在第 10 行上,以 JSON 字符串格式检索了实际贷款申请内容。例如,'{"propertyId":"prop1","landId":"land1","permitId":"permit1","buyerId":"vojha24","personalInfo":{"firstname":"Varun","lastname":"Ojha","dob":"dob","email":"varun@gmail.com","mobile":"99999999"},"financialInfo":{"monthlySalary":10000,"otherExpenditure":0,"monthlyRent":1000,"monthlyLoanPayment":1000},"status":"Submitted","requestedAmount":40000,"fairMarketValue":58000,"approvedAmount":40000,"reviewedBy":"abc1","lastModifiedDate":"21/09/2016 2:30pm"}'
在第 12 行上,调用了 stub.PutState 方法,以便将贷款申请 id 和实际的贷款申请 JSON 内容作为一个键值对存储到区块链账本中。请注意,存储在键值存储中的值必须始终是字节数组。因此,在存储到账本之前,贷款申请 JSON 字符串首先被转换为字节数组。
- func CreateLoanApplication(stub shim.ChaincodeStubInterface, args[] string)([] byte, error) {
- fmt.Println("Entering CreateLoanApplication")
- if len(args) < 2 {
- fmt.Println("Invalid number of args") return nil,
- errors.New("Expected at least two arguments for loan application creation")
- }
- var loanApplicationId = args[0]
- var loanApplicationInput = args[1]
- err: =stub.PutState(loanApplicationId, [] byte(loanApplicationInput)) if err != nil {
- fmt.Println("Could not save loan application to ledger", err) return nil,
- err
- }
- fmt.Println("Successfully saved loan application") return nil,
- nil
- }
在清单 8 中,第 1 行上的 GetLoanApplication 方法接受两个参数。第一个参数是 ChaincodeStubInterface,它拥有实用的 API 来与区块链账本、交易上下文、调用方证书等交互。第二个参数是一个字符串数组,该方法的调用方可使用该数组传入需要的参数。
第 2-7 行记录和验证了输入参数。
在第 9 行上,检索了贷款申请的 Id 值,该值将用作从账本中检索实际贷款申请对象的键。
在第 10 行上,通过传入 loanApplicationId 键,调用 stub.GetState 方法以字节数组格式检索贷款申请 JSON 内容。请注意,存储在键值存储中的值必须始终是字节数组。因此,在存储到账本之前,贷款申请 JSON 字符串首先被转换为字节数组。
- func GetLoanApplication(stub shim.ChaincodeStubInterface, args[] string)([] byte, error) {
- fmt.Println("Entering GetLoanApplication")
- if len(args) < 1 {
- fmt.Println("Invalid number of arguments") return nil,
- errors.New("Missing loan application ID")
- }
- var loanApplicationId = args[0] bytes,
- err: =stub.GetState(loanApplicationId) if err != nil {
- fmt.Println("Could not fetch loan application with id " + loanApplicationId + " from ledger", err) return nil,
- err
- }
- return bytes,
- nil
- }
备注:也可以使用传统关系数据模型处理数据。例如,ChaincodeStubInterface 能够创建表格并处理行和列。但这只是一种逻辑抽象,而且数据将作为键值对存储在 RocksDB 中。在撰写本文时,Hyperledger Fabric 1.0 版正在开发之中,而且表数据结构已被弃用。因此,本教程不会讨论它,以便最大限度地减少链代码开发人员从 v0.6 过渡到 v1.0 所需执行的更改。
如清单 7 和清单 8 中所示,stub.PutState 和 stub.GetState 方法只处理字节数组。所以,能够将链代码中使用的常规结构对象与 JSON 字符串进行相互转换很重要。
清单 9 展示了如何将一种结构编组为可存储在账本中的 JSON 字符串字节数组。第 2 行创建了 PersonalInfo 对象的一个实例。第 3 行使用 json 包将对象编组为 JSON 字符串,并返回同一个字符串的字节数组。
可以通过将 "encoding/json" 包含在顶部的 import 代码块中来导入 json 包。然后此字节数组可以使用清单 7 中给出的 stub.PutState 方法来存储到账本中。
- var personalInfo PersonalInfo
- personalInfo = PersonalInfo{"Varun", "Ojha", "dob", "varun@gmail.com", "9999999999"}
- bytes, err ;= json.Marshal (&personalInfo)
- if err != nil {
- fmt.Println("Could not marshal personal info object", err)
- return nil, err
- }
- err = stub.PutState("key", bytes)
清单 10 展示如何将一种结构从字节数组解组为已填充的结构。第 1 行使用关联的键从账本中获取 PersonalInfo JSON 字符串字节。第 3 行将第 1 行中检索的字节解组为变量 personalInfo 所引用的 PersonalInfo 对象。
现在可以使用第 4 行所示的点记法来访问和修改 personalInfo 对象。
- piBytes, err := stub.GetState(la1)
- var personalInfo PersonalInfo
- err = json.Unmarshal(piBytes, &personalInfo)
- fmt.Println(personalInfo.Firstname)
Hyperledger 与其他区块链结构的主要区别之一是,它有一个安全的、带许可的账本,该账本适合用于实现企业级解决方案。
Hyperledger 中的 "成员服务" 组件对在区块链网络中实现安全性和许可发挥着关键作用。它负责向用户发放登记和交易证书来响应注册和登记。
清单 11 展示了如何从调用方的交易证书检索属性。前面已经提到过,用户或客户端应用程序需要在每个链代码调用请求中传入用户的证书,以便对区块链网络上的目标对等节点执行身份验证。HFC SDK 负责自动在请求中传递证书。
清单 11 的第 11 行上的 Invoke 函数已经过修改,以便检查输入函数名称并将调用工作委托给合适的处理函数。此外,Invoke 函数还会验证发送 CreateLoanApplication 调用请求的调用方的访问权限和角色。Invoke 函数调用自定义的 GetCertAttribute 函数从调用方的交易证书中检索特定属性。
GetCertAttribute 函数通过在第 3 行传入属性名称来获取属性值。
第 15 行检查调用方是否拥有 Bank Admin 角色并能调用 CreateLoanApplication 函数。如果调用方没有所需的角色,则会返回相应的错误。这样就可以在链代码中实现基于属性的访问控制。
ChaincodeStubInterface 有一些实用程序函数可以处理属性,比如 ReadCertAttribute、VerifyAttribute 和 VerifyAttributes。所有这些方法都依靠 github.com/hyperledger/fabric/core/chaincode/shim/crypto/attr 包来创建和使用负责处理属性的 AttributeHandlerImpl。
在目前正在开发的 Hyperledger Fabric 版本 (v1.0) 中,这些实用程序函数已从 ChaincodeStubInterface 删除。因此在 v1.0 中,链代码开发人员需要直接使用 AttributeHandlerImpl 来处理属性。
- func GetCertAttribute(stub shim.ChaincodeStubInterface, attributeName string) (string, error) {
- fmt.Println("Entering GetCertAttribute")
- attr, err := stub.ReadCertAttribute(attributeName)
- if err != nil {
- return "", errors.New("Couldn't get attribute " + attributeName + ". Error: " + err.Error())
- }
- attrString := string(attr)
- return attrString, nil
- }
- func (t *SampleChaincode) Invoke(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
- if function == "CreateLoanApplication" {
- username, _ := GetCertAttribute(stub, "username")
- role, _ := GetCertAttribute(stub, "role")
- if role == "Bank_Home_Loan_Admin" {
- return CreateLoanApplication(stub, args)
- } else {
- return nil, errors.New(username + " with role " + role + " does not have access to create a loan application")
- }
- }
- return nil, nil
- }
Hyperledger 包含一个事件框架,可以使用该框架发布 / 订阅预定义的或自定义的事件。您可以自由地在链代码中创建和发出自定义事件。例如,只要区块链的状态发生更改,就会生成一个事件。通过向区块链上的事件中心注册一个事件适配器,客户端应用程序可以订阅和使用这些事件。本系列的后续教程将详细介绍客户端应用程序如何使用 HFC SDK 订阅和使用通过链代码生成的事件。
除了自定义事件之外,Hyperledger 中包含的一些预定义的内部事件还包括:
清单 12 中的代码展示了如何创建和发布自定义事件。
第 1-4 行定义了一个包含类型和描述字段的自定义事件对象。
CreateLoanApplication 函数从第 6 行开始,已被修改为包括在成功创建贷款申请时创建事件。
第 23 行创建了 customEvent 对象的实例并填入适当的事件细节。
第 24 行按照前面解释的方法将事件对象编组为 JSON 字符串字节数组。
第 28 行设置了自定义事件。stub.SetEvent 方法接受两个参数:事件 name 和 payload。客户端应用程序可订阅同一个事件名称 / 主题,以便在链代码生成事件时接收它们。
- type customEvent struct {
- Type string`json: "type"`Description string`json: "description"`
- }
- func CreateLoanApplication(stub shim.ChaincodeStubInterface, args[] string)([] byte, error) {
- fmt.Println("Entering CreateLoanApplication")
- if len(args) < 2 {
- fmt.Println("Invalid number of args") return nil,
- errors.New("Expected at least two arguments for loan application creation")
- }
- var loanApplicationId = args[0]
- var loanApplicationInput = args[1]
- err: =stub.PutState(loanApplicationId, [] byte(loanApplicationInput)) if err != nil {
- fmt.Println("Could not save loan application to ledger", err) return nil,
- err
- }
- var event = customEvent = {
- "createLoanApplication",
- "Successfully created loan application with ID " + loanApplicationId
- }
- eventBytes,
- err; = json.Marshal( & event) if err != nil {
- return nil,
- err
- }
- err = stub.SetEvent("evtSender", eventBytes) if err != nil {
- fmt.Println("Could not set event for loan application creation", err)
- }
- fmt.Println("Successfully saved loan application") return nil,
- nil
- }
要在链代码中处理日志,既可以使用标准 fmt 包和 print 语句,也可以使用 shim 包中的 ChaincodeLogger 类型。
ChaincodeLogger 支持以下日志级别:
可以通过 3 种方式设置日志级别:
清单 13 展示了如何创建、配置和使用 ChaincodeLogger。
- func SampleLogging() {
- //Different Logging Levels
- criticalLevel, _ := shim.LogLevel("CRITICAL")
- errorLevel, _ := shim.LogLevel("ERROR")
- warningLevel, _ := shim.LogLevel("WARNING")
- noticeLevel, _ := shim.LogLevel("NOTICE")
- infoLevel, _ := shim.LogLevel("INFO")
- debugLevel, _ := shim.LogLevel("DEBUG")
- //Logging level at the shim level
- shim.SetLoggingLevel(infoLevel)
- //Create a logger instance
- myLogger := shim.NewLogger("SampleChaincodeLogger")
- //Set logging level on logger instance
- myLogger.SetLevel(infoLevel)
- //Check logging level
- fmt.Println(myLogger.IsEnabledFor(infoLevel))
- //Log statements
- myLogger.Info("Info Message")
- myLogger.Critical("Critical Message")
- myLogger.Warning("Warning Message")
- myLogger.Error("Error Message")
- myLogger.Notice("Notice Message")
- myLogger.Debug("Debug Message")
- }
在与客户一起开发区块链应用程序时,我常常被问及以下问题。
以下两种方法在最新的 Hyperledger Fabric (v0.6) 中都有效:
此问题是在一个供应链场景中提出的,区块链解决方案的一个最终用户不满意在对所有对等节点可见的智能合约中共享私有业务逻辑 / 合同信息(比如与不同供应商谈判的不同价格)。在 v0.6 中,可以使用外部系统集成来处理这种情况。
解决方案:对等节点希望保持为私有的业务逻辑 / 规则 / 合同,可以作为一组业务规则在外部应用程序(比如服务)中运行。链代码本身能够执行出站调用。所以举例而言,链代码可对业务规则 / 逻辑服务执行 REST API 调用并获取结果,以便隐藏逻辑,让实际链代码看不见它。
可以从链代码内与区块链外的系统集成。例如,可以使用链代码与外部数据库、API 等通信。但重要的是确保与这些系统的交互不会让链代码变得不确定。
局限性和问题
备注:在目前正在开发的 Hyperledger Fabric v1.0 中,此问题已通过更改架构本身得到系统解决。
本教程首先介绍了链代码的基础知识,然后深入介绍了链代码的构建块,以及可用于在链代码中执行重要任务(比如访问控制、数据建模和事件管理)的不同 API。请下载 SampleChaincode.go 文件中的统一代码段。
本系列的后续教程将介绍测试驱动的链代码开发,开发可与区块链网络通信的 Node.js 客户端应用程序,从而将开发流程补充完整。
来源: http://www.ibm.com/developerworks/cn/analytics/library/ba-cn-apache-spark-memory-management/index.html