Using FabToken

FabToken允许用户轻松标记Hyperledger Fabric上的资产。Token作为Fabric v2.0中的Alpha版本特性引入。您可以使用以下操作指南了解FabToken并开始使用token。您可以在本指南的末尾找到一个在Fabric上创建token的示例,该示例扩展了BYFN教程。

What is FabToken

将资产表示为Token允许您使用区块链账本来建立项目的惟一状态和所有权,并使用多方信任的共识机制来转移所有权。只要账本是安全的,资产就是不可变的,未经所有者同意不能转让。

Token可以代表有形资产,比如流经供应链的货物或正在交易的金融工具。代币也可以代表无形资产,如忠诚度点数。因为没有所有者的同意不能转让token,而且交易是在分布式账本上验证的,所以将资产表示为token可以降低跨多方转让资产的风险和难度。

FabToken是一个token管理系统,允许您使用Hyperledger Fabric发出、转账和赎回token。Token存储在channel账本上,可以由channel的任何成员拥有。FabToken使用Fabric的成员服务来验证token所有者的身份,并管理其公钥和私钥。Fabric token交易只有由具有有效MSP标识符的token所有者发出时才有效。

FabToken提供了一个简单的接口来标记Fabric channel上的资产,同时利用channel提供的验证和信任。Token使用channel排序节点和peer节点进行共识和验证。Token还使用channel策略来控制允许哪些成员拥有和发出Token。然而,用户不需要使用智能合约来创建或管理token。Token可以建立资产的不变性和所有权,而不需要channel成员编写和批准复杂的业务逻辑来创建和管理这些资产。Token所有者可以使用自己信任的节点来创建token交易,而不必依赖于属于其他组织的节点来执行或背书交易。

The token lifecycle

Token在Hyperledger Fabric内部有一个闭环的生命周期,可以发行、转让、赎回。

  • Tokens are created by being issued. The token issuer defines the type of asset represented by the tokens and the quantity. The issuer also assigns issued tokens to their original owners.

  • Tokens are “spent” by being transferred. The token owner transfers the asset represented by token to a new owner that is a member of the fabric channel. Once the token has been transferred, it can no longer be spent or accessed by the previous owner.

  • Tokens are removed from the channel by being redeemed. Redeemed tokens are no longer owned by any channel member and thus can no longer be spent.

FabToken使用一种未花费的交易输出(UTXO)模型来验证token交易。UTXO交易是一种强大的保证,可以保证资产是唯一的,只能由所有者转移,并且不能双花。每笔交易都需要一组特定的输出和输入。输出是交易创建的新token。这些在账本上以“未花费”状态列出。输入需要作为另一个交易的输出创建的未花费token。当交易被验证时,从通道账本的状态数据库中删除已使用的token,从而销毁已使用的token。

Token生命周期构建在UTXO模型之上,以确保token是唯一的,并且只能使用一次。当发行token时,它将以发行方指定的属于所有者的未花费状态创建。然后,所有者可以转让或赎回token。在做token转账时,将创建转账交易者拥有的token作为输入。交易的输出是转账的接收方拥有的新token。输入token变为“已花费”,并从状态数据库中删除。所转移的token表示的资产数量需要与输出的数量相同。被赎回的token被转移到一个空的所有者。这使得赎回的token不可能由通道的任何成员再次转账。

下面的指南描述了如何在Fabric中创建和使用token。这些说明详细说明了在使用Fabric token client、Fabric SDKs提供的API或token CLI时,使用FabToken需要哪些步骤和信息。您可以在本指南的末尾找到一个FabToken示例。

Issuing tokens

Token只能由发行者创建。发行者是通道成员,他们被发行策略授予发行token的权限。满足策略的用户可以使用发行交易将token添加到账本中。

Token有三个属性:

  • Owner identifies the channel member that can transfer or redeem the new token through its MSP identity.

  • Type describes the asset the token represents, such as USD, EUR, or BYFNcoins in the example below.

  • Quantity is the number of units of Type that the Token represents.

例如,美元类型的每个token可以表示100美元。每一美元不需要是一个单独的token。为了消费50美元的token,或者添加50美元,将创建表示新数量单位的新token。

发行策略还可以限制哪些用户可以发布特定类型的token。在Fabric v2.0 Alpha版中,IssuingPolicy被设置为ANY,这意味着所有通道成员都可以发出任何类型的token。在将来的版本中,用户可以限制此策略。

List

您可以使用List方法或命令查询您拥有的未花费的token。一个成功执行的list命令返回以下值:

  • TokenID is the identifier of each token you own.

  • Type is the asset your tokens represent.

  • Quantity is number of units of Type in hexadecimal format of each asset that you own.

Transfer

您可以通过将自己拥有的token转账给其他通道成员来花费它们。您可以通过提供以下值来进行token转账:

  • Token ID: The ID of the tokens you want to transfer.

  • Quantity: The amount of the asset represented by each token to be transferred.

  • Recipient: The MSP identifier of the channel member you want to transfer the assets to.

注意,转账交易针对token所表示的基础资产,而不是交易token本身。相反,新的token是由转账交易创建的。例如,如果您拥有一个值100美元的token,您可以使用该token花费50美元。转账交易将创建两个新的token作为输出。一个代表50美元的token将属于您,另一个代表50美元的token将属于收款人。

被转账到交易接收者的资产数量需要与输入token所代表的资产数量相同。如果您不想转出token所表示的资产的全部数量,您可以只转资产的一部分,并且交易将自动使您成为余额的所有者。使用上面的示例,如果只花费100美元token中的50美元,那么转账交易将自动创建一个价值50美元的新token,并将您作为所有者。

为取得成功,转账须符合下列条件:

  • The tokens being transferred need to belong to the transaction initiator and are unspent.

  • All input tokens of the transaction need to be of the same type.

Redeem

赎回的token不能再花费。赎回token的操作会从由通道管理的业务网络中删除资产,并确保不能再转账或更改该资产。如果供应链中的物品到达了最终目的地,或者金融资产到达了期限,则表示该资产的token可以被赎回,因为该资产不再需要由通道成员使用。

所有者赎回token需要提供以下参数:

  • Token ID: The ID of the token you want to redeem.

  • Quantity: The quantity of the asset represented by each token you want to redeem.

只有token所有者提交赎回交易时才能赎回token。不需要赎回token所表示的全部资产数量。例如,如果您有一个表示100美元的token,并且想赎回50美元,那么赎回交易将创建一个值50美元的新token,并将另一个50美元转移到一个没有所有者的受限帐户。因为该账户没有所有者,50美元不能再被任何通道的成员转移。

The token transaction flow

Fabtoken绕过了标准的Hyperledger Fabric背书流程。针对链码的交易需要在足够多的组织的peer节点上执行,以满足链码背书策略。这将确保交易的结果与智能合约的逻辑保持一致,并且该逻辑的结果已被多个组织验证。由于token是资产的唯一表示形式,只能由其所有者转移或赎回,因此不需要多个组织验证初始交易。

Token CLI和Node.js版的Fabric SDK都包含FabToken client模块,模块可以利用可信节点,或称为校准节点,创建token交易。例如,属于操作节点的组织的用户可以使用该节点查询其token并使用它们。如果连接到启用了V2_0功能的通道,那么任何具有Fabric 2.0 Alpha代码的节点都可以用作校准节点。

  • In the case of an issue transaction, the prover peer will verify that the requested operation satisfies the IssuingPolicy associated with the tokens being created.

  • In the case of transfer, redeem and list, the peer checks that the input tokens are unspent and belong to the entity requesting the transaction.

  • In the case of transfer and redeem, the peer checks that the input and output tokens are all of the same type and that the output tokens have the same type and sum up to the same quantity as the input tokens.

一旦client在校准节点的帮助下生成了token交易,它就将交易发送给排序服务。然后,排序服务将交易发送给提交节点以进行验证并添加到账本中。提交节点检查交易是否符合UTXO交易模型,以及基础资产是否被双花或超额使用。

FabToken Example

您可以尝试使用BYFN教程中的示例网络亲自处理token,比如发行和转账token。在本例中,我们将使用Token CLI在./byfn.sh脚本创建的通道上交易一些token化的BYFNcoins。

您还可以使用Node.js版的Fabric SDK来处理token。访问Node.js版Fabric SDK文档中的“如何执行token操作”教程。您还可以找到一个示例,它使用Node.js版Fabric SDK,在fabric-samples中发行、转账和赎回token。

Start the network

第一步是打开示例网络。./byfn.sh脚本创建了一个由两个组织Org1和Org2组成的Fabric网络,其中的节点连接到一个名为mychannel的通道。我们将使用mychannel发行token,并在Org1和Org2之间转移它们。

首先,我们需要清理我们的环境。下面的命令将导航到fabric-samples目录,杀死所有活动的或陈旧的Docker容器,并删除以前生成的内容:

cd fabric-samples/first-network
./byfn.sh down

首先需要生成示例网络所需的构件。运行以下命令:

./byfn.sh generate

我们需要添加一些在后面步骤中需要的文件。导航到first-network目录中的crypto-config目录。

cd crypto-config

Token CLI使用的来自每个组织的配置文件,包含关于组织信任哪些节点以及往哪个排序节点发送交易的信息。下面是Org1的配置文件。请注意,Org1使用它自己的节点作为校准节点,并在文件的“ProverPeer”部分中提供节点端点信息。

**Org1 Configuration file** ``` { "ChannelID":"", "MSPInfo":{ "MSPConfigPath":"", "MSPID":"Org1MSP", "MSPType":"bccsp" }, "Orderer":{ "Address":"orderer.example.com:7050", "ConnectionTimeout":0, "TLSEnabled":true, "TLSRootCertFile":"/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem", "ServerNameOverride":"" }, "CommitterPeer":{ "Address":"peer0.org1.example.com:7051", "ConnectionTimeout":0, "TLSEnabled":true, "TLSRootCertFile":"/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt", "ServerNameOverride":"" }, "ProverPeer":{ "Address":"peer0.org1.example.com:7051", "ConnectionTimeout":0, "TLSEnabled":true, "TLSRootCertFile":"/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt", "ServerNameOverride":"" } } ```

将上面的文件粘贴到文本编辑器中,并将其保存为configorg1.json。保存configorg1之后,在文本编辑器中创建一个新文件,并粘贴下面的json文件。在相同位置将文件保存为configorg2.json:

**Org2 Configuration file** ``` { "ChannelID":"", "MSPInfo":{ "MSPConfigPath":"", "MSPID":"Org2MSP", "MSPType":"bccsp" }, "Orderer":{ "Address":"orderer.example.com:7050", "ConnectionTimeout":0, "TLSEnabled":true, "TLSRootCertFile":"/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem", "ServerNameOverride":"" }, "CommitterPeer":{ "Address":"peer0.org2.example.com:9051", "ConnectionTimeout":0, "TLSEnabled":true, "TLSRootCertFile":"/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt", "ServerNameOverride":"" }, "ProverPeer":{ "Address":"peer0.org2.example.com:9051", "ConnectionTimeout":0, "TLSEnabled":true, "TLSRootCertFile":"/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt", "ServerNameOverride":"" } } ```

现在我们需要保存一个额外的文件,以便在转移token时使用。在文本编辑器中创建一个新文件,并将下面的文件保存为share.json:

**shares.json** ``` [ { "recipient":"Org2MSP:/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/User1@org2.example.com/msp", "quantity":"50" } ] ```

现在,您可以导航回fabric-samples目录并启动sample网络:

cd ..
/byfn.sh up

该命令将创建用于发行和转账token的orgs、peers、orderers和channel。当命令成功完成时,您应该会看到以下结果:

========= All GOOD, BYFN execution completed ===========

 _____   _   _   ____
| ____| | \ | | |  _ \
|  _|   |  \| | | | | |
| |___  | |\  | | |_| |
|_____| |_| \_| |____/

Issue tokens

我们将标记100个BYFNcoins,这只能由我们信任的朋友在我们的示例网络上发行和交易。使用以下命令导航到CLI容器:

docker exec -it cli bash

使用下面的命令作为Org1管理员发出一个值100 BYFNcoins的token。该命令使用configorg1.json查找org1的校准节点的端口地址,它将使用该端口地址组装交易。注意,Org1管理员提交交易,但是Org1的User1将是token所有者。

# Issue the token as Org1

token issue --config /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/configorg1.json --mspPath /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp --channel mychannel --type BYFNcoins --quantity 100 --recipient Org1MSP:/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp

一个成功的命令会产生一个类似如下的响应:

2019-03-12 00:49:43.864 UTC [token.client] BroadcastReceive -> INFO 001 calling OrdererClient.broadcastReceive
Orderer Status [SUCCESS]
Committed [true]

您可以使用list命令查看创建的token。此命令由User1发出,它是新token的所有者。

# List the tokens belonging to User1 of Org1

token list --config /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/configorg1.json --mspPath /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp --channel mychannel

一个成功的命令会产生一个类似如下的响应:

{"tx_id":"4e2664225d6a67508cfa539108383e682f3d03debb768aa7920851fdeea6f5b7"}
[BYFNcoins,100]

在命令输出中,您可以找到tokenID、类型和数量。tokenID是创建token的交易的transactionID。

Transferring tokens

既然已经创建了token,那么Org1的User1现在可以通过将BYFNcoins转账给另一个用户来使用token。Org1的User1将给Org2的User1 50 BYFNcoins,同时自己保留50。

使用下面的命令启动转账。使用 tokenIDs 来标记转账列表中返回的tokenID。注意 – Shares 标记如何把一个JSON文件传递给Token CLI,该文件在Org2中将50 BYFNcoins分配给User1。这是您在启动网络之前在 crypto-config文件夹中创建的文件。因为输入token表示100个BYFNcoins,所以转账交易将自动创建一个属于Org1的User1的新token,它表示没有转账给Org2的50个BYFNcoins。

# Transfer 50 BYFNcoins to User1 of Org2
# The split of coins tranfered to Org1 and Org2 is in shares.json

token transfer --config /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/configorg1.json --mspPath /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp --channel mychannel --tokenIDs '[{"tx_id":"4e2664225d6a67508cfa539108383e682f3d03debb768aa7920851fdeea6f5b7"}]' --shares /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/shares.json

一旦你提交了上面的命令,你可以再次运行list命令来验证Org1的User1现在只有50 BYFNcoins:

# List the tokens belonging to User1 of Org1

token list --config /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/configorg1.json --mspPath /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp --channel mychannel

注意,现在的BYFNcoins与之前的有不同的tokenID。这次转账销毁了之前的token,并创建了一个价值50 BYFNcoins的新token。

{"tx_id":"4eaf466884586106f480dd0bb4f675ddaa54d1290ea53e9c24a2c1344fb71d2c"}
[BYFNcoins,50]

您可以运行下面的命令来验证Org2的User1收到了50 BYFNcoins:

# List the tokens belonging to User1 of Org2

token list --config /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/configorg2.json --mspPath /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/User1@org2.example.com/msp --channel mychannel

Org2拥有的币的tokenID使用与Org1拥有的币相同的交易ID,因为它是由相同的交易创建的。但是,因为它是交易的第二个输出,所以还会给它一个索引,以区别于Org1拥有的token。

{"tx_id":"4eaf466884586106f480dd0bb4f675ddaa54d1290ea53e9c24a2c1344fb71d2c","index":1}
[BYFNcoins,50]

Redeeming tokens

Tokens只能由其所有者赎回。一旦用token表示的资产被赎回,该token就不能再转移给任何其他所有者。

使用下面的命令赎回属于Org2的25个BYFNcoins。

# Redeem tokens belonging to User1 of Org2

token redeem --config /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/configorg2.json --mspPath /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/User1@org2.example.com/msp --channel mychannel  --tokenIDs '[{"tx_id":"4eaf466884586106f480dd0bb4f675ddaa54d1290ea53e9c24a2c1344fb71d2c","index":1}]' --quantity 25

Org2现在只有一个值25 BYFNcoins的token。使用list命令验证Org2的User1拥有的BYFNcoins的数量。

# List the tokens belonging to User1 of Org2

token list --config /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/configorg2.json --mspPath /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/User1@org2.example.com/msp --channel mychannel

关注作为赎回交易的输出创建的新TokenID。

让我们尝试赎回属于其他用户的tokens。使用下面的命令尝试作为Org2赎回属于Org1的面值为50 BYFNcoins的token:

# Redeem tokens as Org1 belonging to Org2

token redeem --config /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/configorg2.json --mspPath /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/User1@org2.example.com/msp --channel mychannel  --tokenIDs '[{"tx_id":"4eaf466884586106f480dd0bb4f675ddaa54d1290ea53e9c24a2c1344fb71d2c"}]' --quantity 50

结果将出现以下错误:

error from prover: the requestor does not own inputs

Future features

FabToken Alpha版只支持有限的发行和交易功能。以后的版本将通过支持不可替代的tokens和链码互操作功能,为用户提供更大的能力来将token集成到业务逻辑中,

不可替换的tokens不能合并或分割。一旦它们被创建,它们只能被转账到一个新的所有者或赎回。您可以使用不可替换的token来表示惟一的资产,比如映射到特定座位的音乐会门票。

链码互操作功能允许使用链码发行、转账和赎回tokens。这将允许通道使用通道成员同意的业务逻辑发行和定义token。例如,您可以使用链码设置链码的属性,并将某些属性与不同的交易关联起来。