Hyperledger Fabric v1.4(LTS) 系列(5.4):Developing Applications-Smart Contract Processing

姜阳
2023-12-01

Hyperledger Fabric v1.4(LTS) 系列(5.4):Developing Applications-Smart Contract Processing

Smart Contract Processing

Audience: Architects, Application and smart contract developers

At the heart of a blockchain network is a smart contract. In PaperNet, the code
in the commercial paper smart contract defines the valid states for commercial
paper, and the transaction logic that transition a paper from one state to
another. In this topic, we’re going to show you how to implement a real world
smart contract that governs the process of issuing, buying and redeeming
commercial paper.

区块链网络的核心就是智能合约。在PaperNet,商业票据智能合约的代码定义了商票的合法状态,以及状态变迁的交易逻辑。
本节探讨实现一个真实世界里实现商票发行买卖赎回的智能合约。

We’re going to cover:

包括,定义智能合约,定义并实现交易,在智能合约里表现业务对象,在账本里保存和查询对象。

If you’d like, you can download the sample and even run it
locally
. It is written in JavaScript, but
the logic is quite language independent, so you’ll be easily able to see what’s
going on! (The sample will become available for Java and GOLANG as well.)

有一个JS写的例子,可以自己下载来玩。

Smart Contract

A smart contract defines the different states of a business object and governs
the processes that move the object between these different states. Smart
contracts are important because they allow architects and smart contract
developers to define the key business processes and data that are shared across
the different organizations collaborating in a blockchain network.

智能合约定义业务对象的不同状态及状态变迁过程。智能合约使得架构师和合约开发者定义区块链网络上合作者间共享的关键业务过程和数据。

In the PaperNet network, the smart contract is shared by the different network
participants, such as MagnetoCorp and DigiBank. The same version of the smart
contract must be used by all applications connected to the network so that they
jointly implement the same shared business processes and data.

Contract class

A copy of the PaperNet commercial paper smart contract is contained in
papercontract.js. View
it

with your browser, or open it in your favourite editor if you’ve downloaded it.

You may notice from the file path that this is MagnetoCorp’s copy of the smart
contract. MagnetoCorp and DigiBank must agree the version of the smart contract
that they are going to use. For now, it doesn’t matter which organization’s copy
you look at, they are all the same.
你看到的是MagnetoCorp持有的合约版本,但是无所谓,大家的都一样。

Spend a few moments looking at the overall structure of the smart contract;
notice that it’s quite short! Towards the top of papercontract.js, you’ll see
that there’s a definition for the commercial paper smart contract:

papercontract.js 文件头部是智能合约的总体结构,相当短。

class CommercialPaperContract extends Contract {...}

The CommercialPaperContract class contains the transaction definitions for commercial paper – issue, buy and redeem. It’s these transactions that bring commercial papers into existence and move them through their lifecycle. We’ll examine these transactions soon, but for now notice how CommericalPaperContract extends the Hyperledger Fabric Contract class. This built-in class, and the Context class, were brought into scope earlier:

类 CommercialPaperContract 包含了商票三类交易的定义,后边我们仔细看交易。现在先看CommercialPaperContract 如何扩展 Fabric的 Contract类,以及引入的Context类。

const { Contract, Context } = require('fabric-contract-api');

Our commercial paper contract will use built-in features of these classes, such as automatic method invocation, a per-transaction context, transaction handlers, and class-shared state.
我们的商票合约使用这些类的内建特性,如自动的方法触发,各交易上下文,交易句柄,以及类共享状态。

Notice also how the class constructor uses its superclass to initialize itself with an explicit contract name:
类创建方法使用父类方法以独立的合约名称初始化自身。

constructor() {
    super('org.papernet.commercialpaper');
}

Most importantly, org.papernet.commercialpaper is very descriptive – this smart contract is the agreed definition of commercial paper for all PaperNet organizations.

Usually there will only be one smart contract per file – contracts tend to have different lifecycles, which makes it sensible to separate them. However, in some cases, multiple smart contracts might provide syntactic help for applications, e.g. EuroBond, DollarBond, YenBond, but essentially provide the same function. In such cases, smart contracts and transactions can be disambiguated.
一般情况都是一个文件一份智能合约,因为合约会有不同的生命周期。

Transaction definition

Within the class, locate the issue method.

定位到issue方法

async issue(ctx, issuer, paperNumber, issueDateTime, maturityDateTime, faceValue) {...}

This function is given control whenever this contract is called to issue a commercial paper. Recall how commercial paper 00001 was created with the following transaction:

合约被调用来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

We’ve changed the variable names for programming style, but see how these properties map almost directly to the issue method variables.

The issue method is automatically given control by the contract whenever an application makes a request to issue a commercial paper. The transaction property values are made available to the method via the corresponding variables. See how an application submits a transaction using the Hyperledger Fabric SDK in the application topic, using a sample application program.
应用请求发行商票时,issue被自动赋权。交易的属性值通过对应的变量对方法可见。application一节会详述应用如何使用Fabric SDK提交交易。

You might have noticed an extra variable in the issue definition – ctx. It’s called the transaction context, and it’s always first. By default, it maintains both per-contract and per-transaction information relevant to transaction logic. For example, it would contain MagnetoCorp’s specified transaction identifier, a MagnetoCorp issuing user’s digital certificate, as well as access to the ledger API.
定义中有一个特殊变量ctx,即交易上下文。它维护每个合约和交易中与交易逻辑相关的信息。如MagnetoCorp的特定交易识别信息,MagnetoCorp发行者用于访问账本API的数字证书。

See how the smart contract extends the default transaction context by implementing its own createContext() method rather than accepting the default implementation:

下边看看智能合约通过实现自己的createContext()来扩展默认的交易上下文。

createContext() {
  return new CommercialPaperContext()
}

This extended context adds a custom property paperList to the defaults:
加了自定义的paperList属性。

class CommercialPaperContext extends Context {

  constructor() {
    super();
    // All papers are held in a list of papers
    this.paperList = new PaperList(this);
}

We’ll soon see how ctx.paperList can be subsequently used to help store and retrieve all PaperNet commercial papers.
ctx.paperList 随后用于保存和查询所有的PaperNet商票。

To solidify your understanding of the structure of a smart contract transaction, locate the buy and redeem transaction definitions, and see if you can see how they map to their corresponding commercial paper transactions.
下边看看buy and redeem交易的定义来加深对智能合约交易结构的印象。

The buy transaction:

async buy(ctx, issuer, paperNumber, currentOwner, newOwner, price, purchaseTime) {...}
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

The redeem transaction:

async redeem(ctx, issuer, paperNumber, redeemingOwner, redeemDateTime) {...}
Txn = redeem
Issuer = MagnetoCorp
Paper = 00001
Redeemer = DigiBank
Redeem time = 31 Dec 2020 12:00:00 EST

In both cases, observe the 1:1 correspondence between the commercial paper transaction and the smart contract method definition. And don’t worry about the async and await keywords – they allow asynchronous JavaScript functions to be treated like their synchronous cousins in other programming languages.
两种情况下,商票交易和智能合约方法定义之间是一一对应的。async,await这些JS的异步方法在其他语言中会同步执行。

Transaction logic

Now that you’ve seen how contracts are structured and transactions are defined, let’s focus on the logic within the smart contract.
前边是合约结构和交易的定义,现在看合约的逻辑。

Recall the first issue transaction:

Txn = issue
Issuer = MagnetoCorp
Paper = 00001
Issue time = 31 May 2020 09:00:00 EST
Maturity date = 30 November 2020
Face value = 5M USD

It results in the issue method being passed control:

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();
}

The logic is simple: take the transaction input variables, create a new commercial paper paper, add it to the list of all commercial papers using paperList, and return the new commercial paper (serialized as a buffer) as the transaction response.
逻辑很简单,注意最后序列化到缓存并返回。

See how paperList is retrieved from the transaction context to provide access to the list of commercial papers. issue(), buy() and redeem() continually re-access ctx.paperList to keep the list of commercial papers up-to-date.
注意paperList从交易上下文中被获取,后续方法持续访问并更新商票列表。

The logic for the buy transaction is a little more elaborate:

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();
}

See how the transaction checks currentOwner and that paper is TRADING before changing the owner with paper.setOwner(newOwner). The basic flow is simple though – check some pre-conditions, set the new owner, update the commercial paper on the ledger, and return the updated commercial paper (serialized as a buffer) as the transaction response.
买卖交易稍微复杂一些。要做一些前置条件检查。

Why don’t you see if you can understand the logic for the redeem transaction?

Representing an object

We’ve seen how to define and implement the issue, buy and redeem transactions using the CommercialPaper and PaperList classes. Let’s end this topic by seeing how these classes work.
前边使用类CommercialPaper and PaperList 定义和实现多种交易,下边看看这些类如何工作。

Locate the CommercialPaper class in the paper.js file:
paper.js 中的CommercialPaper

class CommercialPaper extends State {...}

This class contains the in-memory representation of a commercial paper state. See how the createInstance method initializes a new commercial paper with the provided parameters:

static createInstance(issuer, paperNumber, issueDateTime, maturityDateTime, faceValue) {
  return new CommercialPaper({ issuer, paperNumber, issueDateTime, maturityDateTime, faceValue });
}

Recall how this class was used by the issue transaction:

let paper = CommercialPaper.createInstance(issuer, paperNumber, issueDateTime, maturityDateTime, faceValue);

See how every time the issue transaction is called, a new in-memory instance of a commercial paper is created containing the transaction data.

A few important points to note:

  • This is an in-memory representation; we’ll see later how it appears on the ledger.

  • The CommercialPaper class extends the State class. State is an application-defined class which creates a common abstraction for a state. All states have a business object class which they represent, a composite key, can be serialized and de-serialized, and so on. State helps our code be more legible when we are storing more than one business object type on the ledger. Examine the State class in the state.js file.

  • A paper computes its own key when it is created – this key will be used when the ledger is accessed. The key is formed from a combination of issuer and paperNumber.

    constructor(obj) {
      super(CommercialPaper.getClass(), [obj.issuer, obj.paperNumber]);
      Object.assign(this, obj);
    }
    
  • A paper is moved to the ISSUED state by the transaction, not by the paper class. That’s because it’s the smart contract that governs the lifecycle state of the paper. For example, an import transaction might create a new set of papers immediately in the TRADING state.

这部分没什么难懂的。注意如下几点:

  • 这些是内存中实现,后边讲在账本中的实现。
  • CommercialPaper 扩展了 State ,State创建了一个通用的状态抽象。所有的状态都有通过复合key展现的业务对象类,可以被序列化和反序列化。State类帮我们把多个业务对象保存到账本。
  • 商票在创建时通过组合issuerpaperNumber计算自己的key,

The rest of the CommercialPaper class contains simple helper methods:
CommercialPaper 类的剩余部分有一些简单的帮助方法。

getOwner() {
    return this.owner;
}

Recall how methods like this were used by the smart contract to move the commercial paper through its lifecycle. For example, in the redeem transaction we saw:

if (paper.getOwner() === redeemingOwner) {
  paper.setOwner(paper.getIssuer());
  paper.setRedeemed();
}

Access the ledger

Now locate the PaperList class in the paperlist.js file:
现在看PaperList

class PaperList extends StateList {

This utility class is used to manage all PaperNet commercial papers in Hyperledger Fabric state database. The PaperList data structures are described in more detail in the architecture topic.
这个通用类用来管理Fabric状态数据库中的所有PaperNet商票,数据结构见architecture一节。

Like the CommercialPaper class, this class extends an application-defined StateList class which creates a common abstraction for a list of states – in this case, all the commercial papers in PaperNet.
CommercialPaper 类相似,PaperList 扩展了StateList

The addPaper() method is a simple veneer over the StateList.addState() method:

async addPaper(paper) {
  return this.addState(paper);
}

You can see in the StateList.js file how the StateList class uses the Fabric API putState() to write the commercial paper as state data in the ledger:
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);
}

Every piece of state data in a ledger requires these two fundamental elements:
账本中的状态数据需要两个基本元素:Key和Data。

  • Key: key is formed with createCompositeKey() using a fixed name and the key of state. The name was assigned when the PaperList object was constructed, and state.getSplitKey() determines each state’s unique key.

  • Data: data is simply the serialized form of the commercial paper state, created using the State.serialize() utility method. The State class serializes and deserializes data using JSON, and the State’s business object class as required, in our case CommercialPaper, again set when the PaperList object was constructed.

Notice how a StateList doesn’t store anything about an individual state or the total list of states – it delegates all of that to the Fabric state database. This is an important design pattern – it reduces the opportunity for ledger MVCC collisions in Hyperledger Fabric.
StateList 本身不保存单独state或状态列表,保存的事交给Fabric状态数据库。

The StateList getState() and updateState() methods work in similar ways:

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);
}

See how they use the Fabric APIs putState(), getState() and createCompositeKey() to access the ledger. We’ll expand this smart contract later to list all commercial papers in paperNet – what might the method look like to implement this ledger retrieval?
通过Fabric API的putState(), getState() and createCompositeKey() 来访问账本。

That’s it! In this topic you’ve understood how to implement the smart contract for PaperNet. You can move to the next sub topic to see how an application calls the smart contract using the Fabric SDK.

 类似资料: