feat: add probation and ban enforcement

This commit is contained in:
Dobromir Popov
2026-06-29 09:58:32 +03:00
parent 39f6f23c83
commit 792a9fd97f
8 changed files with 266 additions and 20 deletions

View File

@@ -26,7 +26,10 @@ def test_client_can_fund_api_key_with_testnet_sol():
def test_epoch_settlement_debits_clients_and_rewards_nodes_and_validator():
"""Settlement distributes native token rewards according to recorded work."""
contracts = LocalSolanaContracts(cost_per_layer_token_lamport=2)
contracts = LocalSolanaContracts(
cost_per_layer_token_lamport=2,
probationary_job_count=0,
)
contracts.payment.fund_api_key("api-key-a", lamports=1_000)
contracts.payment.record_attribution(
session_id="session-a",
@@ -61,6 +64,28 @@ def test_epoch_settlement_debits_clients_and_rewards_nodes_and_validator():
assert contracts.settlement.get_token_balance("validator-wallet") == 32
def test_failed_settlement_does_not_advance_probation_or_settle_attribution():
"""Settlement state is unchanged when caller balance validation fails."""
contracts = LocalSolanaContracts(starting_credit_lamports=0, probationary_job_count=1)
contracts.payment.record_attribution(
session_id="session-a",
api_key="api-key-a",
node_wallet="node-wallet-a",
layer_start=0,
layer_end=0,
tokens=1,
)
try:
contracts.settlement.settle_epoch(epoch=1)
raise AssertionError("Expected insufficient balance")
except ValueError as exc:
assert "insufficient SOL balance" in str(exc)
assert contracts.registry.get_wallet("node-wallet-a").completed_job_count == 0
assert contracts.payment.list_attributions(include_settled=False)
def test_slash_proof_reduces_stake_increments_strikes_and_warns(capsys):
"""Submitting a slash proof updates registry state and notifies the operator."""
contracts = LocalSolanaContracts()
@@ -79,3 +104,69 @@ def test_slash_proof_reduces_stake_increments_strikes_and_warns(capsys):
assert wallet.strike_count == 1
assert wallet.banned is False
assert "WARNING: node node-wallet-a slashed" in capsys.readouterr().out
def test_probationary_wallet_earns_only_after_required_jobs():
"""A new wallet's first N completed jobs are counted but not rewarded."""
contracts = LocalSolanaContracts(probationary_job_count=2)
contracts.payment.fund_api_key("api-key-a", lamports=1_000)
for job_number in range(3):
contracts.payment.record_attribution(
session_id=f"session-{job_number}",
api_key="api-key-a",
node_wallet="new-wallet",
layer_start=0,
layer_end=0,
tokens=10,
)
result = contracts.settlement.settle_epoch(epoch=1)
assert result.client_debits == {"api-key-a": 30}
assert result.node_rewards == {"new-wallet": 10}
assert contracts.registry.get_wallet("new-wallet").completed_job_count == 3
assert contracts.registry.probationary_jobs_remaining("new-wallet") == 0
assert contracts.settlement.get_token_balance("new-wallet") == 10
def test_probationary_wallet_receives_no_rewards_before_threshold():
"""Settlement state verifies a wallet earns zero during probation."""
contracts = LocalSolanaContracts(probationary_job_count=50)
contracts.payment.fund_api_key("api-key-a", lamports=1_000)
contracts.payment.record_attribution(
session_id="session-a",
api_key="api-key-a",
node_wallet="new-wallet",
layer_start=0,
layer_end=9,
tokens=1,
)
result = contracts.settlement.settle_epoch(epoch=1)
assert result.node_rewards == {"new-wallet": 0}
assert contracts.registry.get_wallet("new-wallet").completed_job_count == 1
assert contracts.registry.probationary_jobs_remaining("new-wallet") == 49
assert contracts.settlement.get_token_balance("new-wallet") == 0
def test_slash_proof_at_threshold_marks_wallet_banned():
"""Registry marks a wallet banned when strikes reach the configured threshold."""
contracts = LocalSolanaContracts()
contracts.registry.submit_stake("node-wallet-a", 500)
contracts.registry.submit_slash_proof(
wallet_address="node-wallet-a",
slash_amount=10,
strike_threshold=2,
)
receipt = contracts.registry.submit_slash_proof(
wallet_address="node-wallet-a",
slash_amount=10,
strike_threshold=2,
)
assert receipt.banned is True
wallet = contracts.registry.get_wallet("node-wallet-a")
assert wallet.strike_count == 2
assert wallet.banned is True