This commit is contained in:
smin-k 2024-04-05 19:56:33 +09:00
commit f0995b4f1a

View file

@ -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