diff --git a/README.md b/README.md index bb229a0..003d08a 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,6 @@ indices for you): ```bash export KEYSYNC_API_KEY=ovi_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -export KEYSYNC_OWNER_NAME="acme-mainnet" export KEYSYNC_BEACON_URL=http://localhost:5052 export KEYSYNC_PUBKEYS_FILE=/etc/optimum/validators.pubkeys # one pubkey per line @@ -41,7 +40,7 @@ Pick whichever list you already have. All three may be combined. | --- | --- | --- | --- | | BLS pubkeys | `KEYSYNC_PUBKEYS_FILE` | one pubkey per line | required (resolves the index) | | Validator indices | `KEYSYNC_INDEX_LIST_FILE` | one index per line | required (resolves the pubkey) | -| Pre-computed records | `KEYSYNC_INDICES_FILE` | JSON of `{validator_index, chain_id, validator_key, owner_name}` | not used | +| Pre-computed records | `KEYSYNC_INDICES_FILE` | JSON of `{validator_index, chain_id, validator_key}` | not used | `KEYSYNC_INDICES_FILE` is the no-lookup option for air-gapped setups, and it wins over a beacon-resolved hit for the same `(validator_index, chain_id)`. See @@ -59,7 +58,6 @@ All settings come from environment variables, each with a matching flag override | `KEYSYNC_API_KEY` | yes | `ovi_live_*` from the partners dashboard | | `KEYSYNC_NETWORK` | no | `mainnet`, `hoodi`, etc. Default: `mainnet` | | `KEYSYNC_CHAIN_ID` | no | Corresponding chain ID string. Default: `0x1` | -| `KEYSYNC_OWNER_NAME` | yes | Stored in `eth_validator_keys.owner_name` | | `KEYSYNC_BEACON_URL` | conditional | Required when using a pubkeys or index-list file | | `KEYSYNC_PUBKEYS_FILE` | conditional | One BLS pubkey per line; beacon resolves the index | | `KEYSYNC_INDEX_LIST_FILE` | conditional | One validator index per line; beacon resolves the pubkey | diff --git a/examples/indices.json b/examples/indices.json index 0c3f1e2..807bad6 100644 --- a/examples/indices.json +++ b/examples/indices.json @@ -2,8 +2,7 @@ { "validator_index": 720396, "chain_id": "0x1", - "validator_key": "0x800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "owner_name": "acme-mainnet" + "validator_key": "0x800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" }, { "validator_index": 720397, diff --git a/examples/kubernetes/cronjob.yaml b/examples/kubernetes/cronjob.yaml index 6f328f9..9f3f3e1 100644 --- a/examples/kubernetes/cronjob.yaml +++ b/examples/kubernetes/cronjob.yaml @@ -23,7 +23,7 @@ spec: args: ["sync", "--apply", "--log-format", "json"] envFrom: - configMapRef: - name: optimum-keysync-config # KEYSYNC_API_URL, KEYSYNC_AUTH_URL, KEYSYNC_NETWORK, KEYSYNC_CHAIN_ID, KEYSYNC_OWNER_NAME, KEYSYNC_BEACON_URL (KEYSYNC_OPERATOR_ID auto-resolved via /me) + name: optimum-keysync-config # KEYSYNC_API_URL, KEYSYNC_AUTH_URL, KEYSYNC_NETWORK, KEYSYNC_CHAIN_ID, KEYSYNC_BEACON_URL (KEYSYNC_OPERATOR_ID auto-resolved via /me) - secretRef: name: optimum-keysync-secret # KEYSYNC_API_KEY volumeMounts: diff --git a/src/optimum_keysync/api_client.py b/src/optimum_keysync/api_client.py index 3241aab..d2fd9fd 100644 --- a/src/optimum_keysync/api_client.py +++ b/src/optimum_keysync/api_client.py @@ -50,7 +50,6 @@ class ValidatorKeyRegistration: validator_index: int chain_id: str validator_key: str # 0x-prefixed BLS pubkey hex - owner_name: str class ApiError(RuntimeError): @@ -123,7 +122,6 @@ def batch_register_keys( "validator_index": k.validator_index, "chain_id": k.chain_id, "validator_key": k.validator_key, - "owner_name": k.owner_name, } for k in chunk ], diff --git a/src/optimum_keysync/cli.py b/src/optimum_keysync/cli.py index e848916..6ee372c 100644 --- a/src/optimum_keysync/cli.py +++ b/src/optimum_keysync/cli.py @@ -77,11 +77,6 @@ def _common_options(fn): fn = click.option( "--chain-id", envvar="KEYSYNC_CHAIN_ID", help="chain id string (default: 0x1)" )(fn) - fn = click.option( - "--owner-name", - envvar="KEYSYNC_OWNER_NAME", - help="label stored in eth_validator_keys.owner_name", - )(fn) fn = click.option( "--beacon-url", envvar="KEYSYNC_BEACON_URL", @@ -127,7 +122,6 @@ def _build_config(kwargs: dict[str, object]) -> KeysyncConfig: "operator_id": "operator_id", "network": "network", "chain_id": "chain_id", - "owner_name": "owner_name", "beacon_url": "beacon_url", "pubkeys_file": "pubkeys_file", "index_list_file": "index_list_file", @@ -306,7 +300,6 @@ def _compute_plan(cfg: KeysyncConfig) -> tuple[ReconcilePlan, AuthClient, ApiCli try: desired = resolve_desired( chain_id=cfg.chain_id, - owner_name=cfg.owner_name, pubkeys_file=cfg.pubkeys_file, index_list_file=cfg.index_list_file, indices_file=cfg.indices_file, diff --git a/src/optimum_keysync/config.py b/src/optimum_keysync/config.py index 72d5348..e3625ec 100644 --- a/src/optimum_keysync/config.py +++ b/src/optimum_keysync/config.py @@ -96,7 +96,6 @@ class KeysyncConfig(BaseModel): # a testnet (e.g. network=hoodi with its chain_id). network: Annotated[str, Field(min_length=1)] = _DEFAULT_NETWORK chain_id: Annotated[str, Field(min_length=1)] = _DEFAULT_CHAIN_ID - owner_name: Annotated[str, Field(min_length=1, max_length=128)] beacon_url: str | None = None pubkeys_file: Path | None = None @@ -181,7 +180,6 @@ def pick_path(name: str) -> Path | None: operator_id=pick("operator_id"), network=pick("network") or _DEFAULT_NETWORK, chain_id=pick("chain_id") or _DEFAULT_CHAIN_ID, - owner_name=pick("owner_name") or "", beacon_url=pick("beacon_url"), pubkeys_file=pick_path("pubkeys_file"), index_list_file=pick_path("index_list_file"), diff --git a/src/optimum_keysync/desired_state.py b/src/optimum_keysync/desired_state.py index 1bd7c8c..5e1a70a 100644 --- a/src/optimum_keysync/desired_state.py +++ b/src/optimum_keysync/desired_state.py @@ -2,7 +2,8 @@ The result is a `DesiredSet` carrying every validator the operator wants assigned, with all the metadata the console API requires to register it -(validator_index, chain_id, validator_key, owner_name). +(validator_index, chain_id, validator_key). The registry's owner_name label +is filled in server-side from the operator the API key resolves to. """ from __future__ import annotations @@ -23,15 +24,14 @@ class DesiredValidator: """One entry in the desired set. - `validator_key` and `owner_name` are required for the - `validator-keys/batch` register call; `validator_index` + `chain_id` - are also enough to drive the assignment call. + `validator_key` is required for the `validator-keys/batch` register + call; `validator_index` + `chain_id` are also enough to drive the + assignment call. """ validator_index: int chain_id: str validator_key: str - owner_name: str class DesiredSet: @@ -62,7 +62,6 @@ def registrations(self) -> list[ValidatorKeyRegistration]: validator_index=e.validator_index, chain_id=e.chain_id, validator_key=e.validator_key, - owner_name=e.owner_name, ) for e in self._entries ] @@ -71,7 +70,6 @@ def registrations(self) -> list[ValidatorKeyRegistration]: def resolve( *, chain_id: str, - owner_name: str, pubkeys_file: Path | None, index_list_file: Path | None, indices_file: Path | None, @@ -95,7 +93,6 @@ def resolve( validator_index=v.validator_index, chain_id=chain_id, validator_key=v.pubkey, - owner_name=owner_name, ) ) @@ -114,12 +111,11 @@ def resolve( validator_index=v.validator_index, chain_id=chain_id, validator_key=v.pubkey, - owner_name=owner_name, ) ) if indices_file is not None: - for e in _read_indices(indices_file, fallback_owner_name=owner_name): + for e in _read_indices(indices_file): entries.append(e) return DesiredSet(entries) @@ -149,7 +145,7 @@ def _read_index_list(path: Path) -> list[int]: return out -def _read_indices(path: Path, *, fallback_owner_name: str) -> list[DesiredValidator]: +def _read_indices(path: Path) -> list[DesiredValidator]: raw = json.loads(path.read_text()) if not isinstance(raw, list): raise ValueError(f"{path} must be a JSON array of validator records") @@ -163,7 +159,6 @@ def _read_indices(path: Path, *, fallback_owner_name: str) -> list[DesiredValida validator_index=int(row["validator_index"]), chain_id=str(row["chain_id"]), validator_key=str(row["validator_key"]), - owner_name=str(row.get("owner_name") or fallback_owner_name), ) ) except (KeyError, TypeError, ValueError) as e: diff --git a/src/optimum_keysync/reconcile.py b/src/optimum_keysync/reconcile.py index 733dd28..3a78626 100644 --- a/src/optimum_keysync/reconcile.py +++ b/src/optimum_keysync/reconcile.py @@ -66,7 +66,6 @@ def plan( validator_index=desired_by_key[k].validator_index, chain_id=desired_by_key[k].chain_id, validator_key=desired_by_key[k].validator_key, - owner_name=desired_by_key[k].owner_name, ) for k in sorted(add_keys) ) diff --git a/tests/fixtures/validators.json b/tests/fixtures/validators.json index 7e8150e..3b58275 100644 --- a/tests/fixtures/validators.json +++ b/tests/fixtures/validators.json @@ -2,14 +2,12 @@ { "validator_index": 720396, "chain_id": "0x1", - "validator_key": "0xa1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1", - "owner_name": "acme-mainnet" + "validator_key": "0xa1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1" }, { "validator_index": 720397, "chain_id": "0x1", - "validator_key": "0xb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2", - "owner_name": "acme-mainnet" + "validator_key": "0xb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2" }, { "validator_index": 720398, diff --git a/tests/test_api_client.py b/tests/test_api_client.py index 37b6634..dfb5e73 100644 --- a/tests/test_api_client.py +++ b/tests/test_api_client.py @@ -66,7 +66,6 @@ def test_batch_register_keys_aggregates_chunk_counts() -> None: validator_index=i, chain_id="0x1", validator_key=f"0x{i:096x}", - owner_name="acme", ) for i in range(4) ] diff --git a/tests/test_cli_apply.py b/tests/test_cli_apply.py index 3883819..c894cc8 100644 --- a/tests/test_cli_apply.py +++ b/tests/test_cli_apply.py @@ -77,7 +77,6 @@ def _plan(*, assigns: list[tuple[int, str]], unassigns: list[tuple[int, str]]) - validator_index=idx, chain_id=cid, validator_key=f"0x{idx:096x}", - owner_name="acme", ) for idx, cid in assigns ) @@ -146,7 +145,6 @@ def test_build_config_threads_index_list_file(tmp_path: Any) -> None: "operator_id": "42", "network": "mainnet", "chain_id": "0x1", - "owner_name": "acme", "beacon_url": "https://beacon.test", "index_list_file": index_list, } diff --git a/tests/test_cli_lazy_me.py b/tests/test_cli_lazy_me.py index 3193f7b..7f38be1 100644 --- a/tests/test_cli_lazy_me.py +++ b/tests/test_cli_lazy_me.py @@ -30,7 +30,6 @@ def _opts(indices_path: Path) -> list[str]: "--api-key", "ovi_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "--network", "mainnet", "--chain-id", "0x1", - "--owner-name", "acme", "--indices-file", str(indices_path), ] diff --git a/tests/test_config.py b/tests/test_config.py index b56628c..ef0f77d 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -6,7 +6,7 @@ import pytest -from optimum_keysync.config import KeysyncConfig, from_env +from optimum_keysync.config import from_env _BASE: dict[str, str | Path] = { "api_url": "https://api.example", @@ -15,7 +15,6 @@ "operator_id": "42", "network": "mainnet", "chain_id": "0x1", - "owner_name": "acme", } @@ -196,20 +195,6 @@ def test_plaintext_beacon_url_rejected(tmp_path: Path) -> None: ) -def test_owner_name_length_capped() -> None: - with pytest.raises(ValueError): - KeysyncConfig( - api_url="https://b", - auth_url="https://a", - api_key="ovi_live_xxxxxxxxxxxxxxxxxxxx", - operator_id="42", - network="mainnet", - chain_id="0x1", - owner_name="x" * 129, - indices_file=Path("/tmp/x.json"), - ) - - def test_url_network_chain_defaults_applied_when_unset( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: diff --git a/tests/test_desired_state.py b/tests/test_desired_state.py index 0694c9d..6636377 100644 --- a/tests/test_desired_state.py +++ b/tests/test_desired_state.py @@ -46,14 +46,12 @@ def test_dedup_indices_file_overrides_beacon(tmp_path: Path) -> None: "validator_index": 1, "chain_id": "0x1", "validator_key": "0x" + "b" * 96, - "owner_name": "explicit", } ] ) ) out = resolve( chain_id="0x1", - owner_name="default", pubkeys_file=pubkeys, index_list_file=None, indices_file=indices, @@ -62,7 +60,6 @@ def test_dedup_indices_file_overrides_beacon(tmp_path: Path) -> None: entries = list(out) assert len(entries) == 1 assert entries[0].validator_key == "0x" + "b" * 96 - assert entries[0].owner_name == "explicit" def test_indices_file_only(tmp_path: Path) -> None: @@ -80,14 +77,15 @@ def test_indices_file_only(tmp_path: Path) -> None: ) out = resolve( chain_id="0x1", - owner_name="fallback", pubkeys_file=None, index_list_file=None, indices_file=indices, beacon=None, ) entries = list(out) - assert entries[0].owner_name == "fallback" # picked up from kwarg + assert len(entries) == 1 + assert entries[0].validator_index == 7 + assert entries[0].validator_key == "0x" + "c" * 96 _FIXTURE = Path(__file__).parent / "fixtures" / "validators.json" @@ -98,7 +96,6 @@ def test_resolve_from_indices_fixture_without_beacon() -> None: desired set resolves with no beacon node (beacon=None).""" out = resolve( chain_id="0x1", - owner_name="fallback", pubkeys_file=None, index_list_file=None, indices_file=_FIXTURE, @@ -107,9 +104,6 @@ def test_resolve_from_indices_fixture_without_beacon() -> None: entries = {e.validator_index: e for e in out} assert set(entries) == {720396, 720397, 720398} assert entries[720396].validator_key.startswith("0xa1a1") - assert entries[720396].owner_name == "acme-mainnet" - # The third record omits owner_name, so the resolve() arg fills it in. - assert entries[720398].owner_name == "fallback" def test_pubkeys_without_beacon_raises(tmp_path: Path) -> None: @@ -118,7 +112,6 @@ def test_pubkeys_without_beacon_raises(tmp_path: Path) -> None: with pytest.raises(ValueError): resolve( chain_id="0x1", - owner_name="x", pubkeys_file=pubkeys, index_list_file=None, indices_file=None, @@ -134,7 +127,6 @@ def test_pubkeys_file_skips_blank_and_comment_lines(tmp_path: Path) -> None: beacon = _StubBeacon([]) resolve( chain_id="0x1", - owner_name="x", pubkeys_file=pubkeys, index_list_file=None, indices_file=None, @@ -160,7 +152,6 @@ def test_index_list_resolves_pubkeys_via_beacon(tmp_path: Path) -> None: index_list.write_text("\n# comment\n42\n \n") out = resolve( chain_id="0x1", - owner_name="acme", pubkeys_file=None, index_list_file=index_list, indices_file=None, @@ -171,7 +162,6 @@ def test_index_list_resolves_pubkeys_via_beacon(tmp_path: Path) -> None: assert len(entries) == 1 assert entries[0].validator_index == 42 assert entries[0].validator_key == "0x" + "a" * 96 - assert entries[0].owner_name == "acme" def test_index_list_without_beacon_raises(tmp_path: Path) -> None: @@ -180,7 +170,6 @@ def test_index_list_without_beacon_raises(tmp_path: Path) -> None: with pytest.raises(ValueError, match="no beacon client"): resolve( chain_id="0x1", - owner_name="x", pubkeys_file=None, index_list_file=index_list, indices_file=None, @@ -195,7 +184,6 @@ def test_index_list_rejects_non_integer_line(tmp_path: Path) -> None: with pytest.raises(ValueError, match="not a validator index"): resolve( chain_id="0x1", - owner_name="x", pubkeys_file=None, index_list_file=index_list, indices_file=None, @@ -213,7 +201,6 @@ def test_index_list_rejects_int_parsing_surprises(tmp_path: Path, line: str) -> with pytest.raises(ValueError, match="not a validator index"): resolve( chain_id="0x1", - owner_name="x", pubkeys_file=None, index_list_file=index_list, indices_file=None, @@ -240,8 +227,6 @@ def test_beacon_normalise_rejects_wrong_length() -> None: def test_desired_set_dedup_is_idempotent() -> None: - a = DesiredValidator( - validator_index=1, chain_id="0x1", validator_key="0xk", owner_name="x" - ) + a = DesiredValidator(validator_index=1, chain_id="0x1", validator_key="0xk") s = DesiredSet([a, a, a]) assert len(s) == 1 diff --git a/tests/test_reconcile.py b/tests/test_reconcile.py index 4be94d9..d14d8ab 100644 --- a/tests/test_reconcile.py +++ b/tests/test_reconcile.py @@ -18,7 +18,6 @@ def _desired(*pairs: tuple[int, str]) -> DesiredSet: validator_index=idx, chain_id=cid, validator_key=f"0x{'a'*94}{idx:02d}", - owner_name="acme", ) for idx, cid in pairs )