编写属于你的第一个APP
Set up the blockchain network
Launch the network
./network.sh down
./network.sh up createChannel -c mychannel -ca
注意该指令需要分成两条指令来执行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y6QhPlML-1634268920958)(file://C:/Users/62483/AppData/Roaming/Typora/typora-user-images/image-20210513113241530.png?lastModify=1621323966)]
如果您决定通过断开网络并重新进行备份来重新开始,则必须在重新运行javascript应用程序之前删除wallet文件夹及其标识,否则会出现错误。 发生这种情况的原因是,在关闭测试网络时,关闭了证书颁发机构及其数据库,但是原始钱包仍保留在application-javascript目录中,因此必须将其删除。 当您重新运行示例javascript应用程序时,将生成一个新的钱包和凭据。
此命令将部署具有两个peer方,一个排序服务和三个证书颁发机构(Orderer,Org1,Org2)的Fabric测试网络。 代替使用cryptogen工具,我们使用证书颁发机构(因此使用-ca标志)启动测试网络。 此外,启动证书颁发机构时会引导组织管理员用户注册。 在后续步骤中,我们将显示示例应用程序如何完成管理员注册。 接下来,通过调用带有链码名称和语言选项的./network.sh脚本来部署链码。
./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-javascript/ -ccl javascript
注意:在后台,此脚本使用链码生命周期来打包,安装,查询已安装的链码,批准Org1和Org2的链码,最后提交链码。
Sample application
打开新的终端,进入application-javascript目录:
cd asset-transfer-basic/application-javascript
该目录包含使用Fabric SDK for Node.js开发的示例程序。 运行以下命令以安装应用程序依赖项。 最多可能需要一分钟才能完成:
npm install
此过程将安装在应用程序的package.json中定义的关键应用程序依赖项。 其中最重要的是结构网络Node.js模块。 它使应用程序能够使用身份,钱包和网关来连接到通道,提交交易并等待通知。 本教程还使用fabric-ca-client模块使用各自的证书颁发机构来注册用户,生成有效的身份,然后由fabric-network模块使用该身份与区块链网络进行交互。
npm安装完成后,一切就绪,可以运行该应用程序。 让我们看一下将在本教程中使用的示例JavaScript应用程序文件。 运行以下命令以列出此目录中的文件:
ls
可以看到有如下相关文件:
app.js node_modules package.json package-lock.json
注意:下一节的第一部分涉及与证书颁发机构的通信。 您可能会发现通过运行新的终端外壳并运行docker logs -f ca_org1在运行即将到来的程序时流式传输CA日志很有用。
第一步,当我们启动Fabric测试网络时,创建了一个管理员用户(字面上称为admin)作为证书颁发机构(CA)的注册商。 我们的第一步是通过让应用程序调用enrollAdmin来生成admin的私钥,公钥和X.509证书。 此过程使用证书签名请求(CSR)-专用密钥和公用密钥首先在本地生成,然后将公用密钥发送到CA,CA返回编码的证书供应用程序使用。 这些凭证然后存储在钱包中,使我们能够充当CA的管理员。
让我们运行该应用程序,然后逐步完成与智能合约功能的每种交互。 在asset-transfer-basic / application-javascript目录中,运行以下命令:
node app.js
First, the application enrolls the admin user
第一步,当我们启动Fabric测试网络时,创建了一个管理员用户(字面上称为admin)作为证书颁发机构(CA)的注册商。 我们的第一步是通过让应用程序调用enrollAdmin来生成admin的私钥,公钥和X.509证书。 此过程使用证书签名请求(CSR)-首先在本地生成私钥和公钥,然后将公钥发送到CA,CA返回编码的证书以供应用程序使用。 这些凭证然后存储在钱包中,使我们能够充当CA的管理员。
在下面的示例应用程序代码中,您将看到在参考公共连接配置文件路径之后,确保连接配置文件存在,并指定创建钱包的位置,然后执行enrollAdmin()并从证书生成管理凭据。 权威。
async function main() {
try {
// build an in memory object with the network configuration (also known as a connection profile)
const ccp = buildCCP();
// build an instance of the fabric ca services client based on
// the information in the network configuration
const caClient = buildCAClient(FabricCAServices, ccp);
// setup the wallet to hold the credentials of the application user
const wallet = await buildWallet(Wallets, walletPath);
// in a real application this would be done on an administrative flow, and only once
await enrollAdmin(caClient, wallet);
此命令将CA管理员的凭据存储在wallet目录中。 您可以在钱包中找到管理员的证书和私钥 。
对于应用程序用户,我们需要应用程序在下一步中注册和注册用户。
Second, the application registers and enrolls an application user
现在,我们已经在钱包中拥有管理员的凭据,该应用程序将使用admin用户注册并注册一个将与区块链网络进行交互的应用程序用户。 应用程序代码部分如下所示。
// in a real application this would be done only when a new user was required to be added
// and would be part of an administrative flow
await registerUser(caClient, wallet, userId, 'org1.department1');
与管理员注册类似,此功能使用CSR来注册和注册appUser并将其凭据与admin的凭据一起存储在钱包中。 现在,我们有两个独立用户的身份-admin和appUser-可以由我们的应用程序使用。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fpfAVUOc-1634268920959)(file://C:/Users/62483/AppData/Roaming/Typora/typora-user-images/image-20210513150922817.png?lastModify=1621323966)]
Third, the sample application prepares a connection to the channel and smart contract
在前面的步骤中,应用程序生成了管理员和应用程序用户凭据,并将其放置在钱包中。 如果凭据存在并且具有与之关联的正确权限属性,则示例应用程序用户将能够在引用通道名称和合同名称之后调用链码功能。
我们的连接配置仅指定您自己的组织中的peer。 我们告诉节点客户端sdk使用服务发现(在同级上运行),该服务获取当前处于联机状态的其他同级,元数据(如相关的认可策略)以及任何其拥有的静态信息,否则需要与其余节点进行通信。 设置为true的asLocalhost告诉它以localhost连接,因为我们的客户端与其他结构节点在同一网络上运行。 在不与其他光纤网络节点在同一网络上运行客户端的部署中,asLocalhost选项将设置为false。
您会注意到,在以下应用程序代码行中,应用程序正在通过网关使用合同名称和信道名称来引用合同:
// Create a new gateway instance for interacting with the fabric network.
// In a real application this would be done as the backend server session is setup for
// a user that has been verified.
const gateway = new Gateway();
try {
// setup the gateway instance
// The user will now be able to create connections to the fabric network and be able to
// submit transactions and query. All transactions submitted by this gateway will be
// signed by this user using the credentials stored in the wallet.
await gateway.connect(ccp, {
wallet,
identity: userId,
discovery: {enabled: true, asLocalhost: true} // using asLocalhost as this gateway is using a fabric network deployed locally
});
// Build a network instance based on the channel where the smart contract is deployed
const network = await gateway.getNetwork(channelName);
// Get the contract from the network.
const contract = network.getContract(chaincodeName);
当链码包包含多个智能合约时,可以在getContract()API上指定链码包的名称和要定位的特定智能合约。 例如:
const contract = await network.getContract('chaincodeName', 'smartContractName');
Fourth, the application initializes the ledger with some sample data
现在我们已经到了实际让示例应用程序提交交易的地步,让我们按顺序进行处理。 为每个被调用的函数以及终端输出提供了应用程序代码片段和调用的链代码片段。
SubmitTransaction()函数用于调用链码InitLedger函数,以使用一些样本数据填充账本。 在幕后,submitTransaction()函数将使用服务发现来为链码找到一组所需的认可peer体,在所需数目的peer体上调用链码,从这些peer体收集链码认可的结果,最后将交易提交给排序服务。
示例app调用’InitLedger’:
// Initialize a set of asset data on the channel using the chaincode 'InitLedger' function.
// This type of transaction would only be run once by an application the first time it was started after it
// deployed the first time. Any updates to the chaincode deployed later would likely not need to run
// an "init" type function.
console.log('\n--> Submit Transaction: InitLedger, function creates the initial set of assets on the ledger');
await contract.submitTransaction('InitLedger');
console.log('*** Result: committed');
'InitLedger’链码:
async InitLedger(ctx) {
const assets = [
{
ID: 'asset1',
Color: 'blue',
Size: 5,
Owner: 'Tomoko',
AppraisedValue: 300,
},
{
ID: 'asset2',
Color: 'red',
Size: 5,
Owner: 'Brad',
AppraisedValue: 400,
},
{
ID: 'asset3',
Color: 'green',
Size: 10,
Owner: 'Jin Soo',
AppraisedValue: 500,
},
{
ID: 'asset4',
Color: 'yellow',
Size: 10,
Owner: 'Max',
AppraisedValue: 600,
},
{
ID: 'asset5',
Color: 'black',
Size: 15,
Owner: 'Adriana',
AppraisedValue: 700,
},
{
ID: 'asset6',
Color: 'white',
Size: 15,
Owner: 'Michel',
AppraisedValue: 800,
},
];
for (const asset of assets) {
asset.docType = 'asset';
await ctx.stub.putState(asset.ID, Buffer.from(JSON.stringify(asset)));
console.info(`Asset ${asset.ID} initialized`);
}
}
Fifth, the application invokes each of the chaincode functions
首先,关于查询账本的一句话。 区块链网络中的每个peer都托管账本的副本。 应用程序可以使用peer上运行的智能合约的只读调用(称为查询)来查看账本中的最新数据。 这是查询工作方式的简化表示:
最常见的查询涉及账本中数据的当前值-其世界状态。 世界状态表示为一组键/值对,应用程序可以查询单个键或多个键的数据。 此外,当您使用CouchDB作为状态数据库并使用JSON建模数据时,可以使用复杂的查询来读取账本上的数据。 当寻找与某些关键字具有特定值匹配的所有资产时,这将非常有用。 例如,具有特定所有者的所有资产。 下面,该示例应用程序仅获取我们在使用数据初始化账本时在上一步中填充的所有资产。 如果您要查询单个peer,而无需向定单服务提交交易,则可以使用validateTransaction()函数。
在序列的下一部分中,示例应用程序进行评估以查看是否存在asset1,这将返回布尔值true,因为在用资产初始化账本时,我们用asset1填充了账本。 您可能还记得资产1的原始评估值为300。应用程序随后提交了一个交易,以用新的评估值更新资产1,然后立即进行评估以从账本中读取资产1,以显示新的评估值350。示例应用程序’ AssetExists”,“ UpdateAsset”和“ ReadAsset”调用
A closer look
让我们仔细研究一下示例javascript应用程序如何使用Fabric Node SDK提供的API与我们的Fabric网络进行交互。 使用编辑器(例如atom或visual studio)打开位于资产转移基础中的app.js
该应用程序首先从fabric-network模块中引入作用域两个关键类:wallet和gateway。 这些类将用于在钱包中找到appUser身份,并使用它来连接到网络:
const { Gateway, Wallets } = require('fabric-network');
首先,该程序使用存储在钱包中的userId设置网关连接,并指定发现选项。
// setup the gateway instance
// The user will now be able to create connections to the fabric network and be able to
// submit transactions and query. All transactions submitted by this gateway will be
// signed by this user using the credentials stored in the wallet.
await gateway.connect(ccp, {
wallet,
identity: userId,
discovery: {enabled: true, asLocalhost: true} // using asLocalhost as this gateway is using a fabric network deployed locally
});
请注意,在示例应用程序代码的顶部,我们需要外部实用程序文件来构建CAClient,registerUser,enrollAdmin,buildCCP(公共连接配置文件)和buildWallet。 这些实用程序位于测试应用程序的AppUtil.js中 ,在test-application/javascript目录下。
在AppUtil.js中,ccpPath描述了应用程序用来连接到我们的网络的连接配置文件的路径。 连接配置文件是从fabric-samples/test-network目录中加载并被解释为JSON文件。
如果您想进一步了解连接配置文件的结构以及它如何定义网络,请查看连接配置文件主题。
一个网络可以划分为多个通道,下一行重要的代码行将应用程序连接到网络中特定的通道mychannel,该通道就是部署我们的智能合约的地方。 请注意,我们在示例应用程序顶部附近分配了常量,以说明信道名称和合同名称:
const channelName = 'mychannel';
const chaincodeName = 'basic';
const network = await gateway.getNetwork(channelName);
在此信道中,我们可以访问资产转移(“基本”)智能合约以与账本进行交互:
const contract = network.getContract(chaincodeName);
在资产转移(“基本”)中,有许多不同的交易,并且我们的应用程序最初使用InitLedger交易用数据填充账本世界状态:
await contract.submitTransaction('InitLedger');
evalidateTransaction函数表示与区块链网络中智能合约最简单的交互之一。它只是选择一个在连接配置文件中定义的peer,然后将请求发送到该peer,并在此对其进行评估。智能合约查询peer账本副本上的资产,并将结果返回给应用程序。这种交互不会导致账本的更新。 submitTransaction比evaluatetransaction更复杂。 SDK不会与单个peer进行交互,而是将根据chaincode的背书策略将submitTransaction提议发送给区块链网络中每个所需组织的peer。这些peer中的每一个都将使用该提议执行所请求的智能合约,以生成交易响应,该交易响应将其认可(签署)并返回到SDK。 SDK将所有已认可的交易响应收集到一个交易中,然后将其提交给排序节点。排序节点将来自各种应用程序客户端的交易收集并排序为一个交易块。这些块被分发到网络中的每个peer,在此对每个交易进行验证和提交。最后,通过事件通知SDK,使其可以将控制权返回给应用程序。
注意:SubmitTransaction包括一个事件侦听器,该侦听器进行检查以确保交易已通过验证并已提交到账本。 应用程序应该利用提交侦听器,或者利用诸如commitTransaction之类的API来为您完成此任务。 否则,您的交易可能无法成功排序,验证并提交到账本。
更新账本
从应用程序的角度来看,更新账本很简单。 应用程序将交易提交到区块链网络,并且在验证和提交交易后,该应用程序会收到有关交易成功的通知。 在幕后,这涉及共识过程,通过该过程,区块链网络的不同组件将共同努力,以确保对账本的每个建议更新均有效并以一致且一致的顺序执行。