深入理解EVM

原文在这里: https://blog.qtum.org/diving-into-the-ethereum-vm-6e8d5d2f3c30

Solidity offers many high-level language abstractions, but these features make it hard to understand what’s really going on when my program is running. Reading the Solidity documentation still left me confused over very basic things.

What are the differences between string, bytes32, byte[], bytes?

Which one do I use, when?What’s happening when I cast a string to bytes? Can I cast to byte[]?How much do they cost?

How are mappings stored by the EVM?

Why can’t I delete a mapping?Can I have mappings of …

我理解的区块链

我从2016年开始接触比特币技术,一年多以前开始读比特币源码,从比特币摸到了以太坊,再从以太坊接触到了EOS, CITA,Hyperledger Fabric等,对区块链的认识和理解也浅入深,虽然离专家水平还太远,打算从技术角度说说我的理解。

小说<三体>有一段类似的话:

一个穷凶极恶的人,如果想让别人认为他是安全的,那么他必须自己把手绑起来,而不是手里拿着刀。

在一个黑暗森林的守宙中,一个文明想证明自己是安全的,那么必须把自己裹在低光速黑域中。

我觉得如果说整个互联网是一个黑暗森林,那么区块链也许就是能证明一个互联网服务是安全的,或者是可信任的。因为区块链技术已经把信息篡改的手已经绑起来了。

分布式账本

首先,区块链是一个分布式账本,人类早就知道鸡蛋不能放在同一个篮子里,要分在不同一篮子里,这个比喻当然不够恰当,因为区块链里的分布式账本,是把一份同样的账本分别存储(备份)到不同的机器,如果是一条公链,任何人都可以备份到这个账本;如果是一个联盟链,加入到这条链的所有许可机器,都可以备份到账本。

这些账本只要有数据更新,就全立即同步,把更新内容同步到其它的机器上。只要网络不出问题,都会努力使自己的账本数据最新,这样账本就有了公开性。分布式存储,这个概念并不新鲜。

把一定时间内(比如10秒)的记账数据(交易)打成一个包(按照一定的结构组织起来),区成一个区块,这个区块中放入了上一个区块的区块hash,当下一个区块打包时,也将当前区块计算出来的hash放入区块中,这样就形成了一条单向链表,从0块到N块的一条链。

那么是怎么做到不可篡改的呢?对于区块链来说,不可篡改这个概念其实并不准确,由于块中只记录了交易,所有的数据状态改变都是通过交易来完成,只能说所有的数据篡改都会留下痕迹。

当机器(节点)收到新的数据(块)时,就会对数据进行验证,把每一条交易都检查一遍,如果是合约,那就用把交易再本地虚拟机里再执行一遍,只有全部都检查通过了,才会接收这个最新的块。(由此也可以想到随机数为什么是伪随机数)

共识

决定谁来打包这个块,就是共识。也就是大家共同维护同一账本的共同约定。

为了维护同一本账本,必须要先出一个打包交易的机器,这样账本才能不断地更新数据和状态。但怎么选择哪台机器来作这个工作,里面却是充满了现实中人类社会协作的影子。

给出一道题,谁最快解出来谁打包(pow)

能力越大,责任越大(pos)

选出一群人来代表群众(dpos)

投票得到大多人承认,就是合法的(BFT,PBFT,Tendermint)

区块存储 

从区块链存储的角度,我觉得区块链只是一种新的信息的组织方式。

经典的软件架构设计模式MVC,即模型,视图,控制器。

如果把MVC模式来理解区块的存储,我觉得可以分为MVS,即模型,视图,存储。

V:视图,区块只是存储数据的一种视图,我们同步到的区块有区块头,交易列表等,这些都只是存储的数据的外在表现,而并非实际的存储就是如此。

M:模型,把底层的数据用各种树的形式结构化起来,目的是为了更好地检索和更新。

S:存储,真正存储在各种key-value数据库中的数据

区块存储以后打算展开再来写。

应用

既然区块链只是一种新的信息组织方式,我觉得区块链的应用在于信息本身。

1.信息公开,区块链的特点并不是不可篡改,而是把所有修改信息的痕迹都记录下来,基于这个特点,可以用来做信息公开,这样信息公开方就相当于把自己修改信息的手绑起来了。想到前几天电影流浪地球的影评被修改的问题,是不是可以使用区块链技术来消除市场的质疑。现在的各种产品溯源,也是这种想法。

但这样会不会又引发另一个问题,就是为了圆一个谎言,需要再编5个谎言去圆它。 

2.信息标准化。联盟链各组织之间,通过区块链实现信息共享和标准化,现在各银行之间已有的安全认证机制是否有可能替代。

挑战

区块链是利用密码学技术,达到某种共识的分布式账本。我觉得在应用上也存在几个挑战:

密码学:这是区块链应用最重要的基础之一,所有账户,交易都基于密码学的安全性,如果密码学本来出现问题,比如sha-2被攻破,公私钥被破解等,那现有的公链的安全性将无复存在。

共识:我觉得找到一种高效安全的共识,能高效地使用区块链的关键

2018以及近况

2018

2018年过得太快了,一恍而过。

这一年在杭州,全身心投入到区块链的研发之中,为公司的技术方案调研的时候,看了很多大大小小项目的源代码,包括读的比较深入的bitcoin,go-ethereum,Cita以及Poa bridge。对区块链的底层技术学习了一遍,以太坊的一整套技术架构也深入地学习了一遍,自己从一个区块链的入门到对区块链技术有一定的理解。都发生在这一年。

这一年要接触的技术太多了,感觉自己以前所积累的还远远不够用,前几年每年解锁一项新技能,除了Lua外,这一年全部用上。还是庆幸自己前几年的坚持。坚持linux和linux方向是最大的益处,这一点上,这一年里也影响了其它同事,不知道是在给他们挖坑还是帮助,不可而知。

这一年,对技术的理解更上一层楼,更加觉得只有掌握底层技术,才能更加好的驱动上层技术。

这一年学了rust语言,这是我遇到过了最麻烦的语言,但是到现在都使用不好,不能用原来的类C的语言的作对比,rust就是一个新的物种,但是它的安全性却是公认的,对于c和c++来说,一个sengmentation fault 就让多少程序员白了头,rust最大的优势就是能避免此类错误,我觉得用来开发区块链是比较合适的。

这一年读的书比较少,但都很厚很厚:

<重构>:如何更好的修改和重新设计既有代码

<unix环境高级编程>:对unix/linux系统开发接口进行了解,但说实话对工作没有直接的帮助

<unix网络编程>:看了卷二,严格意义上讲不应该叫网络编程,应该叫进程间通讯应该更恰当一些,整本书讲的就是讲各种进程间的通讯模型,读完对进程间通讯有了彻底的理解。这本书和<unix环境高级编程>的进程间通讯部分有重合。

年底,工作各种不顺,区块链市场变冷,公司裁员,项目搁浅,只留我带两个人继续开发一个dapp平台。我也有了离职的做想法。

去年一年多没有写博客,一来是工作忙,二来是一些研究性的东西,在没研究明白之前也不敢写出来误导大家,在研究明白后也不想写了,其它说到底,还是自己变懒了,自己的文笔表达也生疏了。今年要坚持写博客。

近况

过年后离职在家,休息了几天,开始找工作。顺便看一些书,把自己的知识点重新梳理一下,把理解不深的知识补一下,做IT的,在大悲剧在于要不断地学习新的知识,我想一大乐趣也在于此吧,试问有谁能趟在老本上吃一辈子,我现在对新的东西还是充满了兴趣。

对于找工作,才发现自己已经有那么久没找过工作了,简历也不会写了,于是慌的一逼的我赶紧去找了一些模板,最后还是发现用markdown的方式写最符合我的性(尿)格(性),理科生思维。但是写自己的经历的时候,怎么看都觉得生硬。总之,找工作这事,也不着急,欲速则不达吧。

对于区块链最近思考的比较多,我一直觉得从技术角度,区块链没有新的东西,新东西只有区块链理念,以及组织信息的方式。从技术上讲没有难的东西,但是一个相当复杂的系统,做区块链系统架构,就比如一个主板厂商,用各种已研发出来的零部件组合成一个稳定高效的集成电路系统。而对于区块链系统来也,保持稳定,安全,高效也是一个最重要的任务。

cita横向扩展的问题思考

在cita白皮书中,服务分片是作为亮点和重点突出的部分,就如同官方的这两张设计架构图

每个节点切分了若干个单独服务,各个服务之中通过一个消息总线(在cita代码中所用的是rabbitmq)进行交互,而每个单独的微服务又可以进行横向扩展,比如多个rpc服务,多个Auth服务,多个exector服务等,总之可以动态地增加或者减少单个微服务的服务能力。

然而通读代码之后,发现并非如此,cita现在做到的只是第一步:把节点分析成多个单独服务,这些服务可以在一台机器上,也可以在不同的机器上。

第二步的将单个微服务进行横向扩展,其实是还没有实现的,而且在现在的cita代码的架构上,也不太容易实现,根据rabbitmq的无状态分发特性,如果是多个相同的微服务同时订阅某一类消息,mq会无差别地分发到各个服务中,而不会根据服务状态。比如一个rpc服务启动了三个相同的微服务A,B,C来同时提供rpc服务,A收到了一个请求后,通过mq转发到其它的服务(比如 Auth服务),在Auth处理请求后,将结果放到mq中,这时候因为有A,B,C三个rpc服务同时订阅了这一类消息,所以A不保证会收到处理结果并返回给用户,导致服务的出错。其它的微服务也一样

还有就是Executor服务,在执行evm的时候会对世界状态树进行修改,有多个executor,就代码着有多个evm在执行,而修改世界状态树必需是线性的,并发会造成很多问题,所以理论上evm也不能并行执行合约。executor只能有一个

以现在的cita代码,这些问题都是不容易解决的。但不排除cita团队后续会跟进并解决这些问题, 这是需要对cita架构重新设计的,毕竟这是他们吹过的牛B。

基于CITA链的web3.js

CITA官方版本的web3太简陋,其中也有很多不合理之处。

工作上需要把以太坊上编写好的脚本,拿到cita链上来能直接跑。

于是自己基于web3.js自己实现了一个,也整合了cita的signer,为了和ethereum的web3兼容,里面尽量用了web3.js的变量命名(虽然对于cita链来说不太合理),这样就基本能满足以太坊的脚本作极少的改动就可以跑在cita链上。

github地址:

https://github.com/ijustgoon/sweb3

记一个CITA的bug

同事发现了solidity代码在cita链上运行有个怪异的问题,而同样的代码之前在以太坊链上测试是没有问题的,我第一反应就是难道cita的evm又有bug。

经调查后发现是solidity中的now变量时间不对,在cita链上now返回的时间单位是毫秒,对比了一下,以太坊上now返回的时间单位是秒。

再去查solidity文档,now是返回当前块的时间戳:

now (uint): current block timestamp (alias for block.timestamp)

于是再去对比以太坊和cita块的时间戳,果然发现以太坊出块时的timestamp为 s 为单位,而cita出块时用了 ms 为单位。

这样问题就明了了,只要把cita的block的timestamp改为秒即可。两个地方,一个python脚本生成的genesis,

一个打包块时的timestamp,cita_bft.rs文件中

修改后block的时间戳为秒,但不确定修改会不会对其它模块造成影响,在待详细测试。

问题已提交到cita的issues中

https://github.com/cryptape/cita/issues/192

 

go-ethereum代码分析

这是一个很久之前的工作了,最近才又翻起这个工程,索性就把它上传到github上去

当时花了很精力去要析和阅读,还添加了很多注释和辅助log打印,希望能对想从头开始阅读源码的人有一点点帮助。

工程是一个idea工程,直接用golang打开就可以阅读和debug,打断点跟踪程序的运行脉络,对于阅读源码,能省不少的时间。

代码在这里:

https://github.com/ijustgoon/go-ethereum-analysis

就这样。

docker保存自定义容器

上一篇介绍如何搭建lnmp环境
现在基本的环境已经部署好了,想把整个容器保存起来,像备份系统一样把容器备份起来。
把容器先保存成镜像,下次直接用保存起来的镜像来生成容器实例来运行,这样运行起来的容器是自己定义好的(自己备份时候的)样子。
将容器保存成镜像

debian是容器名称
mydebian 是保存的镜像名称

再查看镜像列表,就已经多了一个mydebian容器了

保存成镜像后,你就可以用这个镜像生成无数个容器了,而你的镜像不受任务影响。

还可将镜像保存成本地文件

将mydebian镜像保存成当前目录的mydebian.tar的文件

当然也可以从当前的文件中导入到镜像

将mydebian.tar文件导入成testdebian镜像

再查看镜像列表

testdebian已成功导入
当然你还可以把mydebian.tar拷到别的机器上导入。

我觉得开发人员使用docker可以像使用git的分支一样使用。需要一个环境的时候,生成一个docker容器,然后在容器上折腾,等折腾完了,不需要了再把容器删除了即可。等需要另外一个环境,再生成一个容器折腾。
对于我这种时常需要切换不同环境的开发者来说,docker可比虚拟机要好用多了。

docker上搭建LNMP环境

上一篇介绍在一个nginx容器上,如何管理docker容器
继续扩展,搭建一整套的环境,如LNMP环境( nginx + mysql + php)。
网上有介绍是通过容器连接的方式来实现,也就是一个nginx容器,一个mysql容器,一个php容器,然后通过配置来连接起来。形成一整套开发环境。
但对于一个开发环境来说,我并不建议这样子来做,因为开发环境本来就是要一个独立的环境,增加复杂性不说,独立性还不能保证。
我建议的方式是在一个容器里直接搭好所有的环境,然后启动容器的时候,一整套环境就启动起来。
上一篇已经有一个nginx容器上,可以登录到nginx容器中,在上面继续把mysql和php都装了。
但我选择重新下载一个干净有debian容器
使用命令

命令把镜像下载到本地

多了一个debian镜像(当然也可以选择其它的如ubuntu,centos之类的系统,看个人喜好)

用debian新建一个容器并启动起来

 

绑定了80端口和3306端口,3306是方便以后要访问mysql用

登录容器并执行bash

先装些必要的工作,比如

于是开始你的安装环境之旅吧

具体的安装过程网络上有很多,比如这里
How To Install Linux, Nginx, MySQL, PHP (LEMP Stack) on Debian 8

这样lnmp环境就安装好了。但当你再次启动debina容器的时候,这些服务是不会自己起来的,还是要你再登录进容器来手动启动这些服务。

一个解决方法是在根目录下新建个脚本 startup.sh,

填入以下内容

这个脚本的内容是启动mysql,nginx和php服务
保存妈出

给脚本执行权限

 

退出并关闭容器,

 

然后试下再启动

然后执行容器里的 startup.sh脚本来启动服务

服务都启动成功了,环境搭建完成。
下一篇想介绍一下如何保存并导入自定义好的镜像。

管理docker容器

上一篇介绍了安装和启动一个容器,并通过端口绑定成功访问到启动的nginx服务。
然而围住一想,还是什么也做不了:
1.nginx的根目录在哪,我们怎么更改它,怎么让它访问我们想它它访问的文件?
2.nginx的配置文件在哪。怎么加一个服务?或者怎么修改配置文件?

带着这些疑问往下说

一,绑定本地目录到容器目录
和绑定端口一样,子可以绑定本地的目录到容器中,这样可以在本地编辑文件,通过容器中的服务来访问。
首先把昨天创建的容器删除

重新生成并启动容器

通过-v命令,可以把本地的目录绑定到容器中,上面命令是把本地的 /web目录绑定到/var/www/html。
为什么要绑定到/var/www/html目录,好吧,后台看配置文件的时候会看到,当然可以绑定到别的目录,到时候改nginx的配置文件即可。

通过绑定后,就可以在你本地 /web下加任务你想加html文件,就可以通过浏览器访问了。就和编辑你本地的服务器文件一样。

二,登录到容器
运行起来的nginx容器,其它就是一个linux系统,当然也可以登录上去。
通过命令

就可以登录到正在运行的webserver容器了。命令行也已经切换到了容器里面。
-u 是指定登录的用户名
webserver 是当前运行的容器实例
bash 是登录上去运行的命令

登录上去后,这就是一个linux系统,可以查看系统信息

可以

还可以

不过安装软件前先apt-get update

当然也可以登录上去后运行任何命令,比如 ls 命令

这条命令的意思是登录到webserver容器上执行 ls 命令,执行完后就退出了

三,修改容器上的文件
1,直接在服务器上修改
既然已经登录到容器上了,就像使用linux系统一样,怎么改都可以。
但当你很高兴地打出vi /etc/nginx/nginx.conf的时候,会发现

啥?vi都没有,是的,vi也没有,这是一个相当干净的系统。
你只有自己装个vim再修改

安装完成后就可以用vi /etc/nginx/nginx.conf来修改配置文件了

2.在本地和容器间拷贝文件

docker的拷贝命令

这个命令和scp命令有点像

现在拷贝webserver容器中的/etc/nginx/nginx.conf文件 到当前目录

拷贝下来后,在你本地修改这个配置文件

修改完成后,再拷贝到容器中
拷贝当前目录下的nginx.conf文件到web容器的/etc/nginx/目录下

同理也可以用这种方式修改其它的文件和文件夹

好了,有时候光有了nginx还不行,还需要php,mysql等等。
下一篇打算写写如何打造真正的开发环境