無視礦工,以太坊的倫敦硬分叉

Posted by Ian Tsai on Wednesday, June 16, 2021

前言


現今以太坊的手續費過於昂貴,交易者需要付出高額的手續費才可以完成交易。 這次的 Ethereum London Hard Fork (倫敦硬分叉)所做的更新,包含了 EIP-1559、EIP-3198、EIP-3238 三個提案, 其中 EIP-1559 也是這次比較備受關注的部分。

EIP 是什麼?


EIP(Ethereum Improvement Proposals),是為以太坊指定潛在新功能或流程的標準,包含提議更改的技術規範。通過 EIP 流程討論和開發以太坊的網絡升級和應用標準。

提案一: EIP-1559(區塊鏈交易費用模型)


EIP-1559 是對用戶在以太坊網絡上支付 gas 費用方式的更改。(由於現在以太坊交易手續費過高,降低對用戶的吸引力)

  • 以往:礦工可以賺的收入「挖礦獎勵」跟「 100% Gas fee 」
  • EIP 1559 : gas fee 分為基礎費(base fee)和小費兩種,給礦工的是「小費」; base fee 的 50% 會直接銷毀。

EIP-1559 也導入目標塊容量機制,每個區塊的基本費用根據網絡需求而變化。如果一個區塊的交易量超過 50%,則基本費用將增加,低於 50% 的話基本費則會下降。 而基本費可能會隨著交易加入 block 時發生改變,可以先將 price 設定成願意支付的上限,最後沒用到的部分會退還。

這個提案重點在於基本費會被銷毀,並不會所有的 gas fee 都讓礦工拿走。而也藉由『銷毀』機制,讓以太幣的供應量下降,避免通貨膨脹。

交易格式的差異

以下部分段程式碼是從 EIP-1559 擷取。

新交易格式內的 base_fee_per_gaspriority_fee_per_gas 取代了以往的 Legacy Ethereum transactionsgas_price。 但 EIP-1559 可以相容 Legacy Ethereum transactions 的格式。 雖然現有的交易格式仍然有效且也被包含在 block 中,不過 Legacy transactions 沒辦法享有新的 pricing system 所帶來的優點。

@dataclass
class TransactionLegacy:
	signer_nonce: int = 0
	gas_price: int = 0
	gas_limit: int = 0
	destination: int = 0
	amount: int = 0
	payload: bytes = bytes()
	v: int = 0
	r: int = 0
	s: int = 0

@dataclass
class Transaction1559Payload:
	chain_id: int = 0
	signer_nonce: int = 0
	max_priority_fee_per_gas: int = 0
	max_fee_per_gas: int = 0
	gas_limit: int = 0
	destination: int = 0
	amount: int = 0
	payload: bytes = bytes()
	access_list: List[Tuple[int, List[int]]] = field(default_factory=list)
	signature_y_parity: bool = False
	signature_r: int = 0
	signature_s: int = 0


---

def normalize_transaction(self, transaction: Transaction, signer_address: int) -> NormalizedTransaction:
		# legacy transactions
		if isinstance(transaction, TransactionLegacy):
			return NormalizedTransaction(
				signer_address = signer_address,
				signer_nonce = transaction.signer_nonce,
				gas_limit = transaction.gas_limit,
				max_priority_fee_per_gas = transaction.gas_price,
				max_fee_per_gas = transaction.gas_price,
				destination = transaction.destination,
				amount = transaction.amount,
				payload = transaction.payload,
				access_list = [],
			)
		# 1559 transactions
		elif isinstance(transaction, Transaction1559Envelope):
			return NormalizedTransaction(
				signer_address = signer_address,
				signer_nonce = transaction.payload.signer_nonce,
				gas_limit = transaction.payload.gas_limit,
				max_priority_fee_per_gas = transaction.payload.max_priority_fee_per_gas,
				max_fee_per_gas = transaction.payload.max_fee_per_gas,
				destination = transaction.payload.destination,
				amount = transaction.payload.amount,
				payload = transaction.payload.payload,
				access_list = transaction.payload.access_list,
			)
		else:
			raise Exception('invalid transaction: unexpected number of items')

計算 gas fee

gas fee 會分為基礎費和小費兩種,base fee 會直接銷毀,「小費」會給礦工,沒有用到的部分則會返回給交易者。

這邊第一步會判斷地址的 balance 是否有辦法負擔 base fee,如果有的話就會繼續往下計算 fee_per_gas 以及 priority_fee_per_gas,這邊就不逐一解釋程式碼了。


for unnormalized_transaction in transactions:
			# Note: this validates transaction signature and chain ID which must happen before we normalize below since normalized transactions don't include signature or chain ID
			signer_address = self.validate_and_recover_signer_address(unnormalized_transaction)
			transaction = self.normalize_transaction(unnormalized_transaction, signer_address)

			signer = self.account(signer_address)

			signer.balance -= transaction.amount
			assert signer.balance >= 0, 'invalid transaction: signer does not have enough ETH to cover attached value'

			# ensure that the user was willing to at least pay the base fee
			assert transaction.max_fee_per_gas >= block.base_fee_per_gas

			# The first two of these four rules are implicit due to the next two rules
			# Prevent impossibly large numbers
			assert transaction.max_fee_per_gas < 2**256
			# Prevent impossibly large numbers
			assert transaction.max_priority_fee_per_gas < 2**256
			# The total must be the larger of the two
			assert transaction.max_fee_per_gas >= transaction.max_priority_fee_per_gas
			# the signer must be able to afford the transaction
			assert signer.balance >= transaction.gas_limit * transaction.max_fee_per_gas

			# priority fee is capped because the base fee is filled first
			priority_fee_per_gas = min(transaction.max_priority_fee_per_gas, transaction.max_fee_per_gas - block.base_fee_per_gas)
			# signer pays both the priority fee and the base fee
			effective_gas_price = priority_fee_per_gas + block.base_fee_per_gas
			signer.balance -= transaction.gas_limit * effective_gas_price
			assert signer.balance >= 0, 'invalid transaction: signer does not have enough ETH to cover gas'
			gas_used = self.execute_transaction(transaction, effective_gas_price)
			gas_refund = transaction.gas_limit - gas_used
			cumulative_transaction_gas_used += gas_used
			# signer gets refunded for unused gas
			signer.balance += gas_refund * effective_gas_price
			# miner only receives the priority fee; note that the base fee is not given to anyone (it is burned)
			self.account(block.author).balance += gas_used * priority_fee_per_gas

交易順序

多數的人不會在 priority fees 去做競爭,所以基本上以相同優先度會依照交易時間來進行排序。

提案二: EIP-3198(BASEFEE 操作碼)


固定費用 base fee 的實際原始碼。

提案三: EIP-3238(Difficulty Bomb Delay)


難度炸彈是以太坊在 2015 年加入的一段程式碼,透過逐步增加區塊鏈挖礦難度,主要是為了減低以太坊被開發的速度。 EIP-3238 主要是延遲難度炸彈,原本炸彈難題這次將預定在 2021/7 發生,這次預計延到 2022 年第二季度左右才會發生。 過去以太坊也曾爆過三次炸彈難題,並手動調整把難度調回爆炸前的程度。

小結


由於本身是開發人員,不是投資人或是礦工,所以比較關心交易格式有沒有發生變化,是否可以相容,對於此次更新對於以太坊生態的影響可能沒有各界大佬清楚,還請見諒XD

References


  • EIP-3198
  • EIP-3238
  • EIP-1559
  • Introduction to Ethereum Improvement Proposals (EIPs)