汪晓明对区块链、以太坊的思考

记录创业、生活的所思所感,探讨去中心化思想,推动区块链的发展。

HPB42:Solidity开发神器Remix

1 功能

这里我们使用在线编译器,打开网址

https://ethereum.github.io/browser-solidity

1.1 文件夹管理

最左边是文件夹管理,里面列出了当前工作区里的文件,remix可以支持从本地文件夹读取文件。

1.2 工作区

正中间是工作区,工作区上半部是代码编辑区,在这里可以写solidity合约。 下半部是日志区,在执行智能合约时,会显示transaction相关的信息。在输出日志的时候还可以查看Details和Debug信息。

1.3 功能区

最右边的是功能区,里面有编译、运行、设置和分析以及调试器和支持。

在编译器点击Details可以查看编译细节,里面有NAME,METADATA,BYTECODE,ABI 等一些相关信息。

在设置里面可以选择我们的编译器版本,和一些IDE的使用设置。

2 实战例子

一个简单的实现对数组增删改查的智能合约

2.1 打开工作区,输入代码

2.2 代码分析

• string[] strArr; 定义了一个字符串的数组变量strArr,且该变量没有public因此不可见。

• strArr.push(str); 其中的push是数组类型的两个member之一,另一个是length. 这里的push就是给该数组增加一个元素。//这里实现了对数组的新增功能。

• getStrAt(uint n) 是一个简单的读取字符串的函数,//这里实现对数组的读取功能。

• updateStrAt(uint n, string str) // 实现对数组的更新功能。

• deleteStrAt(uint index) 这个值得一说,因为solidity默认的数组类型中没有删除功能,因此此处自己写了一个删除代码, 核心方法就是保证删除某项后,后面的元素依次向前,同时删除数据,同时保证数组的member length正确。

2.3 编译

代码写完之后我们到Compile区域点击编译Start to compile, 如果编译成功没有错误可以看到我Details信息。

2.4 运行

我们从Compile切换到Run区域,在Environment里面选择编译器,记住这里一定要选择Java Script VM.然后再At Address里面输入我们的eth服务端地址,可以输入测试网络,也可以输入自己搭建的私有链和联盟链。输入完成之后点击Create.这个时候我们可以看到:

右下角会生成我们的ABI方法。

2.5 测试

好了,这个时候我们可以开始测试了。

比如这里输入一个hello eth, 注意一定要加引号。然后点击add, 控制台会打印信息。

点击Details可以看到详细信息

刚刚我们添加了一个字符串,这里再调用一下get方法,打印出字符串。

本文由HPB芯链团队整理

HPB41:Web3j实现智能合约

1 获取凭证

Credentials是我们钱包的凭证,在我们交易和创建智能合约的时候都需要用到。

1.1 创建新凭证

file=WalletUtils.generateFullNewWalletFile(pwd,dir);

返回的file不是全路径,而是该文件的路径名,比如UTC—2017-10-30T12-10-45.516005546Z—5f38056f45091ee992298e53681b0a60c999ff95。

前面的是创建时间,后面的是账号标识。

1.2 使用旧凭证

每个账号在创建的时候都会生成一个keystore,它是Json格式的。如果要使用旧凭证,首先需要找到我们的keystore。我这里的服务器搭建在linux服务端,所以这里把keystore拷贝到我们windows本地。

然后生成credentials

2 智能合约

2.1 编写智能合约

以太坊编写智能合约有三种语言:

  • Serpent

Serpent是一种类似于Python的语言,可用于开发契约并编译为EVM字节码。它旨在最大限度地简化和清理,将低级语言的许多效率优势与易于使用的编程风格相结合,同时为合同编程添加特定领域特定功能。蛇是使用LLL编译的。

  • Solidity

这是一种类似于JavaScript的语言,它允许你开发合同并编译为EVM字节码。它目前是以太坊的旗舰语言,也是最受欢迎的。

我们这里编写一份Hello World

contract HelloWorld

{

​ address creator;

​ string greeting;

​ function HelloWorld(string _greeting) public

​ {

​ creator = msg.sender;

​ greeting = _greeting;

​ }

​ function greet() constant returns (string)

​ {

​ return greeting;

​ }

​ function setGreeting(string _newgreeting)

​ {

​ greeting = _newgreeting;

​ }

​ /****

​ Standard kill() function to recover funds

​ ****/

​ function kill()

​ {

​ if (msg.sender == creator)

​ suicide(creator); // kills this contract and sends remaining funds back to creator

​ }

}

  • LLL

Lisp Like Language(LLL)是一种类似于Assembly的低级语言,它意味着非常简单和简约,基本上就是直接在EVM中进行编码的一个小包装。

还有一种已经被弃用了,这里我们选用官方标准的Solidity语言编写。

2.2 编译智能合约

这里使用Solidity的在线编译器https://ethereum.github.io/browser-solidity/

把我们上面编写好的智能合约代码贴进去。

然后点击Details

这里的name就是智能合约的名称,ABI文件会暴露合同的所有构造参数,我们需要用ABI文件生成java类。WEB3是生成好的js文件,可以配合web3.js直接调用。

2.3 Solidity类转换成java类

如果想要避免使用智能合约的底层实现细节,web3j提供了Solidity 智能合约包装,使您可以通过生成的包装对象直接与所有智能合约的方法进行交互。

这里我们把ABI文件复制一下,然后粘贴到本地作为.json文件,名字最好和类名一样,比如我这里叫HelloWorld.json。这里有一个大坑,就是编译器生成的JSON文件格式不对,没有合约名字,如果我们执行转换会报错。

这里我们打开json文件手动编译一下

web3j支持从Solidity ABI文件在Java中自动生成智能合约函数包装器。

web3j 命令行工具附带一个用于生成智能合同函数包装器的命令行工具:

先去官方下载一个命令行工具

https://github.com/web3j/web3j/releases/tag/v3.2.0

下载.zip文件解压到本地。

找到web3.bat文件执行 $ web3j truffle generate [—javaTypes|—solidityTypes] /path/to/<truffle-smart-contract-output>.json -o /path/to/src/main/java -p com.your.organisation.name

上面这个是官方的格式不太通俗易懂,我给大家翻译一下:

Web3j truffle generate json文件地址 -o 生成的文件目录 -p 生成的文件包名。这里要注意 -o 和-p也要带上。

如果web3.bat文件点不开,就直接使用cmd命令执行,在执行的时候带上web3j的全路径就行了,执行完成之后控制条会显示:

或者也可以通过Java类进行调整,添加web3j的相关依赖,然后使用maven指定运行。

img

这里把生成好的java类拷贝到我们的代码里面。

2.4 发布智能合约

合约生效时间

private static final String BINARY =null;

这个BINARY代表智能合约的生效时间,生成的类里面默认是NULL. 如果不修改这个时间执行智能合约默认是失效的,所以这里我们修改一下为官方指定的二进制代码。

在编译器里面也可以看到我们生成的合约二进制文件。

就是这一行,我们拷贝过来就好了。

部署智能合约

//部署智能合约

HelloWorld contract=deploy(admin, credentials,ManagedTransaction.GAS_PRICE, Contract.GAS_LIMIT,“Hello,World”).send();

部署合约需要消耗Gas.这里说一下我对以太坊Gas的理解:

交易的过程一般需要支付一定量的手续费(当然也可以选择不支付)。矿工会优先打包交易手续费高的交易,如果没有支付交易手续费,你的交易可能要等很久才会被打包。创建一笔交易的时候不需要显式的指明支付多少交易手续费,它是根据你的 UTXO额 – 交易额 – 找零 来计算的。举个栗子,A有一个10btc的UTXO(未花费的交易输出)的支配权,它给B账户转1BTC,那么在创建交易的时候,需要指明交易额1btc,和找零8.995,那么(10-8.995-1 = 0.005)就是这笔交易的手续费,会奖励给打包包含这笔交易的区块的矿工,如果没有设置找零那么多余的9btc都会被当作交易手续费奖励给矿工,虽然你的交易会很快的被打包,但是这可能不是你想要的。

  • 这样可以鼓励更加高效的合约代码,减少不必要的计算,避免系统遭受攻击,毕竟攻击者要为他们消耗的资源付出一定的代价,包括带宽,CPU,和存储。 gasPrice 是由交易的发起者来设置的,但是矿工可以选择先打包那些gas价格高的交易,gas价格低的可能要等很久或者不会被打包。
  • 例如一笔交易:{ from:web3.eth.accounts[0], data:tokenCompiled.token.code, gas: 1000000 }, gas参数设置这个交易最多能使用多少gas。交易里面还可以再加一个参数gasPrice,gasPrice可以自己设置, geth会默认设置一个大多数矿工可以接受的 gasPrice, 0.05e12 wei,可以调eth.gasPrice来查看当前的gasPrice.
  • 矿工在启动geth的时候可以设置两个参数—ask 和 —bid , —ask是设置一个最低的gas价格,低于这个价格的交易会被忽略,默认值是500000000000,—bid 设置gas价格竞价,默认值是 500000000000。

合约地址

//获取合约地址

String contractAddress = contract.getContractAddress();

合约发布成功之后会在ETH客户端生成一个合约地址,我们在以后调用的时候都需要用到。

验证合同是否有效

使用这种方法,您可能需要确定已经加载的合同地址是您期望的智能合约。为此,您可以使用isValid智能合约方法,只有在合约地址已部署字节码与智能合约包装中的字节码匹配时才会返回true.

System.out.println(contract.isValid());

如果返回false,可能是因为智能合约代码生成的有问题,也有可能是合同生效时间没有设置。

加载智能合约

如果我们想使用已经部署过的智能合约,这个时候我们就需要加载智能合约了,

在加载智能合约的时候你需要拥有该智能合约的地址。

HelloWorld contract =HelloWorld.load(contractAddress,admin,credentials, GAS_PRICE, GAS_LIMIT);

调用智能合约

String value=contract.greet().send();

log.info(value);

调用我们编译好的get方法,会在控制台打印Hello World。代表我们这次合约部署调用成功。

销毁智能合约

contract.kill();

销毁后的智能合约无法调用,但是可以查询到。

本文由HPB芯链团队整理

HPB40:基于Ubuntu Docker环境下进行以太坊实践

本文是指导以太坊技术爱好者,通过基于Ubuntu环境下通过docker来进行以太坊的客户端安装和调试的指导教程。

目录

1、实践环境要求

2、Ubuntu版本说明

3、Docker安装

4、Ethereum安装与实践

5、ZSH小工具推荐

1 实践环境要求

1.1 概述

https://docs.docker.com/engine/installation/linux/ubuntulinux/

上面这篇文章主要指导你去安装使用Docker-managed发布包及其安装机制。使用这些包确保你获得最近的docker官方发布版本。如果你需要安装使用Ubuntu-managed包,查阅Ubuntu文档。

1.2 docker对操作系统支持

Ubuntu Xenial 16.04[LTS]

Ubuntu Trusty 14.04[LTS]  

Ubuntu Precise 12.04[LTS]

1.3 前置需求

不管你是Ubuntu的哪个版本,Docker需要64的操作系统。此外你的kernel内核至少要在3.10版本之上。最近的3.10小版本或者最新的维护版本也是可以接受的。kernel3.10版本之前的系统缺少一些特性来运行docker容器。这些旧版本有些已知的bugs会导致数据丢失并且在一定条件下会频繁的故障。检查你当前的kernel版本,打开终端,输入uname –r

注意:如果你之前使用APT安装过docker,为了新版本的docker仓库,确保你更新了APT源。

1.4 更新apt源

Docker的APT仓库包含1.7.1以及更高的版本。通过设置APT使用来自docker仓库的包。

1) 登陆机器,用户必须使用sudo或者root权限。

2) 打开终端

3) 更新包信息,确保APT能使用https方式工作,并且CA证书已安装了

1
2
3
#sudo apt-get update

#sudo apt-get install apt-transport-https ca-certificates

 

出现这个问题可能是有另一个程序正在运行,导致资源被锁不可用。而导致资源被锁的原因可能是上次运行安装或更新没有正常完成,解决办法就是删掉。

1
2
3
#sudo rm /var/cache/apt/archives/lock

#sudo rm /var/lib/dpkg/lock

4)添加一个新的GPG密钥

1
#sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D 

5)找到合适你的Ubuntu操作系统的键,这个键决定APT将搜索哪个包。可能的键有:

Ubuntu version: Precise 12.04, Trusty 14.04,Cenial 16.04

注意:docker没有为所有的架构提供包,Binary artifacts are built nightly,你可以从以下链接下载。

https://master.dockerproject.org. 在一个多架构的系统上安装docker,为键添加一个[arch=]条款。更多细节参考Debian Multiarch维基百科。

6)运行下面的命令,用占位符为你的操作系统替换键。

#echo “” | sudo tee /etc/apt/sources.list.d/docker.list

比如你是16.04将上面命令的

替换成deb https://apt.dockerproject.org/repoubuntu-xenial main 执行那条命令,就在那个文件夹下创建了一个docker.list文件,里面的内容就是

deb https://apt.dockerproject.org/repoubuntu-xenial main

7)更新APT包索引

1
#sudo apt-get update

8)校验APT是从一个正确的仓库拉取安装包。

当运行下面命令的时候,这个键会返回你目前可以安装的docker版本,每个键都包括URL:https://apt.dockerproject.org/repo/%E3%80%82%E4%B8%8B%E9%9D%A2%E6%98%AF%E6%88%AA%E5%8F%96%E7%9A%84%E9%83%A8%E5%88%86%E8%BE%93%E5%87%BA%E5%86%85%E5%AE%B9%E3%80%82

1
#apt-cache policy docker-engine

现在当你运行apt-get upgrade的时候,APT就会从新的仓库拉安装包。

2 Ubuntu版本说明

2.1 前置准备操作

Ubuntu Xenial 16.04[LTS],Ubuntu Trusty 14.04[LTS]这两个版本记得安装linux-iamge-extra-*的kernel包。这个包允许你使用aufs存储驱动。

# sudo apt-get install linux-image-extra-$(uname -r) linux-image-extra-virtual

Ubuntu Precise 12.04[LTS]对于这个版本,你需要3.13以上的kernel版本,你必须升级。下面表格指导你需要哪些包:

你可以执行以下命令:

1
2
3
#sudo apt-get install linux-image-generic-lts-trusty

#sudo reboot

3 Docker安装

3.1 前置操作

1).登陆系统,用你的账号使用sudo全权限

2).运行命令apt-get install openssh-server安装ssh 查看是否安装成功ps -ef|grep ssh

3).更新APT包索引:sudo apt-get update

4).安装docker:sudo apt-get install docker-engine

5).开启docker后台进程:sudo service docker start

6).校验docker是否安装成功:sudo docker run hello-world

这个命令会下载一个测试镜像,并且运行在一个容器中。当容器运行时,他会打印一些信息,并且退出。

3.2 创建一个docker组

docker后台进程是绑定的Unix的socket而不是TCP端口。默认情况下,Unix的socket属于用户root,其它用户要使用要通过sudo命令。由于这个原因,docker daemon通常使用root用户运行。为了避免使用sudo当你使用docker命令的时候,创建一个Unix组名为docker并且添加用户。当docker daemon启动,它会分配Unix socket读写权限给所属的docker组。

注意:docker组不等价于用户root,如果想要知道的更多关于安全影响,查看docker daemon attack surface。

1
2
3
#sudo groupadd docker

#sudo usermod -aG docker $USER

退出再重进,确保该用户有正确的权限。校验生效,通过运行docker命令不带sudo:docker run hello-world,如果失败会有以下类似的信息:Cannot connect to the Docker daemon. Is ‘docker daemon’ running on this host?确保DOCKER_HOST环境变量没有设置。如果有取消它。

3.3 调整内存和交换区计算

当用户运行docker时,他们可能在使用一个镜像时看见下面的信息:

WARNING: Your kernel does not support cgroup swap limit. WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.

为了阻止这些信息,在你的系统中启用内存和交换区计算。这个操作会导致即便docker没有使用也有内存开销以及性能下降。内存开销大概是总内存的1%。性能降低了大约10%。

修改/etc/default/grub文件。vi或者vim命令都行,设置GRUB_CMDLINE_LINUX的值,如下:

GRUB_CMDLINE_LINUX=“cgroup_enable=memory swapaccount=1”。

保存文件并关闭。

1
#sudo update-grub

更新启动项,reboot重启你的系统。

3.4 启动UFW转发

当你运行docker时,在同一台主机上使用UFW(Uncomplicated Firewall) ,你需要额外的配置。docker使用桥接方式来管理容器的网络。默认情况下,UFW废弃所有的转发流量。因此,docker运行时UFW可以使用,你必须设置合适UFW的转发规则。

UFW默认配置规则拒绝了所有传入流量。如果你想要从另一个主机到达你的容器需要允许连接docker的端口。docker的默认端口是2376如果TLS启用,如果没有启动则是2375,会话是不加密的。默认情况,docker运行在没有TLS启动的情况下。

  • 为了配置UFW并且允许进入的连接docker端口:
  • 检查UFW是否安装并启用:
1
#sudo ufw status
  • 打开/etc/default/ufw文件并编辑:
1
#sudo nano /etc/default/ufw
  • 设置DEFAULT_FORWARD_POLICY:DEFAULT_FORWARD_POLICY=“ACCEPT”
  • 保存退出并重启使用新的设置:
1
#sudo ufw reload
  • 允许所有的连接到docker端口:
1
#sudo ufw allow 2375/tcp

3.5 为docker配置DNS服务器

系统运行桌面的Ubuntu或者Ubuntu衍生产品通常使用127.0.0.1作为默认的nameserver文件/etc/resolv.conf文件中。NetworkManager也通常设置dnsmasq nameserver 127.0.0.1在/etc/resolv.conf。

当在桌面机器运行容器,使用这些配置时,docker的使用者会看见这些警告:

WARNING: Local (127.0.0.1) DNS resolver found in resolv.conf and containers can’t use it. Using default external servers : [8.8.8.8 8.8.4.4]

这个警告发生是因为docker容器不能使用本地DNS命名服务器。此外docker默认使用一个额外的nameserver。

为了避免这个警告,你可以在使用docker容器的时候指定一个DNS服务器。或者你可以禁用dnsmasq在NetworkManager中。但是,禁用会导致DNS协议在某些网络中变慢。

下面的说明描述了如何在Ubuntu14.0或以下版本配置docker守护进程。Ubuntu15.04及之上的使用systemd用于启动项和服务管理。指导通过使用systemd来配置和控制一个守护进程。

  • 设置指定的DNS服务:

打开/etc/default/docker文件并编辑:sudo nano /etc/default/docker,添加配置项:DOCKER_OPTS=“—dns 8.8.8.8”。将8.8.8.8用一个本地的DNS服务例如192.168.1.1替换。你也可以配置多个DNS服务器。

用空格隔开它们,如:—dns 8.8.8.8 —dns 192.168.1.1。

警告:当你在笔记本连接了不同网络的情况时做这些操作,确保选择一个公用的DNS服务器。保存文件并退出,重启docker守护进程:sudo service docker restart。或者另一个选择,禁用dnsmasq在网络管理器中,这可能导致你的网速变慢:

  • 打开/etc/NetworkManager/NetworkManager.conf文件,
  • 编辑它:sudo nano /etc/NetworkManager/NetworkManager.conf。
  • 找到行dns=dnsmasq,注释掉。
  • 保存关闭文件,重启网络管理器和docker.
1
2
3
#sudo restart network-manager  

#sudo restart docker

3.6 配置docker引导启动

Ubuntu15.04之后使用systemd作为引导启动和服务管理,14.10及以下版本是upstart。15.04以上,需要配置docker守护进程boot启动,

  • 运行命令:
1
#sudo systemctl enable docker

14.10及以下版本安装方法会自动配置upstart来启动docke daemon在boot。

3.7 升级卸载docker

  • 升级:
1
#sudo apt-get upgrade docker-engine
  • 卸载:
1
#sudo apt-get purge docker-engine
  • 卸载及依赖:
1
#sudo apt-get autoremove --purge docker-engine
  • 上述命令不会卸载images,containers,volumes或者用户自己创建的配置文件。

你如果想删除这些东西,执行下面的命令:

1
#rm -rf /var/lib/docker
  • 安装最简单的方法是:
1
2
3
#sudo apt-get update  

#sudo apt-get install 

3.8 执行docker-compose安装

docker-compose 是用于定义和运行复杂docker应用的工具,以yaml定义语言在一个docker-compose.yaml文件中定义一个包括多容器的应用,用一条命令即可启动应用中包括的所有docker container,容器启动所有依赖的动作都会被工具自动完成。

1
2
3
#curl -L https://github.com/docker/compose/releases/download/1.16.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose

#chmod +x /usr/local/bin/docker-

4 Ethereum安装与实践

4.1 通过docker安装Ethereum

  • 运行如下命令
1
#docker pull docker.io/ethereum/client-go

4.2 直接安装Ethereum

  • 运行如下命令
1
2
3
#apt-get update

#apt-get install software-properties-common add-apt-repository -y ppa:ethereum/ethereum
  • 安装一个稳定版本的以太坊
1
2
3
4
5
#apt-get update

#apt-get install ethereum

#apt-get update
  • 生成引导节点ID
1
#bootnode --genkey=boot.key
  • 运行引导节点
1
#bootnode --nodekey=boot.key

4.3 新建genesis.json文件

  • 运行以下命令
1
vi genesis.json
  • 输入

4.4 初始化创始区块

  • 运行如下命令
1
#docker run -itd --privileged=true -v /path/docker/dev1:/root/ethdev --name gethDev1 ethereum/client-go --datadir /root/ethdev --networkid 8765639736937780 init /root/ethdev/genesis.json

4.5 创建Ethereum节点的容器

  • 运行如下命令
1
2
3
#docker rm -f gethDev1

#docker run -itd -m 512M --privileged=true --memory-swap -1 --net=host -p 8545:8545 -p 40303:40303 -v /path/docker/dev1:/root/ethdev --name gethDev1 ethereum/client-go --ipcdisable --port 40303 --bootnodes "enode://2039a49989e45bf119ecd21403607ea9f5888b13a6bb7a03ed81687deabb251095e4193a77eca067076f77ed40e4c6fd51539038c440337beffbbb36953d1d75@192.168.3.43:30301" --debug --nodiscover --rpcapi "db,eth,net,web3,personal,admin,miner,txpool" --datadir /root/ethdev --networkid 8765639736937780 --wsapi "db,eth,net,web3,personal,admin,miner,txpool" --ws --wsaddr "0.0.0.0" --rpc --rpcaddr "0.0.0.0" --cache=512 --verbosity 6 --mine --minerthreads=1 --etherbase=0x5f38056f45091ee992298e53681b0a60c999ff95 console

4.6 查看Ethereum节点docker日志

  • 运行如下命令
1
#docker logs -f gethDev1

4.7启动Ethereum节点

  • 运行如下命令
1
#docker startgethDev1

4.8 Attach Ethereum节点

  • 运行如下命令
1
#docker attach gethDev1

4.9 Detach Ethereum节点

先后按下键盘ctrl+p+q退出节点,注意:有先后顺序

4.10 停止Ethereum节点

  • 运行命令如下
1
#docker stopgethDev1

4.11 Ethereum日志释疑

  • 首先,告警提示geth抱怨没有定义etherbase,etherbase是成功挖掘区块,执行智能合约并在区块链内返回结果之后用来接收以太奖励的“默认以太坊地址”。这个帐户,在开发合同时也很方便。

  • 接下来,我们看到blockchain数据被写入/root/.ethereum/chaindata,因为我们已经从我们的主机挂载了这个目录,我们应该可以在本地磁盘上看到出现的数据:

  • docker exec –i ethereum geth account new ls –l /opt/docker/ethereum/keystore/. docker exec ethereum apt-get install –y ntpdate docker exec ethereum ntpdate –s ntp.ubuntu.com

在当前配置中,我们有一个可以挂载到我们的容器中的以太坊数据目录。这不是因为区块链数据只能在任何情况下由一个进程访问,而是访问可由Ethereum节点用于进程间通信的IPC文件描述符。因此,我们可以在这里继续,而不需要访问网络。

5 zsh小工具推荐

zsh是一款小工具,对命令补全功能非常强大,可以补齐路径,补齐命令,补齐参数等。

5.1 修改root用户SHELL

先进入root用户,命令:

1
su root

查看默认SHELL命令:

1
echo $SHELL

然后查看是否安装了zsh,命令:

1
cat /etc/shells

默认没有安装,那么先安装zsh,命令:

1
apt-get install zsh

确认zsh是否安装成功,命令:

1
zsh --version

接下来替换bash为zsh,命令:

1
chsh -s /bin/zsh

然后reboot重启,之后查看默认SHELL,发现修改为/bin/zsh

查看是否安装git,命令:

1
git –version

如果没有安装则安装,命令:

1
apt-get install git

最后下载oh-my-zsh,命令:

1
sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"

出现如上则安装成功,重新进入终端即可。

5.2 修改普通用户SHELL

查看默认SHELL,命令:

1
echo $SHELL

如果是/bin/zsh则直接:

1
sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"

是/bin/bash则,切换:

1
chsh -s /bin/zsh

然后用root权限reboot,然后:

1
sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"

安装成功后可以(vi ~/.zshrc)来修改配置文件以改变样式(注意root和非root都要改,可以选择不一样的样式)。

root的样式配置文件在根目录root下,为隐藏文件;非root在/home/root下。

具体样式选择https://github.com/robbyrussell/oh-my-zsh/wiki/themes

本文由HPB(芯链)团队整理。

HPB39:RLP机制分析

目录

1 RLP 定义

2 RLP 编码规则

3 RLP 编码实例

4 RLP 分析

1 RLP 定义

RLP,即 Recursive Length Prefix, 递归长度前缀编码,是以太坊数据序列化的主要方法, 具有较好的数据处理效率,尤其是将长度和类型统一作为前缀,实际上 RLP 是基于 ASCII 编码的一种结构化扩充,既能表示长度还能表示类型,是一种非常紧凑的结构化编码方案。 该编码方案用于编码任意的嵌套结构的二进制数据,区块、交易等数据结构在持久化时会先 经过 RLP 编码后再存储到数据库中。RLP 的唯一目标就是解决结构体的编码问题;对原子 数据类型(比如,字符串,整数型,浮点型)的编码则交给更高层的协议;以太坊中要求数 字必须是一个大端字节序的、没有零占位的存储的格式(也就是说,一个整数 0 和一个空数 组是等同的)。如果要编码一个字典,推荐使用两种规范的编码格式——一是通过 key 的字 典序来组织字典[[k1,v1],[k2,v2]……],另一种是以太坊中使用的高层的 Patricia Tree。

RLP 编码的定义只处理两类数据:

一类是字符串(例如字节数组),即一串二进制数据(strings);

一类是列表。即一个嵌套递归的结构,里面可以包含字符串和列表。

例如:[“cat”,[“puppy”,“cow”],“horse”,[[]],“pig”,[“”],“sheep”]

其他类型的数据需要转成以上的两类,转换的规则不是 RLP 编码定义的,可以根据自己的 规则转换,例如 struct 可以转成列表,int 可以转成二进制(属于字符串一类),以太坊中整 数都以大端形式存储。

从 RLP 编码的名字可以看出它的特点:

一是递归,被编码的数据是递归的结构,编码算法也是递归进行处理的;

二是长度前缀,即 RLP 编码都带有一个前缀,这个前缀是跟被编码数据的长度相关的。

2 RLP 编码规则

规则一:单字节值在[0x00,0x7f]之间的,编码就是自身。需要注意的是 0x7f 这个边界,因为 ASCII 编码最大值就是 0x7f(即二进制 1111,1111),也就是说在 0x7f 以内完全当做 ASCII 编码使用。

规则二:如果一个 string 长度在 0-55 之间,编码结果的为在 string 开头加一个字节,这个字 节的值是 0x80 加上 string 的长度。由于被编码的字符串最大长度是 55=0x37,因此单字节前 缀的最大值是 0x80+0x37=0xb7,即编码的第一个字节的取值范围是[0x80, 0xb7]。 (128—183)

(0x80+[string 的长度]) || string

规则三:如果一个 string 长度超过了 55 个字节,编码结果的第 1 个字节为 0xb7+string 的长度 值(字节表示)的长度,后跟着string的长度,后跟着string。所以第一个字节的范围为[0xb8,0xbf]。

(0xb7+string 长度值的长度) || (string 的长度) || string

比如某个 string 长度为 1024(0x0400),0x0400 的长度为 2,因此第 1 个字节为前缀应该是 0xb7+2=0xb9,后面跟着0x0400,再后面跟着string,即整个RLP编码应该是\xb9\x0400\string。 编码的第一个字节即前缀的取值范围是[0xb8, 0xbf],因为字符串长度二进制形式最少是 1个字节,因此最小值是 0xb7+1=0xb8,字符串长度二进制最大是 8 个字节,因此最大值是 0xb7+8=0xbf。(184—191)

规则四:如果一个数组中所有元素的长度之和在 0-55 之间,编码结果的第 1 个字节为 0xc0+ 所有元素的长度,后面跟着列表中各元素的编码串,因此第 1 个字节的范围在[0xc0,0xf7]。 [192—247]

(0xc0+列表元素总长度) || 列表各元素的编码串

规则五:如果数组中所有元素的长度超过 55 个字节,编码结果的第 1 个字节为 0xf7 加上所 有元素长度值(字节表示)的长度,后跟所有元素长度,后面跟着数组。第 1 个字节的范围是 [0xf8,0xff]。 [248—255]

(0xf7+所有元素长度值的长度) || 列表元素总长度 || 列表各元素的编码串

3 RLP 编码实例

字符串 “dog” = [0x83, ’d’, ‘o’, ‘g’ ] (规则二)

列表 [“cat”,“dog”] = [0xc8, 0x83, ‘c’, ‘a’, ’t’, 0x83, ’d’, ‘o’, ‘g’ ] (规则四)

空字符串 “” = 0x80 (规则二)

空列表 [] = [0xc0] (规则四)

整数 15(‘\x0f’) = 0x0f (规则一)

整数 1024(‘\x04\00’) = [0x82, 0x04, 0x00] (规则二)

列表 [ [], [[]], [ [], [[]] ] ] = [0xc7, 0xc0, 0xc1, 0xc0, 0xc3, 0xc0, 0xc1, 0xc0] (规则四)

字符串 “Lorem ipsum dolor sit amet, consectetur adipisicing elit” = [0xb8, 0x38, ‘L’, ‘o’, ‘r’, ‘e’, ’m’, ‘ ’, … , ‘e’, ‘l’, ‘i’, ’t’](规则三)

4 RLP 分析

RLP 编码的设计思想,就是通过首字节快速判断一串编码的类型,充分利用了一个字 节的存储空间,将 0x7f 以后的值赋予了新的含义。相比于 Unicode 的对指定长度字节进行 编码,处理这些编码时一般按照指定长度进行拆分解码,最大的弊端是传统编码无法表现一 个结构,即列表。RLP 最大的优点是在充分利用字节的情况下,同时支持列表结构,也就 是说可以很轻易的利用 RLP 存储一个树状结构。

程序处理 RLP 编码时也非常容易,根据首字节就可以判断出这段编码的类型,同时调用不 同的方法进行解码,类似于 json 结构, RLP 支持嵌套的结构,通过递归调用可以将整个 RLP 快速还原成一颗树,或者转译成一个 json 结构,便于其他程序使用。

RLP 使用首字节存储长度的位数,再用后续的字节表明整体字符串的长度,根据规则二计 算,RLP 可以支持的单个最大字符串长度为 2 的 64 次方,再加上嵌套规则,理论上 RLP 可 以编码任何数据。

本文由HPB(芯链)团队整理。

HPB38:网络服务分析

目录

1 网络分层 ………………………………………………………………………………..4

2 会话层…………………………………………………………………………………….4

2.1 Peer 介绍……………………………………………………………………………..5

2.2 Peer 管理 …………………………………………………………………………….5

2.2.1 Peer 动态添加删除流程 ………………………………………………………5

2.2.2 Peer 握手机制…………………………………………………………………….6

3 表示层:RLP 编码 ……………………………………………………………………..6

4 应用层:Eth 协议……………………………………………………………………..6

1 网络分层

以太坊所有网络功能如下图所示: 所有网络功能建立在以太网的传输层之上,TCP 及 UDP 均有应用。

2 会话层

会话层主要包括 Peer 管理,NodeTable 管理和 RPC 协议,本文着重介绍 Peer 管理, NodeTable 请参考《P2P 网络及邻居节点发现机制》。 涉及到会话层的关键代码:

2.1 Peer 介绍

Peer 指通过了通信握手的邻居节点,只有邻居节点才能变为 Peer,只有 Peer 列表中的 节点,才能进行正常的通信。

2.2 Peer 管理

Peers 在代码中以 map 的结构存在,由 server 运行方法 run创建,并在 run 方法中进行 添加和删除维护。Pees 最大默认数量为 25(node/defaults.go 定义)

2.2.1 Peer 动态添加删除流程

Peer 添加分为两种:被动添加和主动添加。 1) 被动添加指其他节点发起握手,流程如下:

2) 每当当前 peers 有变动时,如添加,删除,或者一次 dial任务完成,则会执行一次主动 握手流程如下,其中要进行 Dial(拨号,即握手通信)的节点有以下几部分组成:

  • 静态节点,系统启动时配置文件写入
  • nodeTable 中随机选取(当前 needDynDials 的二分之一,needDynDials 的值为 (s.MaxPeer+1)/2=13)
  • loobbuf 中的节点(discovery task 中的邻居节点)
  • lookbuf 中的节点 Peer 数量不足时,会强制进行一次 nodetable 刷新,刷新到的node 写入 lookbuf。

3) Peer 删除有三种方式: RPC 命令删除,一次应用层通信完成自动删除,通信过程读写错误。

2.2.2 Peer 握手机制

参考《以太坊底层技术研究:Peer 握手机制》

3 表示层:RLP 编码

以太坊所相关有网络上 x 发送的数据均遵循 RLP 编码,参考《RLP 机制分析》

4 应用层:Eth 协议

Peer 握手成功后,即可进行应用层通信,Eth 协议数据包如下表所示:

Eth 协议应用层包括如下命令:

本篇文章由芯链团队整理。

HPB37:POA区块生成机制

目录

1 名词介绍

2 POA区块数据结构

3 新区块生成周期

4 新区块生成优先级

1 名词介绍

节点:普通的以太坊节点,没有区块生成的权利。

矿工:具有区块生成权利的以太坊节点

委员会:所有矿工的集合

2 POA区块数据结构

POA共识中,区块数据与POW有些区别,主要体现在header结构:

序号 字段 POW POA
1 Coinbase 挖矿奖励地址 被提名为矿工的节点地址 |
2 Nonce 随机数 提名分类,添加或者删除 |
3 Extra 其他数据 在Epoch时间点,存储当前委员会集合Singners |
4 Difficulty 挖矿难度 优先级,1或者2,同一个Number的区块,只有一个矿工是2 |

3 新区块生成周期

矿工在三中情况下开始生成区块:

● 程序启动时,执行newWorker方法初始化worker对象时,调用commitNewWork方法,开始生成新的区块。(miner/worker.go)

● 网络接收到其他矿工广播过来的新区块,该区块验证有效插入到区块链后,会产生ChainHeadEvent日志,worker对象的update协程检测到到该日志后,会调用commitNewWork方法,开始生成新的区块。(miner/worker.go)

● 矿工自己生成新的区块并入链后,会调用commitNewWork方法,开始生成新的区块。

(wait协程,miner/worker.go)

● 生成新区块时,矿工会进行一定的延时,延时算法:

高优先级矿工:

header.Time = new(big.Int).Add(parent.Time, new(big.Int).SetUint64(c.config.Period))

delay := time.Unix(header.Time.Int64(), 0).Sub(time.Now())

(consensus/clique/clique.go中的prepare和seal**两个方法定义)

其他矿工:

header.Time = new(big.Int).Add(parent.Time, new(big.Int).SetUint64(c.config.Period))

delay := time.Unix(header.Time.Int64(), 0).Sub(time.Now())

wiggle := time.Duration(len(snap.Signers)/2+1) * wiggleTime

delay += time.Duration(rand.Int63n(int64(wiggle)))

(consensus/clique/clique.go中的prepare和seal两个方法定义)

4 新区块生成优先级

POA共识算法中,委员会中的每一个矿工都会持续的生成新的区块,对于同一个Number的区块,不通的矿工生成该块时优先级不同。

优先级计算方法:

● Number:要生成的区块的块号

● Signers:snapshot中记录的委员会集合,并根据矿工的地址进行了升序排列

● Offset:矿工在Signers集合中的位置

● 若:(number % uint64(len(signers))) == uint64(offset),则优先级最高,header. Difficulty =2;否则,header.Difficulty = 1

本文由HPB(芯链)团队整理。

HPB36:POA委员会选举机制

目录

1 名词介绍

2 矿工投票方法

3 委员会确定投票流程

​ 3.1 关键概念描述

​ 3.1.1 Epoch & checkpointInterval

​ 3.1.2 Snapshot

​ 3.2 投票方法

1.名词介绍

节点:普通的以太坊节点,没有区块生成的权利。

矿工:具有区块生成权利的以太坊节点

委员会:所有矿工的集合

2.矿工投票方法

  • 用户通过RPC接口,调用Propose(address common.Address, auth bool)方法(consensus/clique/api.go),进行投票,address表示要投票的节点的地址,auth表示要从将该地址加入委员会,还是从委员会中删除。
  • Propose方法将address和auth两个输入参数写入到clique.proposals集合中。
  • 任何一个委员会的委员,可以在任意时刻进行投票,投票包括两种,即加入委员会和从委员会中删除。

3.委员会确定投票流程

3.1 关键概念描述

3.1.1 Epoch & checkpointInterval

  • CheckpointInterval:为常量1024(consensus/clique/clique.go中定义),即每当区块链的高度为1024的整数倍时,到达checkpointInterval时间点。
  • Epoch:默认为30000(cmd/puppet/wizard_genesis.go中makeGenesis方法中定义),即每当区块链的高度为30000的整数倍时,到达Epoch时间点。

3.1.2 Snapshot

Snapshot是一个快照,矿工程序在区块链高度为CheckpointInterval的整数倍时,会对当前相关数据和状态形成快照,并存储到数据库中。

snapshot结构体(consensus/clique/snapshot.go)关键成员:

  • Number:生成快照时的区块链高度
  • Signers:生成快照时的委员会地址
  • Votes:生成快照时所有的投票集合
  • Tally:被投票的节点集合,其中的Tally是该节点被投票的次数

3.2投票方法

所有投票都是在委员生成新区块的过程中完成,具体流程如下:

1)委员生成新区块时,先为该区块初始化一个header。

prepare方法,consensus/clique/clique.go)

2)从proposals中随机获取一个投票,将被投票的节点地址写入header.coinbase,将提名是添加还是删除写入header.Nonce(添加:0xffffffffffffffff 删除:0),若该委员生成的这个区块最终被写入区块链,则header中的投票也被写入区块链。

prepare方法,consensus/clique/clique.go)

3)委员在生成新区块时,会创建新的snapshot,新的snapshot是由上一checkponitinterval时间点存储到数据库中的快照加入当前时间点和checkpointinterval时间点之间所有的headers数据组成。添加header过程中,若该header的number是Epoch时间点,则会将snap中的Votes和Tally两个集合清零。

apply方法,consensus/clique/snapshot.go)

4)新的snapshot添加header过程中,会检查每一个header中存储的投票,若该投票snap.Votes中已经存在,则将snap.Votes和snap.Tally两个集合的该投票删除。

apply方法,consensus/clique/snapshot.go)

将每一个header中有效的提名写入新snapshot的snap.Votes和snap.Tally集合。

apply方法,consensus/clique/snapshot.go

5)判断snap.Tally集合中某个被提名的节点,提名的次数是否大于snap.Signers的1/2,即是否有超过一半的委员对该节点进行投票,若超过,则投票成功,该节点会被添加到委员会或者从委员会中删除。

apply方法,consensus/clique/snapshot.go)

注释:snapshot快照中的记录的委员会,即Signers集合,初始化时来源于创世块header中的Extra。

本文由HPB(芯链)团队整理。

HPB35:交易收发机制

目录

1、交易的主要数据结构

2、交易收发相关协程

3、关键流程描述

​ 3.1 交易数据验证流程

​ 3.2 交易入池流程

1、交易的主要数据结构

2、交易收发相关协程

3、关键流程描述

​ 3.1 交易数据验证流程

​ 3.2 交易入池流程

本文由HPB(芯链)团队整理。

HPB34:P2P网络及节点发现机制

P2P网络及节点发现机制

1 分布式网络介绍

1.1 Kad网介绍

1.2 Kad网络节点距离

1.3 K桶

1.4 Kad通信协议

2 邻居节点

2.1 NodeTable类主要成员

2.2 邻居节点发现方法

2.3 邻居节点网络拓扑及刷新机制。

1 分布式网络介绍

以太坊底层分布式网络即P2P网络,使用了经典的Kademlia网络,简称kad。

1.1 Kad网介绍

Kademlia在2002年由美国纽约大学的PetarP.Manmounkov和DavidMazieres提出,是一种分布式散列表(DHT)技术,以异或运算为距离度量基础,已经在BitTorrent、BitComet、Emule等软件中得到应用。

1.2 Kad网络节点距离

以太坊网络节点距离计算方法:

  • Node1:节点1 NodeId
  • Node2:节点2 NodeId

1.3 K桶

Kad的路由表是通过称为K桶的数据构造而成,K桶记录了节点NodeId,distance,endpoint,ip等信息。以太坊K桶按照与target节点距离进行排序,共256个K桶,每个K桶包含16个节点。

图1.1

1.4 Kad通信协议

​ 以太坊Kad网络中节点间通信基于UDP,主要由以下几个命令构成,若两个节点间PING-PONG握手通过,则认为相应节点在线。

2 邻居节点

2.1 NodeTable类主要成员

C++版本以太坊源码中,NodeTable是以太坊 P2P网络的关键类,所有与邻居节点相关的数据和方法均由NodeTable类实现。

2.2 邻居节点发现方法

​ 邻居节点是指加入到K桶,并通过PING-PONG握手的节点。

图2.1

邻居节点发现流程说明:

  • 系统第一次启动随机生成本机节点NodeId,记为LocalId,生成后将固定不变,本地节点记为local-eth。
  • 系统读取公共节点信息,ping-pong握手完成后,将其写入K桶。
  • 系统每隔7200ms刷新一次K桶。
  • 刷新K桶流程如下:
  • 随机生成目标节点Id,记为TargetId,从1开始记录发现次数和刷新时间。

  • 计算TargetId与LocalId的距离,记为Dlt

  • K桶中节点的NodeId记为KadId,计算KadId与TargetId的距离,记为Dkt

  • 找出K桶中Dlt大于Dkt的节点,记为k桶节点,向k桶节点发送FindNODE命令,FindNODE命令包含TargetId

  • K桶节点收到FindNODE命令后,同样执行b-d的过程,将从K桶中找到的节点使用Neighbours命令发回给本机节点。

  • 本机节点收到Neighbours后,将收到的节点写入到K桶中。

  • 若搜索次数不超过8次,刷新时间不超过600ms,则返回到b步骤循环执行。

2.3 邻居节点网络拓扑及刷新机制。

图2.2

1 TargetId为随机生成的虚拟节点ID。

2 以太坊Kad网络与传统Kad网络的区别:

1) 以太坊节点在发现邻居节点的8次循环中,所查找的节点均在距离上向随机生成的TargetId收敛。

2) 传统Kad网络发现节点时,在距离上向节点本身收敛。

本文由HPB(芯链)团队整理。