Skip to content

Commit 44bb159

Browse files
committed
Apply gas limit rules correctly
Also, deduplicate some logic, to ease maintenance and improve correctness. Note that the 'diff' approach had a bug when 'diff' was negative. That approach needed a math.abs() to correctly validate the limit.
1 parent 2936da0 commit 44bb159

File tree

8 files changed

+79
-77
lines changed

8 files changed

+79
-77
lines changed

eth/_utils/headers.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
GENESIS_PARENT_HASH,
1616
GAS_LIMIT_EMA_DENOMINATOR,
1717
GAS_LIMIT_ADJUSTMENT_FACTOR,
18+
GAS_LIMIT_MAXIMUM,
1819
GAS_LIMIT_MINIMUM,
1920
GAS_LIMIT_USAGE_ADJUSTMENT_NUMERATOR,
2021
GAS_LIMIT_USAGE_ADJUSTMENT_DENOMINATOR,
@@ -99,13 +100,13 @@ def fill_header_params_from_parent(
99100
return header_kwargs
100101

101102

102-
def compute_gas_limit_bounds(parent: BlockHeaderAPI) -> Tuple[int, int]:
103+
def compute_gas_limit_bounds(previous_limit: int) -> Tuple[int, int]:
103104
"""
104105
Compute the boundaries for the block gas limit based on the parent block.
105106
"""
106-
boundary_range = parent.gas_limit // GAS_LIMIT_ADJUSTMENT_FACTOR
107-
upper_bound = parent.gas_limit + boundary_range
108-
lower_bound = max(GAS_LIMIT_MINIMUM, parent.gas_limit - boundary_range)
107+
boundary_range = previous_limit // GAS_LIMIT_ADJUSTMENT_FACTOR
108+
upper_bound = min(GAS_LIMIT_MAXIMUM, previous_limit + boundary_range)
109+
lower_bound = max(GAS_LIMIT_MINIMUM, previous_limit - boundary_range)
109110
return lower_bound, upper_bound
110111

111112

eth/abc.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4016,13 +4016,6 @@ def validate_seal(self, header: BlockHeaderAPI) -> None:
40164016
"""
40174017
...
40184018

4019-
@abstractmethod
4020-
def validate_gaslimit(self, header: BlockHeaderAPI) -> None:
4021-
"""
4022-
Validate the gas limit on the given ``header``.
4023-
"""
4024-
...
4025-
40264019
@abstractmethod
40274020
def validate_uncles(self, block: BlockAPI) -> None:
40284021
"""

eth/chains/base.py

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,6 @@
3131
from eth._utils.datatypes import (
3232
Configurable,
3333
)
34-
from eth._utils.headers import (
35-
compute_gas_limit_bounds,
36-
)
3734
from eth._utils.rlp import (
3835
validate_imported_block_unchanged,
3936
)
@@ -543,28 +540,11 @@ def validate_block(self, block: BlockAPI) -> None:
543540
vm.validate_seal(block.header)
544541
vm.validate_seal_extension(block.header, ())
545542
self.validate_uncles(block)
546-
self.validate_gaslimit(block.header)
547543

548544
def validate_seal(self, header: BlockHeaderAPI) -> None:
549545
vm = self.get_vm(header)
550546
vm.validate_seal(header)
551547

552-
def validate_gaslimit(self, header: BlockHeaderAPI) -> None:
553-
parent_header = self.get_block_header_by_hash(header.parent_hash)
554-
low_bound, high_bound = compute_gas_limit_bounds(parent_header)
555-
if header.gas_limit < low_bound:
556-
raise ValidationError(
557-
f"The gas limit on block {encode_hex(header.hash)} "
558-
f"is too low: {header.gas_limit}. "
559-
f"It must be at least {low_bound}"
560-
)
561-
elif header.gas_limit > high_bound:
562-
raise ValidationError(
563-
f"The gas limit on block {encode_hex(header.hash)} "
564-
f"is too high: {header.gas_limit}. "
565-
f"It must be at most {high_bound}"
566-
)
567-
568548
def validate_uncles(self, block: BlockAPI) -> None:
569549
has_uncles = len(block.uncles) > 0
570550
should_have_uncles = block.header.uncles_hash != EMPTY_UNCLE_HASH

eth/validation.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424

2525
from eth_utils.toolz import itertoolz
2626

27+
from eth._utils.headers import (
28+
compute_gas_limit_bounds,
29+
)
2730
from eth.abc import VirtualMachineAPI
2831
from eth.constants import (
2932
SECPK1_N,
@@ -247,3 +250,15 @@ def validate_header_params_for_configuration(header_params: Dict[str, Any]) -> N
247250
f"({', '.join(tuple(sorted(ALLOWED_HEADER_FIELDS)))}). "
248251
f"The provided fields ({', '.join(tuple(sorted(extra_fields)))}) are not supported"
249252
)
253+
254+
255+
def validate_gas_limit(gas_limit: int, parent_gas_limit: int) -> None:
256+
low_bound, high_bound = compute_gas_limit_bounds(parent_gas_limit)
257+
if gas_limit < low_bound:
258+
raise ValidationError(
259+
f"The gas limit {gas_limit} is too low. It must be at least {low_bound}"
260+
)
261+
elif gas_limit > high_bound:
262+
raise ValidationError(
263+
f"The gas limit {gas_limit} is too high. It must be at most {high_bound}"
264+
)

eth/vm/base.py

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,6 @@
4848
PowConsensus,
4949
)
5050
from eth.constants import (
51-
GAS_LIMIT_ADJUSTMENT_FACTOR,
52-
GAS_LIMIT_MAXIMUM,
53-
GAS_LIMIT_MINIMUM,
5451
GENESIS_PARENT_HASH,
5552
MAX_PREV_HEADER_DEPTH,
5653
MAX_UNCLES,
@@ -74,6 +71,7 @@
7471
)
7572
from eth.validation import (
7673
validate_length_lte,
74+
validate_gas_limit,
7775
)
7876
from eth.vm.execution_context import (
7977
ExecutionContext,
@@ -613,19 +611,7 @@ def validate_gas(
613611
cls,
614612
header: BlockHeaderAPI,
615613
parent_header: BlockHeaderAPI) -> None:
616-
617-
gas_limit = header.gas_limit
618-
parent_gas_limit = parent_header.gas_limit
619-
620-
if gas_limit < GAS_LIMIT_MINIMUM:
621-
raise ValidationError(f"Gas limit {gas_limit} is below minimum {GAS_LIMIT_MINIMUM}")
622-
if gas_limit > GAS_LIMIT_MAXIMUM:
623-
raise ValidationError(f"Gas limit {gas_limit} is above maximum {GAS_LIMIT_MAXIMUM}")
624-
diff = gas_limit - parent_gas_limit
625-
if diff > (parent_gas_limit // GAS_LIMIT_ADJUSTMENT_FACTOR):
626-
raise ValidationError(
627-
f"Gas limit {gas_limit} difference to parent {parent_gas_limit} is too big {diff}"
628-
)
614+
validate_gas_limit(header.gas_limit, parent_header.gas_limit)
629615

630616
def validate_seal(self, header: BlockHeaderAPI) -> None:
631617
try:

eth/vm/forks/london/__init__.py

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
BlockHeaderAPI,
77
)
88
from eth.rlp.blocks import BaseBlock
9+
from eth.validation import validate_gas_limit
910
from eth.vm.forks.berlin import BerlinVM
1011
from eth.vm.state import BaseState
1112

1213
from .blocks import LondonBlock
1314
from .constants import (
1415
ELASTICITY_MULTIPLIER,
15-
MINIMUM_GAS_LIMIT,
1616
)
1717
from .headers import (
1818
LondonBackwardsHeader,
@@ -45,37 +45,15 @@ def validate_gas(
4545
parent_header: BlockHeaderAPI) -> None:
4646

4747
if parent_header.base_fee_per_gas is not None:
48-
# This is not the first block of the fork
48+
# Follow normal gas limit rules if the previous block had a base fee
4949
parent_gas_limit = parent_header.gas_limit
50-
parent_gas_target = parent_gas_limit // ELASTICITY_MULTIPLIER
5150
else:
52-
# On the fork block, don't account for the ELASTICITY_MULTIPLIER
53-
# to avoid unduly halving the gas target.
51+
# On the fork block, double the gas limit.
52+
# That way, the gas target (which is half the London limit) equals the
53+
# previous (pre-London) gas limit.
5454
parent_gas_limit = parent_header.gas_limit * ELASTICITY_MULTIPLIER
55-
parent_gas_target = parent_header.gas_limit
5655

57-
if header.gas_used > header.gas_limit:
58-
raise ValidationError(
59-
f"Block used too much gas: {header.gas_used} "
60-
f"(max: {header.gas_limit})"
61-
)
62-
63-
if header.gas_limit > parent_gas_limit + (parent_gas_limit // 1024):
64-
raise ValidationError(
65-
f"Gas limit increased too much (from {parent_gas_limit} "
66-
f"to {header.gas_limit})"
67-
)
68-
69-
if header.gas_limit < parent_gas_target - (parent_gas_target // 1024):
70-
raise ValidationError(
71-
f"Gas limit decreased too much (from {parent_gas_target} "
72-
f"to {header.gas_limit})"
73-
)
74-
75-
if header.gas_limit < MINIMUM_GAS_LIMIT:
76-
raise ValidationError(
77-
f"Gas limit is lower than the minimum ({header.gas_limit} < {MINIMUM_GAS_LIMIT})"
78-
)
56+
validate_gas_limit(header.gas_limit, parent_gas_limit)
7957

8058
expected_base_fee_per_gas = calculate_expected_base_fee_per_gas(parent_header)
8159
if expected_base_fee_per_gas != header.base_fee_per_gas:

eth/vm/forks/london/constants.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,4 @@
1010

1111
BASE_FEE_MAX_CHANGE_DENOMINATOR = 8
1212
INITIAL_BASE_FEE = 1000000000
13-
MINIMUM_GAS_LIMIT = 5000
1413
ELASTICITY_MULTIPLIER = 2

tests/core/vm/test_vm.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def noproof_consensus_chain(vm_class):
3737
MiningChain,
3838
api.fork_at(vm_class, 0),
3939
api.disable_pow_check(),
40-
api.genesis(),
40+
api.genesis(params=dict(gas_limit=100000)),
4141
)
4242

4343

@@ -127,3 +127,53 @@ def test_validate_header_fails_on_invalid_parent(noproof_consensus_chain):
127127

128128
with pytest.raises(ValidationError, match="Blocks must be numbered consecutively"):
129129
vm.validate_header(block2.header.copy(block_number=3), block1.header)
130+
131+
132+
def test_validate_gas_limit_almost_too_low(noproof_consensus_chain):
133+
block1 = noproof_consensus_chain.mine_block()
134+
block2 = noproof_consensus_chain.mine_block()
135+
136+
barely_valid_low_gas_limit = block1.header.gas_limit - (block1.header.gas_limit // 1024)
137+
barely_valid_header = block2.header.copy(gas_limit=barely_valid_low_gas_limit)
138+
139+
vm = noproof_consensus_chain.get_vm(block2.header)
140+
141+
vm.validate_header(barely_valid_header, block1.header)
142+
143+
144+
def test_validate_gas_limit_too_low(noproof_consensus_chain):
145+
block1 = noproof_consensus_chain.mine_block()
146+
block2 = noproof_consensus_chain.mine_block()
147+
148+
invalid_low_gas_limit = block1.header.gas_limit - (block1.header.gas_limit // 1024) - 1
149+
invalid_header = block2.header.copy(gas_limit=invalid_low_gas_limit)
150+
151+
vm = noproof_consensus_chain.get_vm(block2.header)
152+
153+
with pytest.raises(ValidationError, match="[Gg]as limit"):
154+
vm.validate_header(invalid_header, block1.header)
155+
156+
157+
def test_validate_gas_limit_almost_too_high(noproof_consensus_chain):
158+
block1 = noproof_consensus_chain.mine_block()
159+
block2 = noproof_consensus_chain.mine_block()
160+
161+
barely_valid_high_gas_limit = block1.header.gas_limit + (block1.header.gas_limit // 1024)
162+
barely_valid_header = block2.header.copy(gas_limit=barely_valid_high_gas_limit)
163+
164+
vm = noproof_consensus_chain.get_vm(block2.header)
165+
166+
vm.validate_header(barely_valid_header, block1.header)
167+
168+
169+
def test_validate_gas_limit_too_high(noproof_consensus_chain):
170+
block1 = noproof_consensus_chain.mine_block()
171+
block2 = noproof_consensus_chain.mine_block()
172+
173+
invalid_high_gas_limit = block1.header.gas_limit + (block1.header.gas_limit // 1024) + 1
174+
invalid_header = block2.header.copy(gas_limit=invalid_high_gas_limit)
175+
176+
vm = noproof_consensus_chain.get_vm(block2.header)
177+
178+
with pytest.raises(ValidationError, match="[Gg]as limit"):
179+
vm.validate_header(invalid_header, block1.header)

0 commit comments

Comments
 (0)