以太坊程式設計師的常見誤解

買賣虛擬貨幣
最近,我偶然讀到了一篇題為《程式設計師關於時區的誤解》的文章,讓我爆笑不已。這篇文章讓我想到了程式設計師在其它方面的誤解,如人名和時間,於是我開始尋找有沒有關於以太坊的。奈何尋覓無果,我只得儘自己的綿薄之力。關於 Gas 的誤解呼叫 estimateGas 會返回交易所需消耗的 gas 量呼叫 estimateGas 確實會返回一個 gas 耗費量,但這是該筆交易在 當前狀態 下被打包會花費的 gas 量。而區塊鏈的當前狀態可能與你需要該筆交易上鍊時的狀態大相徑庭。因此,當你的交易被有效打包進區塊時,可能會採用不同的程式碼路徑,需要消耗的 gas 量也有可能完全不同。如果執行的程式碼相同,我的交易所需消耗的 gas 量也相同。不對。即使你使用相同的引數來執行相同的指令,gas 成本也有可能不同。例如,相比已經有非零值的儲存位置,如果你要寫入新的儲存位置,SSTORE(寫入儲存操作)的成本會高得多(參見 EIP2200)。這就意味著,如果你向一個新地址傳送兩筆 ERC20 代幣轉賬,第一筆交易的成本會比第二筆高得多,即使二者執行的程式碼完全相同。
如果狀態完全相同,我的交易所需消耗的 gas 量也相同通常情況下是的,除非你很倒黴地碰上了硬分叉,導致一些操作重新定價。雖然這聽起來很複雜,但說白了就是,你無法針對 dApp 中交易的 gas 上限進行安全的硬編碼,除非你決心在每次發生硬分叉後都發布 dApp 更新。如果程式碼相同,狀態也相同,且沒有發生硬分叉,我就可以相信 estimateGas的返回值了嗎?這下你可以相信 estimateGas 的返回值就是你的交易所需消耗的 gas 量了,但是你不知道這筆交易是否會如你所願的那般進行。所謂的 gas 估測,就是節點將使用不同的 gas 值來嘗試你的交易,並返回確保你的交易不會失敗的最低 gas 值。但是,節點只會看你的交易,不會看交易的內部呼叫。這就意味著,如果你呼叫的合約程式碼有一個 try/catch 塊,導致內部呼叫發生後無法撤銷,你獲得的 gas 估測值對呼叫合約來說是夠用的,但是對被呼叫合約來說就不夠了。在多簽名錢包中,這種情況經常發生:即使是在交易失敗的情況下,大多數多籤錢包會將操作標記為已執行,也就是說它們無法撤銷最外層的交易(所帶來的影響)。因此,一個原生的 gas 估測返回的值可能對多籤程式碼來說是足夠的,對你實際想執行的操作來說不一定足夠。這就是為什麼 Gnosis Safe 有一個專門的 gas 估測方法。請注意,這也就是為什麼因為 gas 不夠而導致操作失敗的情況很難察覺。內部呼叫可能會因為被分配到的 gas 太少而將 gas 耗盡,而交易本身可能還有很多 gas 可用。這就意味著,檢視交易的 gas 使用量和 gas 上限並非檢測 gas 錯誤的可靠方式。
管他呢,我每次多傳送點 gas 就好了多數情況下,這個方法是管用的。但是請記住,合約是可以檢視它在一筆交易中收到的 gas 的。因此,可以輕而易舉地將合約編寫成,一旦收到過多 gas,交易就會失敗。不過我懷疑的是,除了證明這一點外,這麼做沒有任何意義。關於交易的誤解只要節點接受了交易,交易就會被挖出想得美哦。以太坊的網路擁堵會導致 gas 價格波動很大,因此你的交易可能會被逐出 mempool(等待被挖出的交易集合)。如果 gas 價格飆升,你就需要重新傳送交易。我可以略微提高 gas 價格然後重新提交交易
只要你將 gas 價格提高到與你互動的節點所需的最小量(參見 txpool.pricebump ),那就沒什麼問題,否則還是會被拒絕。礦工總選擇 gas 價格最高的交易並不一定。礦工可以隨心所欲進行選擇。他們可能會為了自己的利益而塞入自己的交易,甚至可以開一個協議外通道,為符合自己要求的使用者打包交易。但是,即使他們依據收益來決定打包優先順序,如何以最優方式填滿區塊也是一個揹包問題(knapsack problem)。由於交易無法被分割成幾部分,所以,在 gas 上限為 10M 的區塊中打包兩個 5M gas 交易,而不是一個 6M gas 的交易,可能更為有利可圖,即使 5M gas 交易的 gas 價格低於 6M gas 交易。如果我以更高的 gas 價格傳送相同的交易,礦工會選擇後一個交易來替換前一個交易嗎替換交易必須在舊交易上鍊之前傳送到礦工那裡。也就是說,如果你傳送了替換交易,你依然需要監控你之前傳送的同一個 nonce 下的所有交易的雜湊值。
關於 Nonce 的誤解我可以透過 getTransactionCount得到我的下一筆交易的 nonce這取決於你所使用的區塊引數。如果你根據最新區塊來查詢你的交易記數,就會忽略你的未打包交易,並進一步導致你不小心覆蓋你的某筆未打包交易。我可以透過 getTransactionCount('pending')得到我的下一筆交易的 nonce雖然這在大多數情況下可行,但是你不能保證你的所有未打包交易都在你所查詢的節點的 mempool 中。如果你有很多未打包交易,你所通訊的節點可能已經丟棄了其中一些交易,但是這些交易仍有可能存在於其它地方!關於 Log 的誤解
我可以透過持續呼叫 getLogs 來有效監控事件儘管這是一個非常管用的方法(沒錯,說的就是輪詢!),但是遇上鍊重組就會出問題。如果你要輪詢最新區塊上的新 log,你不會收到關於區塊重組的通知,也不知道你所看到的事件是否需要重新調整。我可以透過安裝過濾程式來有效監控事件直到兩週前,這還不是一種常見選擇,因為 Infura 不支援基於 http 的過濾程式,MetaMask 預設使用基於 http 的過濾程式,也就是說你的 dApp 有 99% 的使用者都使用這種過濾程式(注:我可能有些誇大)。除了新事件之外,過濾程式還會通知你因區塊重組而刪除的事件。但是,這就要求你正在與之互動的基礎設施和節點保持線上。如果它們碰巧丟失了過濾程式的狀態,你就有可能錯過重組事件。我可以透過 websocket 訂閱來有效監控事件太好了!這樣下來,除了要相信你的節點會保持線上之外,你還要相信你本人會保持線上,你和節點之間的連線是可靠的。我想知道這周你在參加 Zoom 會議時掉線了幾次?
現在,我必須承認,我已經對這個話題有點著迷了,以至於我在 Devcon 5 上就此進行了一場閃電演講。如果你想了解更多內容,EIP234 很好地闡述了這些挑戰的基本原理,ethereumjs-blockstream 則解決了這一問題。關於合約的誤解智慧合約是不可更改的兄弟,如果你還有這種想法,你真的 out 了。我在一篇長達 30 頁的文章中闡述過這一點,真的非常長。不包含任何 DELEGATECALL的智慧合約就是不可更改的實際上,合約可以定期呼叫( CALL)到一個可變地址中,並將結果作為計算的一部分,或者作為更改狀態的指令,從而更改正在執行的程式碼。
那不包含任何 DELEGATECALL或 CALL的智慧合約,總是不可更改的了吧?還有 STATICCALL。別忘了 STATICCALL!不包含任何 CALL 的智慧合約是不可更改的你還得排除一種情況:這個智慧合約是透過 CREATE2 部署的,會在其初始碼(initcode)中動態載入執行時,並且可以自毀。在這種情況下,“所有者” 可以銷燬合約,並使用不同的程式碼在同一個地址上重新建立這個合約。不包含任何 CALL 且不透過 CREATE2 部署的智慧合約是不可更改的還得排除一種情況:這個合約是透過由 CREATE2 部署的合約部署的。因此,你需要追溯整個部署鏈條,找到最初建立合約的以太坊外部賬戶,確保沒有任何貓膩,而且不存在自毀操作。這篇文章深入探究了這一問題。
關於 ERC20 代幣的誤解我就不展開了,這個話題更適合寫成一篇完整的文章。在與代幣互動時,使用 OpenZeppelin 的 SafeERC20(你可以在這篇文章中閱讀更多相關內容)就好。請記住,在轉賬時,接收者所收到的代幣並不一定等於傳送者被扣除的代幣。我們來看下一部分吧。關於以太幣的誤解以太幣的總供應量只會增加我們都知道,有很多以太幣是無法使用的,有的是因為外部賬戶的私鑰丟失,有的是因為意外傳送到全零地址,還有的是因為被卡在合約中無法處理(對不起,我沒忍住)。總而言之,這部分以太幣依然存在,但是無法訪問。不過,有一種方法可以銷燬以太幣。如果你指令一個合約自毀 selfdestruct 並指定其自身作為資金的接收方,這個合約內的所有以太幣都將被銷燬。這就意味著,只要願意銷燬比區塊獎勵更多的以太幣,就可以讓以太幣通縮。
我可以寫一個能拒絕任何以太幣轉入的合約你或許知道,如果你沒有宣告任何 payable 方法,Solidity 會拒絕所有傳送到你的合約的以太幣轉賬,防止資金被卡在合約內。但是,我們也可以在不觸發任何程式碼的情況下,將資金髮送到合約內:要麼將該合約指定為自毀操作獎勵的接收方,要麼將其指定為區塊獎勵的接收方。正如 @gorgos 在評論中指出的那樣,可以預先計算出合約部署地址,並在合約部署前將以太幣傳送到該地址。也就是說,如果你追蹤所有傳送到你的合約的以太幣轉賬,你的總餘額可能大於你處理的所有轉賬的總和。

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;