Skip to content

Commit 373c18a

Browse files
committed
Calculate miner tip correctly for EIP-1559
- add tests to verify - Push backwards-compatible usages of get_tip() to Frontier - Bonus fix: setting coinbase when mining The coinbase address was not correctly receiving transaction fees when the address is passed as a kwarg to MiningChain.mine_all or .mine_block - Bonus fix: Correctly decode dynamic-fee txns in London
1 parent cfdc701 commit 373c18a

File tree

12 files changed

+163
-123
lines changed

12 files changed

+163
-123
lines changed

eth/abc.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2679,6 +2679,18 @@ def get_gas_price(self, transaction: SignedTransactionAPI) -> int:
26792679
"""
26802680
...
26812681

2682+
@abstractmethod
2683+
def get_tip(self, transaction: SignedTransactionAPI) -> int:
2684+
"""
2685+
Return the gas price that gets allocated to the miner/validator.
2686+
2687+
Pre-EIP-1559 that would be the full transaction gas price. After, it
2688+
would be the tip price (potentially reduced, if the base fee is so high
2689+
that it surpasses the transaction's maximum gas price after adding the
2690+
tip).
2691+
"""
2692+
...
2693+
26822694
#
26832695
# Access to account db
26842696
#

eth/chains/base.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,12 @@ def import_block(self,
663663
def set_header_timestamp(self, timestamp: int) -> None:
664664
self.header = self.header.copy(timestamp=timestamp)
665665

666+
@staticmethod
667+
def _custom_header(base_header: BlockHeaderAPI, **kwargs: Any) -> BlockHeaderAPI:
668+
header_fields = {'coinbase'}
669+
header_params = {k: v for k, v in kwargs.items() if k in header_fields}
670+
return base_header.copy(**header_params)
671+
666672
def mine_all(
667673
self,
668674
transactions: Sequence[SignedTransactionAPI],
@@ -676,7 +682,8 @@ def mine_all(
676682
else:
677683
base_header = self.create_header_from_parent(parent_header)
678684

679-
vm = self.get_vm(base_header)
685+
custom_header = self._custom_header(base_header, **kwargs)
686+
vm = self.get_vm(custom_header)
680687

681688
new_header, receipts, computations = vm.apply_all_transactions(transactions, base_header)
682689
filled_block = vm.set_block_transactions(vm.get_block(), new_header, transactions, receipts)
@@ -697,7 +704,8 @@ def mine_block(self, *args: Any, **kwargs: Any) -> BlockAPI:
697704
return self.mine_block_extended(*args, **kwargs).block
698705

699706
def mine_block_extended(self, *args: Any, **kwargs: Any) -> BlockAndMetaWitness:
700-
vm = self.get_vm(self.header)
707+
custom_header = self._custom_header(self.header, **kwargs)
708+
vm = self.get_vm(custom_header)
701709
current_block = vm.get_block()
702710
mine_result = vm.mine_block(current_block, *args, **kwargs)
703711
mined_block = mine_result.block

eth/vm/forks/berlin/transactions.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,7 @@ class BerlinTransactionBuilder(TransactionBuilderAPI):
388388
"""
389389
legacy_signed = BerlinLegacyTransaction
390390
legacy_unsigned = BerlinUnsignedLegacyTransaction
391+
typed_transaction = TypedTransaction
391392

392393
@classmethod
393394
def decode(cls, encoded: bytes) -> SignedTransactionAPI:
@@ -396,21 +397,21 @@ def decode(cls, encoded: bytes) -> SignedTransactionAPI:
396397

397398
type_id = to_int(encoded[0])
398399
if type_id in VALID_TRANSACTION_TYPES:
399-
return TypedTransaction.decode(encoded)
400+
return cls.typed_transaction.decode(encoded)
400401
else:
401402
return rlp.decode(encoded, sedes=cls.legacy_signed)
402403

403404
@classmethod
404405
def deserialize(cls, encoded: DecodedZeroOrOneLayerRLP) -> SignedTransactionAPI:
405406
if isinstance(encoded, bytes):
406-
return TypedTransaction.deserialize(encoded)
407+
return cls.typed_transaction.deserialize(encoded)
407408
else:
408409
return cls.legacy_signed.deserialize(encoded)
409410

410411
@classmethod
411412
def serialize(cls, obj: SignedTransactionAPI) -> DecodedZeroOrOneLayerRLP:
412-
if isinstance(obj, TypedTransaction):
413-
return TypedTransaction.serialize(obj)
413+
if isinstance(obj, cls.typed_transaction):
414+
return cls.typed_transaction.serialize(obj)
414415
else:
415416
return cls.legacy_signed.serialize(obj)
416417

@@ -489,4 +490,4 @@ def new_access_list_transaction(
489490
r,
490491
s,
491492
)
492-
return TypedTransaction(ACCESS_LIST_TRANSACTION_TYPE, transaction)
493+
return cls.typed_transaction(ACCESS_LIST_TRANSACTION_TYPE, transaction)

eth/vm/forks/frontier/state.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ def build_computation(self,
145145
def finalize_computation(self,
146146
transaction: SignedTransactionAPI,
147147
computation: ComputationAPI) -> ComputationAPI:
148+
transaction_context = self.vm_state.get_transaction_context(transaction)
149+
148150
# Self Destruct Refunds
149151
num_deletions = len(computation.get_accounts_for_deletion())
150152
if num_deletions:
@@ -155,7 +157,7 @@ def finalize_computation(self,
155157
gas_refunded = computation.get_gas_refund()
156158
gas_used = transaction.gas - gas_remaining
157159
gas_refund = min(gas_refunded, gas_used // 2)
158-
gas_refund_amount = (gas_refund + gas_remaining) * transaction.gas_price
160+
gas_refund_amount = (gas_refund + gas_remaining) * transaction_context.gas_price
159161

160162
if gas_refund_amount:
161163
self.vm_state.logger.debug2(
@@ -167,8 +169,8 @@ def finalize_computation(self,
167169
self.vm_state.delta_balance(computation.msg.sender, gas_refund_amount)
168170

169171
# Miner Fees
170-
transaction_fee = \
171-
(transaction.gas - gas_remaining - gas_refund) * transaction.gas_price
172+
gas_used = transaction.gas - gas_remaining - gas_refund
173+
transaction_fee = gas_used * self.vm_state.get_tip(transaction)
172174
self.vm_state.logger.debug2(
173175
'TRANSACTION FEE: %s -> %s',
174176
transaction_fee,

eth/vm/forks/frontier/validation.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,22 @@
1212

1313
def validate_frontier_transaction(state: StateAPI,
1414
transaction: SignedTransactionAPI) -> None:
15-
gas_cost = transaction.gas * transaction.gas_price
15+
max_gas_cost = transaction.gas * state.get_gas_price(transaction)
1616
sender_balance = state.get_balance(transaction.sender)
1717

18-
if sender_balance < gas_cost:
18+
if sender_balance < max_gas_cost:
1919
raise ValidationError(
2020
f"Sender {transaction.sender!r} cannot afford txn gas "
21-
f"{gas_cost} with account balance {sender_balance}"
21+
f"{max_gas_cost} with account balance {sender_balance}"
2222
)
2323

24-
total_cost = transaction.value + gas_cost
24+
total_cost = transaction.value + max_gas_cost
2525

2626
if sender_balance < total_cost:
27-
raise ValidationError("Sender account balance cannot afford txn")
27+
raise ValidationError(
28+
f"Sender does not have enough balance to cover transaction value and gas "
29+
f" (has {sender_balance}, needs {total_cost})"
30+
)
2831

2932
sender_nonce = state.get_nonce(transaction.sender)
3033
if sender_nonce != transaction.nonce:

eth/vm/forks/london/state.py

Lines changed: 12 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
1-
from eth.vm.forks.frontier.constants import REFUND_SELFDESTRUCT
21
from typing import Type
32

43
from eth_hash.auto import keccak
5-
from eth_utils.exceptions import ValidationError
64
from eth_utils import (
75
encode_hex,
86
)
97

108
from eth.abc import (
119
AccountDatabaseAPI,
12-
ComputationAPI,
1310
MessageAPI,
1411
SignedTransactionAPI,
1512
StateAPI,
@@ -18,7 +15,6 @@
1815
)
1916
from eth.constants import (
2017
CREATE_CONTRACT_ADDRESS,
21-
SECPK1_N,
2218
)
2319
from eth.db.account import (
2420
AccountDB
@@ -90,82 +86,31 @@ def build_evm_message(self, transaction: SignedTransactionAPI) -> MessageAPI:
9086
)
9187
return message
9288

93-
def finalize_computation(
94-
self,
95-
transaction: SignedTransactionAPI,
96-
computation: ComputationAPI
97-
) -> ComputationAPI:
98-
transaction_context = self.vm_state.get_transaction_context(transaction)
99-
100-
# Self Destruct Refunds
101-
num_deletions = len(computation.get_accounts_for_deletion())
102-
if num_deletions:
103-
computation.refund_gas(REFUND_SELFDESTRUCT * num_deletions)
104-
105-
# Gas Refunds
106-
gas_remaining = computation.get_gas_remaining()
107-
gas_refunded = computation.get_gas_refund()
108-
gas_used = transaction.gas - gas_remaining
109-
gas_refund = min(gas_refunded, gas_used // 2)
110-
gas_refund_amount = (gas_refund + gas_remaining) * transaction_context.gas_price
111-
112-
if gas_refund_amount:
113-
self.vm_state.logger.debug2(
114-
'TRANSACTION REFUND: %s -> %s',
115-
gas_refund_amount,
116-
encode_hex(computation.msg.sender),
117-
)
118-
119-
self.vm_state.delta_balance(computation.msg.sender, gas_refund_amount)
120-
121-
# Miner Fees
122-
transaction_fee = \
123-
(transaction.gas - gas_remaining - gas_refund) * transaction.max_priority_fee_per_gas
124-
self.vm_state.logger.debug2(
125-
'TRANSACTION FEE: %s -> %s',
126-
transaction_fee,
127-
encode_hex(self.vm_state.coinbase),
128-
)
129-
self.vm_state.delta_balance(self.vm_state.coinbase, transaction_fee)
130-
131-
# Process Self Destructs
132-
for account, _ in computation.get_accounts_for_deletion():
133-
# TODO: need to figure out how we prevent multiple selfdestructs from
134-
# the same account and if this is the right place to put this.
135-
self.vm_state.logger.debug2('DELETING ACCOUNT: %s', encode_hex(account))
136-
137-
# TODO: this balance setting is likely superflous and can be
138-
# removed since `delete_account` does this.
139-
self.vm_state.set_balance(account, 0)
140-
self.vm_state.delete_account(account)
141-
142-
return computation
143-
14489

14590
class LondonState(BerlinState):
14691
account_db_class: Type[AccountDatabaseAPI] = AccountDB
14792
computation_class = LondonComputation
14893
transaction_executor_class: Type[TransactionExecutorAPI] = LondonTransactionExecutor
14994

95+
def get_tip(self, transaction: SignedTransactionAPI) -> int:
96+
return min(
97+
transaction.max_fee_per_gas - self.execution_context.base_fee_per_gas,
98+
transaction.max_priority_fee_per_gas,
99+
)
100+
101+
def get_gas_price(self, transaction: SignedTransactionAPI) -> int:
102+
return min(
103+
transaction.max_fee_per_gas,
104+
transaction.max_priority_fee_per_gas + self.execution_context.base_fee_per_gas,
105+
)
106+
150107
def validate_transaction(
151108
self,
152109
transaction: SignedTransactionAPI
153110
) -> None:
154-
# frontier validation (without the gas price checks)
155-
sender_nonce = self.get_nonce(transaction.sender)
156-
if sender_nonce != transaction.nonce:
157-
raise ValidationError(
158-
f"Invalid transaction nonce: Expected {sender_nonce}, but got {transaction.nonce}"
159-
)
160-
161-
# homestead validation
162-
if transaction.s > SECPK1_N // 2 or transaction.s == 0:
163-
raise ValidationError("Invalid signature S value")
164-
165111
validate_london_normalized_transaction(
166112
state=self,
167113
transaction=transaction,
168-
base_fee_per_gas=self.execution_context.base_fee_per_gas
169114
)
170115

171116
def get_transaction_context(self: StateAPI,

eth/vm/forks/london/transactions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ class LondonTypedTransaction(TypedTransaction):
222222
class LondonTransactionBuilder(BerlinTransactionBuilder):
223223
legacy_signed = LondonLegacyTransaction
224224
legacy_unsigned = LondonUnsignedLegacyTransaction
225+
typed_transaction = LondonTypedTransaction
225226

226227
@classmethod
227228
def new_unsigned_fee_burn_transaction(

eth/vm/forks/london/validation.py

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,29 @@
1+
from eth_utils.exceptions import ValidationError
2+
13
from eth.abc import (
24
SignedTransactionAPI,
35
StateAPI
46
)
5-
6-
from eth_utils.exceptions import ValidationError
7+
from eth.vm.forks.homestead.validation import (
8+
validate_homestead_transaction,
9+
)
710

811

912
def validate_london_normalized_transaction(
1013
state: StateAPI,
1114
transaction: SignedTransactionAPI,
12-
base_fee_per_gas: int
1315
) -> None:
1416
"""
1517
Validates a London normalized transaction.
1618
1719
Raise `eth.exceptions.ValidationError` if the sender cannot
1820
afford to send this transaction.
1921
"""
22+
base_fee_per_gas = state.execution_context.base_fee_per_gas
2023
if transaction.max_fee_per_gas < base_fee_per_gas:
2124
raise ValidationError(
2225
f"Sender's max fee per gas ({transaction.max_fee_per_gas}) is "
2326
f"lower than block's base fee per gas ({base_fee_per_gas})"
2427
)
2528

26-
sender_balance = state.get_balance(transaction.sender)
27-
if sender_balance < transaction.value:
28-
# This check is redundant to the later total_transaction_cost check,
29-
# but is helpful for clear error messages.
30-
raise ValidationError(
31-
f"Sender {transaction.sender!r} cannot afford txn value"
32-
f"{transaction.value} with account balance {sender_balance}"
33-
)
34-
35-
priority_fee_per_gas = min(
36-
transaction.max_priority_fee_per_gas,
37-
transaction.max_fee_per_gas - base_fee_per_gas,
38-
)
39-
40-
effective_gas_price = priority_fee_per_gas + base_fee_per_gas
41-
total_transaction_cost = transaction.value + effective_gas_price * transaction.gas
42-
43-
if sender_balance < total_transaction_cost:
44-
raise ValidationError(
45-
f"Sender does not have enough balance to cover transaction value and gas "
46-
f" (has {sender_balance}, needs {total_transaction_cost})"
47-
)
29+
validate_homestead_transaction(state, transaction)

eth/vm/state.py

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -88,18 +88,11 @@ def difficulty(self) -> int:
8888
def gas_limit(self) -> int:
8989
return self.execution_context.gas_limit
9090

91+
def get_tip(self, transaction: SignedTransactionAPI) -> int:
92+
return transaction.gas_price
93+
9194
def get_gas_price(self, transaction: SignedTransactionAPI) -> int:
92-
execution_context = self.execution_context
93-
try:
94-
base_gas_price = execution_context.base_fee_per_gas
95-
except AttributeError:
96-
return transaction.gas_price
97-
else:
98-
effective_price = min(
99-
transaction.max_fee_per_gas,
100-
transaction.max_priority_fee_per_gas + base_gas_price,
101-
)
102-
return effective_price
95+
return transaction.gas_price
10396

10497
#
10598
# Access to account db

0 commit comments

Comments
 (0)