From 6b4035c6b7bdb3e29fc328c1882ca5f538f6e4df Mon Sep 17 00:00:00 2001 From: siddharth0a Date: Fri, 5 Apr 2024 19:52:00 +0900 Subject: [PATCH] EIPs: update eip-1559.md --- EIPs/eip-1559.md | 367 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 367 insertions(+) diff --git a/EIPs/eip-1559.md b/EIPs/eip-1559.md index b78c7ac4bf..b4543c2543 100644 --- a/EIPs/eip-1559.md +++ b/EIPs/eip-1559.md @@ -48,6 +48,373 @@ EIP-1559에서는 이전 블록의 사이즈가 목표 블록 사이즈보다 ![alt text](https://miro.medium.com/v2/resize:fit:1400/format:webp/0*TWrvbTdGD5OR1dtv) +## Code + +```python +from typing import Union, Dict, Sequence, List, Tuple, Literal +from dataclasses import dataclass, field +from abc import ABC, abstractmethod + +@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 Transaction2930Payload: + chain_id: int = 0 + signer_nonce: int = 0 + gas_price: 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 + +@dataclass +class Transaction2930Envelope: + type: Literal[1] = 1 + payload: Transaction2930Payload = Transaction2930Payload() + +@dataclass +class Transaction1559Payload: + chain_id: int = 0 + signer_nonce: int = 0 + # EIP-1559 트랜잭션은 priority_fee 도입 + 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 + +@dataclass +class Transaction1559Envelope: + type: Literal[2] = 2 + payload: Transaction1559Payload = Transaction1559Payload() + +Transaction2718 = Union[Transaction1559Envelope, Transaction2930Envelope] + +Transaction = Union[TransactionLegacy, Transaction2718] + +@dataclass +class NormalizedTransaction: + signer_address: 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) + +@dataclass +class Block: + parent_hash: int = 0 + uncle_hashes: Sequence[int] = field(default_factory=list) + author: int = 0 + state_root: int = 0 + transaction_root: int = 0 + transaction_receipt_root: int = 0 + logs_bloom: int = 0 + difficulty: int = 0 + number: int = 0 + gas_limit: int = 0 # note the gas_limit is the gas_target * ELASTICITY_MULTIPLIER + gas_used: int = 0 + timestamp: int = 0 + extra_data: bytes = bytes() + proof_of_work: int = 0 + nonce: int = 0 + base_fee_per_gas: int = 0 + +@dataclass +class Account: + address: int = 0 + nonce: int = 0 + balance: int = 0 + storage_root: int = 0 + code_hash: int = 0 + +INITIAL_BASE_FEE = 1000000000 +INITIAL_FORK_BLOCK_NUMBER = 10 # TBD +BASE_FEE_MAX_CHANGE_DENOMINATOR = 8 +ELASTICITY_MULTIPLIER = 2 + +class World(ABC): + # EIP-1559에서 제안된 baseFee 계산 방식과 블록 검증 로직 구현 + # geth, consensus/misc/eip1559/eip1559.go + # VerifyEIP1559Header: EIP-1559이후 변경된 헤더 속성을 검증 + # CalcBaseFee: header의 basefee를 계산 + # geth, consensus/misc/gaslimit.go + # VerifyGaslimit은 header gas limit관 parentgas limit의 증가/감소관계를 확인한다. + def validate_block(self, block: Block) -> None: + parent_gas_target = self.parent(block).gas_limit // ELASTICITY_MULTIPLIER + parent_gas_limit = self.parent(block).gas_limit + + # on the fork block, don't account for the ELASTICITY_MULTIPLIER to avoid + # unduly halving the gas target. + # 포크 블록에서는 gas target이 과도하게 반감되는 것을 방지하기 위해 Elasticity_multiplier를 고려하지 않음 + # 포크 시점에 가스 목표량은 유지되지만, 가스 한도는 증가하여 네트워크 처리량 감소를 방지 + if INITIAL_FORK_BLOCK_NUMBER == block.number: + parent_gas_target = self.parent(block).gas_limit + parent_gas_limit = self.parent(block).gas_limit * ELASTICITY_MULTIPLIER + + parent_base_fee_per_gas = self.parent(block).base_fee_per_gas + parent_gas_used = self.parent(block).gas_used + transactions = self.transactions(block) + + # check if the block used too much gas + # 가스가 너무 많이 사용되었는지 확인 + assert block.gas_used <= block.gas_limit, 'invalid block: too much gas used' + + # check if the block changed the gas limit too much + # gas limit 이 너무 크게 변경되었는지 확인 + assert block.gas_limit < parent_gas_limit + parent_gas_limit // 1024, 'invalid block: gas limit increased too much' + assert block.gas_limit > parent_gas_limit - parent_gas_limit // 1024, 'invalid block: gas limit decreased too much' + + # check if the gas limit is at least the minimum gas limit + # gas limit 이 최소 gas limit 이상인지 확인 + assert block.gas_limit >= 5000 + + # check if the base fee is correct + # base fee 계산 및 확인 + # geth, consensus/misc/eip1559/eip1559.go, CalcBaseFee + if INITIAL_FORK_BLOCK_NUMBER == block.number: + expected_base_fee_per_gas = INITIAL_BASE_FEE + elif parent_gas_used == parent_gas_target: + expected_base_fee_per_gas = parent_base_fee_per_gas + elif parent_gas_used > parent_gas_target: + gas_used_delta = parent_gas_used - parent_gas_target + base_fee_per_gas_delta = max(parent_base_fee_per_gas * gas_used_delta // parent_gas_target // BASE_FEE_MAX_CHANGE_DENOMINATOR, 1) + expected_base_fee_per_gas = parent_base_fee_per_gas + base_fee_per_gas_delta + else: + gas_used_delta = parent_gas_target - parent_gas_used + base_fee_per_gas_delta = parent_base_fee_per_gas * gas_used_delta // parent_gas_target // BASE_FEE_MAX_CHANGE_DENOMINATOR + expected_base_fee_per_gas = parent_base_fee_per_gas - base_fee_per_gas_delta + assert expected_base_fee_per_gas == block.base_fee_per_gas, 'invalid block: base fee not correct' + + # execute transactions and do gas accounting + # 트랜잭션을 실행하고 gas 집계를 수행 + cumulative_transaction_gas_used = 0 + 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 + # normalized transaction에는 signature와 chainID가 포함되지 않기 때문에 트랜잭션 일반화를 하기 전에 트랜잭션 서명과 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' + # the signer must be able to afford the transaction + # 최소한 트랜잭션을 보낼 수 있는 가스비 여유가 있는지 확인 + assert signer.balance >= transaction.gas_limit * transaction.max_fee_per_gas + + # ensure that the user was willing to at least pay the base fee + # fee로 최소한 base fee만큼 지불할 의사가 있는지 확인 + assert transaction.max_fee_per_gas >= block.base_fee_per_gas + + # 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 + # total max fee가 priority max fee보다 커야한다 + assert transaction.max_fee_per_gas >= transaction.max_priority_fee_per_gas + + # priority fee is capped because the base fee is filled first + # priority fee는 base fee가 먼저 부과되기 때문에 제한된다. + 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 + # 서명자는 priority fee와 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' + + # effective_gas_price는 GASPRICE (0x3a) opcode에 의해 리턴되는 값이다. + gas_used = self.execute_transaction(transaction, effective_gas_price) + + # 실제로 사용되고 남은 gas는 다시 환불된다. + # signer gets refunded for unused gas + gas_refund = transaction.gas_limit - gas_used + cumulative_transaction_gas_used += gas_used + 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) + # 채굴자는 priority fee만 받고, base fee는 burned 된다. + self.account(block.author).balance += gas_used * priority_fee_per_gas + + # check if the block spent too much gas transactions + # 블록에 속한 모든 트랜잭션의 사용된 가스량의 합(cumulative_transaction_gas_used)과 블록이 사용한 gas량(block.gas_used)이 동일한지 확인한다. + assert cumulative_transaction_gas_used == block.gas_used, 'invalid block: gas_used does not equal total gas used in all transactions' + + # TODO: verify account balances match block's account balances (via state root comparison) + # 계정잔액이 블록의 account balances와 동일한지 확인 + # 블록체인에서는 모든 계정의 상태(잔액, 코드, 저장된 데이터 등)가 블록 헤더에 있는 상태 루트를 통해 요약됩니다. + # 따라서 블록에 대한 유효성 검사를 수행할 때는 블록 헤더에 있는 상태 루트와 실제 계정의 상태를 비교하여 일치하는지 확인해야 함 + # TODO: validate the rest of the block + # 나머지 블록 검증 + + # 트랜잭션 정규화 + 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 = [], + ) + # 2930 transactions + elif isinstance(transaction, Transaction2930Envelope): + 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.gas_price, + max_fee_per_gas = transaction.payload.gas_price, + destination = transaction.payload.destination, + amount = transaction.payload.amount, + payload = transaction.payload.payload, + access_list = 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') + + @abstractmethod + def parent(self, block: Block) -> Block: pass + + @abstractmethod + def block_hash(self, block: Block) -> int: pass + + @abstractmethod + def transactions(self, block: Block) -> Sequence[Transaction]: pass + + # effective_gas_price is the value returned by the GASPRICE (0x3a) opcode + # effective_gas_price는 GASPRICE (0x3a) opcode에 의해 리턴되는 값이다. + @abstractmethod + def execute_transaction(self, transaction: NormalizedTransaction, effective_gas_price: int) -> int: pass + + @abstractmethod + def validate_and_recover_signer_address(self, transaction: Transaction) -> int: pass + + @abstractmethod + def account(self, address: int) -> Account: pass +``` + +```go +// VerifyEIP1559Header verifies some header attributes which were changed in EIP-1559, +// - gas limit check +// - basefee check +// VerifyEIP1559Header 함수는 EIP-1559이후 변경된 헤더 속성을 검증한다 +func VerifyEIP1559Header(config *params.ChainConfig, parent, header *types.Header) error { + // Verify that the gas limit remains within allowed bounds + // gas limit이 허용된 범위 안에 있는지 검증한다 + parentGasLimit := parent.GasLimit + if !config.IsLondon(parent.Number) { + parentGasLimit = parent.GasLimit * config.ElasticityMultiplier() + } + if err := misc.VerifyGaslimit(parentGasLimit, header.GasLimit); err != nil { + return err + } + // Verify the header is not malformed + // 헤더가 잘못된 형식이 아닌지 확인한다(baseFee) + if header.BaseFee == nil { + return errors.New("header is missing baseFee") + } + // Verify the baseFee is correct based on the parent header. + // baseFee가 parent header에 근거해 올바른지 확인한다. + expectedBaseFee := CalcBaseFee(config, parent) + if header.BaseFee.Cmp(expectedBaseFee) != 0 { + return fmt.Errorf("invalid baseFee: have %s, want %s, parentBaseFee %s, parentGasUsed %d", + header.BaseFee, expectedBaseFee, parent.BaseFee, parent.GasUsed) + } + return nil +} + +// CalcBaseFee calculates the basefee of the header. +// CalcBaseFee는 header의 basefee를 계산한다. +func CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int { + // If the current block is the first EIP-1559 block, return the InitialBaseFee. + // 현재 블록이 EIP-1559의 첫번째 블록이면, InitialBaseFee를 반환한다. + // -----Config가 런던인지 검사하는데 첫번째 블록과 어떤 관련성 있는지? + // ----->EIP-1559는 런던 하드포크에서 시행됨, 즉 런던 하드포크의 첫번째 블록부터 BaseFee시행 + if !config.IsLondon(parent.Number) { + return new(big.Int).SetUint64(params.InitialBaseFee) + } + + parentGasTarget := parent.GasLimit / config.ElasticityMultiplier() + // If the parent gasUsed is the same as the target, the baseFee remains unchanged. + // 부모블록의 gasUsed가 target과 같으면, baseFee는 변경되지 않음. + if parent.GasUsed == parentGasTarget { + return new(big.Int).Set(parent.BaseFee) + } + + var ( + num = new(big.Int) + denom = new(big.Int) + ) + + if parent.GasUsed > parentGasTarget { + // If the parent block used more gas than its target, the baseFee should increase. + // max(1, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator) + // 만약 부모 블록이 타겟보다 많은 gas를 사용했으면, baseFee는 증가한다. + num.SetUint64(parent.GasUsed - parentGasTarget) + num.Mul(num, parent.BaseFee) + num.Div(num, denom.SetUint64(parentGasTarget)) + num.Div(num, denom.SetUint64(config.BaseFeeChangeDenominator())) + baseFeeDelta := math.BigMax(num, common.Big1) + + return num.Add(parent.BaseFee, baseFeeDelta) + } else { + // Otherwise if the parent block used less gas than its target, the baseFee should decrease. + // max(0, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator) + // 만약 부모 블록이 타겟보다 적은 gas를 사용했으면, baseFee는 감소한다. + num.SetUint64(parentGasTarget - parent.GasUsed) + num.Mul(num, parent.BaseFee) + num.Div(num, denom.SetUint64(parentGasTarget)) + num.Div(num, denom.SetUint64(config.BaseFeeChangeDenominator())) + baseFee := num.Sub(parent.BaseFee, num) + // 코드가 위 로직이랑 일관되지 않은데 개선사항? + return math.BigMax(baseFee, common.Big0) + } +} +``` ## Reference