前言
以太坊是新兴的区块链技术平台,其目标是成为“永不停机的世界计算机”,人们在其上可以部署各种应用供全世界使用。以太币是以太坊公链这台虚拟世界计算机器运行的“燃油”(以太坊英文名Ethereum词根eum含有燃油的意思),其理念是以太坊平台运行需要消耗资源,比如各节点的CPU、存储、带宽等资源,而这些资源消耗由以太坊平台给以计量和计价,并通过挖矿时对矿工的奖励给予补偿,这种奖励同时也是以太币去中心化的发行机制。
一、以太坊转账手续费的特点
对资源消耗的计量和计价,在以太坊中有专有的名词,叫某笔交易的gasUsed和gasPrice。前者体现了这笔交易消耗了以太坊这台虚拟世界计算机的资源的量(比如多少CPU时间之类),后者体现了这笔交易消耗的资源的量的单位价格(比如每单位CPU时间值多少以太币)。用开车打个比方,比如你出去旅行一趟,过程消耗燃油20升,当前油价每升6元,相当于你这次旅行的燃油成本是(20升 * 6元每升 = 120元)。同样地,一笔以太坊交易的手续费,其计算公式就是:
交易手续费Fee(单位:以太) = 过程油耗gasUsed * 当前油价gasPrice
以太坊交易的手续费,有三个特点,值得注意:
- 手续费和转账金额无关。不像银行,转钱越多手续费越高。
- 手续费给谁事先是不知道的。不像银行,手续费肯定是给银行的。
- 费用不会固定。由于油价gasPrice的实时变化,经常是相隔几分钟的交易,其消耗的手续费都是不同的。
例如给4位同事转以太币,每个同事2个以太币,实际情况取截图说明。如下交易都是相隔较短时间内相继完成的。
- 交易编号:0x1ed1dbbe611cbaf6447aab61165b4d5ff49c061cad7665d6a23ec8df346412d1 (给A同事,价格10 Gwei每单位油耗)
- 交易编号:0x0b6e798951aa7786b6484df6e2924489af338c9dbb8c30bd64c9954e56ea55c0 (给B同事,价格7 Gwei每单位油耗)
- 交易编号:0x7df8116e1825c944f260e71ab8f0e00da6e45859afa7a196ebe7258656f31d60 (给C同事,价格12 Gwei每单位油耗)
- 交易编号:0xa9787e69f484329723dbb46b0c446f1756301c28e70929fda5f358958ebbee50 (给D同事,价格9 Gwei每单位油耗)
如上记录的发给4位同事的交易记录表明同样类型的交易但金额不同;每次油耗都一样,但是油价却不一样。看来,以太坊交易的gasPrice就像现实生活的油价,也会起起落落,取决于计算资源和交易量之间的供求关系。
- 手续费具体的计算和扣收过程
一笔交易,手续费计算有3次,最后进行扣收。
- 第一次检查是交易pending的时候,这时候的手续费计算是简单看其余额是否足够支付手续费,是单个交易的模拟检查,并没有将这笔交易纳入到整个区块的整体中计算
|--JsonRpcImpl.eth_sendTransaction(CallArguments args)
|---前端新发一笔交易请求到节点时的程序入口
|----EthereumImpl.submitTransaction(tx)
|-----节点接收到后,进行初步的数据非空检查后创建一个Transaction对象并提交
|------pendingState.addPendingTransaction(transaction)
|-------节点新建一个广播任务,将交易异步广播出去;并加入本地的待确认交易表
|--------PendingStateImpl.addPendingTransactionImpl(final Transaction tx)
|---------对该笔交易进行验证,各数据项格式是否符合要求,并起模拟环境验证
|----------PendingStateImpl.executeTx(tx)
|-----------该处执行交易沙盒验证,因为是单笔交易验证,会创建一个假区块
|-----------假区块中无其他交易,已发生油耗也设为0,这样只校验单笔是否超标
2)第二次检查是区块挖出前进行模拟计算,这时候就是一个区块的整体了,除了检查账户中是否有足够支付单笔交易手续费的余额,还看区块中所有交易的手续费加起来是否超过区块的手续费总额限制,检查开始有了整体观
3)第三次检查及最后的手续费扣收,是区块挖出后的实际执行,检查限制是同第二次,但这时候是实际扣缴了,而且就算合约交易在虚拟机中执行失败,手续费是照旧扣收的
如上第2、3小点流程基本一致,区别在于执行环境是否为真,调用关系如下:
|----BlockchainImpl.createNewBlock(parent, txs, uncles, time)
|-----第2点中,创建新区块时需要模拟校验该手续费情况,沙盒执行
|----BlockchainImpl.addPendingTransaction(transaction)
|-----第3点中,区块挖矿出来后最终执行交易,扣收手续费,真实环境执行
|--------BlockchainImpl.applyBlock(repo, block)
|---------传入执行环境和当前块,对区块中的交易进行验证
|----------for (Transaction tx : block.getTransactionsList())
|-----------对于区块中的每笔交易进行手续费的检查并执行
|-----------2、3小点区别在于前者是模拟执行环境,后者是真是执行环境
以太坊交易过程中有4个重要的函数,分别是:init()、execute()、go()、finalization()
(图全貌见附录)
对应的手续费有3个动作:
- 事前:真正开始交易钱先检查手续费,这时候要计算一次手续费,看账户中余额是否够手续费
- 事中:交易过程中对手续费的扣缴,因为以太坊交易分3类,普通转账,合约创建,合约执行,这3种口手续费的地方都不一样
- 事后:最后一次计算,有手续费剩余的时候(leftover),这部分预交冻结的手续费要返回给发起者
注意,上述手续费检查的限制都是针对gas量,最终手续费的计算还要乘以gasPrice。
针对这部分的代码分析:
四个变量:
- currentBlock.getGasLimit() 动态的本区块所能消耗的gas量的限制
- gasUsedInTheBlock 简单累加计算得到的本区块已经消耗的gas量
- tx.getGasLimit() 复杂计算统计各个指令得到的本交易所能消耗的gas量
- basicTxCost 读取配置得基本交易gas量
两个判断关系:
1)如果:(tx.getGasLimit() + gasUsedInTheBlock) > currentBlock.getGasLimit()
则提示:Too much gas used in this block:
业务含义是本区块已经消耗的gas,加上本交易消耗的gas如果超过了整个区块的gasLimit,就报错
2)如果:txGasLimit.compareTo(BigInteger.valueOf(basicTxCost)) < 0
则提示:Not enough gas for transaction execution: Require: basicTxCost Got: txGasLimit
业务含义是如果这个交易消耗的gas,太大了就超出了区块的gasLimit,如果太小了就不符合最低交易手续费的要求
- 手续费中油耗是如何计算的
以太坊不像比特币那样,手续费统一,因为以太坊设计成可以运行智能合约,而各个智能合约其消耗的资源显然是不一样的,所以不能收统一的手续费。故而,以太坊将各种基本操作分类,评估每个细节操作的耗费,汇总起来就是一个指令的油耗
如何计算见《以太坊黄皮书》中附录,费率表:
不同种类指令有不同的费用,目前最贵的合约创建,花费32000单位的油耗,最便宜的跳转指令只要花费1单位的油耗,最贵最便宜资源消耗量相差了32000倍!
最常用的交易指令就是普通的转账交易,按下表定义是21000个单位的油耗,之前的实际操作例子也显示确实是21000单位的油耗。
- 手续费中油价的形成机制
以太坊的油价是每时每刻在变动的,如果你打开以太坊钱包的客户端,比如Mist,打开转账界面,在未输入金额等要素的时候,钱包就已经显示了本次手续费,并随着油价的变动,页面会刷新并发生变化。
那么油价的形成机制是怎么样的呢?有点类似于股票交易所的股票报价。
首先,油价由矿工节点生成。
其次,矿工是根据待确认交易池中的交易列表,按gasPrice进行排序,考虑历史区块的影响后,最终取一个中间值并发布生成的。由于新交易不断的涌入,各交易gasPrice报价可能不同,从而产生不断变化的gasPrice报价。
|--GasPriceTracker.onBlock
|---每当一个新区块加入区块链时,就用区块中的交易的gasPrice计算更新本类参数
|----GasPriceTracker.onTransaction
|-----该类有长度为512的价格池,每笔价格从后往前加入池,加满则循环覆盖
|------GasPriceTracker.getGasPrice
|-------另一个线程负责发布实时油价
具体如何综合价格池中价格发布一个油价,代码如下:
public long getGasPrice() {
if (!filled) { //如果始终没有交易,则返回默认油价
return defaultPrice;
} else {
if (lastVal == 0) { //若该标志为0,则重新计算油价,这标志是价格池满时设置的
long[] longs = Arrays.copyOf(window, window.length);
Arrays.sort(longs); //排序
lastVal = longs[longs.length / 4]; // 取第25% percentile位置的油价
}
return lastVal;
}
}
有个专门统计以太坊油价的网站:http://ethgasstation.info/,图形化方式展示了油价变化。
- 以太坊手续费发挥的经济调控作用
以太坊手续费不单纯是个技术问题,更是一个经济学问题。前面文章提到过,我们可以把以太坊当作一台超级计算机,而智能合约是这个计算机上运行的程序。因为这个计算机不是我们自己的,更像一台我们在网上租用的服务器,因此,我们想使用的话必须花费成本。
如果gas使用得越多,而gasPrice价格不变的话,使用以太坊搭建分布式应用的成本将会越来越高(上图等式cost越来越大)。而实际上,以太坊为了解决这个问题,特地将ether(ETH的单位)与gas进行解耦,保持gasPrice与ether的一种动态变化,使得ETH价格大幅上涨时,gasPrice价格下降(以ether计算的价格);ETH价格下跌时,gasPrice价格上升(以ether计算的价格)。这样才能使得使用以太坊的成本处于一个合理的、不会大幅波动的范围。
以太坊gasPrice的动态变化图:
之前以太币价格低,gasPrice相对比较高,随着以太币的升值,gasPrice价格下降,维持了以太坊运行成本保持稳定,发挥了市场的调节作用。
另外,比特币目前遇到扩容问题,因为刚开始设计时的考虑不足,比特币的手续费未能像以太坊那样进行起动态调节的机制,目前遇到交易拥堵的问题,交易体验远不如以太坊。因此比特币社区目前在讨论扩容问题。而以太坊就没有这个问题,以太坊的gasLimit可以动态调整,容纳交易的能力也可以动态扩展,如下是截稿时,最新10个区块的交易数量:
最新区块(第300多万个)的交易容纳量可以达到100级别,而比对刚开始的第13万个区块附近的交易容量和gasLimit如下:
gasLimit从3141592增长到4712394,交易容量相应增加,由此可见,以太坊自身可以动态调整容量,适应了交易量增加的需求。
附件:以太坊交易整体过程:
- 公链调用实验:为了弄清楚以太坊公链上部署合约和调用,实际上花多少钱,进行了如下真金白银的实验:
目前调用了5次,费用每次约10元人民币左右。
注:由于gasPrice等因素,每次调用花费不一样,综合同事们提供的调用费用,列举如下:
调用次数 | 手续费(Ether) |
1 | 0.0058 |
2 | 0.0052 |
3 | 0.0048 |
4 | 0.0049 |
5 | 0.0079 |
目前以太坊上的合约调用活动已经比较频繁,各种合约账户达75万个(也就是有75万种合约),区块链应用呈现落地的趋势。如下是调用存证系统时,同区块的其他调用合约的交易。