Fabric

官方文档:关键概念 — hyperledger-fabricdocs master 文档

1. 关键概念

1.1 区块链网络

1681270713030

这个 Fabric 区块链网络包括了两个应用程序通道以及一个排序通道

组织 R1 和 R4 负责排序通道,R1 和 R2 负责蓝色的应用程序通道,R2 和 R3 负责红色的应用程序通道。

客户端应用程序 A1 是组织 R1 的元素,CA1 是它的证书颁发机构

组织 R2 的节点 P2 可以使用蓝色的通信设施,也可以使用红色的应用程序通道。

每个应用程序通道具有它自己的通道配置,这里是 CC1 和 CC2。

系统通道的通道配置是网络配置 NC4 的一部分。

一个有四个组织的网络,带有两个通道和三个 Peer 节点,两个智能合约和一个排序服务。

并由四个证书颁发机构来支撑。

它为三个客户端应用程序提供了账本及智能合约服务,这些应用程序可以通过两个通道与账本和智能合约进行交互。

1681271030975

1681270977420

1.2 身份

  • 确定了对资源的确切权限以及对参与者在区块链网络中拥有的信息的访问权限
  • 要使身份可以被验证,它必须来自可信任的权威机构
  • 成员服务提供者(Membership Service Provider,MSP)是 Fabirc 中可以信任的权威机构
  • MSP 将可验证的身份转变为区块链网络的成员

1.2.1 PKI

  • 公钥基础结构(PKI)是一组互联网技术,可在网络中提供安全通信
  • 关键要素
    • 数字证书
    • 公钥和私钥
    • 证书授权中心
    • 证书撤销列表

1.3 成员服务提供者 (MSP)

以太坊属于匿名网络,hpyerledger 是实名制的网络

1.4 Peer 节点

区块链网络主要由 Peer 节点(或者简单称之为 Peer)组成

1681285693747区块链网络是由 Peer 节点组成的,每个节点都保存着账本和智能合约的副本。

在这个例子中,网络 N 是由节点 P1、P2 和 P3 组成的,每个节点都维护这他们自己的分布式账本 L1。P1、P2 和 P3 使用相同的链码 S1 来访问他们的分布式账本的副本。

在 Fabric 中,链码等同于智能合约,因为它们是使用一个被称为链码的技术概念来实现智能合约

Peer 节点是账本及链码的宿主,应用程序及管理员如果想要访问这些资源,他们必须要和 Peer 节点进行交互

1.4.1 多账本

  • 一个 Peer 节点可以维护多个账本,并且每个账本具有零个或者多个链码使用账本

1681285960829

在这个例子中,我们能够看到 Peer 节点 P1 维护着账本 L1 和 L2。账本 L1 通过链码 S1 来访问。账本 2 通过链码 S1 和 S2 访问

1.4.2 多链码

  • 账本数量和访问账本的链码的数量之间没有固定的关系。一个 Peer 节点可能会有很多链码和账本

1.4.3 应用程序和 Peer 节点

1681287048342

1.4.4 Peer 节点和排序节点

  • 一个更新的交易和一个查询的交易区别很大,因为一个单独的 Peer 节点不能够由它自己来更新账本——更新需要网络中其他节点的同意

  • 在一个账本的更新被应用到 Peer 节点的本地账本之前, Peer 节点会请求网络中的其他 Peer 节点来批准这次更新。这个过程被称为共识,这会比一个简单的查询花费更长的时间来完成

  • 想要更新账本的应用程序会被引入到一个三阶段的流程,这确保了在一个区块链网络中所有的 Peer 节点都彼此保持着一致的账本。

    • 在第一个阶段,应用程序会跟背书节点的子集一起工作,其中的每个节点都会向应用程序为提案的账本更新提供背书,但是不会将提案的更新应用到他们的账本副本上。
    • 在第二个阶段,这些分散的背书会被搜集到一起当做交易被打包进区块中。
    • 在最后一个阶段,这些区块会被分发回每个 Peer 节点,在这些 Peer 节点上每笔交易在被应用到 Peer 节点的账本副本之前会被验证。

    排序节点在这个流程中处于中心地位

1.5 chaincode 智能合约

  • 智能合约用可执行的代码定义了不同组织之间的规则。应用程序调用智能合约来生成被记录到账本上的交易。

1681294843518

在上图中,我们可以看到组织 ORG1 和 ORG2 是如何通过定义一个 car 智能合约来实现 查询转移 和 更新 汽车的。来自这些组织的应用程序调用此智能合约执行业务流程中已商定的步骤,例如将特定汽车的所有权从 ORG1 转移到 ORG2

可以将智能合约看成交易的管理者,而链码则管理着如何将智能合约打包以便用于部署。

一个智能合约定义在一个链码中。而多个智能合约也可以定义在同一个链码中。当一个链码部署完毕,该链码中的所有智能合约都可供应用程序使用。

  • 智能合约的核心是一组 交易 定义。例如,在 fabcar.js中,你可以看到一个创建了一辆新车的智能合约交易:
1
2
3
4
5
6
7
8
9
10
11
12
async createCar(ctx, carNumber, make, model, color, owner) {

const car = {
color,
docType: 'car',
make,
model,
owner,
};

await ctx.stub.putState(carNumber, Buffer.from(JSON.stringify(car)));
}

1.5.1 背书

  • 每个链码都有一个背书策略与之相关联,该背书策略适用于此链码中定义的所有智能合约。背书策略非常重要,它指明了区块链网络中哪些组织必须对一个给定的智能合约所生成的交易进行签名,以此来宣布该交易有效
    • 一个示例背书策略可能这样定义:参与区块链网络的四个组织中有三个必须在交易被认为有效之前签署该交易。所有的交易,无论是有效的还是无效的,都会被添加到分布式账本中,但只有有效交易会更新世界状态。
    • 背书策略是 Hyperledger Fabric 与以太坊(Ethereum)或比特币(Bitcoin)等其他区块链的区别所在

1.5.2 有效交易

注意,在执行智能合约时世界状态没有更新

  • 所有的交易都有一个识别符、一个提案和一个被一群组织签名的响应。所有交易,无论是否有效,都会被记录在区块链上,但仅有效交易会更新世界状态

  • 有效交易举例:

    1681296263304

    检查 车辆转移 交易。您可以看到 ORG1 和 ORG2 之间为转移一辆车而进行的交易 t3。看一下交易是如何通过输入 {CAR1,ORG1,ORG2} 和输出 {CAR1.owner=ORG1,CAR1.owner=ORG2} 来表示汽车的所有者从 ORG1 变为了 ORG2。注意输入是如何由应用程序的组织 ORG1 签名的,输出是如何由背书策略标识的两个组织( ORG1 和 ORG2 )签名的。这些签名是使用每个参与者的私钥生成的,这意味着网络中的任何人都可以验证网络中的所有参与者是否在交易细节上达成了一致

  • 一项交易被分发给网络中的所有节点,各节点通过两个阶段对其进行验证。首先,根据背书策略检查交易,确保该交易已被足够的组织签署。其次,继续检查交易,以确保当该交易在受到背书节点签名时它的交易读集与世界状态的当前值匹配,并且中间过程中没有被更新。如果一个交易通过了这两个测试,它就被标记为有效。所有交易,不管是有效的还是无效的,都会被添加到区块链历史中,但是仅有效的交易才会更新世界状态。

1.5.3 通道

  • Hyperledger Fabric 允许一个组织利用通道同时参与多个、彼此独立的区块链网络。通过加入多个通道,一个组织可以参与一个所谓的网络的网络
  • 通道在一群组织之间提供了一种完全独立的通信机制。当链码定义被提交到通道上时,该通道上所有的应用程序都可以使用此链码中的智能合约。

1681296849809

在上面的示例中,car 智能合约被定义在 VEHICLE 通道上,insurance 智能合约被定义在 INSURANCE 通道上。car 的链码定义明确了以下背书策略:任何交易在被认定为有效之前必须由 ORG1 和 ORG2 共同签名。insurance 智能合约的链码定义明确了只需要 ORG3 对交易进行背书即可。ORG1 参与了 VEHICLE 通道和 INSURANCE 通道这两个网络,并且能够跨网络协调与 ORG2 和 ORG3 的活动。

1.6 账本

  • 由“世界状态“和”区块链“这两部分组成
    • 世界状态是一个数据库,它存储了一组账本状态的当前值,通过世界状态,程序可以直接访问一个账本状态的当前值,不需要遍历整个交易日志来计算当前值
    • 区块链是交易日志,它记录了促成当前世界状态的所有改变

1681301669101

账本 L 由区块链 B 和世界状态 W 组成,其中世界状态 W 由区块链 B 决定。我们也可以说世界状态 W 是源自区块链 B

1.6.1 世界状态

  • 世界状态被作为数据库来实现

1681301818989

示例展示的是 CAR1 和 CAR2 这两辆车的账本状态,二者都各有一个值和一个键。应用程序可以调用智能合约,该合约使用简单的账本 API 来获取写入删除状态。注意状态值可以是简单值(Audi…),也可以是复合值(type:BMW…)。经常会通过查询世界状态来检索具有某些特定属性的对象,例如查找所有红色宝马汽车。

  • 应用程序提交那些会更改世界状态的交易,这些交易最终被提交到账本区块链上。
  • 应用程序无法看到 Hyperledger Fabric SDK(软件开发工具包)设定的共识机制的细节内容,它们能做的只是调用智能合约以及在交易被收进区块链时收到通知(所有被提交的交易,无论有效与否,都会被收进区块链),但是只有那些受到相关背书组织签名的交易才会更新世界状态。如果一个交易没有得到足够背书节点的签名,那么它不会更新世界状态
  • 每个状态都有一个版本号,版本号是供 Hyperledger Fabric 内部使用的,并且每次状态更改时版本号会发生递增。每当更新状态时,都会检查该状态的版本,以确保当前状态与背书时的版本相匹配。

1.6.2 区块链

  • 世界状态存储了与业务对象当前状态相关的事实信息
  • 区块链是一种历史记录,它记录了这些业务对象是如何到达各自当前状态的相关事实。区块链记录了每个账本状态之前的所有版本以及状态是如何被更改的
  • 区块链的结构是一群相互链接的区块的序列化日志,其中每个区块都包含一系列交易,各项交易代表了一个对世界状态进行的查询或更新操作
  • 区块链总是以文件实现,而与之相反的是,世界状态以数据库实现

1681302297465

B0 是该区块链的第一个区块,也叫创世区块

它并不包含任何用户交易,但却是账本的起始点

相反的,创世区块包含了一个配置交易,该交易含有网络配置(未显示)的初始状态

1.6.3 区块

组成

  • 区块头

    • 区块编号:编号从0(初始区块)开始,每在区块链上增加一个新区块,编号的数字都会加1。
    • 当前区块的哈希值:当前区块中包含的所有交易的哈希值。
    • 前一个区块头的哈希值:区块链中前一个区块头的哈希值。

    1681302542402

    区块头详情:区块 B2 的区块头 H2 包含了区块编号 2,当前区块数据 D2 的哈希值 CH2,以及前一个区块头 H1 的哈希值。

  • 区块数据

    • 这部分包含了一个有序的交易列表。区块数据是在排序服务创建区块时被写入的
  • 区块元数据

    • 这个部分包含了区块被写入的时间,还有区块写入者的证书、公钥以及签名。随后,区块的提交者也会为每一笔交易添加一个有效或无效的标记,但由于这一信息与区块同时产生,所以它不会被包含在哈希中。

1.6.4 交易

  • 交易记录了世界状态发生的更新
  • 把交易包含在区块中的区块数据结构

1681303391932

交易详情:交易 T4 位于区块 B1 的区块数据 D1 中,T4包括的内容如下:交易头 H4,一个交易签名 S4,一个交易提案 P4,一个交易响应 R4 和一系列背书 E4。

这部分用 H4 表示,它记录了关于交易的一些重要元数据,比如,相关链码的名字以及版本。

签名

这部分用 S4 表示,它包含了一个由客户端应用程序创建的加密签名。该字段是用来检查交易细节是否未经篡改,因为交易签名的生成需要用到应用程序的私钥。

提案

这部分用 P4 表示,它负责对应用程序供给智能合约的输入参数进行编码,随后该智能合约生成提案账本更新。在智能合约运行时,这个提案提供了一套输入参数,这些参数同当前的世界状态一起决定了新的账本世界状态。

响应

这部分用 R4 表示,它是以读写集 (RW-set)的形式记录下世界状态之前和之后的值。交易响应是智能合约的输出,如果交易验证成功,那么该交易会被应用到账本上,从而更新世界状态。

背书

  • 示例账本

    1681304156885

    账本 L包含了一个世界状态 W 和一个区块链 B。其中 W 包含了四个状态,各状态的键分别是:CAR0,CAR1,CAR2 和 CAR3 。而 B 包含了两个区块 0和 1。区块1包含了四笔交易:T1,T2,T3,T4

1.7 排序服务

  • Hyperledger Fabric 的工作方式不同。它有一种称为排序节点的节点使交易有序,并与其他排序节点一起形成一个排序服务。因为 Fabric 的设计依赖于确定性的共识算法,所以 Peer 节点所验证的区块都是最终的和正确的。账本不会像其他分布式的以及无需许可的区块链中那样产生分叉
  • 排序节点还维护着允许创建通道的组织列表。此组织列表称为“联盟”
  • 排序节点还对通道执行基本访问控制,限制谁可以读写数据,以及谁可以配置数据

1.7.1 排序节点和交易流程

更新账本的应用程序涉及到三个阶段,该过程确保区块链网络中的所有节点保持它们的账本彼此一致。

在第一阶段,客户端应用程序将交易提案发送给一组节点,这些节点将调用智能合约来生成一个账本更新提案,然后背书该结果。背书节点此时不将提案中的更新应用于其账本副本。相反,背书节点将向客户端应用程序返回一个提案响应。

已背书的交易提案最终将在第二阶段经过排序生成区块,

然后在第三阶段分发给所有节点进行最终验证和提交。

  • 将交易排序并打包到区块中
    • 在此阶段,应用程序客户端把包含已背书交易提案响应的交易提交到排序服务节点。排序服务创建交易区块,这些交易区块最终将分发给通道上的所有 Peer 节点,以便在第三阶段进行最终验证和提交。
    • 排序服务节点的工作是将提交的交易按定义好的顺序安排成批次,并将它们打包成区块。这些区块将成为区块链的区块

1681360371573

排序节点的第一个角色是打包提案的账本更新。在本例中,应用程序 A1 向排序节点 O1 发送由 E1 和 E2 背书的交易 T1。同时,应用程序 A2 将 E1 背书的交易 T2 发送给排序节点 O1。O1 将来自应用程序 A1 的交易 T1 和来自应用程序 A2 的交易 T2 以及来自网络中其他应用程序的交易打包到区块 B2 中。我们可以看到,在 B2 中,交易顺序是 T1、T2、T3、T4、T6、T5,但这可能不是这些交易到达排序节点的顺序!(这个例子显示了一个非常简单的排序服务配置,只有一个排序节点。)

  • 验证和提交

1681360785332

排序节点的第二个角色是将区块分发给 Peer 节点。在本例中,排序节点 O1 将区块 B2 分配给节点 P1 和 P2。节点 P1 处理区块 B2,在 P1 上的账本 L1 中添加一个新区块。同时,节点 P2 处理区块 B2,从而将一个新区块添加到 P2 上的账本 L1中。一旦这个过程完成,节点 P1 和 P2 上的账本 L1 就会保持一致的更新,并且每个节点都可以通知与之连接的应用程序交易已经被处理。

1.7.2 排序服务的实现

  • 推荐 Raft
    • Raft 是一种基于 etcd中 Raft 协议实现的崩溃容错(Crash Fault Tolerant,CFT)排序服务。Raft 遵循“领导者跟随者”模型,这个模型中,在每个通道上选举领导者节点,其决策被跟随者复制。Raft 排序服务会比基于 Kafka 的排序服务更容易设置和管理,它的设计允许不同的组织为分布式排序服务贡献节点。
    • 节点总是处于以下三种状态之一:跟随者、候选人或领导者。所有节点最初都是作为跟随者开始的。在这种状态下,他们可以接受来自领导者的日志条目(如果其中一个已经当选),或者为领导者投票。如果在一段时间内没有接收到日志条目或心跳(例如,5秒),节点将自己提升到候选状态。在候选状态中,节点从其他节点请求选票。如果候选人获得法定人数的选票,那么他就被提升为领导者。领导者必须接受新的日志条目并将其复制到跟随者。

2. 入门

2.1 配置环境

此处参考b站视频,虽然可能不全,并且后半段实现不了

但是前面的跟下来都还不错!但是要注意自己的版本问题!

从0开始快速安装Hyperledger Fabric_哔哩哔哩_bilibili

2.1.1 ubuntu 联网

1
2
3
sudo systemctl restart NetworkManager.service
sudo apt-get install network-manager
sudo systemctl restart NetworkManager.service

1681387612411

2.1.2 apt 换源

1
2
3
https://mirrors.tuna.tsinghua.edu.cn/help/ubuntu/
sudo gedit /etc/apt/sources.list
sudo apt update

2.1.3 安装 docker

1
2
3
4
// 安装docker、docker-compose
sudo apt install docker docker-compose
sudo systemctl enable docker
sudo usermod -a -G docker <username>

2.1.4 安装 golang

1
2
3
4
5
6
7
8
// 安装golang
https://go.dev/doc/install
sudo su
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.17.6.linux-amd64.tar.gz
gedit /etc/profile
export PATH=$PATH:/usr/local/go/bin
gedit ~/.bashrc
source /etc/profile

2.1.5 docker 加速器

1
2
// docker加速器
https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors

2.1.6 安装fabric-sample

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1、手动创建脚本,安装samples、docker
https://github.com/hyperledger/fabric/blob/main/scripts/bootstrap.sh
修改binaries=false
sudo chmod u+x bootstrap.sh
./bootstrap.sh

2、安装binaries
https://github.com/hyperledger/fabric/releases/download/v2.4.1/hyperledger-fabric-linux-amd64-2.4.1.tar.gz
https://github.com/hyperledger/fabric-ca/releases/download/v1.5.2/hyperledger-fabric-ca-linux-amd64-1.5.2.tar.gz
tar -xzvf 压缩包名 -C 目的地

3、配置go代理
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct

3. 开发

3.1 分析

3.1.1 商业票据

  • 票据 00001 是 5 月 31 号由 MagnetoCorp 发行的。该票据的第一个状态,它具有不同的属性和值:

    1
    2
    3
    4
    5
    6
    7
    Issuer = MagnetoCorp
    Paper = 00001
    Owner = MagnetoCorp
    Issue date = 31 May 2020
    Maturity = 30 November 2020
    Face value = 5M USD
    Current state = issued

    票据的状态是 发行 交易的结果,它使得 MagnetoCorp 公司的第一张商业票据面世!注意该票据在今年晚些时候如何兑换面值 500 万美元。当票据 00001 发行后 Issuer 和 Owner 具有相同的值。该票据有唯一标识 MagnetoCorp00001——它是 Issuer 属性和 Paper 属性的组合。最后,属性 Current state = issued 快速识别了 MagnetoCorp 票据 00001 在它生命周期中的阶段。

  • 发行后不久,该票据被 DigiBank 购买。由于购买交易,同一个商业票据如何发生变化:

    1
    2
    3
    4
    5
    6
    7
    Issuer = MagnetoCorp
    Paper = 00001
    Owner = DigiBank
    Issue date = 31 May 2020
    Maturity date = 30 November 2020
    Face value = 5M USD
    Current state = trading

    最重要的变化是 Owner 的改变——票据初始拥有者是 MagnetoCorp 而现在是 DigiBank。我们可以想象该票据后来如何被出售给 BrokerHouse 或 HedgeMatic,以及相应的变更为相应的 Owner。注意 Current state 允许我们轻松的识别该票据目前状态是 trading

  • 6 个月后,如果 DigiBank 仍然持有商业票据,它就可以从 MagnetoCorp 那里兑换:

    1
    2
    3
    4
    5
    6
    7
    Issuer = MagnetoCorp
    Paper = 00001
    Owner = MagnetoCorp
    Issue date = 31 May 2020
    Maturity date = 30 November 2020
    Face value = 5M USD
    Current state = redeemed

    最终的兑换交易结束了这个商业票据的生命周期——它可以被认为票据已经终止。通常必须保留已兑换的商业票据的记录,并且 redeemed 状态允许我们快速识别这些。通过将 Owner 跟交易创建者的身份进行比较,一个票据的 Owner 值可以被用来在兑换交易上进行访问控制

3.1.2 交易

  • 我们已经看到票据 00001 的生命周期相对简单——由于发行购买兑换交易,它在 issuedtrading 和 redeemed 状态之间转移

  • 注意交易和票据不同!!

    • 发行交易
    1
    2
    3
    4
    5
    6
    Txn = issue
    Issuer = MagnetoCorp
    Paper = 00001
    Issue time = 31 May 2020 09:00:00 EST
    Maturity date = 30 November 2020
    Face value = 5M USD
    • 购买交易
    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
    Txn = redeem
    Issuer = MagnetoCorp
    Paper = 00001
    Current owner = HedgeMatic
    Redeem time = 30 Nov 2020 12:00:00 EST

3.2 流程和数据设计

3.2.1 生命周期

  • 在处理商业票据时有两个重要的概念:状态交易

  • 对状态和交易的有效分析是成功实施的重要起点,可以用状态转移表来表示商业票据的生命周期:

    1681607417562

    商业票据的状态转移表。商业票据通过发行购买兑换交易在已发行交易中已兑换之间进行状态转移。

3.2.2 账本状态

  • 商业票据的结构:

    1681607716814

    商业票据可以被表示为属性集,每个属性都对应一个值。通常,这些属性的组合会为每个票据提供一个唯一键

    结合来看,属性的完整集合构成了商业票据的状态。此外,这些商业票据的全部集合构成了账本的世界状态。

  • 查看 MagnetoCorp 的票据 00001 如何表示为一个状态向量,根据不同的交易刺激进行转换:

    1681607900927

    注意每个独立的票据都起于空状态,技术上被称作 nil,来表示票据不存在!通过发行交易,票据 00001 问世,然后由于购买兑换交易而更新状态

3.2.3 状态键值

  • 大多数的实际应用中,状态会有一个属性组合在给定的上下文中唯一识别它——它就是主键
  • PaperNet 商业票据的主键是通过 Issuer 属性和 paper 属性拼接得到的,所以 MagnetoCorp 的第一个票据的主键就是 MagnetoCorp00001
  • Fabric 需要账本中的每个状态都有唯一的主键
  • 当唯一主键在可用的属性集中不能获得,应用决定的唯一键会被指定为交易的输入来创建状态。这个唯一键的形式一般是 UUID

3.2.3 逻辑表示

为了满足不同类型的查询任务,把所有相关的商业票据按逻辑顺序排列在一起是很有帮助的。PaperNet 的设计包含了商业票据列表的思想——一个逻辑容器,每当商业票据发行或发生其他更改时,该容器都会更新

  • 把所有的 PaperNet 商业票据放在一个商业票据列表中是有帮助的:

    1681608349698

    新票据由于发行交易被加入到列表中,然后列表中已存在的票据因为购买交易和兑换交易可以被更新状态。列表有一个描述性的名称:org.papernet.papers;使用这种DNS 名真的是一个好主意,因为适当的名称会让你的区块链设计对其他人来说是直观的。这种想法同样也适用于智能合约的名字。

3.2.4 物理表现

  • 我们可以正确地想到 PaperNet 中的单个票据列表—— org.papernet.papers ——列表最好作为一组单独的 Fabric 状态来实现,其复合键将状态与其列表关联起来。这样,每个状态的复合键都是惟一的,并支持有效的列表查询

    1681608524162

  • 状态向量的物理设计对于优化性能和行为非常重要。保持状态的独立!

3.3 智能合约处理

 连接到网络的所有应用程序必须使用相同版本的智能合约,以便它们共同实现相同的共享业务流程和数据。

在 Java 中,类必须使用 @Contract(...) 标注进行包装。它支持额外的智能合约信息,比如许可和作者。 @Default() 标注表明该智能合约是默认合约类。在智能合约中标记默认合约类在一些有多个合约类的智能合约中会很有用。

fabric-samples/CommercialPaperContract.java at master · hyperledger/fabric-samples (github.com)

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/*
SPDX-License-Identifier: Apache-2.0
*/
package org.example;

import java.util.logging.Logger;

import org.example.ledgerapi.State;
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;
import org.hyperledger.fabric.shim.ChaincodeStub;

/**
* A custom context provides easy access to list of all commercial papers
*/

/**
* Define commercial paper smart contract by extending Fabric Contract class
*
*/
@Contract(name = "org.papernet.commercialpaper", info = @Info(title = "MyAsset contract", description = "", version = "0.0.1", license = @License(name = "SPDX-License-Identifier: Apache-2.0", url = ""), contact = @Contact(email = "java-contract@example.com", name = "java-contract", url = "http://java-contract.me")))
@Default
public class CommercialPaperContract implements ContractInterface {

// use the classname for the logger, this way you can refactor
private final static Logger LOG = Logger.getLogger(CommercialPaperContract.class.getName());

@Override
public Context createContext(ChaincodeStub stub) {
return new CommercialPaperContext(stub);
}

public CommercialPaperContract() {
}

/**
* Define a custom context for commercial paper
*/

/**
* Instantiate to perform any setup of the ledger that might be required.
*
* @param {Context} ctx the transaction context
*/
@Transaction
public void instantiate(CommercialPaperContext ctx) {
// No implementation required with this example
// It could be where data migration is performed, if necessary
LOG.info("No data migration to perform");
}

/**
* Issue commercial paper
*
* @param {Context} ctx the transaction context
* @param {String} issuer commercial paper issuer
* @param {Integer} paperNumber paper number for this issuer
* @param {String} issueDateTime paper issue date
* @param {String} maturityDateTime paper maturity date
* @param {Integer} faceValue face value of paper
*/
@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;
}

/**
* Buy commercial paper
*
* @param {Context} ctx the transaction context
* @param {String} issuer commercial paper issuer
* @param {Integer} paperNumber paper number for this issuer
* @param {String} currentOwner current owner of paper
* @param {String} newOwner new owner of paper
* @param {Integer} price price paid for this paper
* @param {String} purchaseDateTime time paper was purchased (i.e. traded)
*/
@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;
}

/**
* Redeem commercial paper
*
* @param {Context} ctx the transaction context
* @param {String} issuer commercial paper issuer
* @param {Integer} paperNumber paper number for this issuer
* @param {String} redeemingOwner redeeming owner of paper
* @param {String} redeemDateTime time paper was redeemed
*/
@Transaction
public CommercialPaper redeem(CommercialPaperContext ctx, String issuer, String paperNumber, String redeemingOwner,
String redeemDateTime) {

String paperKey = CommercialPaper.makeKey(new String[] { paperNumber });

CommercialPaper paper = ctx.paperList.getPaper(paperKey);

// Check paper is not REDEEMED
if (paper.isRedeemed()) {
throw new RuntimeException("Paper " + issuer + paperNumber + " already redeemed");
}

// Verify that the redeemer owns the commercial paper before redeeming it
if (paper.getOwner().equals(redeemingOwner)) {
paper.setOwner(paper.getIssuer());
paper.setRedeemed();
} else {
throw new RuntimeException("Redeeming owner does not own paper" + issuer + paperNumber);
}

ctx.paperList.updatePaper(paper);
return paper;
}

}

3.4 应用

3.4.1 基本流程

  • 应用程序如何调用商业票据智能合约的简化图表:

    1681609886914

  • 应用程序必须遵循六个基本步骤来提交交易:

    • 从钱包中选择一个身份
    • 连接到网关
    • 访问所需的网络
    • 构建智能合约的交易请求
    • 将交易提交到网络
    • 处理响应

4. 工作流程

1681614315146