openroboto-protocol 0.3.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openroboto_protocol-0.3.0 → openroboto_protocol-0.4.0}/PKG-INFO +5 -5
- {openroboto_protocol-0.3.0 → openroboto_protocol-0.4.0}/README.md +4 -4
- {openroboto_protocol-0.3.0 → openroboto_protocol-0.4.0}/pyproject.toml +1 -1
- openroboto_protocol-0.4.0/src/openroboto_protocol/weights.py +116 -0
- {openroboto_protocol-0.3.0 → openroboto_protocol-0.4.0}/LICENSE +0 -0
- {openroboto_protocol-0.3.0 → openroboto_protocol-0.4.0}/src/openroboto_protocol/__init__.py +0 -0
- {openroboto_protocol-0.3.0 → openroboto_protocol-0.4.0}/src/openroboto_protocol/commitment.py +0 -0
- {openroboto_protocol-0.3.0 → openroboto_protocol-0.4.0}/src/openroboto_protocol/constants.py +0 -0
- {openroboto_protocol-0.3.0 → openroboto_protocol-0.4.0}/src/openroboto_protocol/model_format.py +0 -0
- {openroboto_protocol-0.3.0 → openroboto_protocol-0.4.0}/src/openroboto_protocol/model_hash.py +0 -0
- {openroboto_protocol-0.3.0 → openroboto_protocol-0.4.0}/src/openroboto_protocol/py.typed +0 -0
- {openroboto_protocol-0.3.0 → openroboto_protocol-0.4.0}/src/openroboto_protocol/schemas.py +0 -0
- {openroboto_protocol-0.3.0 → openroboto_protocol-0.4.0}/src/openroboto_protocol/seed.py +0 -0
- {openroboto_protocol-0.3.0 → openroboto_protocol-0.4.0}/src/openroboto_protocol/status.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openroboto-protocol
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Shared protocol contract for the OpenRoboto subnet on Bittensor (netuid 80): seed derivation, commitment codec, model hashing, status vocabulary and API schemas.
|
|
5
5
|
Keywords: bittensor,openroboto,subnet,protocol
|
|
6
6
|
Author: OpenRoboto
|
|
@@ -31,9 +31,9 @@ Pin the exact version. A floating range means two sides of the subnet can resolv
|
|
|
31
31
|
to different code, which is the failure this package was created to prevent.
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
|
-
uv add "openroboto-protocol==0.
|
|
34
|
+
uv add "openroboto-protocol==0.4.0"
|
|
35
35
|
# or
|
|
36
|
-
pip install "openroboto-protocol==0.
|
|
36
|
+
pip install "openroboto-protocol==0.4.0"
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
```python
|
|
@@ -51,7 +51,7 @@ seed and decode a commitment; that must not cost a `pydantic-core` wheel build o
|
|
|
51
51
|
GPU box. Only `schemas.py` needs pydantic, and only the backend needs `schemas.py`:
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
|
-
uv add "openroboto-protocol[schemas]==0.
|
|
54
|
+
uv add "openroboto-protocol[schemas]==0.4.0" # backend only
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
## Why this package exists
|
|
@@ -121,7 +121,7 @@ The version number *is* the contract version.
|
|
|
121
121
|
| `minor` | New optional field. Old data missing the key **must** have a default |
|
|
122
122
|
| `major` | Breaking change. Requires an on-chain data migration plan and review |
|
|
123
123
|
|
|
124
|
-
Consumers pin an exact version (`openroboto-protocol==0.
|
|
124
|
+
Consumers pin an exact version (`openroboto-protocol==0.4.0`). Floating versions are
|
|
125
125
|
rejected in CI, as is any vendored copy of this code.
|
|
126
126
|
|
|
127
127
|
## What consumers must add to their own CI
|
|
@@ -9,9 +9,9 @@ Pin the exact version. A floating range means two sides of the subnet can resolv
|
|
|
9
9
|
to different code, which is the failure this package was created to prevent.
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
uv add "openroboto-protocol==0.
|
|
12
|
+
uv add "openroboto-protocol==0.4.0"
|
|
13
13
|
# or
|
|
14
|
-
pip install "openroboto-protocol==0.
|
|
14
|
+
pip install "openroboto-protocol==0.4.0"
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
```python
|
|
@@ -29,7 +29,7 @@ seed and decode a commitment; that must not cost a `pydantic-core` wheel build o
|
|
|
29
29
|
GPU box. Only `schemas.py` needs pydantic, and only the backend needs `schemas.py`:
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
uv add "openroboto-protocol[schemas]==0.
|
|
32
|
+
uv add "openroboto-protocol[schemas]==0.4.0" # backend only
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
## Why this package exists
|
|
@@ -99,7 +99,7 @@ The version number *is* the contract version.
|
|
|
99
99
|
| `minor` | New optional field. Old data missing the key **must** have a default |
|
|
100
100
|
| `major` | Breaking change. Requires an on-chain data migration plan and review |
|
|
101
101
|
|
|
102
|
-
Consumers pin an exact version (`openroboto-protocol==0.
|
|
102
|
+
Consumers pin an exact version (`openroboto-protocol==0.4.0`). Floating versions are
|
|
103
103
|
rejected in CI, as is any vendored copy of this code.
|
|
104
104
|
|
|
105
105
|
## What consumers must add to their own CI
|
|
@@ -10,7 +10,7 @@ name = "openroboto-protocol"
|
|
|
10
10
|
# (0.1.0 → 0.2.0)这样收紧 schemas 的哨兵默认值是允许的。
|
|
11
11
|
#
|
|
12
12
|
# ⚠️ 0.1.0 已经发到 PyPI(2026-08-19,OIDC)。PyPI 版本号不可重用,只能往前发。
|
|
13
|
-
version = "0.
|
|
13
|
+
version = "0.4.0"
|
|
14
14
|
description = "Shared protocol contract for the OpenRoboto subnet on Bittensor (netuid 80): seed derivation, commitment codec, model hashing, status vocabulary and API schemas."
|
|
15
15
|
readme = "README.md"
|
|
16
16
|
license = "MIT"
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Weight normalisation — the last step before emissions land on chain.
|
|
2
|
+
|
|
3
|
+
**What it promises**: given the backend's `{hotkey: share}` and the metagraph's
|
|
4
|
+
hotkey list, everyone computes the same `(uid, u16)` pair. The backend's
|
|
5
|
+
chain-writer and every external validator running the CLI feed the chain from
|
|
6
|
+
this one function, so "the backend computed one thing and the validator wrote
|
|
7
|
+
another" cannot happen by divergence.
|
|
8
|
+
|
|
9
|
+
It used to happen by copying. Two implementations existed --
|
|
10
|
+
`openroboto-backend/app/services/chain_writer.py` and
|
|
11
|
+
`openroboto-cli/src/openroboto/chain/weights.py` -- byte-for-byte identical in
|
|
12
|
+
arithmetic and different in every name, return type and log string. Nothing
|
|
13
|
+
compared them. Two copies of an expression whose floating-point shape *is* the
|
|
14
|
+
behaviour is the definition of a red line worth centralising: a "cleanup" in
|
|
15
|
+
one of them, months apart, silently changes who gets paid.
|
|
16
|
+
|
|
17
|
+
**What it is not responsible for**:
|
|
18
|
+
|
|
19
|
+
- Deciding the shares. That is King-of-the-Hill, and it stays in the backend
|
|
20
|
+
(`app/domain/ranking.py`) -- it needs scores, history and a database.
|
|
21
|
+
- Sending the extrinsic. Both callers keep their own `set_weights` because the
|
|
22
|
+
SDK result shapes and the retry policy differ between them.
|
|
23
|
+
- Knowing which hotkeys are registered. The caller passes the metagraph list.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from dataclasses import dataclass
|
|
29
|
+
from typing import Final
|
|
30
|
+
|
|
31
|
+
__all__ = ["U16_MAX", "NormalizedWeights", "normalize_weights"]
|
|
32
|
+
|
|
33
|
+
#: Maximum weight the chain accepts, as an unsigned 16-bit integer.
|
|
34
|
+
U16_MAX: Final = 65535
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True, slots=True)
|
|
38
|
+
class NormalizedWeights:
|
|
39
|
+
"""What `set_weights` needs: two parallel lists plus the evidence.
|
|
40
|
+
|
|
41
|
+
`uids` and `weights` are positional pairs, not a mapping, because that is
|
|
42
|
+
the shape the chain call takes.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
uids: list[int]
|
|
46
|
+
#: u16 in [0, 65535]. **Not shares** -- shares are what came in.
|
|
47
|
+
weights: list[int]
|
|
48
|
+
#: Per-entry lines, written straight to the log. When weights turn out
|
|
49
|
+
#: wrong, this is the only evidence of what went in and what came out.
|
|
50
|
+
detail: list[str]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def normalize_weights(
|
|
54
|
+
weights_raw: dict[str, float], hotkeys: list[str]
|
|
55
|
+
) -> NormalizedWeights:
|
|
56
|
+
"""Turn `{hotkey: share}` into the `(uid, u16)` lists the chain takes.
|
|
57
|
+
|
|
58
|
+
🔴 **The shape of these expressions is the behaviour. Do not tidy them.**
|
|
59
|
+
Three ways that has already gone wrong:
|
|
60
|
+
|
|
61
|
+
- `weight > 0` is strictly greater. A miner on zero is **left out of the uid
|
|
62
|
+
list**, not written as a zero. Writing an explicit zero is a different
|
|
63
|
+
statement to the chain.
|
|
64
|
+
- `share = w / total` first, then `share * U16_MAX` -- **not**
|
|
65
|
+
`w * U16_MAX / total`. Those are not equal in floating point.
|
|
66
|
+
- `int(...)` truncates, it does not round. The evidence is on chain: in
|
|
67
|
+
snapshot 122 the burn address holds 0.9 of the total, and
|
|
68
|
+
`0.9 * 65535 == 58981.5` exactly. `int` gives 58981, `round` gives 58982 --
|
|
69
|
+
**switching to `round()` rewrites a value that is already on chain.**
|
|
70
|
+
Truncation is also why the u16 values sum to slightly under 65535 (65533
|
|
71
|
+
for that snapshot); the chain accepts the shortfall.
|
|
72
|
+
|
|
73
|
+
⚠️ Both former copies illustrated this with `1/3 → int(21844.999…) = 21844`.
|
|
74
|
+
That example is wrong -- `(1/3) * 65535` is exactly `21845.0` and three
|
|
75
|
+
equal shares sum to exactly 65535 -- and being wrong, it invited someone
|
|
76
|
+
to "correct" the code to match it. The property is real; that
|
|
77
|
+
illustration never was.
|
|
78
|
+
|
|
79
|
+
Hotkeys present in `weights_raw` but absent from `hotkeys` (a miner who
|
|
80
|
+
deregistered) are dropped, and what remains is renormalised over its own
|
|
81
|
+
total. That is the behaviour in production; callers that want to notice it
|
|
82
|
+
should diff the two key sets themselves before calling.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
weights_raw: shares keyed by hotkey, as the backend computed them.
|
|
86
|
+
hotkeys: the metagraph's hotkey list. **The index is the uid.**
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
The uid list, the u16 list, and the per-entry log lines. Empty lists
|
|
90
|
+
when nothing has a positive weight -- the caller must not send an
|
|
91
|
+
extrinsic in that case, and must treat it as an event worth reporting:
|
|
92
|
+
it means no miner is being paid this round.
|
|
93
|
+
"""
|
|
94
|
+
positive: dict[int, float] = {}
|
|
95
|
+
detail: list[str] = []
|
|
96
|
+
for uid, hotkey in enumerate(hotkeys):
|
|
97
|
+
weight = weights_raw.get(hotkey, 0.0)
|
|
98
|
+
if weight > 0:
|
|
99
|
+
positive[uid] = weight
|
|
100
|
+
detail.append(f" uid={uid:3d} hotkey={hotkey[:12]}... raw={weight:.6f}")
|
|
101
|
+
|
|
102
|
+
if not positive:
|
|
103
|
+
return NormalizedWeights([], [], ["no positive weights"])
|
|
104
|
+
|
|
105
|
+
total = sum(positive.values())
|
|
106
|
+
normed = {uid: weight / total for uid, weight in positive.items()}
|
|
107
|
+
|
|
108
|
+
uids = list(normed.keys())
|
|
109
|
+
weights = [int(share * U16_MAX) for share in normed.values()]
|
|
110
|
+
|
|
111
|
+
detail.append(f" raw total={total:.6f}, normalized to sum=1.0")
|
|
112
|
+
detail.extend(
|
|
113
|
+
f" → uid={uid:3d} u16={w:5d} ({normed[uid]:.6f})"
|
|
114
|
+
for uid, w in zip(uids, weights, strict=True)
|
|
115
|
+
)
|
|
116
|
+
return NormalizedWeights(uids, weights, detail)
|
|
File without changes
|
|
File without changes
|
{openroboto_protocol-0.3.0 → openroboto_protocol-0.4.0}/src/openroboto_protocol/commitment.py
RENAMED
|
File without changes
|
{openroboto_protocol-0.3.0 → openroboto_protocol-0.4.0}/src/openroboto_protocol/constants.py
RENAMED
|
File without changes
|
{openroboto_protocol-0.3.0 → openroboto_protocol-0.4.0}/src/openroboto_protocol/model_format.py
RENAMED
|
File without changes
|
{openroboto_protocol-0.3.0 → openroboto_protocol-0.4.0}/src/openroboto_protocol/model_hash.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|