Fabric智能合约开发基础

区块链网络的核心是智能合约。

智能合约定义业务对象的不同状态,并管理对象在不同状态之间变化的过程。

在 PaperNet 案例中,商业票据智能合约中的代码定义了商业票据的有效状态,以及将票据从一种状态状态转变为另一种状态的交易逻辑。

本案例实现了商业票据中最简单的三个功能:发行、购买、兑换

PaperNet 是一个商业票据网络,允许适当授权的参与者发行,交易,兑换和估价商业票据。develop.systemscontextPaperNet 商业票据网络。六个组织目前使用 PaperNet 网络发行,购买,出售,兑换和估价商业票据。MagentoCorp 发行和兑换商业票据。 DigiBank, BigFund,BrokerHouse 和 HedgeMatic 互相交易商业票据。RateM 为商业票据提供各种风险衡量标准。

目录

合约类

扩展自 Hyperledger Fabric Contract ,使用Contract类的内置功能,例如自动方法调用,每个交易上下文交易处理器,和类共享状态等。

1
2
3
@Contract(...)
@Default
public class CommercialPaperContract implements ContractInterface {...}

交易定义

参考基于Java的合约代码例子

Java 标注 @Transaction 用于标记该方法为交易定义;无论何时调用此合约来发行商业票据,都会调用该方法

发行交易:

1
2
3
4
5
6
7
@Transaction
public CommercialPaper issue(CommercialPaperContext ctx,
String issuer,
String paperNumber,
String issueDateTime,
String maturityDateTime,
int faceValue) {...}

CommercialPaperContext ctx 称为交易上下文,默认情况下,它维护与交易逻辑相关的每个合约和每个交易的信息。

购买交易:

1
2
3
4
5
6
7
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
1
2
3
4
5
6
7
8
@Transaction
public CommercialPaper buy(CommercialPaperContext ctx,
String issuer,
String paperNumber,
String currentOwner,
String newOwner,
int price,
String purchaseDateTime) {...}

兑换交易:

1
2
3
4
5
Txn = redeem
Issuer = MagnetoCorp
Paper = 00001
Redeemer = DigiBank
Redeem time = 31 Dec 2020 12:00:00 EST
1
2
3
4
5
@Transaction
public CommercialPaper redeem(CommercialPaperContext ctx,
String issuer,
String paperNumber,
String redeemingOwner,

交易逻辑

  • 发行交易 导致issue方法被传递调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    @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 将其添加到所有商业票据的列表中
    • 将新的商业票据作为交易响应返回
  • 购买交易

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    @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;
    }

    逻辑如下:

    • 检查一些前提条件
    • 设置新拥有者
    • 更新账本上的商业票据
    • 并将更新的商业票据作为交易响应返回
  • 兑换交易类似

对象的表示

上面的合约、运行逻辑已经有了,但是这些代码怎么工作?

参考 CommercialPaper 类 , 该类包含商业票据状态的内存表示。

1
2
@DataType()
public class CommercialPaper extends State {...}
  • createInstance 方法使用提供的参数初始化一个新的商业票据。
1
2
3
4
5
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);
}

需要注意的几个要点:

  • 这是一个内存中的表示; 我们稍后会看到它如何在帐本上显示。

  • CommercialPaper 类扩展了 State 类。 State 是一个应用程序定义的类,它为状态创建一个公共抽象。所有状态都有一个它们代表的业务对象类、一个复合键,可以被序列化和反序列化,等等。当我们在帐本上存储多个业务对象类型时, State 可以帮助我们的代码更清晰。检查 state.js 文件中的 State 类。

  • 票据在创建时会计算自己的密钥,在访问帐本时将使用此密钥。密钥由 issuerpaperNumber 的组合形成。

    1
    2
    3
    4
    constructor(obj) {
    super(CommercialPaper.getClass(), [obj.issuer, obj.paperNumber]);
    Object.assign(this, obj);
    }
  • 票据通过交易而不是票据类变更到 ISSUED 状态。

访问账本

参考 PaperList 类 , 此工具类用于管理 Hyperledger Fabric 状态数据库中的所有 PaperNet 商业票据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import org.example.ledgerapi.StateList;
import org.hyperledger.fabric.contract.Context;

public class PaperList {

private StateList stateList;

public PaperList(Context ctx) {
this.stateList = StateList.getStateList(ctx, PaperList.class.getSimpleName(), CommercialPaper::deserialize);
}

public PaperList addPaper(CommercialPaper paper) {
stateList.addState(paper);
return this;
}

public CommercialPaper getPaper(String paperKey) {
return (CommercialPaper) this.stateList.getState(paperKey);
}

public PaperList updatePaper(CommercialPaper paper) {
this.stateList.updateState(paper);
return this;
}
}

CommercialPaper 类一样,此类扩展了应用程序定义的 StateList 类,该类为一系列状态创建了一个通用抽象——在本例中是 PaperNet 中的所有商业票据。

StateList 类使用 Fabric API addState() 将商业票据作为状态数据写在帐本中。

帐本中的每个状态数据都需要以下两个基本要素:

  • 键(Key): 由 createCompositeKey() 使用固定名称和 state 密钥形成。在构造 PaperList 对象时分配了名称,state.getSplitKey() 确定每个状态的唯一键。
  • 数据(Data): 只是商业票据状态的序列化形式,使用 State.serialize() 方法创建。State 类使用 JSON 对数据进行序列化和反序列化,并根据需要使用 State 的业务对象类,在我们的例子中为 CommercialPaper,在构造 PaperList 对象时再次设置。

注意 StateList 不存储有关单个状态或状态总列表的任何内容——它将所有这些状态委托给 Fabric 状态数据库。StateList getState()updateState() 方法以类似的方式工作。

参考:

Fabric-Samples commercial-paper案例

Fabric 文档