智能合約處理
受眾:架構師,應用程序和智能合約開發人員
區塊鏈網絡的核心是智能合約。在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文檔:
這些類,注釋和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 {...}
<details open="" style="box-sizing: border-box; display: block;">Java在CommercialPaper.java文件中:
@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調用智能合約。