智能合约处理
受众:架构师,应用程序和智能合约开发人员
区块链网络的核心是智能合约。在PaperNet中,商业票据智能合约中的代码定义了商业票据的有效状态,以及将纸张从一种状态转换为另一种状态的交易逻辑。在本主题中,我们将向您展示如何实施一个现实世界的智能合约,该合约管理着发行,购买和赎回商业票据的过程。
我们将介绍:
- 什么是智能合约及其重要性
- 如何定义智能合约
- 如何定义交易
- 如何进行交易
- 如何在智能合约中表示业务对象
- 如何在分类帐中存储和检索对象
如果愿意,您可以下载示例,甚至可以在本地运行。它是用JavaScript和Java编写的,但是逻辑是完全独立于语言的,因此您可以轻松地看到正在发生的事情!(该示例也将适用于Go。)
智能合约
智能合约定义业务对象的不同状态,并管理在这些不同状态之间移动对象的流程。智能合约之所以重要,是因为它们使架构师和智能合约开发人员能够定义关键业务流程和数据,这些关键业务流程和数据是在区块链网络中进行协作的不同组织之间共享的。
在PaperNet网络中,智能合约由MagnetoCorp和DigiBank等不同的网络参与者共享。连接到网络的所有应用程序必须使用相同版本的智能合约,以便它们共同实现相同的共享业务流程和数据。
实现语言
支持两个运行时,即Java虚拟机和Node.js。这使您有机会使用JavaScript,TypeScript,Java或可以在这些受支持的运行时之一中运行的任何其他语言之一。
在Java和TypeScript中,注释或修饰符用于提供有关智能合约及其结构的信息。这样可以提供更丰富的开发经验-例如,可以强制执行作者信息或返回类型。在JavaScript中,必须遵循约定,因此,围绕自动确定的内容存在限制。
JavaScript和Java都给出了示例。
合同类别
PaperNet商业用纸智能合约的副本包含在一个文件中。使用浏览器查看它,或者如果已下载,则在您喜欢的编辑器中将其打开。
-
papercontract.js
- JavaScript版本 -
CommercialPaperContract.java
- Java版本
您可能会从文件路径中注意到这是MagnetoCorp的智能合约副本。MagnetoCorp和DigiBank必须就他们将要使用的智能合约的版本达成协议。现在,使用哪个组织的副本都无所谓,它们都是相同的。
花一些时间看一下智能合约的整体结构;注意它很短!在文件顶部,您将看到商业票据智能合约的定义:
JavaScript
class CommercialPaperContract extends Contract {...}
Java
@Contract(...)
@Default
public class CommercialPaperContract implements ContractInterface {...}
本CommercialPaperContract
类包含商业票据交易的定义- 问题,购买 和赎回。正是这些交易使商业票据得以存在并在其生命周期中移动。我们将很快检查这些 事务,但是现在对于JavaScript来说, CommericalPaperContract
扩展了Hyperledger Fabric Contract
类。
对于Java,该类必须用@Contract(...)
注释修饰。这提供了提供有关合同的其他信息的机会,例如许可证和作者。该@Default()
注释表明,该合同类是默认的合同类。能够将合同类别标记为默认合同类别在某些具有多个合同类别的智能合约中很有用。
如果您使用的是TypeScript实现,那么会有类似的@Contract(...)
注释实现与Java中相同的目的。
有关可用注释的更多信息,请查阅可用的API文档:
- Java智能合约的API文档
- Node.js智能合约的API文档
这些类,注释和Context
类早已纳入范围:
JavaScript
const { Contract, Context } = require('fabric-contract-api');
Java
import org.hyperledger.fabric.contract.Context;
import org.hyperledger.fabric.contract.ContractInterface;
import org.hyperledger.fabric.contract.annotation.Contact;
import org.hyperledger.fabric.contract.annotation.Contract;
import org.hyperledger.fabric.contract.annotation.Default;
import org.hyperledger.fabric.contract.annotation.Info;
import org.hyperledger.fabric.contract.annotation.License;
import org.hyperledger.fabric.contract.annotation.Transaction;
我们的商业票据合同将使用这些类的内置功能,例如自动方法调用, 每个事务上下文, 事务处理程序和类共享状态。
还请注意,JavaScript类构造函数是如何使用其超类使用 显式协定名称进行初始化的:
constructor() {
super('org.papernet.commercialpaper');
}
对于Java类,构造函数为空白,因为可以在@Contract()
注释中指定显式协定名称。如果不存在,则使用该类的名称。
最重要的是,org.papernet.commercialpaper
它具有非常强的描述性-该智能合约是所有PaperNet组织对商业票据的公认定义。
通常,每个文件只有一个智能合约–合约往往具有不同的生命周期,因此将它们分开是明智的。然而,在某些情况下,多个智能合同可能为应用程序提供语法的帮助,例如EuroBond
,DollarBond
,YenBond
,但本质上提供同样的功能。在这种情况下,可以消除智能合约和交易的歧义。
交易定义
在该类中,找到issue方法。
JavaScript
async issue(ctx, issuer, paperNumber, issueDateTime, maturityDateTime, faceValue) {...}
Java
@Transaction
public CommercialPaper issue(CommercialPaperContext ctx,
String issuer,
String paperNumber,
String issueDateTime,
String maturityDateTime,
int faceValue) {...}
Java批注@Transaction
用于将该方法标记为事务定义。TypeScript具有等效的注释。
只要将此合同称为issue
商业票据,就可以控制此功能。回想一下如何通过以下交易创建商业票据00001:
Txn = issue
Issuer = MagnetoCorp
Paper = 00001
Issue time = 31 May 2020 09:00:00 EST
Maturity date = 30 November 2020
Face value = 5M USD
我们已经更改了编程风格的变量名,但是看到了这些属性如何几乎直接映射到issue
方法变量。
issue
每当应用程序请求发布商业票据时,该方法就会由合同自动授予控制权。交易属性值通过相应的变量可供方法使用。请参阅应用程序主题中的示例应用程序,了解应用程序如何使用Hyperledger Fabric SDK提交事务。
您可能已经注意到问题定义中有一个额外的变量– ctx
。这称为事务上下文,并且始终是第一位。默认情况下,它维护与交易逻辑相关的按合同和按交易的信息。例如,它将包含MagnetoCorp的指定交易标识符,MagnetoCorp颁发用户的数字证书以及对分类帐API的访问。
通过实现自己的createContext()
方法而不是接受默认实现,了解智能合约如何扩展默认交易上下文:
JavaScript
createContext() {
return new CommercialPaperContext()
}
Java
@Override
public Context createContext(ChaincodeStub stub) {
return new CommercialPaperContext(stub);
}
此扩展上下文将自定义属性添加paperList
到默认值:
JavaScript
class CommercialPaperContext extends Context {
constructor() {
super();
// All papers are held in a list of papers
this.paperList = new PaperList(this);
}
Java
class CommercialPaperContext extends Context {
public CommercialPaperContext(ChaincodeStub stub) {
super(stub);
this.paperList = new PaperList(this);
}
public PaperList paperList;
}
我们将很快看到如何ctx.paperList
在随后用于帮助存储和检索所有PaperNet商业论文的方法。
为了巩固您对智能合约交易结构的理解,找到购买和赎回交易定义,并查看是否可以看到它们如何映射到其相应的商业票据交易。
该购买交易:
Txn = buy
Issuer = MagnetoCorp
Paper = 00001
Current owner = MagnetoCorp
New owner = DigiBank
Purchase time = 31 May 2020 10:00:00 EST
Price = 4.94M USD
JavaScript
async buy(ctx, issuer, paperNumber, currentOwner, newOwner, price, purchaseTime) {...}
Java
@Transaction
public CommercialPaper buy(CommercialPaperContext ctx,
String issuer,
String paperNumber,
String currentOwner,
String newOwner,
int price,
String purchaseDateTime) {...}
在赎回交易:
Txn = redeem
Issuer = MagnetoCorp
Paper = 00001
Redeemer = DigiBank
Redeem time = 31 Dec 2020 12:00:00 EST
JavaScript
async redeem(ctx, issuer, paperNumber, redeemingOwner, redeemDateTime) {...}
Java
@Transaction
public CommercialPaper redeem(CommercialPaperContext ctx,
String issuer,
String paperNumber,
String redeemingOwner,
String redeemDateTime) {...}
在这两种情况下,请观察商业票据交易与智能合约方法定义之间的1:1对应关系。
所有JavaScript函数都使用async
和await
关键字,从而可以将JavaScript函数视为同步函数调用。
交易逻辑
现在,您已经了解了合同的结构和交易的定义,让我们集中讨论智能合同中的逻辑。
回顾第一期交易:
Txn = issue
Issuer = MagnetoCorp
Paper = 00001
Issue time = 31 May 2020 09:00:00 EST
Maturity date = 30 November 2020
Face value = 5M USD
结果是发出方法被控制了:
JavaScript
async issue(ctx, issuer, paperNumber, issueDateTime, maturityDateTime, faceValue) {
// create an instance of the paper
let paper = CommercialPaper.createInstance(issuer, paperNumber, issueDateTime, maturityDateTime, faceValue);
// Smart contract, rather than paper, moves paper into ISSUED state
paper.setIssued();
// Newly issued paper is owned by the issuer
paper.setOwner(issuer);
// Add the paper to the list of all similar commercial papers in the ledger world state
await ctx.paperList.addPaper(paper);
// Must return a serialized paper to caller of smart contract
return paper.toBuffer();
}
Java
@Transaction
public CommercialPaper issue(CommercialPaperContext ctx,
String issuer,
String paperNumber,
String issueDateTime,
String maturityDateTime,
int faceValue) {
System.out.println(ctx);
// create an instance of the paper
CommercialPaper paper = CommercialPaper.createInstance(issuer, paperNumber, issueDateTime, maturityDateTime,
faceValue,issuer,"");
// Smart contract, rather than paper, moves paper into ISSUED state
paper.setIssued();
// Newly issued paper is owned by the issuer
paper.setOwner(issuer);
System.out.println(paper);
// Add the paper to the list of all similar commercial papers in the ledger
// world state
ctx.paperList.addPaper(paper);
// Must return a serialized paper to caller of smart contract
return paper;
}
逻辑很简单:获取交易输入变量,创建新的商业票据paper
,使用将其添加到所有商业票据的列表中 paperList
,然后将新的商业票据(序列化为缓冲区)作为交易响应。
查看如何paperList
从交易上下文中检索如何提供对商业票据列表的访问。issue()
,buy()
并redeem()
不断进行重新访问ctx.paperList
以使商业用纸的列表保持最新。
购买交易的逻辑更加复杂:
JavaScript
async buy(ctx, issuer, paperNumber, currentOwner, newOwner, price, purchaseDateTime) {
// Retrieve the current paper using key fields provided
let paperKey = CommercialPaper.makeKey([issuer, paperNumber]);
let paper = await ctx.paperList.getPaper(paperKey);
// Validate current owner
if (paper.getOwner() !== currentOwner) {
throw new Error('Paper ' + issuer + paperNumber + ' is not owned by ' + currentOwner);
}
// First buy moves state from ISSUED to TRADING
if (paper.isIssued()) {
paper.setTrading();
}
// Check paper is not already REDEEMED
if (paper.isTrading()) {
paper.setOwner(newOwner);
} else {
throw new Error('Paper ' + issuer + paperNumber + ' is not trading. Current state = ' +paper.getCurrentState());
}
// Update the paper
await ctx.paperList.updatePaper(paper);
return paper.toBuffer();
}
Java
@Transaction
public CommercialPaper buy(CommercialPaperContext ctx,
String issuer,
String paperNumber,
String currentOwner,
String newOwner,
int price,
String purchaseDateTime) {
// Retrieve the current paper using key fields provided
String paperKey = State.makeKey(new String[] { paperNumber });
CommercialPaper paper = ctx.paperList.getPaper(paperKey);
// Validate current owner
if (!paper.getOwner().equals(currentOwner)) {
throw new RuntimeException("Paper " + issuer + paperNumber + " is not owned by " + currentOwner);
}
// First buy moves state from ISSUED to TRADING
if (paper.isIssued()) {
paper.setTrading();
}
// Check paper is not already REDEEMED
if (paper.isTrading()) {
paper.setOwner(newOwner);
} else {
throw new RuntimeException(
"Paper " + issuer + paperNumber + " is not trading. Current state = " + paper.getState());
}
// Update the paper
ctx.paperList.updatePaper(paper);
return paper;
}
了解如何交易的检查currentOwner
,并且paper
是TRADING
改变与业主之前paper.setOwner(newOwner)
。但是基本流程很简单–检查一些前提条件,设置新所有者,更新分类帐上的商业票据,并将更新后的商业票据(序列化为缓冲区)作为交易响应返回。
您为什么不明白是否可以理解兑换 交易的逻辑?
代表一个对象
我们已经看到了如何 使用和类定义和实施问题,购买和兑换交易。通过查看这些类如何工作来结束本主题。CommercialPaper``PaperList
找到CommercialPaper
课程:
JavaScript*在 paper.js文件中:
class CommercialPaper extends State {...}
@DataType()
public class CommercialPaper extends State {...}
此类包含商业票据状态的内存表示形式。查看该createInstance
方法如何使用提供的参数初始化新的商业票据:
JavaScript
static createInstance(issuer, paperNumber, issueDateTime, maturityDateTime, faceValue) {
return new CommercialPaper({ issuer, paperNumber, issueDateTime, maturityDateTime, faceValue });
}
Java
public static CommercialPaper createInstance(String issuer, String paperNumber, String issueDateTime,
String maturityDateTime, int faceValue, String owner, String state) {
return new CommercialPaper().setIssuer(issuer).setPaperNumber(paperNumber).setMaturityDateTime(maturityDateTime)
.setFaceValue(faceValue).setKey().setIssueDateTime(issueDateTime).setOwner(owner).setState(state);
}
回顾发行事务如何使用此类:
JavaScript
let paper = CommercialPaper.createInstance(issuer, paperNumber, issueDateTime, maturityDateTime, faceValue);
Java
faceValue,issuer,"");
了解每次调用发行交易的方式,都会创建一个包含交易数据的新的商业票据内存实例。
需要注意的几个要点:
这是内存中的表示形式;稍后我们将看到 它如何显示在分类帐中。
本
CommercialPaper
类扩展State
类。State
是一个应用程序定义的类,它为状态创建通用抽象。所有状态都有一个它们表示的业务对象类,一个复合键,可以序列化和反序列化等等。State
当我们在分类账上存储多个业务对象类型时,有助于使代码更清晰易读。检查文件中的State
类。state.js
纸张在创建时会计算自己的密钥-访问分类帐时将使用此密钥。密钥是由
issuer
和组成的paperNumber
。
constructor(obj) {
super(CommercialPaper.getClass(), [obj.issuer, obj.paperNumber]);
Object.assign(this, obj);
}
- 纸张是
ISSUED
通过事务而不是纸张类别转移到状态的。这是因为,智能合约支配着论文的生命周期状态。例如,一项import
交易可能会在该TRADING
州立即创建一组新文件。
CommercialPaper
该类的其余部分包含简单的帮助程序方法:
getOwner() {
return this.owner;
}
回忆一下智能合约如何使用这种方法在商业票据的生命周期中移动。例如,在兑换 交易中,我们看到:
if (paper.getOwner() === redeemingOwner) {
paper.setOwner(paper.getIssuer());
paper.setRedeemed();
}
存取分类帐
现在PaperList
,在paperlist.js
文件中找到该类:
class PaperList extends StateList {
该实用程序类用于管理Hyperledger Fabric状态数据库中的所有PaperNet商业用纸。PaperList数据结构在体系结构主题中有更详细的描述。
与CommercialPaper
该类一样,该类扩展了应用程序定义的 StateList
类,该类为状态列表创建通用抽象-在这种情况下,是PaperNet中的所有商业论文。
该addPaper()
方法是该方法的简单饰面StateList.addState()
:
async addPaper(paper) {
return this.addState(paper);
}
您可以在StateList.js
文件中看到StateList
该类 如何使用Fabric API putState()
将商业票据作为状态数据写入分类帐中:
async addState(state) {
let key = this.ctx.stub.createCompositeKey(this.name, state.getSplitKey());
let data = State.serialize(state);
await this.ctx.stub.putState(key, data);
}
分类帐中的每个状态数据都需要以下两个基本元素:
密钥:
key
是createCompositeKey()
使用固定名称和的密钥形成的state
。名称是在PaperList
构造对象时分配的,并state.getSplitKey()
确定每个状态的唯一键。数据:
data
只是使用State.serialize()
实用程序方法创建的商业票据状态的序列化形式。在State
类和序列化使用JSON反序列化数据,以及该国的业务对象类的要求,在我们的例子CommercialPaper
中,当再次设定PaperList
对象构建。
请注意,a如何StateList
不存储有关单个状态或状态总列表的任何内容,而是将所有这些都委派给Fabric状态数据库。这是一种重要的设计模式–减少了Hyperledger Fabric中分类账MVCC冲突的机会。
StateList getState()
和updateState()
方法以类似的方式工作:
async getState(key) {
let ledgerKey = this.ctx.stub.createCompositeKey(this.name, State.splitKey(key));
let data = await this.ctx.stub.getState(ledgerKey);
let state = State.deserialize(data, this.supportedClasses);
return state;
}
async updateState(state) {
let key = this.ctx.stub.createCompositeKey(this.name, state.getSplitKey());
let data = State.serialize(state);
await this.ctx.stub.putState(key, data);
}
看看他们如何使用面料的API putState()
,getState()
并 createCompositeKey()
访问总帐。稍后,我们将扩展此智能合约以在paperNet中列出所有商业票据-实施此分类账检索的方法将是什么样?
而已!在本主题中,您已经了解了如何为PaperNet实施智能合约。您可以转到下一个子主题,以查看应用程序如何使用Fabric SDK调用智能合约。