Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/actions/build-fixtures/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ inputs:
split_label:
description: "Label for this fork-range split. Empty for unsplit builds."
default: ""
extra_params:
description: "Extra fill params appended after the feature's own (last flag wins). Empty for none."
default: ""
split_retention_days:
description: "retention-days for the split fixture artifact. Empty for the repo default."
default: ""
Expand Down Expand Up @@ -72,7 +75,7 @@ runs:

# Allow exit code 5 (NO_TESTS_COLLECTED) for fork ranges with no tests.
EXIT_CODE=0
just fill-release $EVM_ARGS ${{ steps.properties.outputs.fill-params }} $FORK_ARGS $OUTPUT_ARG --build-name ${{ inputs.release_name }} || EXIT_CODE=$?
just fill-release $EVM_ARGS ${{ steps.properties.outputs.fill-params }} $FORK_ARGS ${{ inputs.extra_params }} $OUTPUT_ARG --build-name ${{ inputs.release_name }} || EXIT_CODE=$?
if [ "$EXIT_CODE" -ne 0 ] && [ "$EXIT_CODE" -ne 5 ]; then
exit "$EXIT_CODE"
fi
Expand Down
11 changes: 11 additions & 0 deletions .github/configs/feature.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,14 @@ benchmark_fast:
devnet:
evm-type: eels
fill-params: --until=Amsterdam --generate-all-formats

# Variant tarballs attached beside the main fixtures asset of `tests`
# and `<feat>-devnet` releases. Each variant fills its own fork range
# with extra fill params on its own runner and is packaged by the
# combine job as `<tarball>_<variant>.tar.gz`. A failed variant fill
# never blocks the release; its tarball is simply absent.
variants:
binary:
from: Amsterdam
until: Amsterdam
fill-params: --state-trie pbt
29 changes: 29 additions & 0 deletions .github/scripts/generate_build_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ def validate_inputs(feature: str, version: str, branch: str, evm: str) -> None:
if feature in ("devnet", "-devnet"):
fail("devnet releases require a <feat>- prefix, e.g. bal-devnet")

# `variants` configures the extra tarballs of mainnet-type
# releases; it is not a releasable feature itself.
if feature == "variants":
fail("'variants' is a reserved config key, not a feature")

# `<feat>-devnet-<n>`: the devnet index belongs in the version (X of
# vX.Y.Z), not in the feature name.
if "-devnet-" in feature:
Expand Down Expand Up @@ -244,9 +249,33 @@ def main() -> None:

build, labels = build_matrix(config[lookup], name, fork_ranges)

# Mainnet-type releases (`tests` and `<feat>-devnet`) also fill
# each configured variant on its own runner. Variant entries ride
# the same matrix; the combine job packages each one into its own
# `<tarball>_<variant>.tar.gz` beside the main fixtures asset.
for entry in build:
entry.setdefault("variant", "")
entry.setdefault("extra_params", "")
variants = config.get("variants") or {}
variant_labels = ""
if lookup in ("tests", "devnet") and variants:
for variant_name, variant in variants.items():
build.append(
{
"feature": name,
"label": variant_name,
"from_fork": variant["from"],
"until_fork": variant.get("until", variant["from"]),
"variant": variant_name,
"extra_params": variant["fill-params"],
}
)
variant_labels = " ".join(variants)

print(f"build_matrix={json.dumps(build)}")
print(f"feature_name={name}")
print(f"combine_labels={labels}")
print(f"variant_labels={variant_labels}")


if __name__ == "__main__":
Expand Down
39 changes: 38 additions & 1 deletion .github/scripts/tests/test_release_scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,42 @@ def test_devnet_name_resolves_to_shared_feature(self):
# Entries keep the friendly name, not the shared "devnet" key.
assert all(e["feature"] == "bal-devnet" for e in matrix)

def test_mainnet_features_gain_variant_entries(self):
"""Verify tests/devnet matrices append the configured variants."""
for args in (
("tests", "v24.0.0"),
("bal-devnet", "v7.0.0", "devnets/bal/7"),
):
result = run_script(BUILD_MATRIX_SCRIPT, *args)
assert result.returncode == 0
out = parse_matrix_output(result.stdout)
matrix = json.loads(out["build_matrix"])
assert out["variant_labels"] == "binary"
(binary,) = [e for e in matrix if e["variant"] == "binary"]
assert binary["label"] == "binary"
assert binary["from_fork"] == "Amsterdam"
assert binary["until_fork"] == "Amsterdam"
assert "--state-trie pbt" in binary["extra_params"]
# Non-variant entries carry the uniform empty fields.
assert all(
e["extra_params"] == "" for e in matrix if e["variant"] == ""
)

def test_feature_only_features_gain_no_variants(self):
"""Verify non-mainnet features are variant-free."""
result = run_script(BUILD_MATRIX_SCRIPT, "benchmark", "v24.0.0")
assert result.returncode == 0
out = parse_matrix_output(result.stdout)
matrix = json.loads(out["build_matrix"])
assert out["variant_labels"] == ""
assert all(e["variant"] == "" for e in matrix)

def test_variants_is_not_a_releasable_feature(self):
"""Verify the reserved `variants` key is rejected."""
result = run_script(BUILD_MATRIX_SCRIPT, "variants", "v1.0.0")
assert result.returncode == 1
assert "reserved" in result.stderr

def test_unknown_feature_fails(self):
"""Verify error exit for unknown feature name."""
result = run_script(BUILD_MATRIX_SCRIPT, "nonexistent", "v1.0.0")
Expand All @@ -101,10 +137,11 @@ def test_output_is_valid_github_actions_format(self):
result = run_script(BUILD_MATRIX_SCRIPT, "tests", "v24.0.0")
assert result.returncode == 0
lines = result.stdout.strip().splitlines()
assert len(lines) == 3
assert len(lines) == 4
assert lines[0].startswith("build_matrix=")
assert lines[1].startswith("feature_name=")
assert lines[2].startswith("combine_labels=")
assert lines[3].startswith("variant_labels=")


class TestValidateInputs:
Expand Down
25 changes: 24 additions & 1 deletion .github/workflows/release_fixtures.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ jobs:
build_matrix: ${{ steps.matrix.outputs.build_matrix }}
feature_name: ${{ steps.matrix.outputs.feature_name }}
combine_labels: ${{ steps.matrix.outputs.combine_labels }}
variant_labels: ${{ steps.matrix.outputs.variant_labels }}
target_sha: ${{ steps.cached.outputs.target_sha || steps.target_sha.outputs.sha }}
short_sha: ${{ steps.target_sha.outputs.short_sha }}
artifact_run_id: ${{ steps.cached.outputs.run_id }}
Expand Down Expand Up @@ -143,6 +144,9 @@ jobs:
if: needs.setup.outputs.run == 'true'
runs-on: [self-hosted-ghr, size-gigachungus-x64]
timeout-minutes: 1440
# A variant tarball is a bonus asset: its fill failing must never
# block the main release, whose tarball it merely rides beside.
continue-on-error: ${{ matrix.variant != '' }}
strategy:
# A release must be complete, so abort on the first failed range; a
# nightly wants every range's result for debugging.
Expand All @@ -161,6 +165,7 @@ jobs:
from_fork: ${{ matrix.from_fork }}
until_fork: ${{ matrix.until_fork }}
split_label: ${{ matrix.label }}
extra_params: ${{ matrix.extra_params }}
# Nightly splits are intermediates consumed by `combine` right
# away; don't retain them for the repo-default period.
split_retention_days: ${{ github.event_name == 'schedule' && '1' || '' }}
Expand Down Expand Up @@ -225,6 +230,23 @@ jobs:
fi
uv run -q .github/scripts/create_release_tarball.py combined "$TARBALL"
echo "path=$TARBALL" >> "$GITHUB_OUTPUT"
- name: Create variant tarballs
if: needs.setup.outputs.variant_labels != ''
shell: bash
env:
GH_TOKEN: ${{ github.token }}
TARBALL: ${{ steps.tarball.outputs.path }}
run: |
for variant in ${{ needs.setup.outputs.variant_labels }}; do
echo "Downloading: fixtures__${variant}"
if gh run download ${{ github.run_id }} -n "fixtures__${variant}" --dir "variant_artifacts/${variant}"; then
uv run -q .github/scripts/create_release_tarball.py \
"variant_artifacts/${variant}" \
"${TARBALL%.tar.gz}_${variant}.tar.gz"
else
echo "No artifact for variant ${variant} (fill failed or empty), skipping"
fi
done
- name: Upload combined fixture tarball
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
Expand All @@ -233,7 +255,8 @@ jobs:
# cached-release resolver derives this exact name from each
# run's head SHA. The tarball inside carries the feature name.
name: fixtures_${{ needs.setup.outputs.short_sha }}
path: ${{ steps.tarball.outputs.path }}
# The main tarball plus any variant tarballs beside it.
path: fixtures*.tar.gz
# Keep nightly tarballs for five days; a quiet nightly re-runs
# after four (see check_new_commits.py), so a live artifact
# always exists. Release tarballs keep the repo default since
Expand Down
15 changes: 15 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,18 @@ jobs:
- uses: ./.github/actions/setup-uv
- name: Run test-ci-scripts
run: just test-ci-scripts

binary-trie:
runs-on: ubuntu-latest
needs: static
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ./.github/actions/setup-uv
- name: Run binary trie unit tests
run: just binary-trie-unit-test
env:
PYTEST_XDIST_AUTO_NUM_WORKERS: auto
- name: Fill in PBT mode
run: just fill-state-trie-pbt
env:
PYTEST_XDIST_AUTO_NUM_WORKERS: auto
33 changes: 33 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,23 @@ fill-release *args:
--log-level=DEBUG \
"$@"

# Fill the consensus tests using EELS in PBT mode (--state-trie pbt)
[group('consensus tests')]
fill-state-trie-pbt *args: (_tmp-logs "fill-state-trie-pbt")
uv run fill \
-m "not slow" \
-n {{ xdist_workers }} --dist=loadgroup \
--skip-index \
--state-trie pbt \
--output="{{ output_dir }}/fill-state-trie-pbt/fixtures" \
--basetemp="{{ output_dir }}/fill-state-trie-pbt/tmp" \
--log-to "{{ output_dir }}/fill-state-trie-pbt/logs" \
--clean \
--until "{{ latest_fork }}" \
--durations=50 \
"$@" \
tests

# --- Integration Tests ---

# Fill the base coverage consensus tests using EELS with PyPy
Expand Down Expand Up @@ -206,6 +223,7 @@ json-loader *args: (_tmp "json-loader")
--cov-report "xml:{{ output_dir }}/json-loader/coverage.xml" \
--durations=50 \
--basetemp="{{ output_dir }}/json-loader/tmp" \
--ignore=tests/binary_trie \
"$@" \
tests/json_loader

Expand Down Expand Up @@ -245,6 +263,21 @@ test-tests-pypy *args: (_tmp "test-tests-pypy")
test-ci-scripts *args:
uv run pytest "$@" .github/scripts/tests/

# Run the binary trie state-provider unit tests
[group('unit tests')]
binary-trie-unit-test *args: (_tmp "binary-trie-unit-test")
uv run pytest \
-n {{ xdist_workers }} \
--cov=ethereum.partitioned_binary_tree \
--cov=ethereum.state_pbt \
--cov-branch \
--cov-report=term \
--cov-report "xml:{{ output_dir }}/binary-trie-unit-test/coverage.xml" \
--no-cov-on-fail \
--basetemp="{{ output_dir }}/binary-trie-unit-test/tmp" \
"$@" \
tests/binary_trie

# --- Benchmarks ---

# test_return_revert is excluded: its max-size INVALID-padded callees make
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,9 @@ class StateCommitment(Enum):
MPT = auto()
"""Merkle-Patricia trie."""

PBT = auto()
"""EIP-8297 partitioned binary tree."""


class AccessList(CamelModel, RLPSerializable):
"""Access List for transactions."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ def _add_default_ignores(self, args: List[str]) -> List[str]:
default_ignores = [
"tests/evm_tools",
"tests/json_loader",
"tests/binary_trie",
"tests/fixtures",
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from _pytest.mark.structures import ParameterSet
from pytest import Mark, Metafunc, StashKey

from execution_testing.base_types import StateCommitment
from execution_testing.client_clis import TransitionTool
from execution_testing.forks import (
ALL_FORKS,
Expand All @@ -40,6 +41,7 @@
get_transition_forks,
transition_fork_to,
)
from execution_testing.forks.base_fork import BaseFork
from execution_testing.logging import (
get_logger,
)
Expand All @@ -50,6 +52,11 @@
# (see `get_unsupported_forks`).
unsupported_forks_key: StashKey[FrozenSet[Fork | TransitionFork]] = StashKey()

STATE_TRIE_CHOICES: Dict[str, StateCommitment] = {
"mpt": StateCommitment.MPT,
"pbt": StateCommitment.PBT,
}


def pytest_addoption(parser: pytest.Parser) -> None:
"""Add command-line options to pytest."""
Expand Down Expand Up @@ -84,6 +91,20 @@ def pytest_addoption(parser: pytest.Parser) -> None:
default="",
help="Fill tests until and including the specified fork.",
)
fork_group.addoption(
"--state-trie",
action="store",
dest="state_trie",
default=None,
type=str.lower,
choices=list(STATE_TRIE_CHOICES),
help=(
"Override the state-commitment scheme used to compute state "
"roots for every fork: 'mpt' for the Merkle-Patricia trie, "
"'pbt' for the partitioned binary tree. By default, each "
"fork defines its own scheme."
),
)


@dataclass(kw_only=True)
Expand Down Expand Up @@ -569,6 +590,9 @@ def get_fork_option(
forks_until = get_fork_option(config, "forks_until", "--until")
show_fork_help = config.getoption("show_fork_help")

if state_trie := config.getoption("state_trie"):
BaseFork.set_state_commitment_override(STATE_TRIE_CHOICES[state_trie])

dev_forks_help = textwrap.dedent(
"To run tests for a fork under active development, it must be "
"specified explicitly via --until=FORK.\n"
Expand Down Expand Up @@ -628,6 +652,11 @@ def get_fork_option(
)


def pytest_unconfigure() -> None:
"""Reset global fork state derived from command-line options."""
BaseFork.set_state_commitment_override(None)


def get_unsupported_forks(
config: pytest.Config,
) -> FrozenSet[Fork | TransitionFork]:
Expand Down
Loading
Loading