Pinvon's Blog

所见, 所闻, 所思, 所想

项目记录

环境搭建

前提

安装Git, Go, Docker, Node.js.

仅介绍Node.js的安装

使用脚本工具一键下载Node.js

cd ~
curl -sL https://deb.nodesource.com/setup_8.x -o nodesource_setup.sh
sudo bash nodesource_setup.sh
sudo apt install nodejs

# 查看node版本
node -v

# 查看npm版本
npm -v

# 为了让某些npm包能正常工作, 还要安装 build-essential
sudo apt install build-essential

下载压缩包安装Node.js

可以通过浏览器下载, 然后解压, 配置环境变量即可.

远程服务器只有终端窗口, 不能使用浏览器. 因此, 有两种方式下载:

  1. 使用终端上网下载
  2. 本地下载完成后, 再传送到远程服务器

先介绍第1种, 使用终端上网下载:

# 安装w3m
sudo apt install w3m w3m-img

# 测试
w3m www.baidu.com

# 如果不能显示中文
sudo apt install zhcon

w3m的上下左右移动, 既可以使用Vim的命令, 也可以使用Emacs的命令, 非常好用.

在需要输入文字的地方, 回车, 然后可以在终端的最下方输入文字.

U: 重新输入网址 B: 后退 T: 新开一个标签页. 标签页之间的切换, 使用"{"和"}" C-q: 关闭当前标签页(C是Ctrl)

通过终端上网, 下载所需的压缩包. 如图所示: 46.png

第2种办法是本地电脑下载压缩包, 然后使用scp来传输:

# 需要使用密钥private-key, 否则需要到服务器设置使用密码登录
sudo scp -i private-key test.sh ubuntu@123.207.62.191:~/private-key

# 这样test.sh就传输到了远程服务器

安装Node.js:

xz -d node-v8.9.3-linux-x64.tar.xz 
tar -xvf node-v8.9.3-linux-x64.tar 

# 打开~/.profile文件, 添加环境变量
export NODE_HOME=~/node
export PATH=$NODE_HOME/bin:$PATH

nvm

Node.js的安装到此就结束了. 但是IBM的Marbles使用的node版本只能是 v6.2.0 - v6.11.1, 而上面安装的node版本为v8.x, 因此, 还要对node的版本降级处理.

我们使用nvm来管理node的版本.

sudo apt install libssl-dev
curl -sL https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh -o install_nvm.sh
bash install_nvm.sh
source ~/.profile

# 查看所有的node版本
nvm ls-remote

# 我们使用v6.10.0的版本
nvm install 6.10.0
nvm use 6.10.0

# 查看已安装的所有node
nvm ls

由于前面安装了v8的node, 且配置了环境变量, 有时用nvm切换版本时, 会出现冲突. 解决办法:

nvm deactivate
ls -la $(which npm)
rm $(which npm)

下载代码

git clone https://github.com/IBM-Blockchain/marbles.git
git clone https://github.com/hyperledger/fabric-samples.git

启动网络

cd fabric-samples
curl -sSL https://raw.githubusercontent.com/hyperledger/fabric/release-1.1/scripts/bootstrap-1.1.0-preview.sh -o setup_script.sh
sudo bash setup_script.sh

# 设置环境变量 打开 ~/.bashrc 或 ~/.profile 加入下面一句
export PATH=$PWD/bin:$PATH

# 配置好环境变量后
source ~/.bashrc
# 或者
source ~/.profile

cd fabcar
./startFabric.sh

# 安装依赖
npm install
node enrollAdmin.js

# 如果要关闭网络
cd ../bashic-network
./stop.sh
./teardown.sh

修改配置(v1.1.0)

复制加密材料

fabcar/hfc-key-store 中的加密材料放到用户主目录下:

cd fabric-samples/fabcar/hfc-key-store
rm ~/.hfc-key-store/*
cp * ~/.hfc-key-store

修改配置文件

修改 marbles/config/connection_profile_local.jsonclient.credentialStore.path 项:

# 原来的路径
"path": "/$HOME/fabric-samples/fabcar/hfc-key-store"

# 改成
"path": "/$HOME/.hfc-key-store"

安装chaincode

cd marbles/scripts
node install_chaincode.js

实例化chaincode

cd marbles/scripts
node instantiate_chaincode.js

修改配置(v1.0.0)

chaincode

进入marbles根目录.

修改 config/connection_profile_local.json:

"credentialStore": {
            "path": "/home/pinvon/go/src/github.com/hyperledger/project/fabric-samples/fabcar/creds"
        }

"x-certJson": {
                "path": "/home/pinvon/go/src/github.com/hyperledger/project/fabric-samples/fabcar/creds/PeerAdmin"
            }

"registrar": [
                {
                    "enrollId": "PeerAdmin",
                    "enrollSecret": "PeerAdminpw"
                }
            ],
# 安装chaincode
cd ./scripts
node install_chaincode.js

# 实例化chaincode
node instantiate_chaincode.js

启动marbles

cd marbles

 npm install gulp -g
#  如果失败, npm config seregistry http://registry.cnpmjs.org 再执行.

npm install

gulp marbles_local

项目架构

层次 描述 功能
应用层 移动端/Web端 注册 登录 计步 排行 商城 查看个人信息 邀请 发起提案 接收提案响应 发起交易 ...
业务层 服务端 用户管理 能量管理 提供RestFul接口给应用层 使用Node.js SDK与智能合约交互
智能合约 背书 调用 生成能量 提案背书 增查删改 使用gRPC与区块链交互
区块链   CA 账本

网络部署

程序介绍

chaincode会创建资产, 将它存储到chaincode状态中. 资产在区块链存储(账本)中以键值对的形式创建. 账本与chaincode的交互通过对网络上的一个节点使用gRPC协议来完成. gRPC协议的细节由Hyperledger Fabric Client SDK处理.

项目以Marbles为基础进行修改. 因此, 以Marbles程序的图片来进行介绍.

29.png

  1. admin通过浏览器与Marbles交互.
  2. 客户端JS代码打开一个与后端Node.js应用程序的Websocket连接. admin与该站点交互时, 客户端JS将消息发送到后端.
  3. 读取或写入账本称为提案. 提案由Marbles通过SDK构建, 然后发送到一个区块链节点.
  4. 该节点将与它的Marbles chaincode容器进行通信. chaincode将运行/模拟该交易. 如果没有 问题, 它会对该交易进行背书, 并将其发回Marbles程序.
  5. Marbles通过SDK将背书后的提案发送到Orderer Service, Orderer Service将来自整个网络的许多提案打包到一个区块中, 然后, 它将新的区块广播到网络中的节点.
  6. 最后, 节点会验证该区块, 并将它写入自己的账本中. 该交易现已生效, 所有节点都会过来同步账本.

程序的架构主要分成3个部分:

  1. chaincode: 位于 /chaincode.
  2. 客户端: 用户浏览器中所运行的JavaScript代码, 位于 /public/js 中.
  3. 服务端: 核心部分, 它充当admin与区块链之间的连接器, 位于 /utils & /routes 中.

区块链背景

定义

节点: 节点是区块链的成员, 运行着Hyperledger Fabric. 在marbles中, 节点归弹珠公司所有和操作.

CA: CA负责守卫我们的区块链网络. 它为客户端(如 Marbles node.js 应用程序)提供交易证书.

Orderer: 主要职责是将交易打包到区块中.

区块: 包含交易和一个验证完整性的哈希值.

交易或提案: 表示与区块链账本的交互. 对账本的读取和写入都是以交易/提案的形式发送的.

账本: 区块链在一个节点上的存储区. 它包含由交易参数和键值对组成的实际的区块数据. 由chaincode编写.

chaincode: 定义资产和所有关于资产的规则.

资产: 存在于账本中的实体. 它是一种键值对, 在Marbles中, 资产是一颗弹珠, 或弹珠所有者.

创建一颗弹珠时, 涉及的操作:

  1. 向网络的CA注册管理员用户. 如果成功, CA会向Marbles发送注册证书, SDK将证书存储在本地文件系统中.
  2. 管理员从用户界面创建一颗新弹珠时, SDK会创建一个调用事务.
  3. 创建弹珠的事务被构建为一个调用链代码函数 init_marble() 的提案.
  4. Marbles通过SDK将此提案发送到一个节点进行背书.
  5. 节点运行 init_marble() 来模拟该事务, 并记录它尝试写入账本中的所有更改.
  6. 如果该函数成功返回, 节点会对该提案进行背书, 并将它发回Marbles. 如果失败, 错误也会发送回来, 但不会对提案进行背书.
  7. Marbles通过SDK将背书后的提案发送到Orderer.
  8. Orderer将组织来自整个网络的提案的序列. 它通过查找相互冲突的交易, 检查该交易序列是否有效. 任何由于冲突无法添加到区块中的交易都被标记为错误.
  9. Orderer将新区块广播到网络中的节点.
  10. 节点收到新区块, 并通过查看各种签名和哈希值来验证它. 最后将该区块提交到节点的账本.
  11. 账本中会出现新的弹珠, 并很快会出现在所有节点的账本中.

运动能量

定义运动量化的属性

ID(由于有资产ID, 就说明一个用户如果有多笔资产, 就有多个资产ID, 因此, 应该规定, 多少运动能量可以转化成一个资产), 量化值, 所有者, 产生时间

chaincode 中对这些资产的属性进行定义, 然后定义资产交易的规则.

交易信息

资产信息, 用户信息, 商家信息(或用户信息), 背书节点, 资产历史.

交易的时候, 因为有资产ID, 所以交易应该至少以一个资产为单位. 如果一个交易只涉及到 0.5 个资产, 资产ID 该如何处理? 能否分裂?

注册

CA是最核心的组件, 主要完成对公钥的管理. 密钥有两种类型: 用于签名和用于加解密, 对应称为签名密钥对和加密密钥对. 用户基于PKI体系要申请一个证书, 一般可以由 CA 来生成证书和私钥, 也可以自己生成公钥和私钥, 然后由 CA 来对公钥进行签发.

Fabric的私钥由用户本地存储.

注册过程(参考fabcar例子):

创建SDK实例及保存加密材料的路径

var Fabric_Client = require('fabric-client');
var Fabric_CA_Client = require('fabric-ca-client');
var path = require('path');
var util = require('util');
var os = require('os');

var fabric_client = new Fabric_Client();
var fabric_ca_client = null;
var admin_user = null;
var member_user = null;
var store_path = path.join(__dirname, 'hfc-key-store');

创建键值存储来存储注册证书

Fabric_Client.newDefaultKeyValueStore({ path: store_path
}).then((state_store) => {
    // assign the store to the fabric client
    fabric_client.setStateStore(state_store);

newDefaultKeyValueStore(): 将返回一个KeyValueStore类的实例. 该方法的参数, 一般只填一个路径, 该路径用于存放证书.

证书的参数设置

var crypto_suite = Fabric_Client.newCryptoSuite();
var crypto_store = Fabric_Client.newCryptoKeyStore({path: store_path});
crypto_suite.setCryptoKeyStore(crypto_store);
fabric_client.setCryptoSuite(crypto_suite);

newCryptoSuite(): 设置了证书中的一些内容, 如使用哪些hash算法, 使用哪些数字签名算法.

newCryptoKeyStore(): 设置用户的证书, 密钥等材料的存放位置.

是否开启TLS

var tlsOptions = {
    trustedRoots: [],
    verify: false
};
fabric_ca_client = new Fabric_CA_Client('http://localhost:7054', null , '', crypto_suite);

如果有开启TLS, 则要把 http 改成 https.

检查是否已登记admin

    return fabric_client.getUserContext('admin', true);
}).then((user_from_store) => {
    if (user_from_store && user_from_store.isEnrolled()) {
        console.log('Successfully loaded admin from persistence');
        admin_user = user_from_store;
    } else {
        throw new Error('Failed to get admin.... run enrollAdmin.js');
    }

getUserContext(): 根据用户名字, 返回User类. 根据第二个参数来决定是同步调用(true)还是异步调用(false), 如果是同步调用, 就会返回Promise对象的User.

注册

return fabric_ca_client.register({enrollmentID: 'user1', affiliation: 'org1.department1',role: 'client'}, admin_user);

register() 所需要的参数为: 用户名, 从属关系等, 可通过SDK查看全部参数. 该方法将会返回一个一次性密码.

登记

}).then((secret) => {
    console.log('Successfully registered user1 - secret:'+ secret);
    return fabric_ca_client.enroll({enrollmentID: 'user1', enrollmentSecret: secret});

使用用户名和刚获取的一次性密码进行登记.

创建用户

}).then((enrollment) => {
  console.log('Successfully enrolled member user "user1" ');
  return fabric_client.createUser(
     {username: 'user1',
     mspid: 'Org1MSP',
     cryptoContent: { privateKeyPEM: enrollment.key.toBytes(), signedCertPEM: enrollment.certificate }
     });

createUser(): 基于私钥和签名证书, 返回一个User对象. 也可以使用已经存在的私钥和证书来创建User对象.

设置此用户来对请求签名

}).then((user) => {
     member_user = user;
     return fabric_client.setUserContext(member_user);
}).then(()=>{
     console.log('User1 was successfully registered and enrolled and is ready to intreact with the fabric network');
}).catch((err) => {
    console.error('Failed to register: ' + err);
    if(err.toString().indexOf('Authorization') > -1) {
        console.error('Authorization failures may be caused by having admin credentials from a previous CA instance.\n' +
        'Try again after deleting the contents of the store directory '+store_path);
    }
});

在此以后, 与Fabric的交互, 都会使用该用户的私钥和证书来进行签名.

注册时, 服务器向ECA(enroll ca)发出注册请求. register() 需要两个参数, 第1个参数是json格式的, 内容有登记ID, 属于哪个org等, 第2个参数是执行注册的用户, 一般是admin.

如果传入的登记ID尚未注册, 则ECA返回一个一次性密码.

服务器向ECA发出登记请求. enroll() 包含1个参数, 参数内容为JSON格式, 主要有登记ID, 一次性密码等.

ECA验证后, 返回一个登记证书对. 这个证书对包含两个证书, 一个用于签名, 一个用于加密.

fabric_client.createUser(). 参数包括登记ID, 组织ID, 私钥PEM文件, 签名PEM文件. 返回User对象.

fabric_client.setUserContext(). 参数为User对象. 以后这个用户的私钥和证书会在Fabric后端中为该用户的请求进行签名.

cp

用于获取配置信息. 先解析 config/marbles_local.json 文件, 获取连接的配置文件, 心跳间隔, 客户端连接端口等信息.

然后解析获取的连接配置文件 config/connection_profile_local.json. 这里包含有许多重要信息.

  1. 客户端属于哪个组织, 证书存放位置
  2. channel信息: 包含哪些orderer, peer, chaincode, x-blockDelay
  3. org信息: id, 包含的peer的信息, CA信息, PeerAdmin的证书信息
  4. orderer信息: 地址信息
  5. peer信息: 地址信息
  6. CA信息: 地址信息, 注册员信息

utils/fc_wrangler/parts/enrollment.js

此文件用于注册用户.

注册管理员:

  1. 创建SDK实例.
  2. 我们使用 newDefaultKeyValueStore 创建一个键值存储来存储我们的注册证书.
  3. 注册管理员. 在执行这一步时使用了enrollID和注册密钥向CA执行身份验证. CA将颁发注册证书, SDK将该证书存储在键值存储中. 因为我们使用的是默认的键值存储, 所以它会存储在本地文件系统中.
  4. 成功注册后, 设置orderer URL. 暂时不需要订购者, 但在我们尝试调用链代码时需要它. 仅在拥有自签名证书时, 才需要包含 ssl-target-name-override 的业务. 将此字段与您创建 PEM 文件时使用的常用名设置为相同.
  5. 接下来设置节点 URL. 这些 URL 也是暂时不需要的, 但我们将会完整设置我们的 SDK 链对象.
  6. 此刻, 已对 SDK 进行全面配置并准备好与区块链进行交互.

注册普通用户: 参考fabcar/registerUser.js

查询chaincode

调用栈

query_cc.js query_cc.query_chaincode(obj, options, cb)
index.js fcw.query_chaincode(obj, options, cb_done)
marbles_cc_lib.js fcw.query_chaincode(enrollobj, options, cb)

marbles_cc_lib.jsfcw.query_chaincode(enrollobj, options, cb) 中, 可以查到 options 的内容. 如下:

var opts = {
    peer_urls: g_options.peer_urls,
    peer_tls_opts: g_options.peer_tls_opts,
    channel_id: g_options.channel_id,
    chaincode_id: g_options.chaincode_id,
    chaincode_version: g_options.chaincode_version,
    cc_function: 'read',
    cc_args: ['selftest']
};

Comments

使用 Disqus 评论
comments powered by Disqus