使用fabric-sdk-node的示例2

0 导言

fabric-sdk-node也是分两种写法,一种是底层写法,一种是比较高层的写法。下面先演示比较高层的写法。借助于fabric-network这个模块可以写更加简捷的代码。

本文档演示了一个汽车资产管理的例子,官方提供相应的链码和客户端示例代码。

1 创建项目目录

注:如果已经有该目录则不需 创建了

$ cd $GOPATH/src/github.com
$ mkdir -p fabric-study/sdk-node-study1

注:如果上述mkdir 不能执行,可能是没有权限,加上sudo就可以了

$ sudo mkdir -p fabric-study/sdk-node-study1

2 搭建运行网络

我们不另行去搭建节点网络了,直接拷贝官网提供的basic-network过来用,执行cp命令进行拷贝.

$ cd fabric-study/sdk-node-study1/
$ cp -r ../../hyperledger/fabric-samples/basic-network ./

basic-network会创建如下节点

序号 组织 IP 域名 端口映射 节点
docker 容器1 172.18.0.6 orderer.example.com 0.0.0.0:9051->7051/tcp, 0.0.0.0:9053->7053/tcp orderer节点
docker 容器2 组织1 - org1 172.18.0.7 peer0.org1.example.com 0.0.0.0:10051->7051/tcp, 0.0.0.0:10053->7053/tcp peer1节点
docker 容器3 172.18.0.6 cli客户端
docker 容器4 172.18.0.6 couchDB服务器
docker 容器5 172.18.0.6 ca服务器

3 创建链码放置目录

在这里我们不打算自己编写链码了,直接使用官方提供的fabcar示例的链码,如若对该链码不了解,可通过阅读该链码的源码来熟悉它

$ mkdir chaincode
$ cp -r ../../hyperledger/fabric-samples/chaincode/fabcar ./chaincode

4 创建app1目录

$ mkdir app2

5 用vscode打开sdk-node-study2

Snipaste_2019-03-22_16-58-32.png

6 在app1下创建package.json

内容如下

{
    "name": "fabcar",
    "version": "1.0.0",
    "description": "FabCar application implemented in JavaScript",
    "engines": {
        "node": ">=8",
        "npm": ">=5"
    },
    "scripts": {
        "lint": "eslint .",
        "pretest": "npm run lint",
        "test": "nyc mocha --recursive"
    },
    "engineStrict": true,
    "author": "Hyperledger",
    "license": "Apache-2.0",
    "dependencies": {
        "fabric-ca-client": "~1.4.0",
        "fabric-network": "~1.4.0"
    },
    "devDependencies": {
        "chai": "^4.2.0",
        "eslint": "^5.9.0",
        "mocha": "^5.2.0",
        "nyc": "^13.1.0",
        "sinon": "^7.1.1",
        "sinon-chai": "^3.3.0"
    },
    "nyc": {
        "exclude": [
            "coverage/**",
            "test/**"
        ],
        "reporter": [
            "text-summary",
            "html"
        ],
        "all": true,
        "check-coverage": true,
        "statements": 100,
        "branches": 100,
        "functions": 100,
        "lines": 100
    }
}

7 创建enrollAdmin.js

该js脚本用来向ca-服务器注册管理员

'use strict';

//向ca服务器注册管理员
const FabricCAServices = require('fabric-ca-client');
const { FileSystemWallet, X509WalletMixin } = require('fabric-network');
const fs = require('fs');
const path = require('path');

//connection.json这个文件可以在basic-network文件夹找到,配置了一些连接的信息
const ccpPath = path.resolve(__dirname, '..', '..', 'basic-network', 'connection.json');
const ccpJSON = fs.readFileSync(ccpPath, 'utf8');
const ccp = JSON.parse(ccpJSON);

async function main() {
    try {

        // 创建一个CA客户端
        const caURL = ccp.certificateAuthorities['ca.example.com'].url;
        const ca = new FabricCAServices(caURL);

        // 创建一个wallet文件夹,用于管理身份.
        const walletPath = path.join(process.cwd(), 'wallet');
        const wallet = new FileSystemWallet(walletPath);
        console.log(`Wallet path: ${walletPath}`);

        // 检查是否已经注册管理员.
        const adminExists = await wallet.exists('admin');
        if (adminExists) {
            console.log('An identity for the admin user "admin" already exists in the wallet');
            return;
        }

        // 向ca服务器注册管理员,并将从服务器获得的身份证书导入到wallet.
        const enrollment = await ca.enroll({ enrollmentID: 'admin', enrollmentSecret: 'adminpw' });
        const identity = X509WalletMixin.createIdentity('Org1MSP', enrollment.certificate, enrollment.key.toBytes());
        wallet.import('admin', identity);
        console.log('Successfully enrolled admin user "admin" and imported it into the wallet');

    } catch (error) {
        console.error(`Failed to enroll admin user "admin": ${error}`);
        process.exit(1);
    }
}

main();

8 创建registerUser.js

该js脚本用来向fabric-ca服务器注册普通用户


'use strict';

//注册普通用户
const { FileSystemWallet, Gateway, X509WalletMixin } = require('fabric-network');
const fs = require('fs');
const path = require('path');

const ccpPath = path.resolve(__dirname, '..', '..', 'basic-network', 'connection.json');
const ccpJSON = fs.readFileSync(ccpPath, 'utf8');
const ccp = JSON.parse(ccpJSON);

async function main() {
    try {

        // 创建wallet文件夹用于管理身份信息.
        const walletPath = path.join(process.cwd(), 'wallet');
        const wallet = new FileSystemWallet(walletPath);
        console.log(`Wallet path: ${walletPath}`);

        // 检查是否已经注册过user1用户
        const userExists = await wallet.exists('user1');
        if (userExists) {
            console.log('An identity for the user "user1" already exists in the wallet');
            return;
        }

        // 检查是否已经注册了admin用户
        const adminExists = await wallet.exists('admin');
        if (!adminExists) {
            console.log('An identity for the admin user "admin" does not exist in the wallet');
            console.log('Run the enrollAdmin.js application before retrying');
            return;
        }

        // 创建一个用于连接peer节点的网关
        const gateway = new Gateway();
        await gateway.connect(ccp, { wallet, identity: 'admin', discovery: { enabled: false } });

        // 创建一个用于与ca 服务器交互的ca 客户端对象
        const ca = gateway.getClient().getCertificateAuthority();
        const adminIdentity = gateway.getCurrentIdentity();

        // 注册用户到ca服务器,并将从ca服务器获得的身份信息导入到wallet文件夹.
        const secret = await ca.register({ affiliation: 'org1.department1', enrollmentID: 'user1', role: 'client' }, adminIdentity);
        const enrollment = await ca.enroll({ enrollmentID: 'user1', enrollmentSecret: secret });
        const userIdentity = X509WalletMixin.createIdentity('Org1MSP', enrollment.certificate, enrollment.key.toBytes());
        wallet.import('user1', userIdentity);
        console.log('Successfully registered and enrolled admin user "user1" and imported it into the wallet');

    } catch (error) {
        console.error(`Failed to register user "user1": ${error}`);
        process.exit(1);
    }
}

main();

9 创建query.js

该js脚本用来查询汽车信息



'use strict';

//通过链码查询
const { FileSystemWallet, Gateway } = require('fabric-network');
const fs = require('fs');
const path = require('path');

const ccpPath = path.resolve(__dirname, '..', '..', 'basic-network', 'connection.json');
const ccpJSON = fs.readFileSync(ccpPath, 'utf8');
const ccp = JSON.parse(ccpJSON);

async function main() {
    try {

        // 创建一个用于管理身份的文件夹wallet
        const walletPath = path.join(process.cwd(), 'wallet');
        const wallet = new FileSystemWallet(walletPath);
        console.log(`Wallet path: ${walletPath}`);

        // 检查是否已经注册好普通用户user1
        const userExists = await wallet.exists('user1');
        if (!userExists) {
            console.log('An identity for the user "user1" does not exist in the wallet');
            console.log('Run the registerUser.js application before retrying');
            return;
        }

        // 创建一个用于与peer节点通信的网关对象
        const gateway = new Gateway();
        await gateway.connect(ccp, { wallet, identity: 'user1', discovery: { enabled: false } });

        // 获得我们的链码部署到的channel对象
        const network = await gateway.getNetwork('mychannel');

        // 通过链码名称获得智能合约对象
        const contract = network.getContract('fabcar');

        // 执行指定的交易.
        // queryCar 交易 - 需要1个参数, 例如: ('queryCar', 'CAR4')
        // queryAllCars 交易 - 不需要任何参数, 例如: ('queryAllCars')
        //注意,如果不修改状态使用evaluateTransaction
        const result = await contract.evaluateTransaction('queryAllCars');
        console.log(`Transaction has been evaluated, result is: ${result.toString()}`);

    } catch (error) {
        console.error(`Failed to evaluate transaction: ${error}`);
        process.exit(1);
    }
}

main();


10 创建invoke.js

该js脚本做invoke调用

'use strict';

//调用链码对应的函数
const { FileSystemWallet, Gateway } = require('fabric-network');
const fs = require('fs');
const path = require('path');

const ccpPath = path.resolve(__dirname, '..', '..', 'basic-network', 'connection.json');
const ccpJSON = fs.readFileSync(ccpPath, 'utf8');
const ccp = JSON.parse(ccpJSON);

async function main() {
    try {

        // 创建一个用于管理身份的文件夹wallet
        const walletPath = path.join(process.cwd(), 'wallet');
        const wallet = new FileSystemWallet(walletPath);
        console.log(`Wallet path: ${walletPath}`);

       // 检查是否已经注册好普通用户user1
        const userExists = await wallet.exists('user1');
        if (!userExists) {
            console.log('An identity for the user "user1" does not exist in the wallet');
            console.log('Run the registerUser.js application before retrying');
            return;
        }

      // 创建一个用于与peer节点通信的网关对象
        const gateway = new Gateway();
        await gateway.connect(ccp, { wallet, identity: 'user1', discovery: { enabled: false } });

         // 获得我们的链码部署到的channel对象
        const network = await gateway.getNetwork('mychannel');

        // 创建一个用于与peer节点通信的网关对象
        const contract = network.getContract('fabcar');

        // 提交特定的交易 .
        // createCar 交易 - 需要5个参数, 例如: ('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom')
        // changeCarOwner 交易 - 需要2个参数 , 例如: ('changeCarOwner', 'CAR10', 'Dave')
        //注意,涉及到修改状态使用submitTransaction
        await contract.submitTransaction('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom');
        console.log('Transaction has been submitted');

        // 断开网关连接
        await gateway.disconnect();

    } catch (error) {
        console.error(`Failed to submit transaction: ${error}`);
        process.exit(1);
    }
}

main();

11 启动网络

生成证书(注,只需要执行一次就ok了,以后都不用执行这一步了

$ ./generate.sh

先停止到旧的网络


$ docker stop -f $(docker ps -aq)
$ docker rm -f $(docker ps -aq)
$ docker rmi -f $(docker images | grep fabcar | awk '{print $3}')

然后启动网络,该脚本只启动了ca、orderer、peer、couchDb这几个容器,没有启动cli容器。并且创建名字为mychannel的通道,随后还让peer0.Org1加入了该通道

$ rm -rf ./hfc-key-store
$ cd basic-network
$ ./start.sh

12 启动cli容器

$ docker-compose -f ./docker-compose.yml up -d cli

13 安装和实例化链码

打开一个新的终端,不用进入cli容器,可以在外边执行,用-exec表示在外边执行

安装链码

$ docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp" cli peer chaincode install -n fabcar -v 1.0 -p github.com/fabcar/go -l golang

实例化链码

$ docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp" cli peer chaincode instantiate -o orderer.example.com:7050 -C mychannel -n fabcar -l golang -v 1.0 -c '{"Args":[]}' -P "OR ('Org1MSP.member','Org2MSP.member')"

调用initLedger函数初始化账本

$ docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp" cli peer chaincode invoke -o orderer.example.com:7050 -C mychannel -n fabcar -c '{"function":"initLedger","Args":[]}'

14 进入app1目录安装依赖

到了这一步,证书已经生成,节点网络已经启动,链码也安装和实例化好了,接下来就来测试一下sdk的调用是否成功了。

进入app1目录,并执行npm install安装依赖模块

$ cd app1
$ npm install

15 注册管理员

$ node enrollAdmin.js

输出如下信息

Store path:/home/bob/go/src/github.com/fabric-study/sdk-node-study1/app1/hfc-key-store
Successfully enrolled admin user "admin"
Assigned the admin user to the fabric client ::{"name":"admin","mspid":"Org1MSP","roles":null,"affiliation":"","enrollmentSecret":"","enrollment":{"signingIdentity":"607d571669d90da409ebdaec5855e3a395bbf7af5aab200cd5a902a4054c9cb0","identity":{"certificate":"-----BEGIN CERTIFICATE-----\nMIICAjCCAaigAwIBAgIUGuRlUgmDVeRPKDsf67/6fom2bvMwCgYIKoZIzj0EAwIw\nczELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh\nbiBGcmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMT\nE2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMTkwMzIyMDY0NjAwWhcNMjAwMzIxMDY1\nMTAwWjAhMQ8wDQYDVQQLEwZjbGllbnQxDjAMBgNVBAMTBWFkbWluMFkwEwYHKoZI\nzj0CAQYIKoZIzj0DAQcDQgAE4UAK/Z9BFZE7Fji6S4KTTY/2DGK6Vz0FtuhOQStE\n0rQ5PfwcbUEYFP/Z1m3Lhtvkly09eFWM+vibivprOQAbzaNsMGowDgYDVR0PAQH/\nBAQDAgeAMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFFja6ma5kc1yk5ax5h0Fc0zl\niI3XMCsGA1UdIwQkMCKAIEI5qg3NdtruuLoM2nAYUdFFBNMarRst3dusalc2Xkl8\nMAoGCCqGSM49BAMCA0gAMEUCIQDr6unOLXftqasK83fkthyfykHNI4Mt7y7DA+lX\ns2PyHQIgS54OqyzJ0kulh/xyHd9IkDRAM2iJPVsxCzMUP+t/C1s=\n-----END CERTIFICATE-----\n"}}}

16 注册普通用户

$ node register.js

输出如下信息

 Store path:/home/bob/go/src/github.com/fabric-study/sdk-node-study1/app1/hfc-key-store
Successfully loaded admin from persistence
Successfully registered user1 - secret:OhvdSyIJrqqM
Successfully enrolled member user "user1" 
User1 was successfully registered and enrolled and is ready to interact with the fabric network

17 执行查询

$ node query.js

可以看到,所有的汽车被查出来

Query has completed, checking results
Response is  [{"Key":"CAR0", "Record":{"colour":"blue","make":"Toyota","model":"Prius","owner":"Tomoko"}},{"Key":"CAR1", "Record":{"colour":"red","make":"Ford","model":"Mustang","owner":"Brad"}},{"Key":"CAR2", "Record":{"colour":"green","make":"Hyundai","model":"Tucson","owner":"Jin Soo"}},{"Key":"CAR3", "Record":{"colour":"yellow","make":"Volkswagen","model":"Passat","owner":"Max"}},{"Key":"CAR4", "Record":{"colour":"black","make":"Tesla","model":"S","owner":"Adriana"}},{"Key":"CAR5", "Record":{"colour":"purple","make":"Peugeot","model":"205","owner":"Michel"}},{"Key":"CAR6", "Record":{"colour":"white","make":"Chery","model":"S22L","owner":"Aarav"}},{"Key":"CAR7", "Record":{"colour":"violet","make":"Fiat","model":"Punto","owner":"Pari"}},{"Key":"CAR8", "Record":{"colour":"indigo","make":"Tata","model":"Nano","owner":"Valeria"}},{"Key":"CAR9", "Record":{"colour":"brown","make":"Holden","model":"Barina","owner":"Shotaro"}}]

18 执行调用

$ node invoke.js
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容