工作,学习,生活,这里将会有一些记录. 备用域名:http://meisw.51099.com 注册 | 登陆
浏览模式: 标准 | 列表2016年12月的文章

智能合约初体验

 

什么是智能合约

    智能合约是代码和数据的集合,寄存与Blockchain的具体的地址。智能合约更想是在Blockchain中的一个自动化的代理(或者说是机器人or NPC),智能合约有自己的账户,在时间或事件的驱动下能自动执行一些功能,如可以在相互之间传递信息,修改区块链的状态(账户信息等),以及图灵完备计算(可以用图灵机做到的所有事情,通俗来说就是一般编程语言可以做的所有事情)。以太坊的智能合约是以太坊特定的字节码,被叫做EVM字节码。

智能合约高级语言

    用户不可能直接编写EVM字节码,所以以太坊提供了几种编写智能合约的高级语言。

    Solidity:类JavaScript,这是以太坊推荐的旗舰语言,也是最流行的智能合约语言。具体用法参加Solidity文档

    Serpent:类Python

    LLL:类Lisp

    可以根据不同的习惯选择不同的高级语言。这里选用最流行的Solidity

相关概念

    以下的概念是智能合约可能用到的,这里不做详细介绍,想了解的可以参考 智能合约菜鸟教程

    公钥加密系统:

    点对点网络:

    区块链:区块链可以看做是智能合约的基础设施

    以太坊虚拟机:解释执行智能合约字节码的东西,功能类似于Java虚拟机

    节点:

    矿工:区块链中参与处理区块的节点叫做矿工。当前以太坊活跃的矿工:https://ethstats.net/

    工作量证明:矿工们总是在竞争解决一些数学问题。第一个解出答案的(算出下一个区块)将获得以太币作为奖励。然后所有节点都更新自己的区块链。所有想要算出下一个区块的矿工都有与其他节点保持同步,并且维护同一个区块链的动力,因此整个网络总是能达成共识。

    以太币:ETH,以太坊中的虚拟货币,可以购买和使用,也可以与真实货币交易。以太币的走势图

    Gas:相当于手续费。在以太坊执行程序以保存数据都要消耗一定量的以太币。这个机制可以控制区块链中计算的数量,保证效率。

智能合约与DApp

    以太坊社区把基于智能合约的应用称为去中心化的应用程序(Decentralized App)。DApp的目标是(或者应该是)让你的智能合约有一个友好的界面,外加一些额外的东西,例如IPFS(可以存储和读取数据的去中心化网络,不是出自以太坊团队但有类似的精神)。DApp可以跑在一台能与以太坊节点交互的中心化服务器上,也可以跑在任意一个以太坊平等节点上。(花一分钟思考一下:与一般的网站不同,DApp不能跑在普通的服务器上。他们需要提交交易到区块链并且从区块链而不是中心化数据库读取重要数据。相对于典型的用户登录系统,用户有可能被表示成一个钱包地址而其它用户数据保存在本地。许多事情都会与目前的web应用有不同架构。)

    DApp流程:

  1. 用Solidity(或其他语言)编写智能合约(后缀为.sol)
  2. 用solc编译器将.sol合约编译成EVM字节码
  3. 编译好的字节码回送给dapp前端
  4. 前端将编译好的智能合约部署到区块链中
  5. 区块链返回智能合约地址+ABI(合约接口的二进制表示。合约接口用JSON表示,包括变量,事件和可以调用的方法)
  6. 前端通过Address+ABI+nonce,调用智能合约。智能合约开始处理。

 

智能合约编译器

    Solidity智能合约可以通过多种方式进行编译

    在这里选择solc和web3.eth.compile.solidity方式

geth安装

    参考:go Ethereum client。安装不比较简单,这里详细说了

solc安装

1. 作为cpp-ethereum的一部分安装  

    如果通过编译的方式安装了cpp-ethereum(参考:http://www.cnblogs.com/fengzhiwu/p/5547911.html),那么solc编译器就会作为cpp-ethereum的一个子项目也被编译安装,在webthree-umbrella/build/solidity/solc目录下找到solc编译器的可执行文件。

image

    然后,在/bin或/usr/bin目录下创建软链接就行了

ln -s /home/vagrant/Code/workspace/webthree-umbrella/build/solidity/solc/solc /bin/solc

2. 单独安装solc

参考:http://solidity.readthedocs.io/en/latest/installing-solidity.html

  • 通过npm安装:这种比较简单,运行npm install solc就行了,不过我没有在console中找到solc
  • 通过apt-get安装:

sudo add-apt-repository ppa:ethereum/ethereum

sudo apt-get update

sudo apt-get install solc

which solc

    把solc添加入geth中直接使用(在geth中输入)

admin.setSolc("path/to/solc")
  • 从源码编译安装

    这种方法和安装cpp-etheruemle类似,不过最后的编译步骤改为:

cd webthree-umbrella
./webthree-helpers/scripts/ethupdate.sh --no-push --simple-pull --project solidity # update Solidity repo ./webthree-helpers/scripts/ethbuild.sh --no-git --project solidity --cores 4 -DEVMJIT=0 -DETHASHCL=0 # build Solidity only

测试

    打开一个geth console,输入:web3.eth.getCompilers(),就会打印

image

 

智能合约体验

编译一个简单的合约

    直接在console中编译一个简单的合约代码

> source = "contract test { function multiply(uint a) returns(uint d) { return a * 7; } }"
> clientContract = eth.compile.solidity(source).test

编译返回的结果的JSON格式如下

image
 
其中,
code:编译后的EVM字节码
info:编译器返回的metadata
abiDefination:Application Binary Interface定义。具体接口规则参见这里
compilerVersion:编译此代码的solidity编译器版本
developerDoc:针对开发者的Natural Specification Format,类似于Doxygen。具体规则参见这里
language:合约语言
languageVersion:合约语言版本
source:源代码
userDoc:针对用户的Ethereum的Natural Specification Format,类似于Doxygen。
 
编译器返回的JSON结构反映了合约部署的两种不同的路径。info信息真实的存在于区中心化的云中,作为metadata信息来公开验证Blockchain中合约代码的实现。而code信息通过创建交易的方式部署到区块链中。
 

创建和部署合约

在进行此步骤前,确保你有一个解锁的账户并且账户中有余额。(可以创建自己独立的测试网络,即自己的区块链,初始化的时候就可以初始化一些有余额的账户)。参考:Test Networks
image
 
现在就可以在区块链中创建一个合约了。创建合约的方式是发送一个交易,交易的目的地址是空地址,数据是前面JSON结构中的code字段。
创建合约的流程如下
var primaryAddress = eth.accounts[0] var abi = [{ constant: false, inputs: [{ name: 'a', type: 'uint256' } ]}] var MyContract = eth.contract(abi) var contract = MyContract.new(arg1, arg2, ..., {from: primaryAddress, data: evmByteCodeFromPreviousSection}) //arg1,arg2,...是构造参数,这里没有,需要去掉。红色部分用前面生成的code代替
i. 获得账户
image
ii. 定义一个abi (abi是个js的数组,否则不成功)
image
iii. 创建智能合约
image
iv. 发送交易部署合约
image
 
如果交易被pending,如图说明你的miner没有在挖矿,
image
 
启动一个矿工
miner.setEtherbase(eth.primaryAddress)    //设定开采账户
miner.start(8)
eth.getBlock("pending", true).transactions
这时候发现交易已经在区块中
image
不过会发现,交易还是pending,这是因为该交易区块没有人协助进行运算验证,这时候只需要再启动一个矿工就行了
miner.start(8)
参考:Private Testnet
发现区块1部署了交易
image
image
 

与合约进行交互

可以通过eth.contract()定义一个合约对象,这个对象包含变数合约接口的abi
Multiply7 = eth.contract(clientContract.info.abiDefinition); var myMultiply7 = Multiply7.at(contract.address);
image
image
到这儿,就可以调用智能合约的函数了。
myMultiply7.multiply.call(3)
myMultiply7.multiply.sendTransaction(3, {from: contract.address})
image

 

结束

    到此,对智能合约的初次体验就结束了。另外智能合约以及DApp还可以干很多NB的事情。以后会进一步讨论。

区块链核心技术演进之路-共识机制演进(1)

一般而言,在介绍区块链时经常会提到两个例子:一是由古老的记账模式延伸到分布式账本,二是拜占庭将军问题(Byzantine Generals Problem)。使用分布式账本目的是让每个节点都能够验证交易,而拜占庭将军问题与账本的一致性有关,即本文要讨论的共识机制(Consensus)。

区块链上的共识机制主要解决由谁来构造区块,以及如何维护区块链统一的问题,该问题的理论基础是拜占庭容错(Byzantine Fault-Tolerant,BFT)。BFT从上世纪80年代开始被研究,目前已经是一个被研究得比较透彻的理论,存在解的前提条件及具体实现都已有现成算法。不过本文不打算从BFT说起,因为要分析的是区块链共识机制的演进过程,而中本聪并没有采用BFT。其实在我研究比特币伊始,即便在理解了POW机制之后的很长一段时间内,并不了解拜占庭将军问题。后文分析 HyperLedger Fabric的PBFT以及小蚁项目的DBFT时再全面阐述拜占庭将军问题及传统分布式一致性算法(PAXOS、RAFT)。

共识机制的核心是区块的构建和检验,POW系统构建区块的过程一般称为“挖矿”(mine),POS 系统PPC的区块构建方式一般称为“铸造”(mint),而NXT的区块构建方式一般称为“锻造”(forge)。

 

POW

 

共识机制在以前一般被称为证明方式(Proof),因为比特币采用工作量证明(即Proof-Of-Work,简写为POW)。随着大家对分布式账本一致性问题的不断探索,很多方法被提出来,尤其近期有很多区块链项目回归了对传统BFT算法的改进,在思路上已经跳出了“证明”的语义,因此进一步高度概括为共识机制。我记得第一次碰到工作量证明这一概念时感到很费解,对这种表述方式很头疼,掌握了POW机理后才真正明白,通俗讲就是“通过工作以获得指定成果,用成果来证明曾经付出的努力”。其实我们日常工作生活中经常使用工作量证明,比如学生考试成绩,毕业证以及驾照等,这种证明方式的一个显著特征是往往需要很大的工作量才能拿到指定成果,但这个成果很容易验证。因为我们一般很难去实时监督一个人是否真的付出了这些工作量,所以只能使用工作量的结果来证明。

再回到比特币的设计思路,中本聪已经使用非对称密码解决了电子货币的所有权问题,用区块时间戳解决了交易的存在性问题,用分布式账本解决了剔除第三方结构后交易的验证问题,剩下需要解决的问题是双重支付,这要求所有节点账本统一,而真正的平等又必须赋予人人都有记账的权利,记账是一件简单的事情,每个人都可以做,显然最终会存在众多大同小异的账本,但我们只需要其中的一个账本就够了。

中本聪想到给记账加入成本,总账本由各个分页按照时间先后排序,给每个账本分页设立一个评判标准,以区分账本分页是否合格,这给记账增加了难度,同时给每个账本分页加入一个随机元素,用以调节记账难度以保证一定时间段内只有一个人生成合格的账本分页。增加的成本就是工作量,合格的账本分页就是工作量证明。对于比特币而言,所谓的账本分页就是一个区块,区块通过巧妙设计形成区块链,合格的区块可以表述为:

F(Nonce) < Target

其中Nonce是随机元素,Target是合格区块的量化,每个记账节点的Target一致。此外POW的成功运行还需要配合如下两条约定,
Best chain原则:将最长的链条视为正确的链条。

激励原则:找到合格的区块有奖励收益。

第1条约定为硬性规则,无条件遵守,大家要么不玩,要玩就遵守这条原则,毕竟共同的目标是找到一致性账本,而最长的链条代表最大的工作量,如果没有这条约定,每个人都只会构造自己的区块链,无法统一。第2条为工作量激励,既然记账有成本,那唯有收益才能驱动大家都去记账,参与记账构造区块变成投资行为,其成本和收益风险在第1条约束下形成博弈,驱动所有节点按约定规则老老实实够造区块,最终达到纳什均衡。

具体实现方式,比特币采用哈希(Hash)算法,关于哈希算法的原理和特点在前一篇文章(挖矿演进)已经详细讨论。逻辑上比特币是对整个区块进行哈希运算,但真正实现并非将整个区块数据作为哈希函数的参数,区块大体可分为区块链头和交易列表两部分,交易列表通过构造成Merkle树最终浓缩成Merkleroot内置于区块头,区块头只有6个字段,共80字节,如此设计首先带来的好处是方便哈希运算,每次运算只需要80字节的参数输入,而不是整个区块的数据,但交易列表的任何变化又能体现在哈希运行结果上。

p1

比特币采用SHA256哈希运算,且每次都是连续进行两次SHA256运算才能作为最终结果,前一次运算的结果作为后一次运算的输入,即Double SHA256,一般简称SHA256D,扩展上面的公式,比特币合格区块判断依据如下:

SHA256D(nVersion,hashPreBlock,hashMerkleRoot,nTimes,nBits,Nonce)<MAXTARGET/Diff

其中式子左边的6个参数(区块头)在前一篇文章已经解释,MAXTARGET为最大目标值,常量;Diff代表难度,全网难度一致。MAXTARGET/Diff即通常所说的当前目标值。

很显然,POW的核心要义为:算力越大,挖到块的概率越大,维护区块链安全的权重越大。相对其他共识机制而言,POW逻辑简单,容易实现,容错达50%,其安全有严格的数学论证。

 

POS

 

POW并非完美,其中被指责最多的主要有两点,一是浪费能源,二是风险和收益博弈必然导致联合挖矿,而大算力矿池可能会对系统的去中心化构成威胁。

于是在2011年,一个名为Quantum Mechanic的数字货币爱好者在Bitcointalk论坛提出Proof-of-Stake(POS)证明机制,该机制被充分讨论之后证明具有可行性。如果说POW主要比拼算力,算力越大,挖到一个块的概率越大,POS则是比拼余额,通俗说就是自己的手里的币越多,挖到一个块的概率越大。POS合格区块可以表述为:

F(Timestamp) < Target * Balance

与POW相比,式子左边的搜索空间由Nonce变为Timestamp,Nonce值域是无限的,而Timestamp极其有限,一个合格区块的区块时间必须在前一个区块时间的规定范围之内,时间太早或者太超前的区块都不会被其他节点接纳。式子右边的目标值引入一个乘积因子余额,可见余额越大,整体目标值(Target * Balance)越大,越容易找到一个区块。因为Timestamp有限,POS铸造区块成功率主要与Balance有关。

POS只是代表一种共识机制理念,具体有多种实现方式,下面重点解析两种比较经典的实现思路。

 

Peercoin

 

Peercoin(点点币,PPC)于2012年8月发布,最大创新是其采矿方式混合了POW工作量证明及POS权益证明方式,其中POW主要用于发行货币,未来预计随着挖矿难度上升,产量降低,系统安全主要由POS维护。目前区块链中存在两种类型的区块,POW区块和POS区块。PPC的作者为同样不愿意公开身份的密码货币极客Sunny King,同时也是Primecoin的发明者。

要掌握Peercoin的POS机制,需要重点理解Sunny King专门为PPC设计的几个核心概念:Coinstake,Kernel,Stake Modifier,Modifier Interval,Stake Reward,Coinage等。

Coinstake

为了实现POS,Sunny King专门设计了一种特殊的交易,叫Coinstake,Coinstake的设计借鉴于中本聪的Coinbase设计。本质上Coinbase和Coinsake都是一笔交易,只是对他们的输入输出做了一些硬性限制。

而Coinstake的设计又需要有别于Coinbase,这样才不会扰乱系统原有的POW机制,简单对比一下两者结构上的不同,

Coinbase结构要求:

输入数量必须等于1,且输入的prevout字段(指定前一笔交易的输出)必须置空值。
输出数量必须大于等于1。

p2

Coinstake结构要求:

输入数量大于等于1,且第一个输入的prevout字段不能为空,即要求Kernel必须存在。
输出数量大于等于2,且第一个输出必须置空值。

这两种特殊交易在区块链中存放的位置也有特殊要求,中本聪规定每个区块的第一笔交易必须放置Coinbase,反之,Coinbase不能出现在区块的其他位置。Sunny King显然不想破坏这个规则,他增加了一条规则,对于POS区块,第二笔交易必须放置Coinstake,反之,Coinstake不能出现在其他地方。换言之,只要第二笔交易是Coinstake,那么这个区块就当POS区块来处理。

Coinbase和Coinstake都不会被单独广播,而只存在于区块中,因此客户端节点一般都不允许进入内存池,当花费这两种交易时,都需要检测是否已经成熟。

Kernel Protocal

Coinstake的第一个输入(Input 0)叫Kernel,Kernel在POS机制里确实起到核心作用,合格区块的判定与之息息相关。PPC合格区块判断条件为:

SHA256D(nStakeModifier + txPrev.block.nTime + txPrev.offset + txPrev.nTime + txPrev.vout.n + nTime)< bnTarget * nCoinDayWeight

式子左边的每一个参数都有明确的设计目的,其中,

nStakeModifier:专门为POS设计的调节器,按照以上公式,如果没有参数nStakeModifier,当一个人收到一笔币得到网络确认之后,他立即就能提前计算得知自己在未来何时可以锻造区块,这显然不符合设计目标,Sunny King希望POS矿工和POW矿工一样做盲目探索,以实时在线维护区块链,nStakeModifier的设计就是为了防止POS矿工提前计算。nStakeModifier可以理解为POS区块的一个属性,每一个区块对应一个nStakeModifier值,但nStakeModifier并不是每个区块都变动,不过协议规定每隔一定时间(Modifier Interval)必须重新计算一次,取值与前一个nStakeModifier以及最新区块哈希值有关,因此POS矿工无法提前计算,因为他不知道未来的区块哈希值。

也就是说,在PPC系统中,除了存在区块链,币链(币的交易签名历史),还隐藏着一个很少被提及的链条——权益调节器链条。

值得一提的是,Sunny King是在PPC后来的版本才加入这个调节器的,一开始他使用nBits。

txPrev:Kernel对应的前一笔交易。

txPrev.block.nTime:txPrev所在区块的时间戳,一笔交易被纳入区块的时间是交易发起者不能确定的,节点有可能通过提前计算预估到未来对自己有利的时间戳,这个参数就是为了防止节点利用这种预估优势提前生成大批交易。

txPrev.offset:txPrev在区块中的偏移量,用以降低网路节点同时生成coinstake的概率。

txPrev.nTime:txPrev构造时间,设计目标如txPrev.offset。

txPrev.vout.n:Kernel在txPrev中的输出下标,设计目标如txPrev.offset。

再看等式右边,
bnTarget:全网当前目标难度基准值,类似POW中的当前难度值,由nbits记录。
nCoinDayWeight:Kernel的币龄。

由以上式子可知,Sunny King一方面希望能给POS矿工提供充足的随机性,另一方面搜索空间严格局限于Coinstake的时间戳字段,以保证影响找到合格区块链的最大因素是Kernel的币龄。

节点在锻造区块时,首先从自己所有的UTXO中选定一个作为Kernel,构造coinstake,计算hash,如果不合格,重新构造coinstake,重构时时间戳Time会改变,也可以改变Kernel,以得到不同的Coinstake,如此往复,直到找到合格区块。

Coinage

上面提到了币龄,也叫币天,假如1.5个币存在于区块链中10天,币龄数值为:

Coinage = 1.5*10 = 15

PPC采用币龄,而不是直接采用余额(Balance)来计算。一个UTXO一旦被花费,其币龄被清零,新的UTXO币龄从0开始算起。

stakeReward

权益激励,俗称获得利息,计算公式如下:

stakeReward = nCoinAge * 33 / (365 * 33 + 8) * 0.01 * COIN

公式可简化为:

stakeReward = (0.01 * nCoinAge / 365) * COIN

其中nCoinAge是Coinstake所有输入的币龄总和,由公式可知收益按1%年率计算。理想状态下,假设所有的币全年都参与挖矿,代币总量每年有1%通胀率,这一设计为很多人所诟病,而且,这一设计并不能激励矿工积极参与挖矿以维护区块链的安全,因为如果不考虑手续费,持币用户每隔几个月打开节点铸币,或者实时在线铸币,理论上收益都是一样的。

stakeMinAge

POS系统也存在51%币龄攻击风险,为了增加攻击难度,Sunny King对每一笔UTXO的铸币资格做了最小年龄(stakeMinAge)限制:一个UTXO在区块链存在的时间小于stakeMinAge则没有铸币资格,PPC最小币龄为8小时。

后来有些竞争币种加入了最大年龄(stakeMaxAge)限制:一个UTXO在区块链存在的时间大于stakeMaxAge则币龄始终按stakeMaxAge计算。

在Sunny King设计的POS机制中,一笔UTXO就像是一个矿工,该矿工每成功铸造一个区块后必须休息一段时间,因此,整套系统必须保证足够多的“矿工”同时在线铸造区块,才有可能获得平滑的出块速度。

 

Nextcoin

 

2013年9月,一个名为BCNext的用户在Bitcointalk论坛发起一个帖子,宣布将发行一种全新的纯POS币种,后来取名为Nextcoin,简称NXT。与当时其他山寨币直接Fork自Bitcoin源码的开发思路不同,BCNext另起炉灶,采用JAVA语言从头开发NXT,并对区块结构,交易结构,非对称密码等做了很多改进。NXT有很多创新点,这里只讨论其中最重要的创新——透明锻造(Transparent Forging)。

NXT的POS实现方式与PPC完全不同,合格区块判定方法为:

hit < baseTarget * effectiveBalance * elapseTime

其中,
hit
NXT抛弃中本聪的UTXO设计方案,采用账户(Account)余额方案,每一个账号对应一个私钥。每一个区块都有一个生成签名(generationSignature)字段,hit的生成与这个字段有关。当用户需要锻造区块时,首先计算自己独一无二的hit,计算过程如下:

用户用自己的私钥对上一个区块的generationSignature进行签名,获得自己本区块的generationSignature。
对上一步结果进行SHA256运算,得hashdata。
取hashdata的前8个字节(共64比特位)作为hit变量。

生成签名的设计有点类似于PPC的stakeModifier,也就是说,NXT区块链下隐藏着一个签名链条。

式子右边,
baseTarget:全网难度基准值,这个难度按照每分钟一个区块目标调节。
effectiveBalance:账户有效余额,一笔币转到账户需要足够多的确认才有铸币权利,叫有效余额。
elapseTime:当前时间与上一个区块时间间隔,按照currentTime-lastBlockTime计算。

分析以上式子,如果依然将式子左边视为挖矿,右边视为目标值,可知用户压根就没有搜索空间,因为当全网产生一个最新区块时,对于锻造下一个区块,每个用户自身的hit就固定了。式子右边,每个用户的目标值与自身的账户有效余额成正比关系,而且,随着时间往前推移,目标值不断变大,不等式最终一定会成立,即理论上每个节点都可以挖那个区块,但规定优先选择最早生成的区块。
p3

用上图类比NXT的锻造机制,每个圆柱体自身高度(hit)是固定的,假设限高杆不断升高(目标值target随着时间不断增大),最终所有圆柱体都能通过(合格区块),但高度最矮的圆柱体可以率先通过。

节点段造区块流程为:账户必须实时在线,当全网有最新区块产生时,每个账户立即计算自己对应的hit,然后根据公式elapseTime = hit/(baseTaret * effectiveBalance)计算得知自己锻造区块的期望时间值,并将这个期望时间广播给网络其他节点,如此,全网每个节点都知道其他节点的期望时间,从而也就得知下一个区块优先由谁来锻造。账户在自己的时间窗口锻造好区块并立即广播全网,其他节点检验一个新区块是否有效,首先要检验证区块的生成签名是否有效,还要检验新区块的时间戳是否与产生区块的节点之前发布的期望时间吻合。每次客户端检测到网络中有新的区块产生,都会重新计算自己的期望时间并向全网发布。

因为hit是用户用自己的私钥签名的结果,因此对于不同用户来说具有很大随机性,即便余额很少的用户,如果运气足够好,hit值很小,也有可能快速锻造区块。

NXT区块的生成完全摒弃了竞争的理念,有点“上帝早已安排好一切”的味道,下一个区块由谁来生成冥冥中早就注定了,全网节点能做的就是静静等待那一刻的到来。

p4

如图,如果节点A没有在自己的锻造时间窗口内广播区块怎么办,没问题,网络会等B的区块,但是如果A和B间隔不远,或者由于网络传输原因部分节点先收到A的区块,部分节点先收到B的区块呢,网络就分叉了,此时Bestchain的原则依然是首选最长的链条,长度一致的分支,优先选择最高区块时间戳最小的分支。那如果节点对所有分支都锻造并广播区块呢,那就变成了一种攻击行为,网络最新区块附近的分叉会加剧。缓解问题的办法是让节点只挖最优分支,这一点没法体现在协议中,只能依靠诚实节点的自律。

摒弃了竞争的理念,NXT共识不得不高度依赖于时间轴,节点虽可预知自己在未来何时可生成区块,但必须要等到那个时候才能广播区块,如果节点提前广播,网络其他节点将不会接受,BCNext在客户端实现上做了限制:对于最新区块,客户端只接受本机当前时间前后15秒范围内广播的区块,这种限制也没法体现在协议上,只能依靠客户端实时辅助实现。

也难怪,NXT代币全部预挖,如果采用类似比特币那样由矿工慢慢发行模式,免不了竞争段造区块,而一旦竞争,区块链将立即陷入分叉。NXT整套共识规则的成功运行其实背后有一个潜在的利益博弈,即,持币者就是系统使用者,也是系统的受益者,大家应该联合起来共同维护区块链,做一个诚实的节点。

也许你会想到一种攻击方法:即便手里持币量很少,但可通过生成大批账号并往每个账号转少量币,以每次都能找到很小的hit,也能快速锻造区块,如此一来POS就退化到类似POW的尴尬境地。BCNext首先从非对称签名算法下手,采用ED25519代替比特币的ECDSA,前者的计算难度比后者大。此外成熟期提高到1440个区块(1天),即一个账号有效余额一旦成功锻造一个区块,该部分余额需要等1天才能重新获得锻造资格。

短暂的分叉还是不可避免的,NXT最新区块附近会有很多分支,一笔交易需要多一些确认才足够安全,NXT官方推荐10个确认。

 

POS2.0

 

PPC的成功运行很快就吸引了一批追随者,其中较为出名的包括新星币(Novacoin,NVC)、黑币(blackcoin,BLK)等。黑币社区认为币龄可能会被恶意的节点滥用以获得更高的网络权重并成功实施双花攻击,于是发布POS2.0白皮书,对PPC做了几个细节优化,解决了一些潜在的安全问题,其中最重要的改进是用余额代替币龄,合格区块的条件由:

F(Timastamp) < Target * 币数 * 币的年龄

变为:

F(Timastamp) < Target * 币数

如此一来,一笔UTXO无论放置多久其锻造区块的能力不变,此举可激励节点必须更多的保持在线进行铸币,提高系统安全性,将攻击途径减少到最低限度,并且能够显著提高网络保持运行的节点数量。

 

POS3.0

 

黑币社区后来进一步升级,推出POS3.0版本,对交易手续费,难度调整做了一些优化,其中最显著的改变是将1%年利率奖励机制变为固定数额奖励(每个区块固定奖励1.5BLK),此举不但降低代币通胀率(考虑到会有代币永久丢失,低额奖励机制回归总量恒定的设计思路),同时意味着持币节点必须实时在线才能获得收益。

 

DPOS

 

比特股(Bitshares)项目于2013年8月开始启动,这是一个野心勃勃的项目,对区块链做了很多改造,并引入许多新概念和特征,尤其令人眼花缭乱的 Bitshares X、多态数字资产交易平台、资产锚定等新名词,一时令人无比兴奋而又困惑。此时POW和POS都已成功运行许久,彼此优劣已被反复讨论,两大阵营时至今日依然争论不休。按照项目规划,比特股对交易容量和区块速度有极高要求,显然POW或POS都达不到要求,于是比特股发明了一种新的共识机制——Delegated Proof-Of-Stake(DPOS),即股份授权股权证明。

DPOS很容易理解,类似于现代企业董事会制度,比特股系统将代币持有者称为股东,由股东投票选出101名代表,然后由这些代表负责产生区块。那么需要解决的核心问题主要有:代表如何被选出,代表如何自由退出“董事会”,代表之间如何协作产生区块等。

持币者若想成为一名代表,需先拿自己的公钥去区块链注册,获得一个长度为32位的特有身份标识符,用户可以对这个标识符以交易的形式进行投票,得票数前101位被选为代表。
代表们轮流产生区块,收益(交易手续费)平分。如果有代表不老实生产区块,很容易被其他代表和股东发现,他将立即被踢出“董事会”,空缺位置由票数排名102的代表自动填补。

从某种角度来说,DPOS可以理解为多中心系统,兼具去中心化和中心化的优势。

 

总结

 

最后从几方面来简单对比分析以上几种共识机制的优劣和特点:

安全性

POW的安全性存在完整的数学证明,这一点是POS和DPOS无可比拟的优势。区块链共识机制一般要同时考虑抵御DDOS攻击和双重支付攻击,POW存在51%算力攻击威胁,比特币目前超强的算力使得破坏该系统需付出巨大代价。POS也会存在51%币龄攻击,而DPOS安全性完全取决于代表的诚实程度。NXT理论可以实现快速交易,但需要锻造节点曝光自己的IP,如此一来容易成为DDOS攻击对象,DPOS的代表也容易成为DDOS攻击对象。

环保性

在不可能三角理论(去中心化,安全,环保不能同时兼备)中,POW彻底抛弃节约能源的需求,通过巨大算力来维护系统安全和去中心化特征。POS和DPOS几乎不费多余电力,但不可避免在另外两个特性做出牺牲。

共识速度

POW很难缩短区块时间,POS相对而言可以缩短区块时间,尤其NXT会比PPC的实现方式更快,DPOS也可以在很短时间内达成共识,比特股目前30秒产生一个区块。不过POS更容易产生分叉,尤其NXT,所以交易需要等更多确认才被认为安全。

交易容量

这是区块链未来发展需要解决的核心问题,巨大的交易容易意味着巨大的带宽和存储空间,POW的交易容量很难扩展,而NXT由于每个节点都可以预知下一个区块由谁锻造,可以直接将交易发给锻造节点,因此NXT交易容量有很大扩展性。从某种角度来说,DPOS可以理解为多中心系统,兼具去中心化和中心化的优点,如果代表节点都运行强大的服务器且彼此带宽足够大,理论上交易处理能力可比拟传统中心化系统,比如Visa。

出块平滑度

POW由于哈希算法特性,可以得到平滑出块速度,而且可以间隔一段时间再调整全网难度,POS出块主要与余额有关,而用户余额差距梯度比较大,所以POS一般每个块都要调整全网基础难度。DPOS依靠有限代表人的协同作用,如果代表人不会频繁进出,几乎可以做到固定死出块间距。

最终性

POW和PPC通过竞争达成共识,不存在最终性,理论上如果有足够算力,现在可以从头挖比特币区块链,不过可以依靠检测点实现最终性。NXT和DPOS严格依赖时间轴,依靠节点实时在线检测,所以存在最终性。

综合各方优势,个人认为POW适合应用于公链,如果搭建私链,因为不存在验证节点的信任问题,可以采用POS比较合适,而联盟链由于存在不可信局部节点,采用DPOS比较合适。

基于以太坊的区块链浏览器搭建

区块链目前发展很火,有很大发展前景,本文主要是在本地私有链搭建成功后,利用web3.js、AngularJS和servlet对搭建的私有链上区块信息包括地址、区块信息、交易信息等信息的展示。

效果展示:

相关链接:web3.js api:https://github.com/ethereum/wiki/wiki/JavaScript-API#web3versionnetwork

   AngularJS api:http://www.runoob.com/angularjs/angularjs-http.html

   源代码链接: https://pan.baidu.com/s/1gfpxXkR 密码: 5jai 

代码是包含一些后端java代码,explorer为前端代码,可直接在webstorm运行,需要注意的地方如下:

index.html需要讲Ip地址改为你自己的私有链地址:

 

[html] view plain copy
 
 print?
  1. <script>  
  2. $("#modaltext").text( 'geth --rpc --rpccorsdomain "'+ window.location.protocol + '//' +"10.132.97.27:8545" + '"' );//IP地址改为你启动私有链的地址  
  3. var Web3 = require('web3');  
  4. var web3 = new Web3();  
  5. web3.setProvider(new web3.providers.HttpProvider('http://10.132.97.27:8545'));  
  6. var number=web3.eth.blockNumber;  
  7. console.log(number);  
  8. </script>  

app.js

 

 

[html] view plain copy
 
 print?
  1. .run(function($rootScope,$interval) {  
  2.         var Web3 = require('web3');  
  3.         var web3 = new Web3();  
  4.         web3.setProvider(new web3.providers.HttpProvider('http://10.132.97.27:8545'));<span style="font-family: Arial, Helvetica, sans-serif;">//IP地址改为你启动私有链的地址</span>  
  5.         $rootScope.web3=web3;  
  6.         function sleepFor( sleepDuration ){  
  7.             var now = new Date().getTime();  
  8.             while(new Date().getTime() < now + sleepDuration){ /* do nothing */ }   
  9.         }  
  10.         var connected = false;  
  11.         if(!web3.isConnected()) {  
  12.             $('#connectwarning').modal({keyboard: false, backdrop: 'static'})  
  13.             $('#connectwarning').modal('show')  
  14.         }  
  15.     });  

mainController.js这个对后台数据处理已经对区块的实时刷新将区块数据存入数据库,便于前台读取查看:

 

 

[html] view plain copy
 
 print?
  1. .controller('mainCtrlInit',function($rootScope, $scope, $location,$http,$q,$interval){  
  2.         //$scope.menuState={show: false};  
  3.         $scope.toggleMenu=function(index) {  
  4.             //console.log(index);  
  5.             //$scope.menuState.index=!$scope.menuState.index;  
  6.             $(".closediv").removeClass("opendiv");  
  7.             $(".div"+index).addClass("opendiv").slideToggle("slow");  
  8.         };  
  9.         $scope.blockNum = web3.eth.blockNumber;  
  10.         //获取全部accounts  
  11.         // datainit();  
  12.         /**  
  13.         getAllAddress().then(function(result){  
  14.             var address_data=result;  
  15.             accountinit(address_data);  
  16.         });  
  17.         **/  
  18.         getAllTrancation().then(function(result){  
  19.             $scope.transactions=result;  
  20.         });  
  21.         function getAllTrancation(){  
  22.             var deferred = $q.defer();//声明承诺  
  23.             $http.get('http://10.132.97.27:8080/ethereum/servlet/AddressAction?action=getTransaction')  
  24.                 .success(function(data){  
  25.                     deferred.resolve(data.allAddress);//请求成功  
  26.                 }).error(function(data){  
  27.                     console.log(data);  
  28.                 });  
  29.             return deferred.promise;   // 返回承诺,这里返回的不是数据,而是API  
  30.         }  
  31.         function getAllAddress(){  
  32.             var deferred = $q.defer();//声明承诺  
  33.             $http.get('http://10.132.97.27:8080/ethereum/servlet/AddressAction?action=getAddress')  
  34.                 .success(function(data){  
  35.                     deferred.resolve(data.allAddress);//请求成功  
  36.                 }).error(function(data){  
  37.                     console.log(data);  
  38.                 });  
  39.             return deferred.promise;   // 返回承诺,这里返回的不是数据,而是API  
  40.         }  
  41.         function accountinit(address_data){  
  42.             var result=address_data;//web3.eth.accounts;  
  43.             var accounts=new Array();  
  44.             for(i in result){  
  45.                 var balance = web3.eth.getBalance(result[i]);  
  46.                 var balanceInEther=web3.fromWei(balance, 'ether');  
  47.                 var account=new Object();  
  48.                 account.addressId=result[i];  
  49.                 account.balance=balance.toNumber();  
  50.                 account.balanceInEther=balanceInEther.toNumber();  
  51.                 accounts.push(account);  
  52.             }  
  53.             $scope.accounts=accounts;  
  54.         }  
  55.         //实时刷新区块数据  
  56.         $interval(function () {  
  57.             var deferred = $q.defer();//声明承诺  
  58.             $http.get('http://10.132.97.27:8080/ethereum/servlet/AddressAction?action=getBlockCount')  
  59.                 .success(function(data){  
  60.                     console.log("获取区块数据请求成功"+data.blockNumber);  
  61.                     //deferred.resolve(data.blockNumber);//请求成功  
  62.                     var blockstart=Number(data.blockNumber);  
  63.                     datainit(blockstart);  
  64.                 }).error(function(data){  
  65.                     console.log(data);  
  66.                     console.log("获取区块数据请求失败");  
  67.                 });  
  68.         }, 120000);  
  69.         //添加address  
  70. //        datainit(1);  
  71.         function  datainit(blockstart){  
  72.             var data=new Array();  
  73.             var transactions=new Array();  
  74.             var blockend=web3.eth.blockNumber;  
  75.             console.log("-----"+blockstart+"========"+blockend);  
  76.             for(var i=blockstart;i<blockend;i++){  
  77.                 var blockinfoweb3.eth.getBlock(i);  
  78.                 //console.log(i+"----"+blockinfo.miner);  
  79.                 // accountinit(blockinfo.miner);  
  80.                 var  block={  
  81.                     blockId:blockinfo.number,  
  82.                     address:blockinfo.miner,//地址信息  
  83.                     transactionArr:blockinfo.transactions.join()//交易地址  
  84.                 };  
  85.                 data.push(blockinfo.miner);  
  86.                 if(block.transactionArr.length>0){  
  87.                    // console.log(block.transactionArr);  
  88.                     transactions.push(block);  
  89.                 }  
  90.             }  
  91.             var address_data=unique(data);  
  92.             //console.log(address_data);  
  93.             if(blockend>blockstart){  
  94.                 addAddressJSON(address_data,transactions,blockend);  
  95.             }  
  96.         }  
  97.         function replaceTrans(transactionArr){  
  98.   
  99.         }  
  100.         //数组去重  
  101.         function unique(arr) {  
  102.             var result = [], hash = {};  
  103.             for (var i = 0, elem; (elem = arr[i]) != null; i++) {  
  104.                 if (!hash[elem]) {  
  105.                     result.push(elem);  
  106.                     hash[elem] = true;  
  107.                 }  
  108.             }  
  109.             return result;  
  110.         }  
  111.         //将查询到的地址存入数据库  
  112.         function addAddressJSON(address_data,transactions,blockend){  
  113.             if(address_data.length>0){  
  114.                 var transactionsstr="";  
  115.                 var url="";  
  116.                 if(transactions.length>0 && transactions.length>12){  
  117.                     var n=transactions.length/12;  
  118.                     var m=transactions.length%12;  
  119.                    // console.log(n+"==="+m);  
  120.                     var h=1;  
  121.                             for(var i=0;i<n;i++){  
  122.                                 var transactionsstr01=JSON.stringify(transactions.slice(12*i,12*(i+1)));  
  123.                                 var uri='http://10.132.97.27:8080/ethereum/servlet/AddressAction?action=addTransaction&transactionsstr='+transactionsstr01;  
  124.                                 $http.post(uri).success(function(){  
  125.                                     console.log("transactionsstr保存成功===整除");  
  126.                                 })  
  127.                             }  
  128.                     if(m>0){  
  129.                         var transactionsstr02=JSON.stringify(transactions.slice(12*n,transactions.length));  
  130.                         var uri='http://10.132.97.27:8080/ethereum/servlet/AddressAction?action=addTransaction&transactionsstr='+transactionsstr02;  
  131.                         $http.post(uri).success(function(){  
  132.                             console.log("transactionsstr保存成功==除余");  
  133.                         })  
  134.                     }  
  135.                 }else if(transactions.length>0 && transactions.length<12){  
  136.                         transactionsstr=JSON.stringify(transactions);  
  137.                          var url='http://10.132.97.27:8080/ethereum/servlet/AddressAction?action=addAddress&address_data='+address_data+'&blockend='+blockend+'&transactionsstr='+transactionsstr;  
  138.                         $http.post(url).success(function(){  
  139.                             console.log("address保存成功");  
  140.                         }).error(function(data) {  
  141.                             console.log("address保存失败");  
  142.                         });  
  143.                     }  
  144.                         var  url='http://10.132.97.27:8080/ethereum/servlet/AddressAction?action=addAddress&address_data='+address_data+'&blockend='+blockend;  
  145.                         $http.post(url).success(function(){  
  146.                             console.log("address保存成功");  
  147.                         }).error(function(data) {  
  148.                             console.log("address保存失败");  
  149.                         });  
  150.             }  
  151.         }  
  152.     });  

 

区块链技术方案研究与分析

作者:电子商务与电子支付国家工程实验室研究员 于镳、刘为怀

一、块链是什么

区块链最初是由一位化名中本聪的人为比特币(一种数字货币)而设计出的一种特殊的数据库技术。
从数据的角度来看,区块链是一种把区块以链的方式组合在一起的数据结构,它能够使参与者对全网交易记录的事件顺序和当前状态建立共识。

区块链概括起来是指通过去中心化和去信任的方式集体维护一个可靠数据库的技术。区块链涉及的技术关键点包括:去中心化、去信任、集体维护、可靠数据库、时间戳、非对称加密等。
区块链重新定义了网络中信用的生成方式,在系统中,参与者无需了解其他人的背景资料,也不需要借助第三方机构的担保或保证,区块链保障了系统对价值转移的活动进行记录、传输、存储,其最后的结果一定是可信的。

二、区块链技术现状

比特币之后,多元化区块链共同发展,经过了近8年的发展与实践,人们对区块链技术的了解越来越深入,也在让区块链技术与自身需求更契合的过程中逐渐明白了以下两点:(1)区块链技术本质上是互联网协议的底层技术,是互联网数据层次的技术;(2)区块链倡导的“去中心”并不是反中心,而是分中心(最彻底的分中心就是每个节点自己成为自身的中心)。因此,许多领域试图在比特币区块链的基础之上对其做进一步的改进。目前,区块链已经从比特币完全去中心化的公共区块链,发展出了依附于公有链之上的侧链以及非完全去中心化的私有区块链等。

区块链技术处于理论阶段,尚需实践。区块链技术是伴随比特币的产生而出现的,目前最成熟的区块链就是比特币的区块链。国内外对于区块链技术的投入使用都已经逐渐展开,但目前尚未有完全落地的应用性成果展现出来。从发展的角度来看,区块链技术目前仍然处于理论阶段,今后的技术转换尚需一段很长时间的实践。

三、区块链共识机制

共识机制的作用是为了确认区块链上交易的有效性,目前常见的共识机制有POW工作量证明、POS权益证明、DPOS股份授权证明、Ripple共识机制以及PooL验证池,这几类共识机制各有优劣,应用场景也各不相同,下表展示了从不同维度对这几类共识机制的分析比较:

  • POW(Proof of work)

POW即工作量证明,就是众所周知的挖矿,它是一种竞争共识,通过工作量的证明来获得生成区块的权力。

它的优点是完全的去中心化,各个节点完全平等,而且可以自由的进出;缺点则是POW会造成大量的挖矿资源浪费,达成共识的周期也比较长。

  • POS(Proof of Stake)

POS即权益证明,是POW的一种升级共识机制,它主要解决了POW工作量计算浪费的问题。当前POS已有很多不同的变种,但基本还是根据每个节点所占有代币的数量和时间(即权益)来决定其挖矿的难度。

它的优点是缩短了各个节点之间达成共识的时间,缺点则是同POW一样仍然需要挖矿,并且POS会使得“富者更富”。

  • DPOS(Delegate Proof of Stake)

DPOS即股份授权证明机制,是POS的一个变种。它的原理是让每个持有代币的人进行投票,由此产生一定数量的“超级节点”,由这些节点来轮流产生区块。

它的优点是不再需要通过挖矿来产生区块,从而可以大幅缩小交易确认的时间,能够达到秒级的共识验证,缺点则是DPOS还是得依赖于代币,不适用于一般的商业应用。

  • Ripple Consensus(瑞波共识机制)

Ripple Consensus是一种数据正确性优先的网络交易同步机制,它是基于特殊节点列表达成的共识。在这种共识机制下,必须首先确定若干个初始特殊节点,如果要新接入一个节点,必须获得51%的初始节点的确认,并且只能由被确认的节点产生区块。因此,它区别于前面几类共识机制的主要因素是有一定的“中心化”。

Ripple Consensus的优点是能保证任何时候都不会产生硬分叉,并且交易能被实时的验证;而缺点则是新加入节点要取得与其他节点的共识所需时间较长。

  • POOL验证池

POOL验证池是基于传统分布式一致性算法加上数据验证的机制,也是目前行业链大范围在使用的共识机制。

它的优点是无需建立代币,在成熟的分布式一致性算法(Pasox、Raft)的基础上,能够实现实时共识验证;缺点是性能会随着节点数的增加而变差,并且去中心化程度不够。

除了常见的以上所述的几类共识机制,在区块链的实际应用过程中,还存在着五花八门的依据业务逻辑自定义的共识机制,如小蚁的“中性记账”、类似Ripple Consensus的Stellar共识机制、Factom等众多以“侧链”形式存在的共识机制等。
因此,本文依据共识机制的特性及典型性将其划分为三大类:POW、非POW以及侧链。

四、区块链技术方案分析

区块链技术发展到今天,已经从最初的数字货币领域扩展到社会领域的方方面面,各种各样的区块链技术方案不断涌现出来。通过对市场上现有的区块链技术方案进行研究,从共识机制、区块链使用方式两个维度对区块链技术方案进行分类,并根据技术影响力决定面积大小、技术成熟度决定颜色深浅对各个区块链技术方案进行绘制,分类结果如图1所示。


图1 区块链技术方案分类 
图1中,对15个区块链技术方案进行分类,其中,将共识机制划分为PoW、非PoW、侧链三个方面,区块链使用方式分为开源项目、商业解决方案、区块链云服务、现有区块链四种方式,技术影响力按面积大小进行区分,面积越大影响力越大,技术成熟度按颜色深浅进行区分,颜色越深技术越成熟。
从图1中可以看出,比特币、以太坊、HyperLedger三种区块链技术方案的技术成熟度最高,影响力最大,下面分别介绍这三种区块链技术方案。

1比特币
比特币的概念最初由中本聪在2009年提出,根据中本聪的思路设计发布的开源软件以及建构其上的P2P网络。比特币是一种P2P形式的数字货币。点对点的传输意味着一个去中心化的支付系统。

与大多数货币不同,比特币不依靠特定货币机构发行,它依据特定算法,通过大量的计算产生,比特币使用整个P2P网络中众多节点构成的分布式数据库来确认并记录所有的交易行为,并使用密码学的设计来确保货币流通各个环节安全性。P2P的去中心化特性与算法本身可以确保无法通过大量制造比特币来人为操控币值。基于密码学的设计可以使比特币只能被真实的拥有者转移或支付。这同样确保了货币所有权与流通交易的匿名性。比特币与其他虚拟货币最大的不同,是其总数量非常有限,具有极强的稀缺性。

2以太坊
以太坊是一个专注于智能合约应用的数字货币和区块链平台,它的去中心化技术使交易摆脱了停机和审查的麻烦,解决了传统合约的纠纷等棘手问题,并且还能避免欺诈和第三方的干扰。以太坊的目标就是提供一个带有内置的成熟的图灵完备语言的区块链,用这种语言可以创建合约来编码任意状态转换功能,用户只要简单地用几行代码来实现逻辑,就能够创建各种满足需求的系统。
从技术架构角度看,以太坊是一个与底层区块链和协议无关的通用分布式运用开发平台和编程语言,包括数字货币以太币(Ether)和以太脚本(EtherScript),用于构建和发布分布式应用。它具备开放通用的特性,且内置有图灵完备虚拟机,可以运用任何货币、协议和区块链。网络上的每一个节点都可以运行以太坊虚拟机来发布分布式智能合约程序。

以太坊有自己的分布式系统:包括文件服务Swarm、信息传输Whisper和信誉担保。Swarm是个去中心化文件服务;Whisper是加密通信传输系统;信用担保提供去信任网络中建立信誉和降低发现的系统,可以由第三方提供。

此外,以太坊本身也是开源软件,具备了开源项目的快速创新特性,通过代码共享让新的项目可以快速的迭代出符合市场需求的产品。目前,以太坊正被一些金融机构、银行财团(比如 R3),以及类似三星、Deloitte、RWE 和 IBM 这类的大公司所密切关注,由此也催生出了一批诸如简化和自动化金融交易、商户忠诚指数追踪、旨在实现电子交易去中心化的礼品卡等等区块链应用。
以太坊两年来得到了技术社区和商业机构的大力支持,发展迅速,未来很有可能会成为区块链领域最具竞争力的解决方案之一。

3HyperLedger
Hyperledger是一个由Linux基金会管理的开源区块链项目, 由IBM、Intel、埃森哲、JP摩根等公司领衔参与。HyperLedger被定义为:负责协调多个客户端的服务数据和低层次的“沟通和共识层”,一个致力于提高全球金融基础设施的“数据骨干”。

Hyperledger利用了和比特币相同的UTXO/script 交易决策,并根据金融服务所需要的功能进行了扩展,HyperLedger提供了一种替代POW的共识机制,是一个带有可插拔各种功能模块架构的区块链实施方案,它的目标打造成一个由全身会来共同维护的一个超级账本。是允许将数据骨干理念扩展到多个组织层,作为全新金融基础设施的重要组成部分。

为了解决不同的业务问题,区块链技术方案的侧重点也不尽相同,因此,从去中心化程度、交易确认时间、是否有挖矿费、是否支持编程、是否全节点记账、私钥丢失是否造成用户财产损失、开源程度等几个方面对现有的区块链技术方案进行汇总,具体分析结果见下表。

如何部署、调用智能合约

 

RPC

之前的章节中我们看到了怎么写、部署合约以及与合约互动。现在该讲讲与以太坊网络和智能合约沟通的细节了。

一个以太坊节点提供一个RPC界面。这个界面给Ðapp访问以太坊区块链的权限和节点提供的功能,比如编译智能合约代码,它用JSON-RPC 2.0规范(不支持提醒和命名的参数) 的子集作为序列化协议,在HTTP和IPC (linux/OSX上的unix域接口,在Windows上叫pipe’s)上可用。

如果你对细节不感兴趣,正在寻找使用javascript库的简便方法,你可以略过下面的章节,从Using Web3继续。

惯例

RPC界面使用一些惯例,它们不是JSON-RPC 2.0规范的一部分:

  • 数字是十六进制编码。做这个决定是因为有些语言对运行极大的数字没有或有很少的限制。为了防止这些错误数字类型是十六进制编码,由开发者来分析这些数字并正确处理它们。在维基页百科查看十六进制编码章节查看案例。
  • 默认区块数字,几个RPC 方法接受区块数字。在一些情况下,给出区块数字是不可能的或者不太方便。 在那样的情况下,默认区块数字可以是以下字符串中的一个[”earliest”, “latest”, “pending”]。在维基页面查看使用默认区块参数的RPC方法列表。

部署合约

我们会通过不同的步骤来部署下面的合约,只用到RPC界面。

1 2 3 4 5 6 7
contract Multiply7 {    event Print(uint);    function multiply(uint input) returns (uint) {       Print(input * 7);       return input * 7;    } }

要做的第一件事是确保HTTP RPC界面可用。这意味着我们在开始为geth供应—rpc标志,为eth提供-j标志。在这个例子中我们用私有开发链上的geth节点。通过这种方法,我们就不需要真实网络上的以太币了。

1
> geth --rpc --dev --mine --minerthreads 1 --unlock 0 console 2>>geth.log

这会在http://localhost:8545 上启动HTTP RPC界面。

注意:geth支持CORS查看—rpccorsdomain标志了解更多。

我们可以通过用curl检索coinbase地址和余额来证明界面正在运行。请注意这些例子中的数据在你本地的节点上会有所不同。如果你想要试试这些参数,视情况替换需要的参数。

1 2 3 4
> curl --data '{"jsonrpc":"2.0","method":"eth_coinbase", "id":1}' localhost:8545 {"id":1,"jsonrpc":"2.0","result":["0xeb85a5557e5bdc18ee1934a89d8bb402398ee26a"]} > curl --data '{"jsonrpc":"2.0","method":"eth_getBalance", "params": ["0xeb85a5557e5bdc18ee1934a89d8bb402398ee26a"], "id":2}' localhost:8545 {"id":2,"jsonrpc":"2.0","result":"0x1639e49bba16280000"}

记不记得我们说过数字是十六进制编码?在这个情况下,余额作为十六进制字符串以Wei的形式返还。如果我们希望余额作为数字以太币为单位,我们可以从控制台用web3。

1 2
> web3.fromWei("0x1639e49bba16280000", "ether") "410"

现在我们在私有开发链上有一些以太币,我们就可以部署合约了。第一步是验证solidity编译器可用。我们可以用eth_getCompilers RPC method方法来检索可用的编译器。

1 2
> curl --data '{"jsonrpc":"2.0","method": "eth_getCompilers", "id": 3}' localhost:8545 {"id":3,"jsonrpc":"2.0","result":["Solidity"]}

我们可以看到solidity编译器可用。如果不可用,按照这些说明操作。

下一步是把Multiply7合约编译到可以发送给以太坊虚拟机的字节代码。

1 2
> curl --data '{"jsonrpc":"2.0","method": "eth_compileSolidity", "params": ["contract Multiply7 { event Print(uint); function multiply(uint input) returns (uint) { Print(input {"id":4,"jsonrpc":"2.0","result":{"Multiply7":{"code":"0x6060604052605f8060106000396000f360606040

现在我们有了编译代码,需要决定花多少gas去部署它。RPC界面有eth_estimateGas方法,会给我们一个预估数量。

1 2
> curl --data '{"jsonrpc":"2.0","method": "eth_estimateGas", "params": [{"from": "0xeb85a5557e5bdc18ee1934a89d8bb402398ee26a", "data": "0x6060604052605f8060106000396000f3606060405260e060020a6000350463c6888fa18114601a575b005b60586004356007810260609081526000907f24abdb5865df5079dcc5ac590ff6f01d5c16edbc5fab4e195d9febd1114503da90602090a15060070290565b5060206060f3"}], "id": 5}' localhost:8545 {"id":5,"jsonrpc":"2.0","result":"0xb8a9"}

最后部署合约。

1 2
> curl --data '{"jsonrpc":"2.0","method": "eth_sendTransaction", "params": [{"from": "0xeb85a5557e5bdc18ee1934a89d8bb402398ee26a", "gas": "0xb8a9", "data": "0x6060604052605f8060106000396000f3606060405260e060020a6000350463c6888fa18114601a575b005b60586004356007810260609081526000907f24abdb5865df5079dcc5ac590ff6f01d5c16edbc5fab4e195d9febd1114503da90602090a15060070290565b5060206060f3"}], "id": 6}' localhost:8545 {"id":6,"jsonrpc":"2.0","result":"0x3a90b5face52c4c5f30d507ccf51b0209ca628c6824d0532bcd6283df7c08

交易由节点接受,交易散表被返还。我们可以用这个散表来跟踪交易。

下一步是决定部署合约的地址。每个执行的交易都会创建一个接收。这个接收包含交易的各种信息,比如交易被包含在哪个区块,以太坊虚拟机用掉多少gas。如果交易创建了一个合约,它也会包含合约地址。我们可以用eth_getTransactionReceipt RPC方法检索接收。

1 2
> curl --data '{"jsonrpc":"2.0","method": "eth_getTransactionReceipt", "params": ["0x3a90b5face52c4c5f30d507ccf51b0209ca628c6824d0532bcd6283df7c08a7c"], "id": 7}' localhost:8545 {"id":7,"jsonrpc":"2.0","result":{"transactionHash":"0x3a90b5face52c4c5f30d507ccf51b0209ca628c682

我们可以看到合约在0x6ff93b4b46b41c0c3c9baee01c255d3b4675963d上被创建。如果你得到了零而不是接收,说明还没有被纳入区块。等一下,检查看看你的矿工是否在运行,重新试一遍。

和智能合约互动

现在已经部署了合约,我们可以和它互动了。有两种方法,发送交易或像之前所介绍的那样使用调用。在这个例子中,我们会发送交易到合约的multiply方法。

如果我们看eth_sendTransaction 的档案,可以发现我们需要提供几个参数。

在我们的实例中,需要具体说明from, to 和data参数。From是我们账户的公共地址,to是合约地址。Data参数有一点困难。它包括了规定调用哪个方法和哪个参数的负载量。这就需要ABI发挥作用了。ABI规定了如何为以太坊虚拟机规定和编码数据。你可以在这儿阅读ABI的所有具体信息。

负载量的字节是功能选择符,规定了调用哪个方法。它取Keccak散表的头4个字节,涵盖功能名称参数类型,并进行十六进制编码。multiply功能接受一个单元,也就是uint256的别名。这就让我们进行:

1 2
> web3.sha3("multiply(uint256)").substring(0, 8) "c6888fa1"

在此页查看细节。

下一步是编码参数。我们只有一个unit256,让我们假定提供了值6。ABI有一个章节规定了怎么编码uint字节。

int: enc(X) is the big-endian two’s complement encoding of X, padded on the higher-oder (left) side with 0xff for negative X and with zero 字节s for positive X such that the length is a multiple of 32 bytes.

它编码到0000000000000000000000000000000000000000000000000000000000000006. 将功能选择符和编码参数结合起来,数据就会变成0xc6888fa10000000000000000000000000000000000000000000000000000000000000006.

我们来试一下:

1 2
> curl --data '{"jsonrpc":"2.0","method": "eth_sendTransaction", "params": [{"from": "0xeb85a5557e5bdc18ee1934a89d8bb402398ee26a", "to": "0x6ff93b4b46b41c0c3c9baee01c255d3b4675963d", "data": "0xc6888fa10000000000000000000000000000000000000000000000000000000000000006"}], "id": 8}' localhost:8545 {"id":8,"jsonrpc":"2.0","result":"0x759cf065cbc22e9d779748dc53763854e5376eea07409e590c990eafc0869

由于我们发送了交易,于是有交易散表返回。如果我们检索接收,可以看到一些新内容:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
{ blockHash: "0xbf0a347307b8c63dd8c1d3d7cbdc0b463e6e7c9bf0a35be40393588242f01d55", blockNumber: 268, contractAddress: null, cumulativeGasUsed: 22631, gasUsed: 22631, logs: [{       address: "0x6ff93b4b46b41c0c3c9baee01c255d3b4675963d",       blockHash: "0xbf0a347307b8c63dd8c1d3d7cbdc0b463e6e7c9bf0a35be40393588242f01d55",       blockNumber: 268,       data: "0x000000000000000000000000000000000000000000000000000000000000002a",       logIndex: 0,       topics: ["0x24abdb5865df5079dcc5ac590ff6f01d5c16edbc5fab4e195d9febd1114503da"],       transactionHash: "0x759cf065cbc22e9d779748dc53763854e5376eea07409e590c990eafc0869d74",       transactionIndex: 0   }],   transactionHash: "0x759cf065cbc22e9d779748dc53763854e5376eea07409e590c990eafc0869d74",   transactionIndex: 0 }

接收包含一个日志。日志由以太坊虚拟机在交易执行时生成,包含接收。如果我们看Multiply功能,可以看到打印事件和输入次数7一起被提出。由于打印事件的参数是uint256,我们可以根据ABI规则对它进行编码,这样我们就会得到预期的十进制42.。除数据外,主题用于决定什么事件来创建日志,是毫无意义的:

1 2
> web3.sha3("Print(uint256)") "24abdb5865df5079dcc5ac590ff6f01d5c16edbc5fab4e195d9febd1114503da"

你可以在Solidity教程中阅读更多关于事件,主题和索引的内容。

这只是对一些最常见任务的简单介绍。在RPC维基页面查看可用RPC方法的完整列表。

Web3.js

正如我们在之前的案例所见,使用JSON-RPC界面相当单调乏味且容易出错,尤其是在处理ABI的时候。Web3.js是javascript库,在以太坊RPC界面顶端。它的目标是提供更友好的界面,减少出错机会。

用web3部署Multiply7合约看起来是这样:

1 2 3 4 5 6 7 8 9 10
var source = 'contract Multiply7 { event Print(uint); function multiply(uint input) returns (uint var compiled = web3.eth.compile.solidity(source); var code = compiled.Multiply7.code; var abi = compiled.Multiply7.info.abiDefinition; web3.eth.contract(abi).new({from: "0xeb85a5557e5bdc18ee1934a89d8bb402398ee26a", data: code}, func    if (!err && contract.address)       console.log("deployed on:", contract.address);    } ); deployed on: 0x0ab60714033847ad7f0677cc7514db48313976e2

装载一个部署的合约,发送交易:

1 2 3 4 5
var source = 'contract Multiply7 { event Print(uint); function multiply(uint input) returns (uint) { Print(input var compiled = web3.eth.compile.solidity(source); var Multiply7 = web3.eth.contract(compiled.Multiply7.info.abiDefinition); var multi = Multiply7.at("0x0ab60714033847ad7f0677cc7514db48313976e2") multi.multiply.sendTransaction(6, {from: "0xeb85a5557e5bdc18ee1934a89d8bb402398ee26a"})

注册一个回调,打印事件创建日志的时候会被调用。

1 2
multi.Print(function(err, data) { console.log(JSON.stringify(data)) }) {"address":"0x0ab60714033847ad7f0677cc7514db48313976e2","args": {"":"21"},"blockHash":"0x259c7dc0

在web3.js维基页面查看更多信息。

控制台

geth控制台提供命令行界面和javascript执行时间。它可以连接到本地或远程的geth或eth节点。它会装载用户能使用的web3.js库。这会允许用户从控制台用web3.js部署智能合约并和智能合约互动。实际上Web3.js章节的例子可以被复制进控制台。

查看合约与交易

有几个可用的在线区块链浏览器,能让你查询以太坊区块链。

查看列表:区块链浏览器。

在线区块链浏览器

  • EtherChain
  • EtherCamp
  • EtherScan (为测试网)

其他资源

  • EtherNodes – 节点的地理分配,由客户端区分
  • EtherListen – 实时以太坊交易可视器和可听器

集成开发环境(IDE) Mix介绍

 

Mix

IDE Mix旨在作为开发者帮你创建,排错和部署合约及去中心化应用(后端和前端的合约)

警告 – 有很多OS X上关于Mix的crash-at-boot 事件报告。这个事件是Heisenbug,我们已经跟踪了一两个月。我们现有的最佳变通方案是用排错配置,像这样:

1
cmake -DCMAKE_BUILD_TYPE=Debug ..

警告 – 正在研究一个Mix的替代物,叫做Remix。如果你在Mix经历了事件,在Remix更成熟之前,你最好寻找替代方案。

从创建一个新项目开始,它包括

  • 合约
  • html 文件
  • JavaScript 文件
  • style 文件
  • image 文件

项目编辑器

你可以用项目来管理去中心化应用的创建和测试。项目会包括与后端和前端相关的数据以及和你的场景(区块链互动)相关的数据,用来排错和测试。相关文件 会被创建并自动保存到项目目录中。

创建一个新项目

去中心化应用的开发始于新项目的创建。在“编辑”菜单创建一个新项目。进入项目名称,比如 “等级”,选择项目文件路径。

编辑后端合约文件

一个新项目默认包含一个合约,“Contract”用于使用Solidity语言在区块链后端开发,“index.html”用于前端。查看Solidity教程或参考。 编辑空的默认合约“Contract”,比如

1 2 3 4 5 6
contract Rating {   function setRating(bytes32 _key, uint256 _value) {     ratings[_key] = _value;     mapping (bytes32 => uint256) public ratings;   } }

查看Solidity教程寻求帮助,以solidity编程语言开始。

保存变动

编辑前端html文件 选择默认index.html文件并输入以下代码

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
 .... <script> function getRating() {   var param = document.getElementById(query).value;   var res = contracts[Rating].contract.ratings(param);   document.getElementById(queryres).innerText = res; } function setRating() {         var key = document.getElementById("key").value;         var value = parseInt(document.getElementById("value").value);         var res = contracts["Rating"].contract.setRating(key, value); } </script>     </head>     <body bgcolor="#E6E6FA">         <h1>Ratings</h1> <div> Store:             <input type="string" id="key">             <input type="number" id="value">             <button onclick="setRating()">Save</button> </div> <div>             Query:             <input type="string" id="query" onkeyup='getRating()'>             <div id="queryres"></div>         </div>     </body> </html> 

有可能添加很多合约文件和HTML, JavaScript, css文件

场景编辑器

场景可以用于检测合约和为合约排错。

一个场景就是一个本地区块链,区块可以在这里被挖矿而不需要工作量证明 –否则测试会很慢 ;)

一个场景包含一系列交易。通常,一个场景以去中心化应用的合约创建场景开始。 此外,进一步的交易会被添加测试并对去中心化应用排错。场景可以修正,例如,移除交易。注意如果要修正一个场景使之有效,需要进行重建。可以通过JS API用JS进行进一步测试。

万一打不开,可以点击F7或Windows > 展示右侧或主界面右上方的排错按钮来进入场景和排错器窗格。

创建和设置新场景

第一次启动Mix的时候,会创建一个空场景,比如,不包含任何交易。添加账户并命名为“MyAccount”,设置初始余额为1以太币。点击确认。将场景重命名为“Deploy”。

修正账户的初始以太币余额

实际上我们想做很多测试。编辑初始区块参数,将初始账户余额设置为1000 以太币。重建场景使它变得高效。

重建场景

每次交易被修正或账户被添加的时候,场景必须重建以修正使之高效。注意如果一个场景重建,网络前端(本地存储)可能也需要重置(Mix不会自动操作)。

创建交易

我们发送一些以太币给Bob。创建另一个名为“Bob”的账户,余额为0。在场景窗格创建一个新交易。点击“Add Tx…”,发送300以太币给Bob。添加一个区块。

改变和重新使用场景

创建一个新场景或从一个场景开始,和几个你先复制的交易

重命名场景

通过指定要被移除的交易来修正场景

重建场景

展示调用

一个合约调用就是一个功能调用。这不是交易, 因为合约调用不会改变状态。合约调用不是区块链的一部分,但是出于实用和用户体验设计的原因,在交易的同一功能层次展示调用很方便。JS图标提醒你这不是个交易而是个调用。要展示/隐藏调用,点击菜单里的场景 –> 展示调用。

状态查看器

这个面板区块在区块链面板下面,场景视图中。一旦区块链被运行,这个面板会展示区块链状态。

说到状态,我们意味着所有账户余额(包括合约和正常账户),存储(所有部署合约的全局变量)。这个面板的内容不是动态的,它取决于在区块链面板上选择的交易。这里展示的状态是执行所选择交易的状态结果。

在那种情况下,部署了2个合约,选择的(testCtr的部署)是最后一个。状态视图展示了TestCtr和BasicContract的存储。

交易浏览器

使用交易窗格

交易窗格使你能够探索交易接收,包括

  • 输入参数
  • 返回参数
  • 事件日志

要显示交易浏览器,点击每个交易右侧的倒三角图标,会扩展出交易详情: 

然后你可以复制剪贴板上的交易内容,编辑当前交易(然后你要重新运行区块链), 或为交易排错。

JavaScript console

Mix将以下对象暴露在全局窗口的语境下

web3 – 以太坊JavaScript API

合约:合约对象集合。钥匙代表合约名称。值是包含以下属性的对象:

  • 合约: 合约对象实例 (像在web3.eth.contract里一样创建)
  • 地址: 上一个部署状态的合约地址(查看下面)
  • 界面: 合约ABI

查看JavaScript API 参考了解更多信息。

用JS控制台添加交易和本地调用

如果合约名字是“Sample”,功能名字是“set”,有可能进行一个交易来调用 “set”,方法是写:

1
contracts["Sample"].contract.set(14) 

如果调用可以进行,可以通过写以下命令完成:

1
contracts["Sample"].contract.get.call() 

也有可能用web3对象的所有属性和功能: https://github.com/以太坊/wiki/wiki/JavaScript-API

交易排错器

Mix支持Solidity和组件级别合约代码排错。你可以在两个模式中切换,检索你所需要的相关信息。

在任何执行阶段,以下信息都可用:

虚拟机堆栈 – 查看黄皮书获取虚拟机操作指南描述 调用堆栈 – 合约调用到另一个合约时会生长。双击堆栈框架来查看机器在框架里的状态。 存储 – 与合约相关的存储数据 内存 – 分配到这个执行点的机器内存 调用数据– 交易或调用参数

进入排错模式

交易详情扩展后,你可以转换到排错视图,点击“交易排错”按钮。

在排错模式和单步调试交易之间切换

这里打开了Solidity排错模式。用菜单按钮(排错 –> 显示虚拟机代码)在Solidity和以太坊虚拟机排错模式之前转换

  • 在solidity排错模式下单步调试交易
  • 在以太坊虚拟机排错模式下单步调试交易

Dapps部署

这个功能能够让用户在主区块链将当前的项目作为去中心化应用部署。

这会部署合约和登记前端资源。

部署过程包括3步:

  • 部署合约: 这一步会在主区块链部署合约。
  • 打包Dapp: 这一步用于打包和上传前端资源。
  • 注册: 要变成去中心化应用,以太坊浏览器(Mist或AlethZero)需要进入这个包裹。这一步会在资源存储的地方注册URL。

要部署去中心化应用,请遵守以下指令:

点击Deploy, Deploy to Network

这个模式对话框会显示三部分(参见以上):

  • 部署合约
  • 选择场景

“以太坊节点URL”是节点运行的位置,为了发起部署,一定会有一个节点在运行。

“选择部署场景”是强制步骤。Mix会执行选定场景里的交易(除与合约创建或合约调用不相关的所有交易)。Mix会在下面的面板上展示所有的交易和所有相关参数。

“使用的Gas”:取决于所选定的场景,Mix会展示使用的gas总量。

  • 部署场景

“部署账户”允许选择Mix会用于执行交易的账户。

“Gas价格”显示网络默认的gas价格。你也可以指定一个不同的值。

“部署成本”:取决于你想使用的以及选定场景的gas价格的值。这会显示部署所需的以太币数量。

“部署的合约”:没发生部署之前,这部分是空的。部署一完成,这部分就会被所有创建的合约地址填满。

“验证”:这会显示验证数量(在最后一个区块顶部生成的区块数量,最后一个区块包含最近的部署交易)。Mix跟踪所有的交易。如果有一个丢失(无效),它会展示在面板上。

  • 打包去中心化应用

“生成包裹”这一行为会创建一个新的文件夹并命名为“www”,这个文件夹会包含所有被映射到当前部署合约上的资源和脚本。为了发布去中心化应用,你需要把www文件夹托管在一个网络服务器(很快会被IPFS和SWARM代替)。库中默认是不存在web3.js的。如果你想要在网络浏览器使用去中心化应用,就需要把这个库加进来。

代码编辑器

这个编辑器提供代码编辑器的基本功能。

  • 在Solidity或JavaScript模式下,可以用自动补全插件(Ctrl + Space).
  • 增大/减小字体 (Ctrl +, Ctrl –)
  • 在Solidity模式下,可以显示gas预估值(工具-> 显示Gas预估值)。这会把所有需要最小量gas的所有状态都加亮显示。如果需要的gas变得很重要,颜色会变红。它也会显示最大的交易执行费用(每个功能)。

Dapp及相关开发工具介绍

去中心化应用

去中心化应用是可以使用户和供应商之间直接互动的服务(例如,连接某些市场上的买方和卖方,文件存储里的持有者和储存者)。以太坊去中心化应用典型地通过HTML/Javascript网络应用与用户互动,使用Javascript API与区块链通信。去中心化应用典型地在区块链上有自己的相关合约套件,用来编码商业逻辑,允许持久存储对共识要求严格的状态。记住由于以太坊网络上的运算天生冗余,执行的gas成本会比离链的私人执行成本更高。这就激励着去中心化应用开发者限制执行的代码数量和储存在区块链上的代码数量。

去中心化应用目录

使用以太坊的去中心化应用被编译到以下列表,在开发的不同阶段列出(概念,工作原型,实时/部署的)。如果你在开发去中心化应用, 考虑向这些列表添加入口:

  • Ðapps 的Ethercasts状态
  • Dappslist
  • Dappcentral – 去中心化应用的分类页面,带说明,代码验证和网络数据。
  • Dapps邮件列表 – 以太坊上的开发者邮件列表(停止使用)。

列表中提供的去中心化服务覆盖大范围的领域,包括金融,保险,预测市场,社交网络,运算和存储分配,赌博,市场,物联网,管理,合作,开发和游戏。

将来,去中心化应用可能会在去中心化应用浏览器里集成的dappstores中列出和分配。

去中心化应用浏览器

  • Mist – 由基金会开发的官方GUI去中心化应用浏览器,开发阶段。 Mist as Wallet dapp在试用中。
  • Syng – Jarrad Hope开发的移动以太坊浏览器(开发阶段) – 由DEVgrants支持
  • MetaMask – Aaron Kumavis Davis的浏览器内GUI。Github上的Epicenter Bitcoin访谈 – 由DEVgrants支持
  • AlethZero – C++ eth客户端GUI(停止使用)。
  • Supernova – (停止使用)。

开发者工具

去中心化应用开发需要理解Web3 Javascript API, the JSON RPC API和Solidity编程语言。

注意:有开发者工具帮助你开发、测试和部署去中心化应用,自动使用以下列出的资源。

  • Web3 JavaScript API – 想要和以太坊节点交互的时候,主要用到的JavaScript SDK。
  • JSON RPC API – 与节点交互的低级JSON RPC 2.0界面。这个API被Web3 JavaScript API使用。
  • Solidity Docs – Solidity是以太坊开发的智能合约语言,编译到以太坊虚拟机操作码。
  • 测试网络 – 测试网络帮助开发者开发和测试以太坊代码及网络互动,不需花费主网络上自己的以太币。测试网络选项在下面列出。
  • 去中心化应用开发资源。这会帮助你开发,排错和部署以太坊应用。

去中心化应用开发资源

示例

教程

Mix-IDE

Mix是官方以太坊IDE,它允许开发者在以太坊区块链创建和部署合约及去中心化应用。它包含一个Solidity源代码排错器。Mix

IDEs/Frameworks

下面是用于写以太坊去中心化应用的开发者框架和IDE。

  • Truffle – Truffle是以太坊的开发环境,测试框架和资产管道。
  • Dapple – Dapple是Solidity开发者工具以帮助建立和管理以太坊类似的区块链上复杂的合约体系。
  • Populus – Populus是用Python语言写的智能合约开发框架。
  • Eris-PM – Eris Package Manager部署并测试私有和公共链上的智能合约体系。
  • Embark – Embark是用JavaScript写的去中心化应用开发框架。
  • EtherScripter (已淘汰,停用)
  • 韧性原始交易广播

以太坊控制台

以太坊节点的命令行控制台。

以太坊控制台通过IPC连接到在背景运行的以太坊节点(用eth和geth测试)并提供一个交互的javascript控制台,包括web3对象和管理员附件。

这里可以找到可用管理列表 以太坊节点控制指令

要使用这个控制台,你需要启动一个本地以太坊节点和可用的ipc沟通插口(数据目录中的geth.ipc文件)。开启一个节点后,ipc插口默认位于你的以太坊本地主目录。你也可以设置—test选项来使用特定节点测试指令。

然后你可以在控制台输入

这是—test模式节点指令的释义:

关于节点配置文件的更多信息。

底层服务

Whisper

  • 什么是Whisper,用途是什么 – stackexchange问答
  • Gavin Wood: 嘘!Whisper – youtube上的DEVCON-1演讲视频
  • Whisper概览和dream API用途 –
  • ELI5

Swarm

Swarm是分布式存储平台以及内容分发服务,以太坊web 3堆栈自带的基层服务。Swarm的首要目标是提供足够去中心化和冗余的以太坊公共记录储存,尤其是储存和分配去中心化应用代码和数据,以及区块链数据。从经济观点看,它允许参与者有效集中储存和带宽资源,用以为所有参与者提供之前提到的服务。

从终端用户的观点看,Swarm和WWW并没有很大不同,除了上载并不是针对一个特定的服务商。目标是点对点存储,并提供抗DDOS、零故障、容错、抗审查的解决方案以及自我维护,这归功于一个使用点对点记账体系并允许以付款交换资源的内置激励体系。Swarm被设计为与以太坊devp2p多协议网络层次,为了域名解决方案的以太坊区块链,服务支付和内容可用性保险结合。

swarm 上的ÐΞVcon演讲

  • Viktor Trón, Daniel A. Nagy:Swarm – YouTube上的以太坊 ÐΞVcon-1演讲
  • Daniel A. Nagy:保持公共记录安全可用 – YouTube上的以太坊ÐΞVcon-0演讲

Code and status

在线和离线存储

以太坊定时器

  • 作者: Piper Merriam
  • 网站: alarm_main_website。
  • 文档: alarm_documentation。

促使安排交易稍后发生的市场。提供unix中crontab或javascript 中的setTimeout类似的角色。 •以太坊提案中的去中心化cron服务 – 作者Peter Szilagyi

Ethereum Computation Market

  • 作者: Piper Merriam
  • 网站: computation_market_main_website。
  • 文档: computation_market_main_website。

促使离线运算可验证执行的市场。允许每个昂贵的运算在以太坊虚拟机内使用,不必实际支付在链上执行它们的高额gas成本。

BTCRelay

BTCrelay

  • 更多信息 (关于ETH/BTC双向peg,不需修正比特币代码)。
  • BTCrelay 审查

RANDAO

随机数: https://www.reddit.com/r/ethereum/comments/49yld7/eli5_how_does_a_service_like_szabodice_grab_a/

The EVM

以太坊虚拟机(EVM)是以太坊智能合约的执行环境。它不仅被沙箱化,而且实际上是完全隔离的,这意味着以太坊虚拟机内部运行的代码不可以访问网络,文件系统或其他进程。智能合约甚至对其他智能合约的访问权限都是有限的。

合约存在于区块链上,区块链是以太坊特定的二进制格式(以太坊虚拟机字节代码)。然而,合约典型地是用以太坊高级语言写成,用以太坊编译器编译成字节代码,最终用以太坊客户端上传到区块链。

参考资料:

文章中的列表对应的链接可以参考《Ethereum Homestead Documentation》第94页1.7.5 Dapps

深入浅出智能合约

什么是合约?

合约是代码(它的功能)和数据(它的状态)的集合,存在于以太坊区块链的特定地址。 合约账户能够在彼此之间传递信息,进行图灵完备的运算。合约依靠被称作以太坊虚拟机(EVM) 字节代码(以太坊特有的二进制格式)上的区块链运行。

合约很典型地用诸如Solidity等高级语言写成,然后编译成字节代码上传到区块链上。

另请参阅:

也存在其他语言, 尤其是Serpent和LLL,在此文本的以太坊高级语言章节会进一步阐述。去中心化应用开发资源列出了综合的开发环境,帮助你用这些语言开发的开发者工具,提供测试,和部署支持等功能。

以太坊高级语言

合约依靠被称作以太坊虚拟机(EVM) 字节代码(以太坊特有的二进制格式)上的区块链运行。然而,合约很典型地用诸如Solidity等高级语言写成,然后用以太坊虚拟机编译器编译成字节代码上传到区块链。

下面是开发者可以用来为以太坊写智能合约的高级语言。

Solidity

Solidity是和JavaScript相似的语言,你可以用它来开发合约并编译成以太坊虚拟机字节代码。

它目前是以太坊最受欢迎的语言。

  • Solidity文本 – Solidity是以太坊的旗舰高级语言,用于写合约。
  • Solidity在线实时编译器
  • 标准合约API
  • 有用的去中心化模式 – 用于去中心化应用开发的代码片段。

Serpent

Serpent是和Python类似的语言,可以用于开发合约编译成以太坊虚拟机字节代码。它力求简洁, 将低级语言在效率方面的优点和编程风格的操作简易相结合,同时合约编程增加了独特的领域特定功能。Serpent用LLL编译。

  • 以太坊维基百科上的Serpent
  • Serpent以太坊虚拟机编译器

LLL

Lisp Like Language (LLL)是和Assembly类似的低级语言。它追求极简;本质上只是直接对以太坊虚拟机的一点包装。

  • GitHub上的LIBLLL
  • LLL实例

Mutan (弃用)

Mutan是个静态类型,由Jeffrey Wilcke 开发设计的C类语言。它已经不再受到维护。

写合约

没有Hello World程序,语言就不完整。Solidity在以太坊环境内操作,没有明显的“输出”字符串的方式。我们能做的最接近的事就是用日志记录事件来把字符串放进区块链:

1 2 3 4
contract HelloWorld {   event Print(string out);   function() { Print("Hello, World!"); }  }

每次执行时,这个合约都会在区块链创建一个日志入口,印着“Hello,World!”参数。

另请参阅:

Solidity docs里有更多写Solidity代码的示例和指导。

编译合约

solidity合约的编译可以通过很多机制完成。

  • 通过命令行使用solc编译器。
  • 在geth或eth提供的javascript控制台使用web3.eth.compile.solidity (这仍然需要安装solc 编译器)。
  • 在线Solidity实时编译器。
  • 建立solidity合约的Meteor dapp Cosmo。
  • Mix IDE。
  • 以太坊钱包。

注意:关于solc和编译Solidity合约代码的更多信息可在此查看。

在geth设置solidity编译器

如果你启动了geth节点,就可以查看哪个编译器可用。

1 2
> web3.eth.getCompilers(); ["lll", "solidity", "serpent"]

这一指令会返回到显示当前哪个编译器可用的字符串。

注意:solc编译器和cpp- ethereum一起安装。或者,你可以自己创建。

如果你的solc可执行文件不在标准位置,可以用—solc标志为solc可执行文件指定一个定制路线。

1
$ geth --solc /usr/local/bin/solc

或者你可以通过控制台在执行期间设置这个选项:

1 2 3 4
> admin.setSolc("/usr/local/bin/solc") solc, the solidity compiler commandline interface Version: 0.2.2-02bb315d/.-Darwin/appleclang/JIT linked to libethereum-1.2.0-8007cef0/.-Darwin/appleclang/JIT path: /usr/local/bin/solc

编译一个简单合约

让我们编译一个简单的合约源:

1
> source = "contract test { function multiply(uint a) returns(uint d) { return a * 7; } }"

这个合约提供了一个单一方法multiply,它和一个正整数a调用并返回到a * 7 。

你准备在geth JS控制台用eth.compile.solidity()编译solidity代码:

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
> contract = eth.compile.solidity(source).test {   code: '605280600c6000396000f3006000357c01000000000000000000000000000000000000000000000000000000   info: {     language: 'Solidity',     languageVersion: '0',     compilerVersion: '0.9.13',     abiDefinition: [{       constant: false,       inputs: [{ name: 'a',         type: 'uint256'       } ],       name: 'multiply',       outputs: [{ name: 'd',         type: 'uint256'       } ],       type: 'function'     } ],     userDoc: {       methods: {       }     },     developerDoc: { methods: { } },     source: 'contract test { function multiply(uint a) returns(uint d) { return a * 7; } }'   } }

注意:编译器通过RPC因此也能通过web3.js,对浏览器内任何通过RPC/IPC连接到geth的Ðapp可用。

下面的例子会向你展示如何通过JSON-RPC接合geth来使用编译器。

1 2
$ geth --datadir ~/eth/ --loglevel 6 --logtostderr=true --rpc --rpcport 8100 --rpccorsdomain ' * ' --mine console 2>> ~/eth/eth.log $ curl -X POST --data '{"jsonrpc":"2.0","method":"eth_compileSolidity","params":["contract test {

单源编译器输出会给出你合约对象,每个都代表一个单独的合约。eth.compile.solidity 的实际返还值是合约名字到合约对象的映射。由于合约名字是test,eth.compile.solidity(source).test会给出包含下列领域的测试合约对:

  • Code 编译的以太坊虚拟机字节代码
  • Info 从编译器输出的额外元数据
  • Source 源代码
  • Language 合约语言 (Solidity,Serpent,LLL)
  • LanguageVersion 合约语言版本
  • compilerVersion 用于编译这个合约的solidity编译器版本。
  • abiDefinition 应用的二进制界面定义
  • userDoc 用户的NatSpec Doc。
  • developerDoc 开发者的NatSpec Doc。

编译器输出的直接结构化(到code和info)反映了两种非常不同的部署路径。编译的以太坊虚拟机代码和一个合约创建交易被发送到区块,剩下的(info)在理想状态下会存活在去中心化云上,公开验证的元数据则执行区块链上的代码。

如果你的源包含多个合约,输出会包括每个合约一个入口,对应的合约信息对象可以用作为属性名称的合约名字检索到。你可以通过检测当前的GlobalRegistrar代码来试一下:

1
contracts = eth.compile.solidity(globalRegistrarSrc)

创建和部署合约

开始这一章节之前,确保你有解锁的账户和一些资金。 你现在会在区块链上创建一个合约,方法是用上一章节的以太坊虚拟机代码作为数据给空地址发送交易。

注意:用在线Solidity实时编译器或Mix IDE程序会更容易完成。

1 2 3 4
var primaryAddress = eth.accounts[0] var abi = [{ constant: false, inputs: [{ name: 'a', type: 'uint256' } ] var MyContract = eth.contract(abi) var contract = MyContract.new(arg1, arg2, ..., {from: primaryAddress, data: evmByteCodeFromPrevio

所有的二进制数据都以十六进制的格式序列化。十六进制字符串总会有一个十六进制前缀0x。

注意:注意arg1, arg2, …是合约构造函数参数,以备它要接受参数。如果合约不需要构造函数参数,就可以忽略这些参数。

值得指出的是,这一步骤需要你支付执行。一旦交易成功进入到区块,你的账户余额(你作为发送方放在from领域)会根据以太坊虚拟机的gas规则被扣减。一段时间以后,你的交易会在一个区块中出现,确认它带来的状态是共识。你的合约现在存在于区块链上。 以不同步的方式做同样的事看起来是这样:

1 2 3
MyContract.new([arg1, arg2, ...,]{from: primaryAccount, data: evmCode}, function(err, contract) { if (!err && contract.address)     console.log(contract.address); });

与合约交互

与合约交互典型的做法是用诸如eth.contract()功能的抽象层,它会返回到javascript对象,和所有可用的合约功能一起,作为可调用的javascript功能。 描述合约可用功能的标准方式是ABI定义。这个对象是一个字符串,它描述了调用签名和每个可用合约功能的返回值。

1 2
var Multiply7 = eth.contract(contract.info.abiDefinition); var myMultiply7 = Multiply7.at(address);

现在ABI中具体说明的所有功能调用都在合约实例中可用。你可以用两种方法中的一种来调用这些合约实例上的方法。

1 2 3 4
> myMultiply7.multiply.sendTransaction(3, {from: address}) "0x12345" > myMultiply7.multiply.call(3) 21

当用sendTransaction被调用的时候,功能调用通过发送交易来执行。需要花费以太币来发送,调用会永久记录在区块链上。用这种方式进行的调用返回值是交易散表。

当用call被调用的时候,功能在以太坊虚拟机被本地执行,功能返回值和功能一起返回。用这种方式进行的调用不会记录在区块链上,因此也不会改变合约内部状态。这种调用方式被称为恒定功能调用。以这种方式进行的调用不花费以太币。

如果你只对返回值感兴趣,那么你应该用call 。如果你只关心合约状态的副作用,就应该用sendTransaction。

在上面的例子中,不会产生副作用,因此sendTransaction只会烧gas,增加宇宙的熵。

合约元数据

在之前的章节,我们揭示了怎样在区块链上创建合约。现在我们来处理剩下的编译器输出,合约元数据或者说合约信息。 当与不是你创建的合约交互时,你可能会想要文档或者查看源代码。合约作者被鼓励提供这样的可见信息,他们可以在区块链上登记或者借助第三方服务,比如说EtherChain。管理员API为所有选择登记的合约提供便利的方法来获取这个捆绑。

1 2 3 4
// get the contract info for contract address to do manual verification var info = admin.getContractInfo(address) // lookup, fetch, decode var source = info.source; var abiDef = info.abiDefinition

这项工作的潜在机制是:

  • 合约信息被可以公开访问的URI上传到可辨认的地方
  • 任何人都可以只知道合约地址就找到是什么URI

仅通过2个步骤的区块链注册就可以实现这些要求。第一步是在被称作HashReg的合约中用内容散表注册合约代码(散表)。第二步是在UrlHint合约用内容散表注册一个url。这些注册合约是Frontier版本的一部分,已经参与到Homestead中。

要知道合约地址来查询url,获取实际合约元数据信息包,使用这一机制就足够了。

如果你是个尽职的合约创建者,请遵循以下步骤:

  1. 将合约本身部署到区块链
  2. 获取合约信息json文件
  3. 将合约信息json文件部署到你选择的任意url
  4. 注册代码散表 –>内容散表 –> url

JS API通过提供助手把这个过程变得非常容易。 调用admin.register从合约中提取信息,在指定文件中写出json序列,运算文件的内容散表,最终将这个内容散表注册到合约代码散表。一旦将那个文件部署到任意url,你就能用admin.registerUrl来注册url 和你区块链上的内容散表(注意一旦固定的内容选址模式被用作文件商店,url-hint不再必要了。)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
source = "contract test { function multiply(uint a) returns(uint d) { return a * 7; } }"  // compile with solc contract = eth.compile.solidity(source).test // create contract object var MyContract = eth.contract(contract.info.abiDefinition) // extracts info from contract, save the json serialisation in the given file,  contenthash = admin.saveInfo(contract.info, "~/dapps/shared/contracts/test/info.json")  // send off the contract to the blockchain MyContract.new({from: primaryAccount, data: contract.code}, function(error, contract){    if(!error && contract.address) {     // calculates the content hash and registers it with the code hash in `HashReg`      // it uses address to send the transaction.     // returns the content hash that we use to register a url      admin.register(primaryAccount, contract.address, contenthash)     // here you deploy ~/dapps/shared/contracts/test/info.json to a url     admin.registerUrl(primaryAccount, hash, url)   } });

测试合约和交易

你通常需要低级的测试策略,为交易和合约排除故障。这一章节介绍了一些你可以用到的排错工作和做法。为了测试合约和交易而不产生实际的后果,你最好在私有区块链上测试。这可以通过配置一个替代网络ID (选择一个特别的数字)和/或不能用的端点来实现。推荐做法是,为了测试你用一个替代数据目录和端口 ,这样就不会意外地和实时运行的节点冲突(假定用默认运行。在虚拟机排错模式开启geth,推荐性能分析和最高的日志冗余级别 :

1
geth --datadir ~/dapps/testing/00/ --port 30310 --rpcport 8110 --networkid 4567890 --nodiscover -

提交交易之前,你需要创建私有测试链。参阅测试网络。

1 2 3 4 5 6
// create account. will prompt for password personal.newAccount(); // name your primary account, will often use it primary = eth.accounts[0]; // check your balance (denominated in ether) balance = web3.fromWei(eth.getBalance(primary), "ether");
1 2 3 4 5 6 7 8 9 10
// assume an existing unlocked primary account primary = eth.accounts[0]; // mine 10 blocks to generate ether // starting miner miner.start(4); // sleep for 10 blocks (this can take quite some time). admin.sleepBlocks(10); // then stop mining (just not to burn heat in vain) miner.stop(); balance = web3.fromWei(eth.getBalance(primary), "ether");

创建交易之后,你可以用下面的命令来强制运行:

1 2 3
miner.start(1); admin.sleepBlocks(1); miner.stop();

你可以用以下命令查看即将发生的交易:

1 2 3 4 5 6
// shows transaction pool txpool.status // number of pending txs eth.getBlockTransactionCount("pending"); // print all pending txs eth.getBlock("pending", true).transactions

如果你提交合约创建交易,可以检查想要的代码是否实际上嵌入到当前的区块链:

1 2 3 4
txhash = eth.sendTansaction({from:primary, data: code}) //... mining contractaddress = eth.getTransactionReceipt(txhash); eth.getCode(contractaddress)

下一篇文章我们将会介绍《以太坊连载(23):如何部署、调用智能合约》

Records:72123456789