前言
以太坊是新興的區塊鏈技術平臺,其目標是成爲“永不停機的世界計算機”,人們在其上可以部署各種應用供全世界使用。以太幣是以太坊公鏈這臺虛擬世界計算機器運行的“燃油”(以太坊英文名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萬種合約),區塊鏈應用呈現落地的趨勢。如下是調用存證系統時,同區塊的其他調用合約的交易。