Loryfeng's blog

mark something.


  • 首页

  • 标签

  • 分类

  • 归档

  • 关于

  • 搜索

ios消息推送实现

发表于 2018-08-02 | 分类于 ios | | 阅读次数:

ios消息推送实现

ios远程推送工作原理

什么是APNS?

APNs:Apple Push Notification server 苹果推送通知服务,苹果的APNs允许设备和苹果的推送通知服务器保持连接,支持开发者推送消息给用户设备对应的应用程序。

消息推送的步骤

  1. 注册:为应用程序申请消息推送服务。此时你的设备会向APNs服务器发送注册请求。
  2. APNs服务器接受请求,并将deviceToken返给你设备上的应用程序
  3. 客户端应用程序将deviceToken发送给后台服务器程序,后台接收并储存。
  4. 后台服务器向APNs服务器发送推送消息。
  5. APNs服务器将消息发给deviceToken对应设备上的应用程序。

####

证书制作

  1. 通过mac的钥匙串访问申请证书:
    钥匙串访问->证书助理->从证书机构请求证书

  2. 输入证书信息(邮箱及名称),选择存储到磁盘。此时我们能得到xx.certSigningRequest文件。

  3. 登陆苹果开发者官网,新建App IDs,注意开启”Push Notifications选项”
  4. 配置Push Notifications,选择Create Certificate,然后点击Choose File上传生成的xx.certSigningRequest文件,上传成功后,可以得到aps_development.cer文件。
  1. 双击aps_development.cer,将密钥导入钥匙串,然后右键导出xx.p12证书,设置相关密码并保存。
  2. 生成push_cert.pem证书
    1)转换公钥
    openssl x509 -in aps_developer.cer -inform der -out public.pem
    2)转换私钥
    openssl pkcs12 -nocerts -in xx.p12 -out private.pem (需要输入密码)
    3)把两个证书合成
    cat public.pem private.pem > push_cert.pem

  3. Production SSL Certificate证书的制作类似。

编码实现

(注:需要在ios工程下的TARGETS->Capabilities->Push Notifications选项设置为ON。同时将Background Modes的Remote notifications设置为ON,同时确保Signing的证书信息配置正确)

  • 注册消息推送服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

...

if ([[UIApplication sharedApplication] respondsToSelector:@selector(isRegisteredForRemoteNotifications)]) {
//ios8及以上版本
//创建UIUserNotificationSettings,并设置消息的显示类类型
UIUserNotificationSettings *notiSettings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge | UIUserNotificationTypeAlert | UIUserNotificationTypeSound) categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:notiSettings];
} else { // ios7
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge |UIRemoteNotificationTypeSound |UIRemoteNotificationTypeAlert)];
}
...
}
  • 注册成功获取DeviceToken
1
2
3
4
5
6
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{;
NSLog(@"%@", deviceToken);
NSString *token = [[deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]];
token = [token stringByReplacingOccurrencesOfString:@" " withString:@""];
NSLog(@"deviceToken:%@", token);
}
  • 注册远程推送失败回调
1
2
3
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{
NSLog(@"remote Error! %@",error);
}
  • 获取推送信息
1
2
3
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{
NSLog(@"Receive remote notification : %@",userInfo);
}
  • ios8需要在AppDelegate添加此回调方法
1
2
3
4
5
6
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
// 检查当前用户是否允许通知,如果允许就调用 registerForRemoteNotifications
if (notificationSettings.types != UIUserNotificationTypeNone) {
[application registerForRemoteNotifications];
}
}

推送测试客户端

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
/*
1.使用时替换deviceToken和passwd的值;
2.证书名为apns-dev.pem,证书与脚本文件在同一目录下
*/

<?php
$deviceToken = 'xxxxxxxxxxx';//5s
$message = '测试推送信息';
$passwd = 'ooooooooooo'
$badge = 'badge';
$mode = 'dev';//这里可以更改是测试还是线上

$body = array("aps" => array("alert" => $message, "badge" => (int)$badge, "sound"=>'default'), "c"=>'3|735|www.baidu.com|1149710');//c为自定义的参数

$ctx = stream_context_create();
$fp;
if($mode == "development") {
stream_context_set_option($ctx, "ssl", "local_cert", "push_cert.pem");
stream_context_set_option($ctx, "ssl", "passphrase", passwd);
$fp = stream_socket_client("ssl://gateway.sandbox.push.apple.com:2195", $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
} else {
stream_context_set_option($ctx, "ssl", "local_cert", "push_cert.pem");
stream_context_set_option($ctx, "ssl", "passphrase", "fslteam");
$fp = stream_socket_client("ssl://gateway.push.apple.com:2195", $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
}

if (!$fp) {
echo "Failed to connect $err $errstrn";
return;
}

if($mode == "dev") {
echo "Connection OK ==> apns_dev.pem ==> ssl://gateway.sandbox.push.apple.com:2195<br>";
} else {
echo "Connection OK ==> apns_pro.pem ==> ssl://gateway.push.apple.com:2195<br>";
}

$payload = json_encode($body);
$msg = chr(0) . pack("n",32) . pack("H*", str_replace(' ', '', $deviceToken)) . pack("n",strlen($payload)) . $payload;
echo "---------------------------------<br>";
echo "Sending token:".$deviceToken.'<br>';
echo "Sending message :" . $payload . "<br>";
echo "---------------------------------<br>";
fwrite($fp, $msg);
fclose($fp);

运行效果

参考文档

iOS手把手教你生成推送证书

ios远程推送和python版push server相关笔记

php pushDemo

ios8推送注册方式的改变

解决git error: RPC failed; result=22, HTTP code = 413

发表于 2018-07-17 | 分类于 其它 | | 阅读次数:

错误描述

如上图所示,当采用http方式push代码时,当文件比较大时,就会出现上述问题。这主要是因为通过http协议上传时,上传的文件过大,超过了限制。

解决方法

1、使用ssh的方式上传,不走http的方式。
2、调整http协议上传限制。

需要调整两处:
1、通过git config --global http.postBuffer 524288000 命令调整postBuffer大小。
2、以nginx为例,修改服务端nginx配置,调整client_max_body_size大小。
修改nginx.conf文件

1
2
3
4
5
http {
...
client_max_body_size 500m;
...
}

修改配置后重启nginx服务

备注:若使用docker方式启动的nginx服务器调整参数后还是报同样的错误,可以尝试通过yum/apt/源码的方式安装nginx.

Hyperledger Fabric实践

发表于 2018-06-19 | 分类于 区块链 | | 阅读次数:

Hyperledger Fabric实践

背景

传统商业网络面临的挑战

  • 每个参与方都有自己的账本,在交易发生时各自更改;
  • 因此产生为了协同各参与方而带来额外的工作及中介等附加成本;
  • 由于业务条件“合同”重复分散在各个参与方造成整体业务流程的不有效性;
  • 整个业务网络依赖于一个或几个中心系统,一旦发生问题包括欺诈、网络攻击或错误致使整个商业网络是脆弱的;

区块链架构带来以下改变

  • 区块链架构使每一个商业网络的参与方都具有一个共享的帐本,当交易发生时,通过点对点的复制更改所有账本;
  • 使用密码算法确保网路上的参与者仅仅可以看到和他们相关的账本内容,交易是安全的、授权的和验证的;
  • 区块链也将资产转移交易相关的合同条款嵌入交易数据库以做到满足商务条件下交易才发生;
  • 网络参与者基于共识机制或类似的机制来保证交易是共同验证的。商业网络满足政府监管、合规及审计;

区块链架构具有以下特性

  • 同样的商业参与方,并不是脱媒的游戏;
  • 共识(CONSENSUS) – 所有的参与方认同交易的有效性 ;
  • 可证明性(PROVENANCE) – 每个参与方了解资产从哪里来,其所有权是如何改变的;
  • 永恒性(IMMUTABILITY) – 每个参与方一旦交易被同意发生则无法篡改。 如果交易是错误的,必须由新交易冲正并全可跟踪;
  • 权威性(FINALITY) – 只有一个地方来决定资产的归属权及交易的完整性。 这就是共享账本的作用;

Hyperledger Fabric整体架构

Hyperledger Fabric逻辑架构

Hyperledger Fabric区块链网络

Hyperledger Fabric运行时架构

多通道 & 子账本

  • 通道提供⼀种通讯机制,将peer和 orderer连接在⼀起,形成⼀个个具有保密性的通讯链路(虚拟)
  • Fabric的区块链⺴⽹网络缺省包含⼀一个账本(系统账本)和⼀一个通道;
  • ⼦账本可以被创建,并绑定到一个通道

事务

事务为ChainCode的一次调⽤。每个ChainCode在部署时,都需要和背书(ESCC)和验证(VSCC)的系统ChainCode关联。

  • ESCC决定如何对proposal进⾏行背书;
  • VSCC决定事务的有效性(包括背书的正确性);

事务处理流

  1. 应⽤用向一个或多个Peer节点发送对事务的背书请求;
  2. 背书节点执⾏行ChainCode,但并不将结果提交到本地账本,只是将结果返回给应用;
  3. 应⽤收集所有背书节点的结果后,将结果广播给Orderers;
  4. Orderers执⾏行共识过程,并生成Block,通过消息通道批量的将Block发布给Peer节点;
  5. 各个Peer节点验证交易,并提交到本地账本中。

账本

在Fabric 1.0中,我们存在3种类型的数据存储:

  • Block数据:⽂件系统方式存储区块链数据;
  • State Database:以键值对的方式存储了我们在ChainCode中操作的业务数据;
  • 索引数据库:对历史数据和区块链索引的数据库;

应用场景

  • 积分交换平台

IBM与中国银联正在联手创建一个基于区块链技 术的银行卡积分交换平台。在线上,消费者可 以与他人交易自己通过购物和其他奖励措施所 获得的积分,而在线下,积分平台用户可以去 任何一家配备智能POS机的超市或商场,使用奖 励积分兑换商品。在IBM和中国银联的设想中, 这一概念还能够被应用于航空里程、话费帐单 或者加油卡积分的交易上

  • 商品身份溯源

区块链技术将实现食品的全程数字化跟踪,从供应商 生态系统到商店货架,最终到消费者。对于食品供应 商而言,数字产品信息-诸如农场原产地信息、批号、 工厂和加工数据、有效期、存储温度和运输等都将与 相应的食品建立数字化关联,而整个过程中每一环节 的信息将被输入到区块链中。每笔交易的信息都须得 到商业网络中所有成员的共同许可;一旦达成共识, 就会形成无法更改的永久性记录,以此确保所有的商 品相关信息都准确无误。

  • 托管业务的投资运营

区块链作为共享账本,在资产管理、托管 业务、审计之间共享托管业务的核心业务 数据。区块链智能合约自动判断交易的合理性和合规性通过基于区块链的系统来发送投资指令, 代替原来的传真电话方式。

Hyperledger fabric术语

Anchor Peer - 锚节点

锚节点是通道中能被所有对等节点探测、并能与之进行通信的一种对等节点。通道中的每个成员都有一个(或多个,以防单点故障)锚节点,允许属于不同成员身份的节点来发现通道中存在的其它节点。

Leading Peer - 主导节点

每一个Member在其订阅的channel上可以拥有多个peer,其中一个peer会作为 channel的leading peer代表该Member与ordering service通信。Ordering service将block传递给leading peer,该peer再将此block分发给同一member下的其他peer。

Block - 区块

在一个通道上,(区块是)一组有序交易的集合。区块往往通过密码学手段(Hash 值)连接到前导区块。区块是一组有序的交易集合,在通道中经过加密(哈希加密)后与前序区块连接。

Chain - 链

Chain就是block之间以hash连接为结构的交易日志。Peer从order service接 收交易 block,并根据背书策略和并发冲突标记 block 上的交易是否有效,然后 将该block追加到peer 文件系统中的hash chain上。

Transaction - 交易

Chaincode的invoke或instantiate操作。Invoke是从ledger中请求read-write set;Instantiate是请求在peer上启动Chaincode容器。

Ledger - 账本

Ledger是个channel的chain和由channel中每个peer维护的数据库。

Member - 成员

拥有网络唯一根证书的合法独立实体。像peer节点和app client这样的网络组件会链接到一个Member。

Membership Service Provider - MSP

MSP是指为client和peer提供证书的系统抽象组件。Client用证书来认证他们的交易;peer用证书认证其交易背书。该接口与系统的交易处理组件密切相关,旨在使已定义的成员身份服务组件以这种方式顺利插入而不会修改系统的交易处理组件的核心。

Membership Services - 成员服务

成员服务在许可的区块链网络上认证、授权和管理身份。在peer和order中运行的成员服务的代码都会认证和授权区块链操作。它是基于PKI的MSP实现。Fabric-ca 组件实现了成员服务,来管理身份。特别的,它处理ECert和TCert的颁发和撤销。ECert是长期的身份凭证;TCert是短期的身份凭证,是匿名和不可链接的。

Ordering Service - 排序服务和共识服务

将交易排序放入block的节点的集合。Ordering service独立于peer流程之外,并以先到先得的方式为网络上所有的channel作交易排序。Ordering service支 持可插拔实现,目前默认 Solo(单节点共识)、kafka(分布式队列)和 SBFT(简 单拜占庭容错)、ordering service是整个网络的公用binding,包含与每个Member相关的加密材料。

Peer - 节点

一个网络实体,维护ledger并运行Chaincode容器来对ledger执行read-write操作。Peer由Member拥有和维护。

Proposal - 提案

一种针对channel中某peer的背书请求。每个proposal要么是Chaincode
instantiate要么是Chaincode invoke。

Chaincode - 链码

链码是一个运行在账本上的软件,它可以对资产进行编码,其中的交易指令(或
者叫业务逻辑)也可以用来修改资产。

  • Initialize - 初始化

将chaincode放到peer的文件系统的过程。

  • Install - 安装

将chaincode放到peer的文件系统的过程。

  • Instantiate - 实例化

实例化chaincode(同时启动chaincode容器)的过程。

  • Query - 查询

对于current state中某个key的value的查询请求。

  • Invoke - 调用
    用于调用chaincode内的函数。Chaincode invoke就是一个交易proposal,然后执行 模块化的流程(背书、共识、验证、提交)。Invoke的结构就是一个函数和一个 参数数组。

Channel - 通道

通道是构建在“Fabric”网络上的私有区块链,实现了数据的隔离和保密。通道特定的账本在通道中是与所有对等节点共享的,并且交易方必须通过该通道的正确验证才能与账本逬行交互。通道是由一个“配置块”来定义的。

Concurrency Control Version Check - 并发控制版本检查(CCVC)

CCVC 是保持通道中各对等节点间状态同步的一种方法。对等节点并行的执行交易,在交易提交至账本之前,对等节点会检查交易在执行期间读到的数据是否被修改。如果读取的数据在执行和提交之间被改变,就会引发CCVC冲突,该交易就会在账本中被标记为无效,而且值不会更新到状态数据库中。

Configuration Block - 配置区块

包含为系统链(排序服务)或通道定义成员和策略的配置数据。对某个通道或整个网络的配置修改(比如,成员离开或加入)都将导致生成一个新的配置区块并追加到适当的链上。这个配置区块会包含创始区块的内容加上增量。

Consensus - 共识

用于产生一个对于交易排序的结果共识以及确认构成区块的交易集的正确性。

Current State - 当前状态

ledger 的 current state 表示其 chain 交易 log 中所有 key 的最新值。peer 会将处 理过的block中的每个交易对应的修改value提交到ledger的current state,由 于 current state 表示 channel 所知的所有最新的 k-v,所以 current state 也被称 为 World State。Chaincode 执行交易 proposal 就是针对的 current state。

State Database – stateDB

为了从Chaincode中高效的读写,current state数据存储在stateDB中,包括 levelDB和couchDB。

Endorsement - 背书

Endorsement是指一个peer执行一个交易并返回YES-N0给生成交易proposal的 client app的过程。chaincode具有相应的endorsement policies,其中指定了endorsing peer。

Fabric-ca 证书节点

Fabric-ca是默认的证书管理组件,它向网络成员及其用户颁发基于PKI的证书。 CA为每个成员颁发一个根证书(rootCert),为每个授权用户颁发一个注册证书 (eCert),为每个注册证书颁发大量交易证书(tCerts)。

Genesis Block - 创世区块

Genesis Block是初始化区块链网络或channel的配置区块,也是链上的第一个区块。

Gossip Protocol - Gossip 协议

  • 管理peer发现和channel成员;
  • channel上的所有peer间广播账本数据;
  • channel上的所有peer间同步账本数据;

环境部署

环境准备

  • 安装Go环境
  • 安装Docker
  • 安装Docker-compose
  • Fabric源码下载
  • 下载docker镜像

aws云环境
1 Order + 2 Peer

order.example.com (Order)
peer0.org1.example.com (Peer1)
peer0.org2.example.com (Peer2)

生成公私钥和证书

  • 安装依赖
1
sudo apt-get install build-essential libtool libltdl3-dev
  • 编译cryptogen
1
2
cd ~/go/src/github.com/hyperledger/fabric
make cryptogen

在build/bin文件夹下就可以看到编译出来的cryptogen程序。

  • 配置crypto-config.yaml文件

    主要修改组织的Name,Domain,Count等配置

  • 成生公私钥和证书

1
2
cd examples/e2e_cli/
../../build/bin/cryptogen generate --config=./crypto-config.yaml

成生文件在crypto-config文件夹

1
tree crypto-config

生成创世区块和Channel配置区块

  • 编译configtxgen
1
2
3
cd ~/go/src/github.com/hyperledger/fabric

make configtxgen
  • 配置configtx.yaml

    TwoOrgsOrdererGenesis:配置了由两个Org参与的Orderer共识配置
    TwoOrgsChannel:由2个Org参与的Channel配置
    Organizations:Peer节点的配置包含了MSP的配置,锚节点的配置

  • 生成创世区块
1
2
3
cd examples/e2e_cli/

../../build/bin/configtxgen -profile TwoOrgsOrdererGenesis -outputBlock ./channel-artifacts/genesis.block
  • 生成Channel配置区块
1
../../build/bin/configtxgen -profile TwoOrgsChannel -outputCreateChannelTx ./channel-artifacts/channel.tx -channelID mychannel
  • 生成锚节点更新文件
1
2
3
../../build/bin/configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/Org1MSPanchors.tx -channelID mychannel -asOrg Org1MSP

../../build/bin/configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/Org2MSPanchors.tx -channelID mychannel -asOrg Org2MSP

最终我们可以在channel-artifacts文件夹中看到4个文件:

1
2
3
4
5
channel-artifacts/ 
├── channel.tx
├── genesis.block
├── Org1MSPanchors.tx
└── Org2MSPanchors.tx

修改docker-compose文件

  • 修改base/docker-compose-base.yaml文件(涉及peer节点、端口映射等)

  • 设置orderer节点的docker-compose文件

  • 设置peer节点的docker-compose文件(涉及extra_hosts、depends、couchdb配置、command命令删除等)

启动Fabric网络

启动orderer节点

1
2
3
cd ~/go/src/github.com/hyperledger/fabric/examples/e2e_cli

docker-compose -f docker-compose-orderer.yaml up -d

启动peer节点

  • 创建couchdb数据目录
1
mkdir -p ~/couchdb
  • 启动couchdb实例
1
docker run -p 5984:5984 -d --name my-couchdb -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=admin123 -v ~/couchdb:/opt/couchdb/data hyperledger/fabric-couchdb
  • 验证couchdb服务

访问http://peerip:5984/_utils ,若服务正常可以看到CouchDB的Web管理界面

  • 启动peer节点和cli容器
1
2
cd ~/go/src/github.com/hyperledger/fabric/examples/e2e_cli
CHANNEL_NAME=mychannel TIMEOUT=10000 docker-compose -f docker-compose-peer0org1.yaml up -d

创建channel

  • 进入cli容器
1
docker exec -it cli bash
  • 创建channel

创建Channel的命令是peer channel create,由于前面创建Channel的配置区块时,指定了Channel的名字是mychannel,所以这里我们必须创建同样名字的Channel。
(注:开放防火墙端口)

1
2
3
ORDERER_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem

peer channel create -o orderer.example.com:7050 -c mychannel -f ./channel-artifacts/channel.tx --tls true --cafile $ORDERER_CA
  • 各个peer加入mychannel
1
2
3
4
5
6
7
8
peer channel join -b mychannel.block

//通过修改环境变量,指向其它peer
CORE_PEER_LOCALMSPID="Org2MSP"
CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
CORE_PEER_ADDRESS=peer0.org2.example.com:7051
peer channel join -b mychannel.block
  • 更新相应锚节点(Org1,Org2)
1
2
3
4
5
CORE_PEER_LOCALMSPID="Org1MSP" 
CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
CORE_PEER_ADDRESS=peer0.org1.example.com:7051
peer channel update -o orderer.example.com:7050 -c mychannel -f ./channel-artifacts/Org1MSPanchors.tx --tls true --cafile $ORDERER_CA

chaincode的安装与运行

安装chaincode

在cli上为每一个peer安装链上代码,用peer chaincode install命令可以安装指定的ChainCode并对其命名

1
2
3
4
5
6
CORE_PEER_LOCALMSPID="Org1MSP" 
CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
CORE_PEER_ADDRESS=peer0.org1.example.com:7051

peer chaincode install -n mycc -v 1.0 -p github.com/hyperledger/fabric/examples/chaincode/go/test

安装的过程其实就是对CLI中指定的代码进行编译打包,并把打包好的文件发送到Peer,等待接下来的实例化。切换到其它peer节点服务器,分别安装链码

实例化chaincode

实例化链上代码主要是在Peer所在的机器上对前面安装好的链上代码进行包装,生成对应Channel的Docker镜像和Docker容器。并且在实例化时我们可以指定背书策略。(注:instantiate/upgrade均只需要在一个peer上执行一次即可)

1
2
3
4
5
6
CORE_PEER_LOCALMSPID="Org1MSP" 
CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
CORE_PEER_ADDRESS=peer0.org1.example.com:7051

peer chaincode instantiate -o orderer.example.com:7050 --tls true --cafile $ORDERER_CA -C mychannel -n mycc -v 1.0 -c '{"Args":["init"]}' -P "OR ('Org1MSP.member','Org2MSP.member')"

使用docker ps可以看到有新的容器 dev-peer0.org1.example.com-mycc-1.0正在运行。

chaincode功能测试(不同peer上执行均可)

  • initMarble
1
2
3
4
5
peer chaincode invoke -o orderer.example.com:7050  --tls true --cafile $ORDERER_CA -C mychannel -n mycc -c '{"Args":["initMarble","marble1","blue","35","tom"]}'

peer chaincode invoke -o orderer.example.com:7050 --tls true --cafile $ORDERER_CA -C mychannel -n mycc -c '{"Args":["initMarble","marble2","red","50","tom"]}'

peer chaincode invoke -o orderer.example.com:7050 --tls true --cafile $ORDERER_CA -C mychannel -n mycc -c '{"Args":["initMarble","marble3","blue","70","tom"]}'
  • readMarble
1
peer chaincode query -C mychannel -n mycc -c '{"Args":["readMarble","marble1"]}'
  • transferMarble
1
peer chaincode invoke -o orderer.example.com:7050  --tls true --cafile $ORDERER_CA -C mychannel -n mycc -c '{"Args":["transferMarble","marble2","jerry"]}'
  • deleteMarble
1
peer chaincode invoke -o orderer.example.com:7050  --tls true --cafile $ORDERER_CA -C mychannel -n mycc -c '{"Args":["deleteMarble","marble1"]}'
  • getMarblesByRange
1
peer chaincode query -C mychannel -n mycc -c '{"Args":["getMarblesByRange","marble1","marble2"]}'
  • getHistoryForMarble
1
peer chaincode query -C mychannel -n mycc -c '{"Args":["getHistoryForMarble","marble1"]}'
  • queryMarblesByOwner
1
peer chaincode query -C mychannel -n mycc -c '{"Args":["queryMarblesByOwner","tom"]}'

使用Farbic Node SDK

安装node、npm

Fabric Node SDK支持的Node版本是v6,不支持最新的v8版本,执行以下命令即可安装NodeJS的最新v6版本。

1
2
curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
sudo apt-get install -y nodejs

package.json添加依赖模块

1
2
"fabric-ca-client": "^1.0.0", 
"fabric-client": "^1.0.0"

编写完package.json后,执行npm install即可完成模块安装。

编写fabric的Query和Invoke方法

略

升级Chaincode

安装新版本chaincode,打包到peer节点

1
2
3
docker exec -it cli bash

peer chaincode install -n mycc -v 2.0 -p github.com/hyperledger/fabric/examples/chaincode/go/test2.0

升级chaincode

1
peer chaincode upgrade -o orderer.example.com:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n mycc -v 2.0 -c '{"Args":["init"]}' -P "OR      ('Org1MSP.member','Org2MSP.member')"

开发模式

进入链码开发⽬目录

1
cd fabric-samples/chaincode-docker-devmode

打开3个终端,

  • 终端 1
1
$ docker-compose -f docker-compose-simple.yaml up
  • 终端 2
1
2
3
4
$ docker exec -it chaincode bash 
$ cd test
$ go build
$ CORE_PEER_ADDRESS=peer:7051 CORE_CHAINCODE_ID_NAME=mycc:0 ./test
  • 终端 3
1
2
3
4
$ docker exec -it cli bash
$ peer chaincode install -p chaincodedev/chaincode/test -n mycc -v 0
$ peer chaincode instantiate -n mycc -v 0 -c '{"Args":[]}' -C myc
$ peer chaincode invoke -n mycc -c '{"Args":["func","..."]}' -C myc

背书策略

chaincode在实例化的时候,需要指定背书策略。这里的背书策略就是需要什么节点背书交易才能生效。
发起交易的时候,发起端需要指定交易发给哪些节点进行背书验证。发送后等待背书节点的返回,收集到足够的背书后将交易发送给orderer进行排序打包分发。最后,当每个Peer接受到block数据后,会对其中的交易进行验证,如果交易不符合背书策略,就不会在本地生效。

1
2
3
- AND(‘Org1.member’, ‘Org2.member’, ‘Org3.member’) ,请求3个principle的签名。 
- OR(‘Org1.member’, ‘Org2.member’) 请求两个中的一个的签名。
- OR(‘Org1.member’, AND(‘Org2.member’, ‘Org3.member’)) ,请求一个来自Org1的签名,或者来自org2的成员和来自Org3的成员的签名。

Chaincode

什么是链码

chaincode简称链码,一般是用户使用go语言编写的应用代码。链码被部署在 Fabric 网络节点上,运行在Docker 容器中,并通过gRPC协
议与相应的Peer节点进行交互,以操作分布式账本中的数据。

链码的生命周期

Fabric 提供了 , 和 upgrade 4 个命令管理链码的生 命周期。

通过install安装链码,通过 instantiate实例化链码,然后可以通过query调用链码和查询链码。
如果需要升级链码,则需要先 install安装新版本的链码,通过upgrade升级链码。
在install安装链码前,可以通过package打包并签名生成打包文件,然后在通 过 install 安装。

Chaincode操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 安装链码
peer chaincode install -n sacc -v 1.0 -p sacc

# 实例例化链码
peer chaincode instantiate -n sacc -v 1.0 -c '{"Args":["john","0"]}' -P "OR ('Org1.member','Org2.member')"

# 调⽤用链码
peer chaincode invoke -o orderer.example.com:7050 --tls $CORE_PEER_TLS_ENABLED --cafile $ORDERER_CA -C mychannel -n mycc -c '{"Args":["invoke","a","b","10"]}'

# 查询链码
peer chaincode query -C mychannel -n mycc -c '{"Args":["query","a"]}'

# 安装新版本的链码
peer chaincode install -n mycc -v 1 -p path/to/my/chaincode/v1
# upgrade 升级链码
peer chaincode upgrade -n mycc -v 1 -c '{"Args":["d", "e", "f"]}' -C mychannel

# 打包链码
peer chaincode package -n mycc -p github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02 -v 0 -s -S -i "AND('OrgA.admin')" ccpack.out
# 对打包文件进行签名
peer chaincode signpackage ccpack.out signedccpack.out

Chaincode 接口

每个链码都需要实现一下 Chaincode 接口

1
2
3
4
type Chaincode interface {
Init(stub shim.ChaincodeStubInterface) peer.Response
Invoke(stub shim.ChaincodeStubInterface) peer.Response
}

Init:当链码实例化或者升级的时候,Init方法会被调用
Invoke:当链码收到调用(invoke)或者查询的时候,Invoke 会被调用

链码常用api

  • 参数读取 API

GetFunctionAndParameters 提取调用链码交易中的参数,其中第一个作为 被调用的函数名称,剩下的参数作为函数的执行参数

1
2
3
4
5
6
7
8
func (stub *ChaincodeStub) GetFunctionAndParameters() (function string,
params []string)

# {"Args":["set","tom","100"]}
fn, args := stub.GetFunctionAndParameters()
fmt.Println(fn, args)
# 输出结果
set ["tom", "100"]

GetStringArgs 提取链码交易的指定参数

1
2
3
4
5
6
func (stub *ChaincodeStub) GetStringArgs() []string

# {"Args":["set","tom","100"]}
args = stub.GetStringArgs()
fmt.Println(args)
# 输出结果 ["set","tom","100"]
  • 账本状态交互 API

PutState 在账本中添加或更新一对键值。

1
2
3
4
5
func (stub *ChaincodeStub) PutState(key string, value []byte) error
err := stub.PutState("str",[]byte("hello"))
if err != nil {
fmt.Println("str PutState error: "+err.Error()) }else{ fmt.Println("str PutState success!")
}

GetState 负责查询账本,返回指定键的对应值

1
2
3
4
5
6
7
func (stub *ChaincodeStub) GetState(key string) ([]byte, error)
strValue , err := stub.GetState("str")
if err != nil {
fmt.Println("str GetState error: "+err.Error()) }else { fmt.Printf("str value: %s \n",string(strValue))
}
# 输出结果
str value: hello

DelState 删除一对键值

1
2
func (stub *ChaincodeStub) DelState(key string) error
err = stub.DelState("str")

GetStateByRange 查询指定范围内的键值,startKey为起始key,endKey 为终止 key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func (stub *ChaincodeStub) GetStateByRange(startKey, endKey string)
(StateQueryIteratorInterface, error)
err := stub.PutState("str",[]byte("hello"))
err = stub.PutState("str1",[]byte("hello1"))
err = stub.PutState("str2",[]byte("hello2"))
resultIterator , err := stub.GetStateByRange("str" , "str2")
defer resultIterator.Close()
fmt.Println("-----start resultIterator-----")
for resultIterator.HasNext() {
item, _ := resultIterator.Next() fmt.Println(string(item.Value))
}
fmt.Println("-----end resultIterator-----")
# 运行结果
-----start resultIterator-----
hello
hello1
-----end resultIterator-----

GetHistoryForKey 返回某个键的历史记录

1
2
3
4
5
6
7
8
9
func (stub *ChaincodeStub) GetHistoryForKey(key string)
(HistoryQueryIteratorInterface, error)
historyIterator,err := stub.GetHistoryForKey("str")
defer historyIterator.Close()
fmt.Println("-----start historyIterator-----")
for resultIterator.HasNext() {
item, _ := historyIterator.Next() fmt.Println(string(item.TxId)) fmt.Println(string(item.Value))
}
fmt.Println("-----end historyIterator-----")

链码的基本结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main
// 引入相应的依赖包
import (
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/protos/peer"
)
type SimpleChaincode struct { }
// 链码实例化(instantiate)或 升级(upgrade)时调用 Init 方法
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) peer.Response{
return shim.Success(nil)
}
// 链码收到调用(invoke) 或 查询 (query)时调用 Invoke 方法

func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
return shim.Success(nil)
}
// 主函数 ,调用 shim.Start 方法
func main() {
err := shim.Start(new(SimpleChaincode))
if( err!= nil){
fmt.Printf("Error starting Simple Chaincode is %s \n",err)
}
}

参考

Fabric 1.0的多机部署
深入理解Fabric环境搭建的详细过程
在HyperLedger Fabric中启用CouchDB作为State Database
IBM 开源技术微讲堂 —— 区块链和 HyperLedger 系列

vim常用命令整理

发表于 2018-05-30 | 分类于 linux | | 阅读次数:

基础指令

  • i → Insert 模式,按 ESC 回到 Normal 模式.
  • x → 删除光标所在字符。
  • :wq → 存盘 + 退出
  • dd → 删除当前行,并把行存到剪贴板里
    • 2dd → 删除2行
  • p → 粘贴剪贴板内容
  • hjkl → 对应方向(←↓↑→)
  • :help → 显示相关命令的帮助(注:使用:q退出)

插入模式

  • a → 在光标后插入
  • o → 在当前行后插入一个新行
  • O → 在当前行前插入一个新行
  • cw → 替换光标所在位置后的第一个单词

光标移动

  • 0 → 数字零,到行头
  • $ → 到本行行尾
  • w 光标移动到下个单词首字母
    • W 光标移动到下个字符串首字母
  • e 光标移动到下个单词尾字母
    • 光标移动到下个字符串尾字母
  • b 光标移动到上个单词首字母
    • B 光标移动到上个字符串首
  • /pattern → 搜索 pattern 的字符串(按n键到下一个)
  • NG → 到第N行 (Ngg或:N都可以)
  • gg → 到第一行
  • G → 到最后一行
  • % → 匹配括号移动,包括 (, {, [.
  • * 和 # → 匹配光标当前所在的单词,移动光标到下一个(或上一个)匹配单词
  • fa → 到下一个a字符处
    • 3fa → 到第三处a字符处
    • F → 检索方向相反
  • ta → a前的第一个字符
    • dta → 删除所有的内容直到遇到a
    • T → 检索方向相反

拷贝/粘贴

  • p → 粘贴(P表示在当前位置之后)
    • 3p → 粘贴文本3次
  • P → 粘贴(P表示在当前位置之前)
  • yy → 拷贝当前行当行于 ddP

Undo/Redo

  • u → undo
  • ctrl+r → redo

打开&保存&退出&切换文件

  • :e → 打开一个文件
  • :w → 存盘
  • :saveas → 另存为
  • :x, ZZ 或 :wq → 保存并退出 (:x 表示仅在需要时保存,ZZ不需要输入冒号并回车)
  • :q! → 退出不保存 :qa! 强行退出所有的正在编辑的文件,就算别的文件有更改。
  • :bn 和 :bp → 你可以同时打开很多文件,使用这两个命令来切换下一个或上一个文件。

块操作:

块操作,典型的操作: 0 Ctrl+v I// [ESC]

1
2
3
4
5
6
7
8
9
10
//块行头插入数据
^ → 到行头
<C-v> → 开始块操作
<C-d> → 向下移动 (你也可以使用hjkl来移动光标,或是使用%,或是别的)
I-- [ESC] → I是插入,插入“//”,按ESC键来为每一行生效。

//块行尾插入数据
<C-v> → 选中相关的行
$ → 到行最后
A → 输入字符串,按 ESC。

可视化选择 v,V, Ctrl+v Ctrl+q(windows)

  • J → 把所有的行连接起来(变成一行)
  • < 或 > → 左右缩进
  • = → 自动给缩进

分屏

  • :split → 创建分屏
  • :vsplit → 创建垂直分屏
  • Ctrl+w <dir> → dir就是方向,可以是 hjkl 或是 ←↓↑→ 中的一个,其用来切换分屏。

其它

  • 100i<word> [ESC]
  • . → 重复上一次修改指令
    • 3. → 重复3次修改指令
  • 通过进行操作
    • 0y$ → 从行头开始拷贝到本行最后一个字符
    • d (删除 ) gU (变大写) gu (变小写)
  • 自动补齐:Ctrl+n 和 Ctrl+p (Insert模式下)

极客时间分享海报

发表于 2018-05-28 | 分类于 其它 , 极客时间 | | 阅读次数:

分享一下我的极客时间邀请码,课程都很不错,推荐学习!

左耳朵耗子

趣谈网络协议

深入浅出区块链

从0开始学游戏开发

Java核心技术

Ubuntu非root权限使用docker

发表于 2018-05-03 | 分类于 vue | | 阅读次数:

Ubuntu系统安装完docker后,若直接使用ubuntu账户docker,会出现如下错误提示:

docker命令使用Unix socket与Docker引擎通讯,而默认情况下只有root用户和docker组可以访问Docker引擎的Unix socket。通常我们不会直接操作root账户,而每次docker指令前加sudo又显得非常繁琐,因此最好的解决方法是将用户添加到docker用户组中。

  1. 添加docker group

    1
    sudo groupadd docker
  2. 将用户加入docker group

    1
    2
    3
    sudo gpasswd -a ${USER} docker
    或
    sudo usermod -aG docker ${USER}
  3. 重启docker服务

    1
    sudo service docker restart
  4. 重新打开会话或重新登入群组(否则groups命令获取到缓存的组信息)

    1
    newgrp - docker

Vue中img的src属性绑定

发表于 2018-04-26 | 分类于 vue | | 阅读次数:

img的src属性绑定url变量的正确方式是使用v-bind:

1
2
3
4
5
6
//错误写法
<img src="{{ imgUrl }}"/>

//正确写法
<img v-bind:src="imgUrl"/>
<img :src="imgUrl"/> //省略v-bind

有时候即便使用了正确的语法,依然无法正常加载图片,主要是因为imgUrl使用了本地图片的路径。
例如我们的vue文件在project/src/components/common/header.vue,图片位于project/images/logo.png。
此时如果使用下面的写法:

1
<img :src="../../images/logo.png"/>

运行发现图片加载失败了:

原因主要是因为网页把根域名作为相对路径的根目录了,而我们的路径是相对于项目文件的根目录,因此当然没办法找到图片。

这时候需要把图片放在vue工程的static目录下,在下面我们新建一个image文件夹。

1
<img :src="/static/image/logo.png"/>

这时,无论是npm run dev还是npm run build之后运行图片都能正常显示。像图片、js、css等静态文件,我们都应该放到static文件夹下,这个文件夹下的文件会按照原来的结构放在网站的根目录下。这时我们再去使用static绝对路径,就可以访问这些静态文件了。

vue列表渲染问题

发表于 2018-04-25 | 分类于 vue | | 阅读次数:

vue列表渲染问题

今天在使用v-for进行页面渲染时,发现异步请求数据返回时,v-for渲染不成功,页面数据没有重新加载,具体截图如下:

数组更改检测注意事项

参考vue官方文档,发现Vue不能检测以下变动的数组:
1、当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue
2、当你修改数组的长度时,例如:vm.items.length = newLength

1
2
3
4
5
6
7
var vm = new Vue({
data: {
items: ['a', 'b', 'c']
}
})
vm.items[1] = 'x' // 不是响应性的
vm.items.length = 2 // 不是响应性的

为了解决第一个问题,以下两种方式都可以实现和vm.items[indexOfItem] = newValue相同的效果,同时也将触发状态更新:

1
2
3
4
5
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)

// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

你也可以使用vm.$set实例方法,该方法是全局方法Vue.set的一个别名:

1
vm.$set(vm.items, indexOfItem, newValue)

为了解决第二类问题,你可以使用splice:

1
vm.items.splice(newLength)

对象更改检测注意事项

还是由于JavaScript的限制,Vue不能检测对象属性的添加或删除:

1
2
3
4
5
6
7
8
9
var vm = new Vue({
data: {
a: 1
}
})
// `vm.a` 现在是响应式的

vm.b = 2
// `vm.b` 不是响应式的

对于已经创建的实例,Vue不能动态添加根级别的响应式属性。但是,可以使用 Vue.set(object, key, value)方法向嵌套对象添加响应式属性。例如,对于:

1
2
3
4
5
6
7
var vm = new Vue({
data: {
userProfile: {
name: 'Anika'
}
}
})

你可以添加一个新的 age 属性到嵌套的 userProfile 对象:

1
Vue.set(vm.userProfile, 'age', 27)

你还可以使用 vm.$set 实例方法,它只是全局 Vue.set 的别名:

1
vm.$set(vm.userProfile, 'age', 27)

还有一种更加暴力的解决方式:),调用this.$forceUpdate()强制重新渲染页面,不过这是vue内建方法,会强制更新当前组件,但通常不会用到这个方法;

通过以上两种方式,都可以解决上述的数组渲染问题,页面效果如下图所示:

附——数组更新检测:

变异方法:

Vue 包含一组观察数组的变异方法,所以它们也将会触发视图更新。这些方法如下:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

替换数组:

变异方法 (mutation method),顾名思义,会改变被这些方法调用的原始数组。相比之下,也有非变异 (non-mutating method) 方法,例如:filter(), concat() 和 slice() 。这些不会改变原始数组,但总是返回一个新数组。当使用非变异方法时,可以用新数组替换旧数组:

1
2
3
example1.items = example1.items.filter(function (item) {
return item.message.match(/Foo/)
})

你可能认为这将导致 Vue 丢弃现有 DOM 并重新渲染整个列表。幸运的是,事实并非如此。Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的、启发式的方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。

参考链接:
vue列表渲染

智能合约开发The contract code couldn't be stored

发表于 2018-04-20 | 分类于 区块链 | | 阅读次数:

问题描述

当合约编写完后,执行truffle compile编译没有报错,但truffle migrate进行合约部署时提示:”he contract code couldn’t be stored, please check your gas amount.”;

具体的错误信息如下图所示:

刚开始以为是Ganache的GAS PRICE,GAS LIMIT设置有问题,调大后发觉问题仍然存在。
注:每笔交易都被要求包括一个gas limit(有的时候被称为startGas)和一个交易愿为单位gas支付的费用。矿工可以有选择的打包这些交易并收取这些费用。在现实中,今天所有的交易最终都是由矿工选择的,但是用户所选择支付的交易费用多少会影响到该交易被打包所需等待的时长。如果该交易由于计算,包括原始消息和一些触发的其他消息,需要使用的gas数量小于或等于所设置的gas limit,那么这个交易会被处理。如果gas总消耗超过gas limit,那么所有的操作都会被复原,但交易是成立的并且交易费任会被矿工收取。区块链会显示这笔交易完成尝试,但因为没有提供足够的gas导致所有的合约命令都被复原。

一个交易的交易费由两个因素组成:

  • gasUsed:该交易消耗的总gas数量

  • gasPrice:该交易中单位gas的价格(用以太币计算)

再尝试调整truffle.js把gas设置为一个较高的值:

1
2
3
4
5
6
7
8
9
10
module.exports = {
networks: {
development: {
host: "localhost",
port: 7545,
gas: 800000000, //愿意为本次部署最多支付多少gas
network_id: "*"
}
}
};

问题依旧存在!!!

后面进行代码排查,发现只需要把部署合约的is ERC721继承关系去掉,truffle migrate的时候就可以成功。最后发现,原来是继承关系中,必需实现父contract中的接口方法,否则部署的时候就会报错!Mark!为什么编译的时候不报错呢:)

在逐一实现ERC721的接口方法后,再进行编译部署,此时可成功部署:

ubuntu系统搭建以太坊开发环境

发表于 2018-04-10 | 分类于 区块链 | | 阅读次数:

node、npm安装(apt-get安装的版本不对)

通过 https://nodejs.org 下载最新/稳定的nodejs、npm版本(需要nodejs 5.0+);

Truffle、ganache-cli安装

Truffle 是目前最流行的以太坊开发框架,采用JavaScript编写,支持智能合约的编译、部署和测试。通过以下指令安装Truffle:

1
$ npm install -g truffle

ganache是truffle推出的一个可视化私有链客户端,方便开发,与testrpc类似。

可以下载命令行的ganache-cli

1
npm install -g ganache-cli

也可以下载图形化的ganache

1
2
3
wget https://github.com/trufflesuite/ganache/releases/download/v1.1.0/ganache-1.1.0-x86_64.AppImage
chmod +x ganache-1.1.0-x86_64.AppImage //修改权限
sudo ./ganache-1.1.0-x86_64.AppImage //启动ganache

启动后操作界面如下图如示,其中ganache默认生成了10个Account账户,我们可以清晰的看到钱包地址及账户余额(右侧钥匙为私钥):

可以通过修改hostname和port改变私有私访问地址(端口默认为7545):

基本使用

1、随意建立代码文件夹
mkdir -p ~/blockchain/test_truffle

2、进入刚刚建立的文件夹并执行初始化命令
cd ~/blockchain/test_truffle && truffle init

3、在contracts目录中新建一个HelloWorld.sol文件,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pragma solidity ^0.4.17;

contract HelloWorld {

//say hello world
function say() public pure returns (string) {
return "Hello world";
}

//print name
function print(string name) public pure returns (string) {
return name;
}
}

4、在migrations目录下新建部署脚本,文件名自定义,比如2_deploy_contracts.js,将我们刚才创建的HelloWorld.sol文件添加到发布配置文件中,内容如下:

1
2
3
4
5
var HelloWorld = artifacts.require("./HelloWorld.sol");

module.exports = function(deployer) {
deployer.deploy(HelloWorld);
};

5、编译合约
truffle compile

6、编辑truffle.js
编辑truffle.js(注ganache-cli默认使用8545,ganache图型化客户端默认使用7545),内容如下:

1
2
3
4
5
6
7
8
9
module.exports = {
networks: {
development: {
host: "localhost",
port: 8545,
network_id: "*"
}
}
};

7、部署智能合约
truffle migrate

8、功能验证
输入truffle console命令打开truffle控制台,测试刚才我们部署的HelloWorld合约

1
2
3
4
5
6
7
8
9
10
11
truffle(development)> var contract;
undefined

truffle(development)> HelloWorld.deployed().then(function(instance){contract= instance;});
undefined

truffle(development)> contract.say()
'Hello world'

truffle(development)> contract.print("hi")
'hi'

安装geth客户端

Geth又名Go Ethereum,由Go语言开发,完全开源的项目。它是一个命令行工具,提供很多命令和选项,可以运行以太坊节点、创建和管理账户、发送交易、挖矿、部署智能合约等。

1
2
3
4
sudo apt-get install software-properties-common
sudo add-apt-repository -y ppa:ethereum/ethereum
sudo apt-get update
sudo apt-get install ethereum

搭建以太坊私有链

1、准备创世区块配置文件

以太坊支持自定义创世区块,要运行私有链,我们就需要定义自己的创世区块,创世区块信息写在一个json格式的配置文件中。首先将下面的内容保存到一个json文件中,例如genesis.json。创建blockchain文件夹,并将genesis.json放入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{  
"nonce": "0xdeedbeafdeadbeef",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x00",
"gasLimit": "0x80000000",
"difficulty": "0x400",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"alloc": {},
"config": {
"chainId": 54001,
"homesteadBlock": 0,
"eip155Block": 0,
"eip158Block": 0
}
}

各参数的作用如下:

1
2
3
4
5
6
7
8
9
mixhash:与nonce配合用于挖矿,由上一个区块的一部分生成的hash。
nonce:nonce就是一个64位随机数,用于挖矿
difficulty:设置当前区块的难度,如果难度过大,cpu挖矿就很难,这里设置较小难度
alloc:用来预置账号以及账号的以太币数量
coinbase:矿工的账号,随便填
timestamp:设置创世块的时间戳
parentHash:上一个区块的hash值,因为是创世块,所以这个值是0
extraData:附加信息,随便填,可以填你的个性信息
gasLimit:该值设置对GAS的消耗总量限制,用来限制区块能包含的交易信息总和

2、写入创世区块

此时genesis.json保存在~/blockchain中,准备好创世区块配置文件后,需要初始化区块链,将上面的创世区块信息写入到区块链中。创建node目录,然后执行下列指令:

1
geth --datadir './node' init genesis.json

命令的主体是geth init,表示初始化区块链,命令可以带有选项和参数,其中–datadir选项后面跟一个目录名,这里为node,表示指定数据存放目录为node,genesis.json是init命令的参数。运行上面的命令,会读取genesis.json文件,根据其中的内容,将创世区块写入到区块链中。此时目录结构如下:

blockchain
├── node
│ ├── geth
│ │ ├── chaindata
│ │ └── …
│ └── keystore
└── genesis.json

其中geth/chaindata中存放的是区块数据,keystore中存放的是账户数据。

3、启动私有链节点

初始化完成后,就有了一条自己的私有链,之后就可以启动自己的私有链节点并做一些操作,在终端中输入以下命令即可启动节点。

1
nohup geth --rpc --rpcaddr="0.0.0.0" --rpccorsdomain="*" --rpcapi "db,eth,net,web3" --networkid '54001' --datadir '~/blockchain/node' &

–datadir选项指定使用node作为数据目录,–networkid选项后面跟一个数字,这里是54001,表示指定这个私有链的网络id为54001。网络id在连接到其他节点的时候会用到,以太坊公网的网络id是1,为了不与公有链网络冲突,运行私有链节点的时候要指定自己的网络id。

执行以下命令,即可进入区块链的console

1
geth attach ~/blockchain/node/geth.ipc

运行上面的命令后,就启动了区块链节点并进入了Javascript Console。这是一个交互式的Javascript执行环境,在这里面可以执行Javascript代码,其中>是命令提示符。在这个环境里也内置了一些用来操作以太坊的Javascript对象,可以直接使用这些对象。

  • eth:包含一些跟操作区块链相关的方法
  • net:包含以下查看p2p网络状态的方法
  • admin:包含一些与管理节点相关的方法
  • miner:包含启动&停止挖矿的一些方法
  • personal:主要包含一些管理账户的方法
  • txpool:包含一些查看交易内存池的方法
  • web3:包含了以上对象,还包含一些单位换算的方法

4、探索Javascript Console

进入以太坊Javascript Console后,就可以使用里面的内置对象做一些操作,这些内置对象提供的功能很丰富,比如查看区块和交易、创建账户、挖矿、发送交易、部署智能合约等。
(注:命令都可以按Tab键自动补全。)

1、创建账户
前面只是搭建了私有链,并没有自己的账户,可以在js console中输入eth.accounts来验证:

1
2
> eth.accounts
[]

通过使用personal对象来创建账户:

personal.newAccount()
1
2
3
Passphrase: 
Repeat passphrase:
"0xbe38bf86bb9837cd60c86f70cb344c59c91f9b7f"

接下来就可以查看到刚才创建的账户了:

1
2
> eth.accounts
["0xbe38bf86bb9837cd60c86f70cb344c59c91f9b7f"]

2、查看余额

1
2
3
eth对象提供了查看账户余额的方法:
> eth.getBalance(eth.accounts[0])
0

3、启动&停止挖矿

通过miner.start()来启动挖矿:

1
> miner.start(1)

其中start的参数表示挖矿使用的线程数。第一次启动挖矿会先生成挖矿所需的DAG文件,这个过程有点慢,等进度达到100%后,就会开始挖矿,此时屏幕会被挖矿信息刷屏。

如果想停止挖矿,在js console中输入miner.stop():

1
> miner.stop()

挖到一个区块会奖励5个以太币,挖矿所得的奖励会进入矿工的账户,这个账户叫做coinbase,默认情况下coinbase是本地账户中的第一个账户:

1
2
> eth.coinbase
"0xbe38bf86bb9837cd60c86f70cb344c59c91f9b7f"

要想使挖矿奖励进入其他账户,通过miner.setEtherbase()将其他账户设置成coinbase即可。
getBalance()返回值的单位是wei,wei是以太币的最小单位,1个以太币=10的18次方个wei。要查看有多少个以太币,可以用web3.fromWei()将返回值换算成以太币:

1
> web3.fromWei(eth.getBalance(eth.accounts[0]),'ether')

4、发送交易
通过eth.sendTransaction可以通过发送一笔交易,从账户0转移5个以太币到账户1

1
2
3
4
5
6
> eth.sendTransaction({from:eth.accounts[0],to:eth.accounts[1],value:web3.toWei(3,'ether')})
Error: authentication needed: password or unlock
at web3.js:3119:20
at web3.js:6023:15
at web3.js:4995:36
at <anonymous>:1:1

这里报错是因为账户每隔一段时间就会被锁住,要发送交易需要先解锁账户,由于我们要从账户0发送交易,所以要解锁账户0:

1
2
3
4
> personal.unlockAccount(eth.accounts[0])
Unlock account 0xbe38bf86bb9837cd60c86f70cb344c59c91f9b7f
Passphrase:
true

输入创建账户时设置的密码,就可以成功解锁账户。然后再发送交易:

1
2
> eth.sendTransaction({from:eth.accounts[0],to:eth.accounts[1],value:web3.toWei(3, 'ether')})
"0xd2947d185056a3952d1f1641cae0eb4f89347f1f88770f5e2a75f978414351d0"

此时交易已经提交到区块链,返回了交易的hash,但还未被处理,这可以通过查看txpool来验证:

1
2
3
4
5
> txpool.status
{
pending: 1,
queued: 0
}

其中有一条pending的交易,pending表示已提交但还未被处理的交易。要使交易被处理,必须要挖矿。这里我们启动挖矿,然后等待挖到一个区块之后就停止挖矿。当txpool中pending的交易数量应该为0了,说明交易已经被处理了:

1
2
3
4
5
6
7
8
> txpool.status
{
pending: 0,
queued: 0
}

> eth.getBalance(eth.accounts[1])
3000000000000000000

5、查看交易和区块
eth对象封装了查看交易和区块信息的方法。

查看当前区块总数:

1
2
> eth.blockNumber
33

通过交易hash查看交易:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
> eth.getTransaction("0xd2947d185056a3952d1f1641cae0eb4f89347f1f88770f5e2a75f978414351d0")
{
blockHash: "0x0f00b3a4f8599ebe5867bf793a4ba463dc47211815e20f8d7a1593ac18ff872c",
blockNumber: 634359,
from: "0xf16bae16adb39a886e04c728863812fa23141b8b",
gas: 90000,
gasPrice: 1000000000,
hash: "0xd2947d185056a3952d1f1641cae0eb4f89347f1f88770f5e2a75f978414351d0",
input: "0x",
nonce: 22,
r: "0x64204d18a947be6bb7033248166f06c6b97129077d9b895ed67f3e9610718a07",
s: "0x21e2a1c101c72d36d5b450dc44fb41d56c095306ff48ac666b2e7a14a035223b",
to: "0xbe38bf86bb9837cd60c86f70cb344c59c91f9b7f",
transactionIndex: 0,
v: "0x1a606",
value: 3000000000000000000
}

通过区块号查看区块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
> eth.getBlock(634359)
{
difficulty: 1278282,
extraData: "0xd783010703846765746887676f312e392e32856c696e7578",
gasLimit: 4712388,
gasUsed: 21000,
hash: "0x0f00b3a4f8599ebe5867bf793a4ba463dc47211815e20f8d7a1593ac18ff872c",
logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
miner: "0xf16bae16adb39a886e04c728863812fa23141b8b",
mixHash: "0x9ad2c2c50c3c223078d17853860a1e60dbf1ce4d773ccf22c92db92dee91d866",
nonce: "0x6f1fc6c4e0f51c12",
number: 634359,
parentHash: "0xafe36f1d414fc95100fff815149246c3a449bbfafda897732b235e548f4b513c",
receiptsRoot: "0x299e65dd0d084faf2ae29b3abcc14ab7b99029e3629bbcaeef3a69c14a9e39f5",
sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
size: 654,
stateRoot: "0x832f5fde59c6de9304a50831d8b9d1caa0107b9ec495484d52af6106ee0de856",
timestamp: 1523329711,
totalDifficulty: 1042016387960,
transactions: ["0xd2947d185056a3952d1f1641cae0eb4f89347f1f88770f5e2a75f978414351d0"],
transactionsRoot: "0x668edf6f5eadce730ac49224553a9f0cf761fe518d7a66d89cba0398e6d23b08",
uncles: []
}

6、连接到其他节点
可以通过admin.addPeer()方法连接到其他节点,两个节点要想联通,必须保证网络是相通的,并且要指定相同的networkid。
假设有两个节点:节点一和节点二,networkid都是54001,通过下面的步骤就可以从节点二连接到节点一。
首先要知道节点一的enode信息,在节点一的js console中执行下面的命令查看enode信息:

1
2
> admin.nodeInfo.enode
"enode://07f36d1c6ee18aa231dfac161913f0a3d533108505883418735b838b9453a7e5321f69bbc08a526695313554e9e325ca3eec12242b1b079c246a07d20a4e33e5@[::]:30303"

然后在节点二的js console中执行admin.addPeer(),就可以连接到节点一:

1
> admin.addPeer("enode://07f36d1c6ee18aa231dfac161913f0a3d533108505883418735b838b9453a7e5321f69bbc08a526695313554e9e325ca3eec12242b1b079c246a07d20a4e33e5@ip:30303")

addPeer()的参数就是节点一的enode信息,注意要把enode中的[::]替换成节点二的IP地址。连接成功后,节点二就会开始同步节点一的区块,同步完成后,任意一个节点开始挖矿,另一个节点会自动同步区块,向任意一个节点发送交易,另一个节点也会收到该笔交易。
通过admin.peers可以查看连接到的其他节点信息,通过net.peerCount可以查看已连接到的节点数量。
除了上面的方法,也可以在启动节点的时候指定–bootnodes选项连接到其他节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
> net.peerCount
1
> admin.peers
[{
caps: ["eth/62", "eth/63"],
id: "07f36d1c6ee18aa231dfac161913f0a3d533108505883418735b838b9453a7e5321f69bbc08a526695313554e9e325ca3eec12242b1b079c246a07d20a4e33e5",
name: "Geth/v1.7.3-stable-4bb3c89d/linux-amd64/go1.9.2",
network: {
localAddress: "10.104.28.16:58870",
remoteAddress: "111.230.144.116:30303"
},
protocols: {
eth: {
difficulty: 1042104536363,
head: "0xb7a8c1e47797a06760959935f3d72076398f2244cf12ef2de63e13ac1bd4cce9",
version: 63
}
}
}]

安装MetaMask

MetaMask是一款在谷歌浏览器Chrome上使用的插件类型的以太坊钱包,该钱包不需要下载,只需要在谷歌浏览器添加对应的扩展程序即可,非常轻量级,使用起来也非常方便。

MetaMask安装

这款插件的官方地址为 https://metamask.io/ ,Chrome网上应用商店的下载地址是: https://chrome.google.com/webstore/detail/nkbihfbeogaeaoehlefnkodbefgpgknn ,如果无法访问,可以直接点击下载

MetaMask使用

根据提示同意条款并输入钱包密码:

保存好这些助记词之后,点击下面的按钮,进入钱包页面,如下图所示:

点击左上角的Private Network->Custom RPC即可添加自己的以太坊私链地址:

点击右上角图标可以切换或导入新Account:

参考资料

CRYPTOZOMBIES
Truffle Framework
ETHEREUM PET SHOP
BUILDING ROBUST SMART CONTRACTS WITH OPENZEPPELIN
以太坊学习笔记:私有链搭建操作指南
zeppelin-solidity
GO EthEREUM

12

Lory Feng

fight!

15 日志
9 分类
28 标签
GitHub E-Mail
© 2021 Lory Feng
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
粤ICP备18017208号