Skip to content

Add non-fast-blocks e2e tests each Saturday #2860

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 41 commits into from
May 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
d08bcf7
add `build-patched-image` and `cron-e2e-test-with-non-fast-block`
basfroman May 3, 2025
d9da5c8
improve logic
basfroman May 3, 2025
624fa59
add `NFB` prefix to non-fast-blocks based tests
basfroman May 3, 2025
de3b8ce
test
basfroman May 3, 2025
2c86707
one more
basfroman May 3, 2025
4748ede
real test
basfroman May 3, 2025
ad63f95
update workflow
basfroman May 5, 2025
f42b945
update conftest
basfroman May 5, 2025
a63d38a
add is_fast_block() method
basfroman May 5, 2025
a654105
ruff
basfroman May 5, 2025
1fc420c
improve `tests.e2e_tests.utils.e2e_test_utils.wait_to_start_call`
basfroman May 5, 2025
ae3c886
Update tests to be compatible with non-/fast-blocks localnet nodes
basfroman May 5, 2025
88e86d0
Merge branch 'staging' into feat/roman/add-non-fast-block-tests-each-…
basfroman May 5, 2025
f067275
fix FAST_BLOCKS=0 for non-fast-blocks docker image
basfroman May 5, 2025
a9e4da2
ruff + fix typos
basfroman May 5, 2025
448ba63
temporary python-version ["3.12"] only + fix bugs
basfroman May 5, 2025
9f11505
fix
basfroman May 5, 2025
dbf506a
ruff
basfroman May 5, 2025
1adaa20
all python-version back
basfroman May 5, 2025
370e0a0
add logging to wait_to_start_call
basfroman May 6, 2025
6667aec
make `test_delegates` compatible with both modes
basfroman May 6, 2025
6b4dbf4
update `test_hotkeys.py`
basfroman May 6, 2025
a39f63a
Merge branch 'staging' into feat/roman/add-non-fast-block-tests-each-…
basfroman May 6, 2025
32c94a5
trigger
basfroman May 7, 2025
a6c301a
temp run 2 tests for debug
basfroman May 7, 2025
45d62db
add logging after tests
basfroman May 7, 2025
6b442a2
add logging after tests
basfroman May 7, 2025
af4be54
fix
basfroman May 7, 2025
405236b
Merge branch 'staging' into feat/roman/add-non-fast-block-tests-each-…
basfroman May 7, 2025
a82606b
fix `tests/e2e_tests/test_hotkeys.py`
basfroman May 9, 2025
ab5e594
fix `tests.e2e_tests.test_staking.test_single_operation`
basfroman May 9, 2025
6d49fa3
fix unit tests
basfroman May 9, 2025
58a41b7
adapted `tests.e2e_tests.test_staking.test_transfer_stake` for both mods
basfroman May 9, 2025
e26d787
adapted `tests.e2e_tests.test_staking.test_transfer_stake` for both mods
basfroman May 11, 2025
b774b41
Merge branch 'staging' into feat/roman/add-non-fast-block-tests-each-…
basfroman May 11, 2025
c6f4c58
restore workflow to run all tests
basfroman May 11, 2025
c075065
add all supported wallets for tests
basfroman May 11, 2025
299ed91
run all tests without cron
basfroman May 11, 2025
6669060
run all tests without cron
basfroman May 11, 2025
f899c20
add condition
basfroman May 11, 2025
ef2edcf
add step `Check if today is Saturday`
basfroman May 11, 2025
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
79 changes: 76 additions & 3 deletions .github/workflows/e2e-subtensor-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
run: |
test_files=$(find tests/e2e_tests -name "test*.py" | jq -R -s -c 'split("\n") | map(select(. != ""))')
# keep it here for future debug
# test_files=$(find tests/e2e_tests -type f -name "test*.py" | grep -E 'test_(incentive|commit_weights|set_weights)\.py$' | jq -R -s -c 'split("\n") | map(select(. != ""))')
# test_files=$(find tests/e2e_tests -type f -name "test*.py" | grep -E 'test_(hotkeys|staking)\.py$' | jq -R -s -c 'split("\n") | map(select(. != ""))')
echo "test-files=$test_files" >> "$GITHUB_OUTPUT"
shell: bash

Expand All @@ -66,8 +66,8 @@ jobs:
path: subtensor-localnet.tar

# Job to run tests in parallel
run-e2e-test:
name: ${{ matrix.test-file }} / Python ${{ matrix.python-version }}
run-fast-blocks-e2e-test:
name: "FB: ${{ matrix.test-file }} / Python ${{ matrix.python-version }}"
needs:
- find-tests
- pull-docker-image
Expand Down Expand Up @@ -127,3 +127,76 @@ jobs:
sleep 5
fi
done


cron-run-non-fast-blocks-e2e-test:
if: github.event_name == 'schedule'
name: "NFB: ${{ matrix.test-file }} / Python ${{ matrix.python-version }}"
needs:
- find-tests
- pull-docker-image
runs-on: ubuntu-latest
timeout-minutes: 1440

strategy:
fail-fast: false # Allow other matrix jobs to run even if this job fails
max-parallel: 32 # Set the maximum number of parallel jobs (same as we have cores in ubuntu-latest runner)
matrix:
os:
- ubuntu-latest
test-file: ${{ fromJson(needs.find-tests.outputs.test-files) }}
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]

steps:
- name: Check if today is Saturday
run: |
day=$(date -u +%u)
echo "Today is weekday $day"
if [ "$day" -ne 6 ]; then
echo "⏭️ Skipping: not Saturday"
exit 78
fi
- name: Check-out repository
uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install uv
uses: astral-sh/setup-uv@v4

- name: install dependencies
run: uv sync --extra dev --dev

- name: Download Cached Docker Image
uses: actions/download-artifact@v4
with:
name: subtensor-localnet

- name: Load Docker Image
run: docker load -i subtensor-localnet.tar

- name: Run patched E2E tests
env:
FAST_BLOCKS: "0"
run: |
set +e
for i in 1 2 3; do
echo "🔁 Attempt $i: Running tests"
uv run pytest ${{ matrix.test-file }} -s
status=$?
if [ $status -eq 0 ]; then
echo "✅ Tests passed on attempt $i"
break
else
echo "❌ Tests failed on attempt $i"
if [ $i -eq 3 ]; then
echo "Tests failed after 3 attempts"
exit 1
fi
echo "Retrying..."
sleep 5
fi
done
6 changes: 6 additions & 0 deletions bittensor/core/async_subtensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2488,6 +2488,12 @@ async def immunity_period(
)
return None if call is None else int(call)

async def is_fast_blocks(self):
"""Returns True if the node is running with fast blocks. False if not."""
return (
await self.query_constant("SubtensorModule", "DurationOfStartCall")
).value == 10

async def is_hotkey_delegate(
self,
hotkey_ss58: str,
Expand Down
4 changes: 4 additions & 0 deletions bittensor/core/subtensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1971,6 +1971,10 @@ def immunity_period(
)
return None if call is None else int(call)

def is_fast_blocks(self):
"""Returns True if the node is running with fast blocks. False if not."""
return self.query_constant("SubtensorModule", "DurationOfStartCall").value == 10

Comment on lines +1974 to +1977
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm glad you didn't make these properties.

def is_hotkey_delegate(self, hotkey_ss58: str, block: Optional[int] = None) -> bool:
"""
Determines whether a given hotkey (public key) is a delegate on the Bittensor network. This function checks if
Expand Down
1 change: 1 addition & 0 deletions bittensor/core/subtensor_api/chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]):
self.get_minimum_required_stake = subtensor.get_minimum_required_stake
self.get_vote_data = subtensor.get_vote_data
self.get_timestamp = subtensor.get_timestamp
self.is_fast_blocks = subtensor.is_fast_blocks
self.last_drand_round = subtensor.last_drand_round
self.state_call = subtensor.state_call
self.tx_rate_limit = subtensor.tx_rate_limit
1 change: 1 addition & 0 deletions bittensor/core/subtensor_api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ def add_legacy_methods(subtensor: "SubtensorApi"):
subtensor.get_unstake_fee = subtensor._subtensor.get_unstake_fee
subtensor.get_vote_data = subtensor._subtensor.get_vote_data
subtensor.immunity_period = subtensor._subtensor.immunity_period
subtensor.is_fast_blocks = subtensor._subtensor.is_fast_blocks
subtensor.is_hotkey_delegate = subtensor._subtensor.is_hotkey_delegate
subtensor.is_hotkey_registered = subtensor._subtensor.is_hotkey_registered
subtensor.is_hotkey_registered_any = subtensor._subtensor.is_hotkey_registered_any
Expand Down
19 changes: 17 additions & 2 deletions tests/e2e_tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,13 @@ def stop_existing_test_containers():
"9944:9944",
"-p",
"9945:9945",
LOCALNET_IMAGE_NAME,
params,
str(LOCALNET_IMAGE_NAME),
]

cmds += params.split() if params else []

print("Entire run command: ", cmds)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary or left over from debugging?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useful for checking while running tests


try_start_docker()

stop_existing_test_containers()
Expand Down Expand Up @@ -275,7 +278,19 @@ def bob_wallet():
return wallet


@pytest.fixture
def charlie_wallet():
keypair, wallet = setup_wallet("//Charlie")
return wallet


@pytest.fixture
def dave_wallet():
keypair, wallet = setup_wallet("//Dave")
return wallet


@pytest.fixture
def eve_wallet():
keypair, wallet = setup_wallet("//Eve")
return wallet
20 changes: 13 additions & 7 deletions tests/e2e_tests/test_commit_reveal_v3.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,22 @@ async def test_commit_and_reveal_weights_cr3(local_chain, subtensor, alice_walle
Raises:
AssertionError: If any of the checks or verifications fail
"""
BLOCK_TIME = 0.25 # 12 for non-fast-block, 0.25 for fast block
netuid = 2
BLOCK_TIME = (
0.25 if subtensor.is_fast_blocks() else 12.0
) # 12 for non-fast-block, 0.25 for fast block
netuid = subtensor.get_total_subnets() # 2

logging.console.info("Testing test_commit_and_reveal_weights")

# Register root as Alice
assert subtensor.register_subnet(alice_wallet), "Unable to register the subnet"

# Verify subnet 2 created successfully
assert subtensor.subnet_exists(netuid), "Subnet wasn't created successfully"
assert subtensor.subnet_exists(netuid), (
f"Subnet {netuid} wasn't created successfully"
)

logging.console.info("Subnet 2 is registered")
logging.console.success(f"Subnet {netuid} is registered")

# Enable commit_reveal on the subnet
assert sudo_set_hyperparameter_bool(
Expand Down Expand Up @@ -74,7 +79,7 @@ async def test_commit_and_reveal_weights_cr3(local_chain, subtensor, alice_walle
logging.console.info("sudo_set_weights_set_rate_limit executed: set to 0")

# Change the tempo of the subnet
tempo_set = 50
tempo_set = 50 if subtensor.is_fast_blocks() else 10
assert (
sudo_set_admin_utils(
local_chain,
Expand Down Expand Up @@ -103,7 +108,7 @@ async def test_commit_and_reveal_weights_cr3(local_chain, subtensor, alice_walle
)

# Wait for 2 tempos to pass as CR3 only reveals weights after 2 tempos + 1
subtensor.wait_for_block((tempo_set * 2) + 1)
subtensor.wait_for_block(tempo_set * 2 + 1)

# Lower than this might mean weights will get revealed before we can check them
if upcoming_tempo - current_block < 3:
Expand All @@ -117,7 +122,7 @@ async def test_commit_and_reveal_weights_cr3(local_chain, subtensor, alice_walle
latest_drand_round = subtensor.last_drand_round()
upcoming_tempo = next_tempo(current_block, tempo)
logging.console.info(
f"Post first wait_interval (to ensure window isnt too low): {current_block}, next tempo: {upcoming_tempo}, drand: {latest_drand_round}"
f"Post first wait_interval (to ensure window isn't too low): {current_block}, next tempo: {upcoming_tempo}, drand: {latest_drand_round}"
)

# Commit weights
Expand Down Expand Up @@ -171,6 +176,7 @@ async def test_commit_and_reveal_weights_cr3(local_chain, subtensor, alice_walle
subtensor,
netuid=netuid,
reporting_interval=1,
sleep=BLOCK_TIME,
)

# Fetch the latest drand pulse
Expand Down
20 changes: 13 additions & 7 deletions tests/e2e_tests/test_commit_weights.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa
AssertionError: If any of the checks or verifications fail
"""
netuid = subtensor.get_total_subnets() # 2

set_tempo = 100 if subtensor.is_fast_blocks() else 10
print("Testing test_commit_and_reveal_weights")

# Register root as Alice
Expand Down Expand Up @@ -78,7 +78,7 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa
call_function="sudo_set_tempo",
call_params={
"netuid": netuid,
"tempo": 100,
"tempo": set_tempo,
},
)

Expand Down Expand Up @@ -165,11 +165,11 @@ async def test_commit_weights_uses_next_nonce(local_chain, subtensor, alice_wall
Raises:
AssertionError: If any of the checks or verifications fail
"""
subnet_tempo = 50
subnet_tempo = 50 if subtensor.is_fast_blocks() else 10
netuid = subtensor.get_total_subnets() # 2

# Wait for 2 tempos to pass as CR3 only reveals weights after 2 tempos
subtensor.wait_for_block(subnet_tempo * 2 + 1)
subtensor.wait_for_block(subtensor.block + (subnet_tempo * 2) + 1)

print("Testing test_commit_and_reveal_weights")
# Register root as Alice
Expand Down Expand Up @@ -267,16 +267,22 @@ def send_commit(salt_, weight_uids_, weight_vals_):

send_commit(salt, weight_uids, weight_vals)

# let's wait for 3 (12 fast blocks) seconds between transactions
subtensor.wait_for_block(subtensor.block + 12)
# let's wait for 3 (12 fast blocks) seconds between transactions, next block for non-fast-blocks
waiting_block = (subtensor.block + 12) if subtensor.is_fast_blocks() else None
subtensor.wait_for_block(waiting_block)

logging.console.info(
f"[orange]Nonce after third commit_weights: "
f"{subtensor.substrate.get_account_next_index(alice_wallet.hotkey.ss58_address)}[/orange]"
)

# Wait a few blocks
subtensor.wait_for_block(subtensor.block + subtensor.tempo(netuid) * 2)
waiting_block = (
(subtensor.block + subtensor.tempo(netuid) * 2)
if subtensor.is_fast_blocks()
else None
)
subtensor.wait_for_block(waiting_block)

# Query the WeightCommits storage map for all three salts
weight_commits = subtensor.query_module(
Expand Down
13 changes: 7 additions & 6 deletions tests/e2e_tests/test_commitment.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@

from bittensor import logging
from tests.e2e_tests.utils.chain_interactions import sudo_set_admin_utils
from tests.e2e_tests.utils.e2e_test_utils import wait_to_start_call
from tests.e2e_tests.utils.e2e_test_utils import (
wait_to_start_call,
async_wait_to_start_call,
)

logging.set_trace()

Expand All @@ -15,7 +18,7 @@ def test_commitment(local_chain, subtensor, alice_wallet, dave_wallet):
"Subnet wasn't created successfully"
)

assert wait_to_start_call(subtensor, dave_wallet, dave_subnet_netuid, 10)
assert wait_to_start_call(subtensor, dave_wallet, dave_subnet_netuid)

with pytest.raises(SubstrateRequestException, match="AccountNotAllowedCommit"):
subtensor.set_commitment(
Expand Down Expand Up @@ -93,11 +96,9 @@ async def test_commitment_async(
"Subnet wasn't created successfully"
)

await async_subtensor.wait_for_block(await async_subtensor.block + 20)
status, message = await async_subtensor.start_call(
dave_wallet, dave_subnet_netuid, True, True
assert await async_wait_to_start_call(
async_subtensor, dave_wallet, dave_subnet_netuid
)
assert status, message

async with async_subtensor as sub:
with pytest.raises(SubstrateRequestException, match="AccountNotAllowedCommit"):
Expand Down
20 changes: 18 additions & 2 deletions tests/e2e_tests/test_delegate.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ def test_change_take(local_chain, subtensor, alice_wallet, bob_wallet):


@pytest.mark.asyncio
async def test_delegates(subtensor, alice_wallet, bob_wallet):
async def test_delegates(local_chain, subtensor, alice_wallet, bob_wallet):
"""
Tests:
- Check default Delegates
Expand Down Expand Up @@ -240,6 +240,7 @@ async def test_delegates(subtensor, alice_wallet, bob_wallet):
assert subtensor.get_delegated(bob_wallet.coldkey.ss58_address) == []

alice_subnet_netuid = subtensor.get_total_subnets() # 2
set_tempo = 10
# Register a subnet, netuid 2
assert subtensor.register_subnet(alice_wallet), "Subnet wasn't created"

Expand All @@ -250,6 +251,17 @@ async def test_delegates(subtensor, alice_wallet, bob_wallet):

assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid)

# set the same tempo for both type of nodes (fast and non-fast blocks)
assert (
sudo_set_admin_utils(
local_chain,
alice_wallet,
call_function="sudo_set_tempo",
call_params={"netuid": alice_subnet_netuid, "tempo": set_tempo},
)[0]
is True
)

subtensor.add_stake(
bob_wallet,
alice_wallet.hotkey.ss58_address,
Expand All @@ -259,6 +271,9 @@ async def test_delegates(subtensor, alice_wallet, bob_wallet):
wait_for_finalization=True,
)

# let chain update validator_permits
subtensor.wait_for_block(subtensor.block + set_tempo + 1)

bob_delegated = subtensor.get_delegated(bob_wallet.coldkey.ss58_address)
assert bob_delegated == [
DelegatedInfo(
Expand All @@ -272,9 +287,10 @@ async def test_delegates(subtensor, alice_wallet, bob_wallet):
bob_delegated[0].total_daily_return.rao
),
netuid=alice_subnet_netuid,
stake=get_dynamic_balance(bob_delegated[0].stake.rao),
stake=get_dynamic_balance(bob_delegated[0].stake.rao, alice_subnet_netuid),
),
]
bittensor.logging.console.success("Test [green]test_delegates[/green] passed.")


def test_nominator_min_required_stake(local_chain, subtensor, alice_wallet, bob_wallet):
Expand Down
Loading