-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement and diff test validate_header
- Loading branch information
1 parent
e8e1da9
commit 3911cb1
Showing
6 changed files
with
398 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
// See https://github.com/ethereum/execution-specs/blob/master/src/ethereum/cancun/fork.py | ||
|
||
from starkware.cairo.common.uint256 import Uint256 | ||
from starkware.cairo.common.math import unsigned_div_rem, split_felt | ||
from starkware.cairo.common.math_cmp import is_nn | ||
from starkware.cairo.common.bool import FALSE | ||
|
||
from src.model import model | ||
|
||
using Uint128 = felt; | ||
|
||
const ELASTICITY_MULTIPLIER = 2; | ||
const GAS_LIMIT_ADJUSTMENT_FACTOR = 1024; | ||
const GAS_LIMIT_MINIMUM = 5000; | ||
const BASE_FEE_MAX_CHANGE_DENOMINATOR = 8; | ||
const EMPTY_OMMER_HASH_LOW = 0xd312451b948a7413f0a142fd40d49347; | ||
const EMPTY_OMMER_HASH_HIGH = 0x1dcc4de8dec75d7aab85b567b6ccd41a; | ||
|
||
// @notice See https://github.com/ethereum/execution-specs/blob/master/src/ethereum/cancun/fork.py#L1118-L1154 | ||
// @dev We use the Uint128 alias to strenghten the fact that these felts should have been range_checked before | ||
func check_gas_limit{range_check_ptr}(gas_limit: Uint128, parent_gas_limit: Uint128) { | ||
let (max_adjustment_delta, _) = unsigned_div_rem(parent_gas_limit, GAS_LIMIT_ADJUSTMENT_FACTOR); | ||
|
||
with_attr error_message("InvalidBlock") { | ||
assert [range_check_ptr] = parent_gas_limit + max_adjustment_delta - gas_limit - 1; | ||
assert [range_check_ptr + 1] = gas_limit - (parent_gas_limit - max_adjustment_delta) - 1; | ||
assert [range_check_ptr + 2] = gas_limit - GAS_LIMIT_MINIMUM; | ||
let range_check_ptr = range_check_ptr + 3; | ||
} | ||
|
||
return (); | ||
} | ||
|
||
// @notice See https://github.com/ethereum/execution-specs/blob/master/src/ethereum/cancun/fork.py#L226-L285 | ||
// @dev We use the Uint128 alias to strenghten the fact that these felts should have been range_checked before | ||
func calculate_base_fee_per_gas{range_check_ptr}( | ||
block_gas_limit: Uint128, | ||
parent_gas_limit: Uint128, | ||
parent_gas_used: Uint128, | ||
parent_base_fee_per_gas: Uint128, | ||
) -> Uint128 { | ||
let (parent_gas_target, _) = unsigned_div_rem(parent_gas_limit, ELASTICITY_MULTIPLIER); | ||
|
||
check_gas_limit(block_gas_limit, parent_gas_limit); | ||
|
||
if (parent_gas_used == parent_gas_target) { | ||
return parent_base_fee_per_gas; | ||
} | ||
|
||
let is_parent_gas_used_greater_than_parent_gas_target = is_nn( | ||
parent_gas_used - parent_gas_target - 1 | ||
); | ||
if (is_parent_gas_used_greater_than_parent_gas_target != FALSE) { | ||
let gas_used_delta = parent_gas_used - parent_gas_target; | ||
let parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta; | ||
let (target_fee_gas_delta, _) = unsigned_div_rem(parent_fee_gas_delta, parent_gas_target); | ||
let (base_fee_per_gas_delta, _) = unsigned_div_rem( | ||
target_fee_gas_delta, BASE_FEE_MAX_CHANGE_DENOMINATOR | ||
); | ||
if (base_fee_per_gas_delta == 0) { | ||
return 1; | ||
} | ||
return base_fee_per_gas_delta; | ||
} | ||
|
||
let gas_used_delta = parent_gas_target - parent_gas_used; | ||
let parent_fee_gas_delta = parent_base_fee_per_gas * gas_used_delta; | ||
let (target_fee_gas_delta, _) = unsigned_div_rem(parent_fee_gas_delta, parent_gas_target); | ||
let (base_fee_per_gas_delta, _) = unsigned_div_rem( | ||
target_fee_gas_delta, BASE_FEE_MAX_CHANGE_DENOMINATOR | ||
); | ||
|
||
return parent_base_fee_per_gas - base_fee_per_gas_delta; | ||
} | ||
|
||
// @notice See https://github.com/ethereum/execution-specs/blob/master/src/ethereum/cancun/fork.py#L288-L332 | ||
// @dev Initial range checks for all values because header is filled with a hint | ||
func validate_header{range_check_ptr}(header: model.BlockHeader, parent_header: model.BlockHeader) { | ||
// parent_hash | ||
assert [range_check_ptr] = header.parent_hash.low; | ||
let range_check_ptr = range_check_ptr + 1; | ||
assert [range_check_ptr] = header.parent_hash.high; | ||
let range_check_ptr = range_check_ptr + 1; | ||
// ommers_hash | ||
assert [range_check_ptr] = header.ommers_hash.low; | ||
let range_check_ptr = range_check_ptr + 1; | ||
assert [range_check_ptr] = header.ommers_hash.high; | ||
let range_check_ptr = range_check_ptr + 1; | ||
// coinbase | ||
let (coinbase_high, coinbase_low) = split_felt(header.coinbase); | ||
assert [range_check_ptr] = coinbase_low; | ||
let range_check_ptr = range_check_ptr + 1; | ||
assert [range_check_ptr] = coinbase_high; | ||
let range_check_ptr = range_check_ptr + 1; | ||
assert [range_check_ptr] = 2 ** 32 - coinbase_high - 1; | ||
let range_check_ptr = range_check_ptr + 1; | ||
// state_root | ||
assert [range_check_ptr] = header.state_root.low; | ||
let range_check_ptr = range_check_ptr + 1; | ||
assert [range_check_ptr] = header.state_root.high; | ||
let range_check_ptr = range_check_ptr + 1; | ||
// transactions_root | ||
assert [range_check_ptr] = header.transactions_root.low; | ||
let range_check_ptr = range_check_ptr + 1; | ||
assert [range_check_ptr] = header.transactions_root.high; | ||
let range_check_ptr = range_check_ptr + 1; | ||
// receipt_root | ||
assert [range_check_ptr] = header.receipt_root.low; | ||
let range_check_ptr = range_check_ptr + 1; | ||
assert [range_check_ptr] = header.receipt_root.high; | ||
let range_check_ptr = range_check_ptr + 1; | ||
// withdrawals_root | ||
assert header.withdrawals_root.is_some * (1 - header.withdrawals_root.is_some) = 0; | ||
let withdrawals_root = cast(header.withdrawals_root.value, Uint256*); | ||
assert [range_check_ptr] = withdrawals_root.low; | ||
let range_check_ptr = range_check_ptr + 1; | ||
assert [range_check_ptr] = withdrawals_root.high; | ||
let range_check_ptr = range_check_ptr + 1; | ||
// difficulty | ||
assert [range_check_ptr] = header.difficulty.low; | ||
let range_check_ptr = range_check_ptr + 1; | ||
assert [range_check_ptr] = header.difficulty.high; | ||
let range_check_ptr = range_check_ptr + 1; | ||
// number | ||
assert [range_check_ptr] = header.number; | ||
let range_check_ptr = range_check_ptr + 1; | ||
// gas_limit | ||
assert [range_check_ptr] = header.gas_limit; | ||
let range_check_ptr = range_check_ptr + 1; | ||
// gas_used | ||
assert [range_check_ptr] = header.gas_used; | ||
let range_check_ptr = range_check_ptr + 1; | ||
// timestamp | ||
assert [range_check_ptr] = header.timestamp; | ||
let range_check_ptr = range_check_ptr + 1; | ||
// mix_hash | ||
assert [range_check_ptr] = header.mix_hash.low; | ||
let range_check_ptr = range_check_ptr + 1; | ||
assert [range_check_ptr] = header.mix_hash.high; | ||
let range_check_ptr = range_check_ptr + 1; | ||
// nonce | ||
assert [range_check_ptr] = header.nonce; | ||
let range_check_ptr = range_check_ptr + 1; | ||
// base_fee_per_gas | ||
assert header.base_fee_per_gas.is_some * (1 - header.base_fee_per_gas.is_some) = 0; | ||
assert [range_check_ptr] = header.base_fee_per_gas.value; | ||
let range_check_ptr = range_check_ptr + 1; | ||
// blob_gas_used | ||
assert header.blob_gas_used.is_some * (1 - header.blob_gas_used.is_some) = 0; | ||
assert [range_check_ptr] = header.blob_gas_used.value; | ||
let range_check_ptr = range_check_ptr + 1; | ||
// excess_blob_gas | ||
assert header.excess_blob_gas.is_some * (1 - header.excess_blob_gas.is_some) = 0; | ||
assert [range_check_ptr] = header.excess_blob_gas.value; | ||
let range_check_ptr = range_check_ptr + 1; | ||
// parent_beacon_block_root | ||
assert header.parent_beacon_block_root.is_some * ( | ||
1 - header.parent_beacon_block_root.is_some | ||
) = 0; | ||
let parent_beacon_block_root = cast(header.parent_beacon_block_root.value, Uint256*); | ||
assert [range_check_ptr] = parent_beacon_block_root.low; | ||
let range_check_ptr = range_check_ptr + 1; | ||
assert [range_check_ptr] = parent_beacon_block_root.high; | ||
let range_check_ptr = range_check_ptr + 1; | ||
// requests_root | ||
assert header.requests_root.is_some * (1 - header.requests_root.is_some) = 0; | ||
let requests_root = cast(header.requests_root.value, Uint256*); | ||
assert [range_check_ptr] = requests_root.low; | ||
let range_check_ptr = range_check_ptr + 1; | ||
assert [range_check_ptr] = requests_root.high; | ||
let range_check_ptr = range_check_ptr + 1; | ||
// extra_data_len | ||
assert [range_check_ptr] = header.extra_data_len; | ||
let range_check_ptr = range_check_ptr + 1; | ||
|
||
with_attr error_message("InvalidBlock") { | ||
assert [range_check_ptr] = header.gas_limit - header.gas_used; | ||
let range_check_ptr = range_check_ptr + 1; | ||
let expected_base_fee_per_gas = calculate_base_fee_per_gas( | ||
header.gas_limit, | ||
parent_header.gas_limit, | ||
parent_header.gas_used, | ||
parent_header.base_fee_per_gas.value, | ||
); | ||
assert expected_base_fee_per_gas = header.base_fee_per_gas.value; | ||
assert [range_check_ptr] = header.timestamp - parent_header.timestamp - 1; | ||
assert [range_check_ptr + 1] = header.number - parent_header.number - 1; | ||
assert [range_check_ptr + 2] = 32 - header.extra_data_len; | ||
let range_check_ptr = range_check_ptr + 3; | ||
assert header.difficulty.low = 0; | ||
assert header.difficulty.high = 0; | ||
assert header.nonce = 0; | ||
assert header.ommers_hash.low = EMPTY_OMMER_HASH_LOW; | ||
assert header.ommers_hash.high = EMPTY_OMMER_HASH_HIGH; | ||
} | ||
|
||
// TODO: Implement block header hash check | ||
// block_parent_hash = keccak256(rlp.encode(parent_header)) | ||
// if header.parent_hash != block_parent_hash: | ||
// raise InvalidBlock | ||
return (); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
from programs.fork import check_gas_limit, calculate_base_fee_per_gas, validate_header, Uint128 | ||
from src.model import model | ||
|
||
func test_check_gas_limit{range_check_ptr}() { | ||
tempvar gas_limit: Uint128; | ||
tempvar parent_gas_limit: Uint128; | ||
%{ | ||
ids.gas_limit = program_input["gas_limit"] | ||
ids.parent_gas_limit = program_input["parent_gas_limit"] | ||
%} | ||
check_gas_limit(gas_limit, parent_gas_limit); | ||
|
||
return (); | ||
} | ||
|
||
func test_calculate_base_fee_per_gas{range_check_ptr}() -> Uint128 { | ||
tempvar block_gas_limit: Uint128; | ||
tempvar parent_gas_limit: Uint128; | ||
tempvar parent_gas_used: Uint128; | ||
tempvar parent_base_fee_per_gas: Uint128; | ||
%{ | ||
ids.block_gas_limit = program_input["block_gas_limit"] | ||
ids.parent_gas_limit = program_input["parent_gas_limit"] | ||
ids.parent_gas_used = program_input["parent_gas_used"] | ||
ids.parent_base_fee_per_gas = program_input["parent_base_fee_per_gas"] | ||
%} | ||
return calculate_base_fee_per_gas( | ||
block_gas_limit, parent_gas_limit, parent_gas_used, parent_base_fee_per_gas | ||
); | ||
} | ||
|
||
func test_validate_header{range_check_ptr}() { | ||
alloc_locals; | ||
local header: model.BlockHeader*; | ||
local parent_header: model.BlockHeader*; | ||
%{ | ||
if '__dict_manager' not in globals(): | ||
from starkware.cairo.common.dict import DictManager | ||
__dict_manager = DictManager() | ||
from tests.utils.hints import gen_arg | ||
ids.header = gen_arg(__dict_manager, segments, program_input["header"]) | ||
ids.parent_header = gen_arg(__dict_manager, segments, program_input["parent_header"]) | ||
%} | ||
validate_header([header], [parent_header]); | ||
return (); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
from ethereum.cancun.blocks import Header | ||
from ethereum.cancun.fork import ( | ||
calculate_base_fee_per_gas, | ||
check_gas_limit, | ||
validate_header, | ||
) | ||
from ethereum.exceptions import InvalidBlock | ||
from hypothesis import given | ||
from hypothesis.strategies import integers | ||
|
||
from tests.fixtures.data import block_header_strategy | ||
from tests.utils.errors import cairo_error | ||
from tests.utils.models import BlockHeader | ||
|
||
|
||
class TestFork: | ||
@given( | ||
integers(min_value=0, max_value=2**128 - 1), | ||
integers(min_value=0, max_value=2**128 - 1), | ||
) | ||
def test_check_gas_limit(self, cairo_run, gas_limit, parent_gas_limit): | ||
error = check_gas_limit(gas_limit, parent_gas_limit) | ||
if not error: | ||
with cairo_error("InvalidBlock"): | ||
cairo_run( | ||
"test_check_gas_limit", | ||
gas_limit=gas_limit, | ||
parent_gas_limit=parent_gas_limit, | ||
) | ||
else: | ||
cairo_run( | ||
"test_check_gas_limit", | ||
gas_limit=gas_limit, | ||
parent_gas_limit=parent_gas_limit, | ||
) | ||
|
||
@given( | ||
integers(min_value=0, max_value=2**128 - 1), | ||
integers(min_value=0, max_value=2**128 - 1), | ||
integers(min_value=0, max_value=2**128 - 1), | ||
integers(min_value=0, max_value=2**128 - 1), | ||
) | ||
def test_calculate_base_fee_per_gas( | ||
self, | ||
cairo_run, | ||
block_gas_limit, | ||
parent_gas_limit, | ||
parent_gas_used, | ||
parent_base_fee_per_gas, | ||
): | ||
try: | ||
expected = calculate_base_fee_per_gas( | ||
block_gas_limit, | ||
parent_gas_limit, | ||
parent_gas_used, | ||
parent_base_fee_per_gas, | ||
) | ||
except InvalidBlock: | ||
expected = None | ||
|
||
if expected is not None: | ||
assert expected == cairo_run( | ||
"test_calculate_base_fee_per_gas", | ||
block_gas_limit=block_gas_limit, | ||
parent_gas_limit=parent_gas_limit, | ||
parent_gas_used=parent_gas_used, | ||
parent_base_fee_per_gas=parent_base_fee_per_gas, | ||
) | ||
else: | ||
with cairo_error("InvalidBlock"): | ||
cairo_run( | ||
"test_calculate_base_fee_per_gas", | ||
block_gas_limit=block_gas_limit, | ||
parent_gas_limit=parent_gas_limit, | ||
parent_gas_used=parent_gas_used, | ||
parent_base_fee_per_gas=parent_base_fee_per_gas, | ||
) | ||
|
||
@given(header=block_header_strategy, parent_header=block_header_strategy) | ||
def test_validate_header(self, cairo_run, header, parent_header): | ||
error = None | ||
try: | ||
validate_header(Header(**header), Header(**parent_header)) | ||
except InvalidBlock as e: | ||
error = e | ||
|
||
if error is not None: | ||
with cairo_error("InvalidBlock"): | ||
cairo_run( | ||
"test_validate_header", | ||
header=BlockHeader.model_validate(header), | ||
parent_header=BlockHeader.model_validate(parent_header), | ||
) | ||
else: | ||
cairo_run( | ||
"test_validate_header", | ||
header=BlockHeader.model_validate(header), | ||
parent_header=BlockHeader.model_validate(parent_header), | ||
) |
Oops, something went wrong.