應(yīng)用程序開發(fā) - 智能合約處理
區(qū)塊鏈網(wǎng)絡(luò)的核心是智能合約。在 PaperNet 中,商業(yè)票據(jù)智能合約中的代碼定義了商業(yè)票據(jù)的有效狀態(tài),以及將票據(jù)從一種狀態(tài)轉(zhuǎn)換為另一種狀態(tài)的交易邏輯。在本主題中,我們將向你展示如何實(shí)施一個(gè)現(xiàn)實(shí)世界的智能合約,該合約管理著發(fā)行,購買和贖回商業(yè)票據(jù)的過程。
我們將介紹:
- 什么是智能合約及其重要性
- 如何定義智能合約
- 如何定義交易
- 如何進(jìn)行交易
- 如何在智能合約中表示業(yè)務(wù)對(duì)象
- 如何在帳本中存儲(chǔ)和檢索對(duì)象
如果需要,你可以 下載示例,甚至可以在 本地運(yùn)行。它是用 JavaScript 和 Java 編寫的,但是邏輯是完全獨(dú)立于語言的,因此你可以輕松地看到發(fā)生了什么!(該示例也將適用于 Go。)
1. 智能合約
智能合約定義業(yè)務(wù)對(duì)象的不同狀態(tài),并管理在這些不同狀態(tài)之間移動(dòng)對(duì)象的流程。智能合約之所以重要,是因?yàn)樗鼈兪辜軜?gòu)師和智能合約開發(fā)人員能夠定義關(guān)鍵業(yè)務(wù)流程和數(shù)據(jù),這些關(guān)鍵業(yè)務(wù)流程和數(shù)據(jù)是在區(qū)塊鏈網(wǎng)絡(luò)中進(jìn)行協(xié)作的不同組織之間共享的。
在 PaperNet 網(wǎng)絡(luò)中,智能合約由 MagnetoCorp 和 DigiBank 等不同的網(wǎng)絡(luò)參與者共享。連接到網(wǎng)絡(luò)的所有應(yīng)用程序必須使用相同版本的智能合約,以便它們共同實(shí)現(xiàn)相同的共享業(yè)務(wù)流程和數(shù)據(jù)。
2. 實(shí)現(xiàn)語言
支持兩個(gè)運(yùn)行時(shí),即 Java 虛擬機(jī)和 Node.js。這使你有機(jī)會(huì)使用 JavaScript,TypeScript,Java 或可以在這些受支持的運(yùn)行時(shí)之一中運(yùn)行的任何其他語言之一。
在 Java 和 TypeScript 中,注釋或修飾符用于提供有關(guān)智能合約及其結(jié)構(gòu)的信息。這樣可以提供更豐富的開發(fā)經(jīng)驗(yàn)-例如,可以強(qiáng)制執(zhí)行作者信息或返回類型。在 JavaScript 中,必須遵循約定,因此,圍繞自動(dòng)確定的內(nèi)容存在限制。
JavaScript 和 Java 都給出了示例。
3. 合約類
PaperNet 商業(yè)票據(jù)智能合約的副本包含在一個(gè)文件中。使用瀏覽器查看它,如果已下載,則在你喜歡的編輯器中將其打開。
-
papercontract.js
- JavaScript version -
CommercialPaperContract.java
- Java version
你可能會(huì)從文件路徑中注意到,這是 MagnetoCorp 的智能合約副本。 MagnetoCorp 和 DigiBank 必須就他們將要使用的智能合約的版本達(dá)成協(xié)議。目前,使用哪個(gè)組織的副本都沒關(guān)系,它們都是一樣的。
花一些時(shí)間看一下智能合約的整體結(jié)構(gòu);請(qǐng)注意,它很短!在文件頂部,你會(huì)看到商業(yè)票據(jù)智能合約的定義:
JavaScript
class CommercialPaperContract extends Contract {...}
Java
@Contract(...)
@Default
public class CommercialPaperContract implements ContractInterface {...}
CommercialPaperContract 類包含商業(yè)票據(jù)的交易定義 - 發(fā)行,購買和贖回。正是這些交易使商業(yè)票據(jù)得以存在并在其生命周期中移動(dòng)。我們將很快檢查這些交易,但現(xiàn)在就 JavaScript 而言,CommericalPaperContract 擴(kuò)展了Hyperledger Fabric Contract 類。
對(duì)于 Java,該類必須使用 @Contract(...) 注釋進(jìn)行修飾。這提供了提供有關(guān)合同的其他信息的機(jī)會(huì),例如許可證和作者。@Default() 批注指示此合約類是默認(rèn)合約類。能夠?qū)⒑霞s類別標(biāo)記為默認(rèn)合約類別在某些具有多個(gè)合約類別的智能合約中很有用。
如果你使用的是 TypeScript 實(shí)現(xiàn),則有類似的 @Contract(...) 批注可以實(shí)現(xiàn)與 Java 中相同的目的。
有關(guān)可用注釋的更多信息,請(qǐng)查閱可用的 API 文檔:
這些類,批注和 Context
類在之前已引入范圍:
JavaScript
const { Contract, Context } = require('fabric-contract-api');
Java
我們的商業(yè)票據(jù)合約將使用這些類的內(nèi)置功能,例如自動(dòng)方法調(diào)用,每個(gè)交易上下文,交易處理程序 和類共享狀態(tài)。
還請(qǐng)注意,JavaScript 類構(gòu)造函數(shù)如何使用其 超類 使用顯式 合約名稱 進(jìn)行初始化:
constructor() {
super('org.papernet.commercialpaper');
}
對(duì)于 Java 類,構(gòu)造函數(shù)為空白,因?yàn)榭梢栽?@Contract() 注解中指定顯式合約名稱。如果不存在,則使用類名稱。
最重要的是,org.papernet.commercialpaper
的描述性非常強(qiáng) – 該智能合約是所有 PaperNet 組織對(duì)商業(yè)票據(jù)的公認(rèn)定義。
通常,每個(gè)文件只有一個(gè)智能合約 – 合約往往具有不同的生命周期,因此將它們分開是明智的。但是,在某些情況下,多個(gè)智能合約可能會(huì)為應(yīng)用程序提供語法幫助,例如 EuroBond,DollarBond,YenBond,但本質(zhì)上提供相同的功能。在這種情況下,可以消除智能合約和交易的歧義。
4. 交易定義
在該類中,找到 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 用于將該方法標(biāo)記為交易定義。 TypeScript 具有等效的注釋。
每當(dāng)調(diào)用此合約以發(fā)行商業(yè)票據(jù)時(shí),便會(huì)控制此功能。回想一下如何通過以下交易創(chuàng)建商業(yè)票據(jù) 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
我們已經(jīng)更改了編程風(fēng)格的變量名稱,但是看到了這些屬性如何幾乎直接映射到 issue
方法變量。
每當(dāng)應(yīng)用程序請(qǐng)求簽發(fā)商業(yè)票據(jù)時(shí),合約都會(huì)自動(dòng)授予 issue
方法控制權(quán)。交易屬性值通過相應(yīng)的變量可供方法使用。請(qǐng)參閱 應(yīng)用程序主題 中的示例應(yīng)用程序,了解應(yīng)用程序如何使用 Hyperledger Fabric SDK 提交交易。
你可能已經(jīng)注意到 issue
定義中有一個(gè)額外的變量 ctx
。它稱為 交易上下文,并且始終是第一位。默認(rèn)情況下,它維護(hù)與 交易邏輯 相關(guān)的按合約和按交易的信息。例如,它將包含 MagnetoCorp 的指定交易標(biāo)識(shí)符,MagnetoCorp 頒發(fā)用戶的數(shù)字證書以及對(duì)帳本 API 的訪問。
通過實(shí)現(xiàn)自己的 createContext()
方法而不是接受默認(rèn)實(shí)現(xiàn),了解智能合約如何擴(kuò)展默認(rèn)交易上下文:
JavaScript
createContext() {
return new CommercialPaperContext()
}
Java
@Override
public Context createContext(ChaincodeStub stub) {
return new CommercialPaperContext(stub);
}
此擴(kuò)展上下文將自定義屬性 paperList
添加到默認(rèn)值:
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
隨后如何用于幫助存儲(chǔ)和檢索所有 PaperNet 商業(yè)票據(jù)。
為了鞏固你對(duì)智能合約交易結(jié)構(gòu)的理解,找到購買和贖回交易定義,并查看是否可以看到它們?nèi)绾斡成涞狡湎鄳?yīng)的商業(yè)票據(jù)交易。
購買交易:
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) {...}
在這兩種情況下,請(qǐng)觀察商業(yè)票據(jù)交易與智能合約方法定義之間的 1:1 對(duì)應(yīng)關(guān)系。
所有 JavaScript 函數(shù)都使用 async 和 await 關(guān)鍵字,這些關(guān)鍵字使 JavaScript 函數(shù)可以被視為同步函數(shù)調(diào)用。
5. 交易邏輯
既然你已經(jīng)了解了合約的結(jié)構(gòu)和定義的交易方式,那么讓我們關(guān)注智能合約中的邏輯。
回顧第一筆 issue
交易:
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
導(dǎo)致 issue
方法被調(diào)用:
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;
}
邏輯很簡(jiǎn)單:獲取交易輸入變量,創(chuàng)建一個(gè)新的商業(yè)票據(jù),使用 paperList 將其添加到所有商業(yè)票據(jù)的列表中,然后返回新的商業(yè)票據(jù) (序列化為緩沖區(qū)) 作為交易響應(yīng)。
了解如何從交易上下文中檢索 paperList
以提供對(duì)商業(yè)票據(jù)列表的訪問。 issue()
,buy()
和 redeem()
不斷重新訪問 ctx.paperList
,以使商業(yè)票據(jù)列表保持最新。
購買交易的邏輯更加復(fù)雜:
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;
}
在使用 paper.setOwner(newOwner)
更改所有者之前,請(qǐng)查看交易如何檢查 currentOwner
和 paper
是否 TRADING
。但是基本流程很簡(jiǎn)單 – 檢查一些前提條件,設(shè)置新所有者,更新帳本上的商業(yè)票據(jù),并將更新后的商業(yè)票據(jù) (序列化為緩沖區(qū)) 作為交易響應(yīng)返回。
你為什么不看看能否理解贖回交易的邏輯?
6. 表示一個(gè)對(duì)象
我們已經(jīng)了解了如何使用 CommercialPaper 和 PaperList 類來定義和實(shí)現(xiàn)發(fā)行,購買和贖回交易。通過查看這些類的工作原理來結(jié)束本主題。
找到 CommercialPaper 類:
JavaScript
在文件 paper.js 中:
class CommercialPaper extends State {...}
Java
在文件 CommercialPaper.java 中:
@DataType()
public class CommercialPaper extends State {...}
此類包含商業(yè)票據(jù)狀態(tài)的內(nèi)存表示形式。查看 createInstance
方法如何使用提供的參數(shù)初始化新的商業(yè)票據(jù):
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);
}
回顧發(fā)行交易如何使用此類:
JavaScript
let paper = CommercialPaper.createInstance(issuer, paperNumber, issueDateTime, maturityDateTime, faceValue);
Java
CommercialPaper paper = CommercialPaper.createInstance(issuer, paperNumber, issueDateTime, maturityDateTime,
faceValue,issuer,"");
了解每次調(diào)用發(fā)行交易的方式,都會(huì)創(chuàng)建一個(gè)包含交易數(shù)據(jù)的新的商業(yè)票據(jù)內(nèi)存實(shí)例。
需要注意的幾個(gè)要點(diǎn):
- 這是內(nèi)存中的表示形式;稍后我們將看到它如何顯示在帳本中。
- CommercialPaper 類擴(kuò)展了 State 類。 State 是一個(gè)應(yīng)用程序定義的類,它為狀態(tài)創(chuàng)建通用抽象。所有狀態(tài)都有一個(gè)它們表示的業(yè)務(wù)對(duì)象類,一個(gè)復(fù)合鍵,可以序列化和反序列化等等。當(dāng)我們?cè)谫~本上存儲(chǔ)多個(gè)業(yè)務(wù)對(duì)象類型時(shí),State 可以使我們的代碼更易讀。檢查 state.js 文件 中的 State 類。
票據(jù)在創(chuàng)建時(shí)會(huì)計(jì)算自己的鍵 - 訪問帳本時(shí)將使用此鍵。鍵由 issuer
和 paperNumber
組合而成。
constructor(obj) {
super(CommercialPaper.getClass(), [obj.issuer, obj.paperNumber]);
Object.assign(this, obj);
}
通過交易而不是票據(jù)類將票據(jù)移動(dòng)到 ISSUED 狀態(tài)。這是因?yàn)椋悄芎霞s支配著票據(jù)的生命周期狀態(tài)。例如,import
交易可能會(huì)立即在 TRADING
狀態(tài)下創(chuàng)建一組新文件。
CommercialPaper 類的其余部分包含簡(jiǎn)單的幫助程序方法:
getOwner() {
return this.owner;
}
回憶一下智能合約如何使用這種方法在商業(yè)票據(jù)的生命周期中移動(dòng)。例如,在贖回交易中,我們看到:
if (paper.getOwner() === redeemingOwner) {
paper.setOwner(paper.getIssuer());
paper.setRedeemed();
}
7. 訪問賬本
現(xiàn)在,在 paperlist.js 文件 中找到 PaperList 類:
class PaperList extends StateList {
該通用程序類用于管理 Hyperledger Fabric 狀態(tài)數(shù)據(jù)庫中的所有 PaperNet 商業(yè)票據(jù)。 PaperList 數(shù)據(jù)結(jié)構(gòu)在 體系結(jié)構(gòu)主題 中有更詳細(xì)的描述。
與 CommercialPaper 類類似,該類擴(kuò)展了應(yīng)用程序定義的 StateList 類,該類為狀態(tài)列表創(chuàng)建通用抽象 - 在這種情況下,是 PaperNet 中的所有商業(yè)票據(jù)。
addPaper()
方法是對(duì) StateList.addState()
方法的簡(jiǎn)單修飾:
async addPaper(paper) {
return this.addState(paper);
}
你可以在 StateList.js 文件 中看到 StateList 類如何使用 Fabric API putState() 將商業(yè)票據(jù)作為狀態(tài)數(shù)據(jù)寫入帳本中:
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);
}
帳本中的每個(gè)狀態(tài)數(shù)據(jù)都需要以下兩個(gè)基本元素:
- 鍵:鍵是使用 createCompositeKey() 使用固定名稱和
state
鍵形成的。名稱是在構(gòu)造 PaperList 對(duì)象時(shí)分配的,state.getSplitKey() 確定每個(gè)狀態(tài)的唯一鍵。 - 數(shù)據(jù):數(shù)據(jù)只是使用 State.serialize() 實(shí)用程序方法創(chuàng)建的商業(yè)票據(jù)狀態(tài)的序列化形式。 State 類使用 JSON 序列化和反序列化數(shù)據(jù),并且在構(gòu)造 PaperList 對(duì)象時(shí)再次設(shè)置 State 的業(yè)務(wù)對(duì)象類,在我們的示例中為 CommercialPaper。
請(qǐng)注意,StateList 如何不存儲(chǔ)有關(guān)單個(gè)狀態(tài)或狀態(tài)總列表的任何內(nèi)容,而是將所有這些內(nèi)容委派給 Fabric 狀態(tài)數(shù)據(jù)庫。這是一種重要的設(shè)計(jì)模式 – 減少了 Hyperledger Fabric 中 賬本 MVCC 沖突 的機(jī)會(huì)。
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);
}
了解他們?nèi)绾问褂?Fabric API 的 putState(),getState() 和 createCompositeKey() 來訪問帳本。我們稍后將擴(kuò)展此智能合約,以在 paperNet 中列出所有商業(yè)票據(jù) - 實(shí)現(xiàn)此賬本檢索的方法將是什么樣?
就是這樣!在本主題中,你已經(jīng)了解了如何為 PaperNet 實(shí)現(xiàn)智能合約。你可以轉(zhuǎn)到下一個(gè)子主題,以查看應(yīng)用程序如何使用 Fabric SDK 調(diào)用智能合約。
Reference
- Docs ? Developing Applications ? Smart Contract Processing, https://hyperledger-fabric.readthedocs.io/en/release-1.4/developapps/smartcontract.html
- Docs ? Getting Started ? Install Samples, Binaries and Docker Images, https://hyperledger-fabric.readthedocs.io/en/release-1.4/install.html
- Docs ? Tutorials ? Commercial paper tutorial, https://hyperledger-fabric.readthedocs.io/en/release-1.4/tutorial/commercial_paper.html
- https://fabric-shim.github.io/release-1.4/fabric-contract-api.Contract.html
- https://hyperledger.github.io/fabric-chaincode-java/
- https://fabric-shim.github.io/release-1.4/index.html
- Docs ? Developing Applications ? Application design elements ? Transaction context, https://hyperledger-fabric.readthedocs.io/en/release-1.4/developapps/transactioncontext.html
- Docs ? Developing Applications ? Application design elements ? Transaction handlers, https://hyperledger-fabric.readthedocs.io/en/release-1.4/developapps/transactionhandler.html
- Docs ? Developing Applications ? Application design elements ? Contract names, https://hyperledger-fabric.readthedocs.io/en/release-1.4/developapps/contractname.html
- Docs ? Developing Applications ? Process and Data Design, https://hyperledger-fabric.readthedocs.io/en/release-1.4/developapps/architecture.html
- Docs ? Architecture Reference ? Read-Write set semantics, https://hyperledger-fabric.readthedocs.io/en/release-1.4/readwrite.html
項(xiàng)目源代碼
項(xiàng)目源代碼會(huì)逐步上傳到 Github,地址為 https://github.com/windstamp。
Contributor
- Windstamp, https://github.com/windstamp