robonomics-interface 2.0.0__tar.gz → 3.0.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.
- robonomics_interface-3.0.0/.gitignore +13 -0
- robonomics_interface-3.0.0/CHANGELOG.md +151 -0
- robonomics_interface-3.0.0/MIGRATION.md +394 -0
- robonomics_interface-3.0.0/PKG-INFO +244 -0
- robonomics_interface-3.0.0/README.md +213 -0
- robonomics_interface-3.0.0/docs/source/CHANGELOG.md +2 -0
- robonomics_interface-3.0.0/docs/source/MIGRATION.md +2 -0
- robonomics_interface-3.0.0/pyproject.toml +99 -0
- robonomics_interface-3.0.0/src/robonomicsinterface/__init__.py +124 -0
- robonomics_interface-3.0.0/src/robonomicsinterface/bip39.py +117 -0
- robonomics_interface-3.0.0/src/robonomicsinterface/client.py +714 -0
- robonomics_interface-3.0.0/src/robonomicsinterface/english.txt +2048 -0
- robonomics_interface-3.0.0/src/robonomicsinterface/envelope.py +119 -0
- robonomics_interface-3.0.0/src/robonomicsinterface/errors.py +195 -0
- robonomics_interface-3.0.0/src/robonomicsinterface/extrinsic.py +494 -0
- robonomics_interface-3.0.0/src/robonomicsinterface/keys.py +305 -0
- robonomics_interface-3.0.0/src/robonomicsinterface/pallets/__init__.py +22 -0
- robonomics_interface-3.0.0/src/robonomicsinterface/pallets/_common.py +49 -0
- robonomics_interface-3.0.0/src/robonomicsinterface/pallets/datalog.py +202 -0
- robonomics_interface-3.0.0/src/robonomicsinterface/pallets/rws.py +202 -0
- robonomics_interface-3.0.0/src/robonomicsinterface/pallets/system.py +153 -0
- robonomics_interface-3.0.0/src/robonomicsinterface/py.typed +0 -0
- robonomics_interface-3.0.0/src/robonomicsinterface/rpc.py +16 -0
- robonomics_interface-3.0.0/src/robonomicsinterface/runtime.py +439 -0
- robonomics_interface-3.0.0/src/robonomicsinterface/ss58.py +116 -0
- robonomics_interface-3.0.0/src/robonomicsinterface/storage.py +264 -0
- robonomics_interface-3.0.0/src/robonomicsinterface/sync.py +426 -0
- robonomics_interface-3.0.0/src/robonomicsinterface/transport.py +267 -0
- robonomics_interface-3.0.0/tests/conftest.py +25 -0
- robonomics_interface-3.0.0/tests/fake_node.py +77 -0
- robonomics_interface-3.0.0/tests/fake_server.py +191 -0
- robonomics_interface-3.0.0/tests/fixtures/chain_samples.json +142 -0
- robonomics_interface-3.0.0/tests/fixtures/compat_vectors.json +209 -0
- robonomics_interface-3.0.0/tests/fixtures/event_samples.json +27 -0
- robonomics_interface-3.0.0/tests/fixtures/extrinsic_vectors.json +39 -0
- robonomics_interface-3.0.0/tests/fixtures/generate_compat_vectors.py +106 -0
- robonomics_interface-3.0.0/tests/fixtures/metadata.hex.gz +0 -0
- robonomics_interface-3.0.0/tests/fixtures/metadata_spec43.hex.gz +0 -0
- robonomics_interface-3.0.0/tests/fixtures/substrate_vectors.json +36 -0
- robonomics_interface-3.0.0/tests/integration/test_devchain.py +199 -0
- robonomics_interface-3.0.0/tests/integration/test_mainnet.py +55 -0
- robonomics_interface-3.0.0/tests/test_client.py +390 -0
- robonomics_interface-3.0.0/tests/test_compat.py +85 -0
- robonomics_interface-3.0.0/tests/test_envelope.py +128 -0
- robonomics_interface-3.0.0/tests/test_extrinsic.py +238 -0
- robonomics_interface-3.0.0/tests/test_keys.py +208 -0
- robonomics_interface-3.0.0/tests/test_pallets.py +240 -0
- robonomics_interface-3.0.0/tests/test_runtime.py +164 -0
- robonomics_interface-3.0.0/tests/test_ss58.py +57 -0
- robonomics_interface-3.0.0/tests/test_stable_api.py +96 -0
- robonomics_interface-3.0.0/tests/test_storage.py +179 -0
- robonomics_interface-3.0.0/tests/test_submit.py +173 -0
- robonomics_interface-3.0.0/tests/test_sync.py +159 -0
- robonomics_interface-2.0.0/PKG-INFO +0 -92
- robonomics_interface-2.0.0/README.md +0 -62
- robonomics_interface-2.0.0/pyproject.toml +0 -58
- robonomics_interface-2.0.0/robonomicsinterface/__init__.py +0 -20
- robonomics_interface-2.0.0/robonomicsinterface/classes/__init__.py +0 -13
- robonomics_interface-2.0.0/robonomicsinterface/classes/account.py +0 -57
- robonomics_interface-2.0.0/robonomicsinterface/classes/base.py +0 -40
- robonomics_interface-2.0.0/robonomicsinterface/classes/chain_utils.py +0 -123
- robonomics_interface-2.0.0/robonomicsinterface/classes/common_functions.py +0 -77
- robonomics_interface-2.0.0/robonomicsinterface/classes/datalog.py +0 -102
- robonomics_interface-2.0.0/robonomicsinterface/classes/digital_twin.py +0 -148
- robonomics_interface-2.0.0/robonomicsinterface/classes/launch.py +0 -39
- robonomics_interface-2.0.0/robonomicsinterface/classes/liability.py +0 -252
- robonomics_interface-2.0.0/robonomicsinterface/classes/pubsub.py +0 -114
- robonomics_interface-2.0.0/robonomicsinterface/classes/reqres.py +0 -47
- robonomics_interface-2.0.0/robonomicsinterface/classes/rws.py +0 -176
- robonomics_interface-2.0.0/robonomicsinterface/classes/service_functions.py +0 -191
- robonomics_interface-2.0.0/robonomicsinterface/classes/subscriptions.py +0 -140
- robonomics_interface-2.0.0/robonomicsinterface/constants.py +0 -9
- robonomics_interface-2.0.0/robonomicsinterface/decorators.py +0 -49
- robonomics_interface-2.0.0/robonomicsinterface/exceptions.py +0 -25
- robonomics_interface-2.0.0/robonomicsinterface/robonomics_interface_io.py +0 -128
- robonomics_interface-2.0.0/robonomicsinterface/types.py +0 -13
- robonomics_interface-2.0.0/robonomicsinterface/utils.py +0 -98
- {robonomics_interface-2.0.0 → robonomics_interface-3.0.0}/LICENSE +0 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses
|
|
5
|
+
[Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [3.0.0] — 2026-10-27
|
|
8
|
+
|
|
9
|
+
The release of the 3.0 line; the API is the one of 3.0.0rc2.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- A request timeout no longer drops the connection when the node still answers a
|
|
13
|
+
`system_health` ping. Dropping it ended every subscription on the shared connection,
|
|
14
|
+
so one slow read (`query_map` on a Raspberry Pi, say) turned a pending
|
|
15
|
+
`author_submitAndWatchExtrinsic` into `ExtrinsicOutcomeUnknown`. A timeout on a live
|
|
16
|
+
node is raised as `RequestTimeout` and is not retried on another endpoint; a node that
|
|
17
|
+
answers neither is dropped and failed over as before.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- The Sphinx documentation is back: API reference built from the docstrings, plus the
|
|
21
|
+
README, MIGRATION.md and the changelog, published to GitHub Pages from `main`.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- Checked against the Robonomics runtime `spec_version 52` on Polkadot (Sudo removed
|
|
25
|
+
from production, OpenGov, CPS v1.1): the metadata, signed extensions, and the
|
|
26
|
+
Datalog, RWS, System and Balances pallets are unchanged for this library. The offline
|
|
27
|
+
fixtures are recorded from spec 52. The development-chain tests still run on the
|
|
28
|
+
runtime v50 wasm, because the production runtime no longer has Sudo.
|
|
29
|
+
|
|
30
|
+
## [3.0.0rc2] — 2026-09-27
|
|
31
|
+
|
|
32
|
+
From three days of rc1 in rrs-connector, rrs-admin and rrs-ha-integration.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
- The default TLS context for `wss://` endpoints is built once per client, in a worker
|
|
36
|
+
thread, and shared by every connection. Before, asyncio built one per connection on
|
|
37
|
+
the event loop, reading the CA store from disk — Home Assistant reported a blocking
|
|
38
|
+
call. Passing `ssl=` works as before.
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
- Block hashes given as `at=` or to `client.chain.block_number()` are checked
|
|
42
|
+
(`0x` + 64 hex digits) before any request; a bad one raises `EncodeError`.
|
|
43
|
+
- A test pins the API the Report Service projects use; MIGRATION.md lists it under
|
|
44
|
+
"Stable API".
|
|
45
|
+
- Integration tests that send transactions refuse a non-loopback node unless
|
|
46
|
+
`ROBONOMICS_DEV_ALLOW_REMOTE=1`, and refuse production chains outright.
|
|
47
|
+
- A weekly read-only smoke test against mainnet in CI.
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
- Robonomics on Kusama is legacy and is shutting down. A client refuses a Kusama node
|
|
51
|
+
by default with "switch to Polkadot" in the reason; with `genesis_hash=None` it
|
|
52
|
+
connects and logs a warning. No Kusama constant is provided.
|
|
53
|
+
|
|
54
|
+
## [3.0.0rc1] — 2026-09-24
|
|
55
|
+
|
|
56
|
+
A rewrite for the current Robonomics runtime. Not compatible with 2.x: see
|
|
57
|
+
[MIGRATION.md](MIGRATION.md) for the mapping and the behaviour that changed.
|
|
58
|
+
|
|
59
|
+
### Added
|
|
60
|
+
- `Keypair` (ED25519) from a mnemonic (with optional password), a raw seed, a
|
|
61
|
+
Substrate secret URI with hard derivation (`//Alice`, `<phrase>//a//b///password`),
|
|
62
|
+
a public key or an address. `from_secret()` accepts only what a person should
|
|
63
|
+
paste: a mnemonic or a seed, never a derivation path.
|
|
64
|
+
- Message encryption (`encrypt_message` / `decrypt_message`) byte-compatible with
|
|
65
|
+
substrate-interface.
|
|
66
|
+
- Multi-recipient envelope: `encrypt_for_recipients`, `decrypt_package`,
|
|
67
|
+
`parse_decrypted`, in the format Robonomics Report Service publishes.
|
|
68
|
+
- SS58 helpers: `encode_address`, `decode_address`, `address_format`,
|
|
69
|
+
`is_valid_address` (format 32 by default).
|
|
70
|
+
- BIP39 English: `generate_mnemonic`, `validate_mnemonic`.
|
|
71
|
+
- A typed exception hierarchy rooted at `RobonomicsError`; input errors are also
|
|
72
|
+
`ValueError`. No exception message contains secret material.
|
|
73
|
+
- Runtime metadata (V14) decoded from the chain itself: `Runtime` describes one
|
|
74
|
+
runtime version (pallets, storage entries, constants, SCALE encode/decode);
|
|
75
|
+
`RuntimeCache` fetches metadata per `(genesis_hash, spec_version)`, parses it off
|
|
76
|
+
the event loop and reloads it after a runtime upgrade.
|
|
77
|
+
- Storage: every hasher (`Identity`, `Twox64Concat`, `Twox128`, `Twox256`,
|
|
78
|
+
`Blake2_128`, `Blake2_256`, `Blake2_128Concat`), multi-key maps and maps keyed by
|
|
79
|
+
a tuple; `query`, `query_map` (paged, pinned to one block, keys decoded back) and
|
|
80
|
+
`constant`. An absent value is `None` for an `Optional` item and the declared
|
|
81
|
+
default otherwise.
|
|
82
|
+
- New errors: `MetadataError`, `NoSuchPallet`, `NoSuchStorage`, `NoSuchConstant`,
|
|
83
|
+
`EncodeError`, `DecodeError`.
|
|
84
|
+
- `RobonomicsClient`, an asyncio client over one multiplexed WebSocket: endpoints in
|
|
85
|
+
priority order (e.g. a LAN node first, the public node as fallback), a node is
|
|
86
|
+
used only if its genesis hash matches Robonomics Polkadot and it is neither
|
|
87
|
+
syncing nor without peers, failback to a preferred endpoint, a timeout on every
|
|
88
|
+
request, automatic retry of reads on another endpoint (`retry=False` for requests
|
|
89
|
+
with side effects), subscriptions, and runtime-upgrade tracking through
|
|
90
|
+
`state_subscribeRuntimeVersion`. `query`, `query_map` and `constant` on the client.
|
|
91
|
+
- Transport errors, all `TransportError` (retryable): `ConnectionFailed`,
|
|
92
|
+
`ConnectionLost`, `RequestTimeout`, `AllEndpointsFailed` (with the reason for each
|
|
93
|
+
endpoint). `RpcError` for errors returned by the node.
|
|
94
|
+
- Extrinsics: `compose_call` checks arguments against the metadata and takes natural
|
|
95
|
+
values (`bytes`, `Keypair`, nested `Call`, a plain list for `BoundedVec` — no more
|
|
96
|
+
`[[a, b]]` for `RWS.set_devices`); signed extensions come from the metadata, in its
|
|
97
|
+
order, and an unknown one that carries data is refused (`UnsupportedExtension`);
|
|
98
|
+
mortal era by default (`Era`); `sign`, `validate` (the pool's own check via
|
|
99
|
+
`state_call`), `submit` (waits for the block and reads its events) and
|
|
100
|
+
`submit_nowait`. One submission per account at a time, so nonces never collide.
|
|
101
|
+
- `ExtrinsicResult` with the block, index and events of the extrinsic.
|
|
102
|
+
- Transaction errors, all `TransactionError`: `InvalidTransaction` (kind plus a
|
|
103
|
+
plain explanation — `Payment` says the account does not exist on chain),
|
|
104
|
+
`ExtrinsicFailed` (pallet error name and docs, e.g. `RWS.FreeWeightIsNotEnough`),
|
|
105
|
+
`ExtrinsicDropped`, `ExtrinsicOutcomeUnknown` (with the hash to look up).
|
|
106
|
+
- Pallet helpers on the client:
|
|
107
|
+
- `client.datalog`: `index`, `item` (slot 0 is slot 0 — in 2.x `get_item(index=0)`
|
|
108
|
+
meant "latest"), `latest` (also correct after the ring buffer wraps to 0, where
|
|
109
|
+
2.x returned `None`), `items` (all live records in one request), `record`
|
|
110
|
+
(bytes or UTF-8 text, size checked, optionally through an RWS subscription),
|
|
111
|
+
`erase`. Records come back as `bytes` (`DatalogItem`), never as text-or-hex.
|
|
112
|
+
- `client.rws`: `ledger` (`Ledger` with `is_active`, `days_left`, `expires_at`),
|
|
113
|
+
`devices`, `is_device`, `max_devices`, `set_devices` (plain list, checked and
|
|
114
|
+
de-duplicated, `TooManyDevices` past the limit), `add_devices`,
|
|
115
|
+
`remove_devices`, `call`, `bid`, auction reads.
|
|
116
|
+
- `client.system` (`account` → `AccountInfo` with `exists`, `next_nonce`),
|
|
117
|
+
`client.balances` (`existential_deposit`, `transfer_keep_alive`,
|
|
118
|
+
`transfer_allow_death`), `client.chain` (block hashes and numbers, runtime
|
|
119
|
+
version). `XRT = 10**9`.
|
|
120
|
+
- `RobonomicsSync`, a blocking wrapper for scripts and cron jobs: the async client
|
|
121
|
+
on an event loop in a background thread, with the same methods, arguments,
|
|
122
|
+
errors and timeouts (a test keeps the two APIs in step). `query_map` becomes a
|
|
123
|
+
plain iterator. Calling it from inside a running event loop raises
|
|
124
|
+
`RuntimeError` rather than stalling the loop; closing it stops every thread it
|
|
125
|
+
started.
|
|
126
|
+
- Tests: offline suites on recorded mainnet data and local fake nodes; integration
|
|
127
|
+
tests against a development node running the mainnet runtime
|
|
128
|
+
(`scripts/devchain.sh`), covering transfers, datalog, sudo, RWS subscriptions and
|
|
129
|
+
their failures; a read-only mainnet smoke test.
|
|
130
|
+
- `py.typed`.
|
|
131
|
+
|
|
132
|
+
### Changed
|
|
133
|
+
- Python 3.12+; built with hatchling.
|
|
134
|
+
- Core dependencies are `pynacl`, `scalecodec`, `websockets` and `xxhash` only;
|
|
135
|
+
every one installs from a wheel or as pure Python on musl/aarch64.
|
|
136
|
+
- ED25519 is the only key type. sr25519 returns as an optional extra in a later
|
|
137
|
+
release.
|
|
138
|
+
- Extrinsic failures raise instead of returning a hash: inclusion in a block is
|
|
139
|
+
checked against the block's events.
|
|
140
|
+
- `Keypair.sign()` signs a `str` as its UTF-8 bytes. substrate-interface decoded a
|
|
141
|
+
`str` starting with `0x` as hex first; pass `bytes.fromhex(...)` for that.
|
|
142
|
+
|
|
143
|
+
### Removed
|
|
144
|
+
- The dependency on `substrate-interface` and, with it, `ecdsa`, the Ethereum
|
|
145
|
+
libraries, `smoldot-light` and the Rust bindings.
|
|
146
|
+
- The 2.x API: `Account`, `ServiceFunctions`, `BaseClass`, `ChainUtils`,
|
|
147
|
+
`CommonFunctions`, the per-pallet classes, `Subscriber`/`SubEvent`,
|
|
148
|
+
`TYPE_REGISTRY`, and the `robonomics_interface` CLI.
|
|
149
|
+
- `PubSub` and `ReqRes`: the node RPCs behind them are gone since collators moved
|
|
150
|
+
to `polkadot-omni-node`.
|
|
151
|
+
- IPFS helpers (`web_3_auth`, `ipfs_32_bytes_to_qm_hash`, `ipfs_qm_hash_to_32_bytes`).
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
# Migrating to robonomics-interface 3.0
|
|
2
|
+
|
|
3
|
+
3.0 is a rewrite. It keeps the wire formats — the same address from the same
|
|
4
|
+
mnemonic, the same ciphertext, the same extrinsic bytes — and changes the API.
|
|
5
|
+
This guide is written for moving the Robonomics Report Service projects
|
|
6
|
+
(`rrs-connector`, `rrs-admin`, `rrs-ha-integration`) off `robonomics-interface`
|
|
7
|
+
2.x, `substrate-interface` and the copied `chain/` packages, but applies to any
|
|
8
|
+
2.x user.
|
|
9
|
+
|
|
10
|
+
Pin the exact version everywhere: `robonomics-interface==3.0.0`.
|
|
11
|
+
The API listed in section 8 is the same as in the release candidates.
|
|
12
|
+
|
|
13
|
+
## 1. What changed, and why
|
|
14
|
+
|
|
15
|
+
| | 2.x | 3.0 |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| Dependencies | `substrate-interface` (Rust bindings, `ecdsa`, Ethereum libs, `smoldot-light`) | `pynacl`, `scalecodec`, `websockets`, `xxhash`; installs on musl/aarch64 with no compiler |
|
|
18
|
+
| Keys | sr25519 by default, ed25519 optional | ED25519 only (sr25519 returns as an extra in 3.1) |
|
|
19
|
+
| Connection | hidden, per object, synchronous, no request timeout | explicit `RobonomicsClient` (asyncio) or `RobonomicsSync`; timeout on every request; endpoints in priority order with health checks and failover |
|
|
20
|
+
| Types | hand-written type registry | the chain's own metadata |
|
|
21
|
+
| Writes | returns a hash; failure inside the block goes unnoticed | reads the block's events; a failed call raises `ExtrinsicFailed` naming the pallet error |
|
|
22
|
+
| Errors | whatever the transport library raised | one hierarchy under `RobonomicsError`; network failures are `TransportError` (retryable), everything else is not |
|
|
23
|
+
|
|
24
|
+
## 2. Dependencies
|
|
25
|
+
|
|
26
|
+
Remove `substrate-interface`, `websocket-client` and any copy of the `chain/`
|
|
27
|
+
package. Add `robonomics-interface==3.0.0rc1`. It brings `pynacl`, `scalecodec`,
|
|
28
|
+
`websockets` and `xxhash`; do not list them separately unless the project uses
|
|
29
|
+
them directly.
|
|
30
|
+
|
|
31
|
+
Home Assistant: the manifest `requirements` becomes `["robonomics-interface==3.0.0rc1", ...]`
|
|
32
|
+
(plus the project's own, e.g. `pinatapy-vourhey`). HA already constrains
|
|
33
|
+
`websockets>=15`, which 3.0 accepts. No `aiohttp` session is needed: the library
|
|
34
|
+
has its own WebSocket client.
|
|
35
|
+
|
|
36
|
+
## 3. The client
|
|
37
|
+
|
|
38
|
+
### Async (Home Assistant, any asyncio code)
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from robonomicsinterface import RobonomicsClient
|
|
42
|
+
|
|
43
|
+
client = RobonomicsClient(endpoints) # str or list, in order of preference
|
|
44
|
+
await client.connect() # or: async with RobonomicsClient(...) as client:
|
|
45
|
+
...
|
|
46
|
+
await client.close()
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Create one client and keep it: it holds one multiplexed connection and the
|
|
50
|
+
parsed runtime metadata (parsing takes seconds on a Raspberry Pi; the library
|
|
51
|
+
does it off the event loop). In an integration, create it per config entry and
|
|
52
|
+
close it on unload (`entry.async_on_unload(client.close)`).
|
|
53
|
+
|
|
54
|
+
In Home Assistant, pass HA's shared TLS context:
|
|
55
|
+
`RobonomicsClient(endpoints, ssl=homeassistant.util.ssl.client_context())`. It honours
|
|
56
|
+
HA's CA settings and is built once for all integrations. Without `ssl=`, the library
|
|
57
|
+
builds its own default context once per client, off the event loop (since rc2; rc1
|
|
58
|
+
built one per connection on the loop, which HA reported as a blocking call).
|
|
59
|
+
|
|
60
|
+
### Blocking (scripts, cron jobs, CLIs)
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from robonomicsinterface import RobonomicsSync
|
|
64
|
+
|
|
65
|
+
with RobonomicsSync(endpoints) as client:
|
|
66
|
+
client.datalog.latest(address)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Same methods and arguments without `await`; `query_map` is a plain iterator.
|
|
70
|
+
It must not be called from a thread running an event loop — it raises
|
|
71
|
+
`RuntimeError` there, because blocking would stall that loop.
|
|
72
|
+
|
|
73
|
+
### Options that replace 2.x workarounds
|
|
74
|
+
|
|
75
|
+
| Need | 3.0 |
|
|
76
|
+
| --- | --- |
|
|
77
|
+
| Per-request timeout (was: `interface.websocket.settimeout(...)`) | `RobonomicsClient(..., timeout=30.0, connect_timeout=10.0)` |
|
|
78
|
+
| Several nodes, move on when one fails (was: hand-written `_reconnect` / `change_current_wss`) | pass them all: `RobonomicsClient([local, public])`; reads are retried on the next endpoint (`retries=2`), the client returns to a preferred endpoint on its own (`failback_interval=300`) |
|
|
79
|
+
| Know which node is in use | `client.endpoint` |
|
|
80
|
+
| Kusama | **Legacy: Robonomics on Kusama is shutting down — switch to Polkadot.** The default genesis check refuses a Kusama node and says so; with `genesis_hash=None` the client connects and logs a warning. No Kusama constant is provided. |
|
|
81
|
+
| A node on the local network | put it first: `["ws://192.168.1.10:9944", DEFAULT_ENDPOINT]`; a node that is syncing or has no peers is skipped |
|
|
82
|
+
|
|
83
|
+
## 4. Errors
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
RobonomicsError
|
|
87
|
+
├── TransportError .retryable = True: the node was not reached or stopped answering
|
|
88
|
+
│ ├── ConnectionFailed (.endpoint, .reason)
|
|
89
|
+
│ ├── ConnectionLost
|
|
90
|
+
│ ├── RequestTimeout (also a TimeoutError)
|
|
91
|
+
│ └── AllEndpointsFailed (.failures: one ConnectionFailed per endpoint)
|
|
92
|
+
├── RpcError the node answered with an error (.code, .message, .data)
|
|
93
|
+
├── TransactionError an extrinsic did not do what was asked
|
|
94
|
+
│ ├── InvalidTransaction refused before sending (.kind, .explanation)
|
|
95
|
+
│ ├── ExtrinsicFailed in a block, call failed (.pallet, .error, .docs, .result)
|
|
96
|
+
│ ├── ExtrinsicDropped dropped by the node
|
|
97
|
+
│ └── ExtrinsicOutcomeUnknown sent, outcome unknown (.extrinsic_hash) — do not resend blindly
|
|
98
|
+
├── MetadataError / NoSuchPallet / NoSuchStorage / NoSuchCall / NoSuchConstant / UnsupportedExtension
|
|
99
|
+
├── EncodeError / DecodeError (ValueError) e.g. TooManyDevices is an EncodeError
|
|
100
|
+
├── InvalidAddress / InvalidKey / InvalidMnemonic (ValueError)
|
|
101
|
+
├── NoSecretKey, DecryptionError
|
|
102
|
+
└── EnvelopeError: RecipientError, PackageError (ValueError), PayloadError
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Replace `TRANSIENT_ERRORS = (OSError, TimeoutError, WebSocketException)` and
|
|
106
|
+
`RETRYABLE_ERRORS = (RpcError, ChainError, TimeoutError, OSError)` with
|
|
107
|
+
`TransportError`. The client already retries reads across endpoints, so an
|
|
108
|
+
outer retry loop only needs to cover a whole run, if at all. `RpcError` is not a
|
|
109
|
+
network failure and repeating the request will not help.
|
|
110
|
+
|
|
111
|
+
## 5. API mapping
|
|
112
|
+
|
|
113
|
+
### Keys and addresses
|
|
114
|
+
|
|
115
|
+
| 2.x / substrate-interface / `chain/` | 3.0 |
|
|
116
|
+
| --- | --- |
|
|
117
|
+
| `Keypair.create_from_mnemonic(m, crypto_type=KeypairType.ED25519, ss58_format=32)` | `Keypair.from_mnemonic(m)` |
|
|
118
|
+
| `chain.Keypair.create_from_secret(s)` (mnemonic or `0x` seed) | `Keypair.from_secret(s)` |
|
|
119
|
+
| `Account(seed, crypto_type=KeypairType.ED25519)` | `Keypair.from_secret(seed)` |
|
|
120
|
+
| `account.get_address()`, `keypair.ss58_address` | `keypair.address` |
|
|
121
|
+
| `account.keypair` | the `Keypair` itself |
|
|
122
|
+
| `Keypair(ss58_address=a, crypto_type=KeypairType.ED25519)`, `chain.Keypair.create_from_address(a)` | `Keypair.from_address(a)` (public only) |
|
|
123
|
+
| `chain.Keypair.create_from_public_key(pk)` | `Keypair.from_public_key(pk)` |
|
|
124
|
+
| `Keypair.generate_mnemonic()` | `generate_mnemonic()` or `Keypair.generate_mnemonic()` |
|
|
125
|
+
| `is_valid_ss58_address(a)` | `is_valid_address(a, ss58_format=None)` — see pitfall 4 |
|
|
126
|
+
| `chain.ss58_decode(a)` / `SS58Error` | `decode_address(a)` / `InvalidAddress` (a `ValueError`) |
|
|
127
|
+
| `chain.ss58_encode(pk, 32)` | `encode_address(pk)` |
|
|
128
|
+
| `"//Alice"` for tests | `Keypair.from_uri("//Alice")` (ED25519, as polkadot-sdk derives it) |
|
|
129
|
+
|
|
130
|
+
### Encryption
|
|
131
|
+
|
|
132
|
+
| Before | 3.0 |
|
|
133
|
+
| --- | --- |
|
|
134
|
+
| `keypair.encrypt_message(msg, recipient_pk)` / `decrypt_message(box, sender_pk)` | same names, same bytes; failures raise `DecryptionError` |
|
|
135
|
+
| `chain.envelope.encrypt_for_recipients(data, sender, addresses, meta)` | `encrypt_for_recipients(...)`, same arguments and output |
|
|
136
|
+
| `chain.envelope.decrypt_package(package, recipient, sender_address)`, connector's `multi_envelope_decrypt_data(...)` | `decrypt_package(package, recipient_keypair, sender_address)` |
|
|
137
|
+
| `parse_decrypted(text)` | `parse_decrypted(text)` |
|
|
138
|
+
| `chain.envelope` errors | `RecipientError`, `PackageError`, `PayloadError` (all `EnvelopeError`) |
|
|
139
|
+
|
|
140
|
+
### Reading the chain
|
|
141
|
+
|
|
142
|
+
| Before | 3.0 (async; `RobonomicsSync` is the same without `await`) |
|
|
143
|
+
| --- | --- |
|
|
144
|
+
| `Datalog(Account(remote_ws=url)).get_index(a)` → `{"start", "end"}` | `await client.datalog.index(a)` → `DatalogIndex(start, end, window_size)` with `.count`, `.positions` (oldest first), `.latest_position` |
|
|
145
|
+
| `interface.get_constant("Datalog", "WindowSize").value` | `await client.datalog.window_size()` or `await client.constant("Datalog", "WindowSize")` |
|
|
146
|
+
| `chainstate_query("Datalog", "DatalogItem", [a, i])` → `(ts, content)` | `await client.datalog.item(a, i)` → `DatalogItem(index, timestamp_ms, data)` or `None` |
|
|
147
|
+
| walking `ring_buffer_indices(...)` and calling `get_item` per slot | `await client.datalog.items(a)` — every live record, oldest first, in one request; `await client.datalog.latest(a)` |
|
|
148
|
+
| `Datalog.get_item(a)` (latest) | `await client.datalog.latest(a)` |
|
|
149
|
+
| `substrate.query("RWS", "Devices", [pool]).value or []` | `await client.rws.devices(pool)` → `list[str]` |
|
|
150
|
+
| `substrate.query("RWS", "Ledger", [pool]).value` | `await client.rws.ledger(pool)` → `Ledger` or `None`: `.kind` (`"Daily"`/`"Lifetime"`), `.days`, `.tps`, `.issued_at`, `.expires_at`, `.is_active()`, `.days_left()`, `.free_weight` |
|
|
151
|
+
| `substrate.query("System", "Account", [a]).value` | `await client.system.account(a)` → `AccountInfo`: `.free`, `.reserved`, `.frozen`, `.nonce`, `.exists` |
|
|
152
|
+
| anything else: `substrate.query(pallet, item, [keys])` | `await client.query(pallet, item, *keys)`; maps: `client.query_map(pallet, item)`; constants: `client.constant(pallet, name)` |
|
|
153
|
+
| `RWS.MaxDevicesAmount` | `await client.rws.max_devices()` |
|
|
154
|
+
|
|
155
|
+
### Writing
|
|
156
|
+
|
|
157
|
+
| Before | 3.0 |
|
|
158
|
+
| --- | --- |
|
|
159
|
+
| `chain.RobonomicsClient.record_datalog(kp, data, owner)` | `await client.datalog.record(kp, data, subscription_owner=owner)` |
|
|
160
|
+
| `Datalog(Account(seed), rws_sub_owner=owner).record(data)` | same as above |
|
|
161
|
+
| `compose_call("RWS", "set_devices", {"devices": [devices]})` + sign + `submit_extrinsic(wait_for_inclusion=True)` + `receipt.is_success` | `await client.rws.set_devices(owner_kp, devices)` — a **flat** list |
|
|
162
|
+
| read devices, edit, `set_devices` | `await client.rws.add_devices(owner_kp, *new)` / `remove_devices(owner_kp, *old)` (`None` when nothing changes) |
|
|
163
|
+
| `CommonFunctions.transfer_tokens(to, amount)` | `await client.balances.transfer_keep_alive(kp, to, amount)` |
|
|
164
|
+
| any other call | `call = await client.compose_call(pallet, function, args)`; `await client.submit(call, kp)` |
|
|
165
|
+
|
|
166
|
+
Every write returns an `ExtrinsicResult` (`.block_hash`, `.block_number`,
|
|
167
|
+
`.index`, `.events`, `.find(pallet, name)`) or raises. Options for all writes:
|
|
168
|
+
`wait_for="in_block"|"finalized"`, `tip`, `era_period` (64; `None` for immortal),
|
|
169
|
+
`validate` (ask the runtime first; on by default), `timeout` (120 s).
|
|
170
|
+
|
|
171
|
+
## 6. Pitfalls — behaviour that changed silently in meaning
|
|
172
|
+
|
|
173
|
+
1. **`item(a, 0)` is slot 0.** In 2.x `get_item(index=0)` meant "latest". The
|
|
174
|
+
connector worked around it through `_service_functions`; that code goes.
|
|
175
|
+
2. **Only live slots are returned.** The ring buffer keeps at most
|
|
176
|
+
`WindowSize - 1` (127) records; the slot at `end` may still hold an
|
|
177
|
+
overwritten record in storage. `item()` returns `None` for it, where a raw
|
|
178
|
+
storage read returned the stale record.
|
|
179
|
+
3. **Records are `bytes`.** scalecodec returned a record as text when it was
|
|
180
|
+
printable and as `0x…` hex otherwise, so `str(content)` could silently turn a
|
|
181
|
+
CID into hex. Use `item.text` (UTF-8 or `None`) or `item.data`. When writing,
|
|
182
|
+
a `str` is sent as UTF-8 — `"0x12"` stays the four characters it is.
|
|
183
|
+
4. **`is_valid_address(a)` checks for Robonomics (format 32) by default.**
|
|
184
|
+
`is_valid_ss58_address(a)` accepted any network. Pass `ss58_format=None` to
|
|
185
|
+
keep the old behaviour, or keep the default to reject `5…` addresses.
|
|
186
|
+
`Keypair.from_address` and `decode_address` accept any format unless given one.
|
|
187
|
+
5. **`set_devices` takes a flat list and replaces the whole list.** The
|
|
188
|
+
`[[a, b]]` wrapping and the decode-back guard in `rrs-admin` go. Addresses of
|
|
189
|
+
any SS58 format are accepted, normalised to format 32 and de-duplicated; more
|
|
190
|
+
than `MaxDevicesAmount` (32) raises `TooManyDevices` before anything is sent.
|
|
191
|
+
`add_devices`/`remove_devices` read, edit and write — two concurrent edits of
|
|
192
|
+
one owner can lose one.
|
|
193
|
+
6. **A failed call raises.** Inclusion in a block is not success. Catch
|
|
194
|
+
`ExtrinsicFailed` and look at `.pallet`/`.error`:
|
|
195
|
+
- `RWS.NotLinkedDevice` — the signer is not in the owner's device list;
|
|
196
|
+
- `RWS.FreeWeightIsNotEnough` — no allowance left: an **expired daily
|
|
197
|
+
subscription stays on chain** and fails this way (not with
|
|
198
|
+
`NoSubscription`), and so does a fresh one before it has accrued;
|
|
199
|
+
- `RWS.NoSubscription` — the owner has none.
|
|
200
|
+
7. **`InvalidTransaction` with `.kind == "Payment"`** means the signer does not
|
|
201
|
+
exist on chain (balance below the existential deposit, 0.000001 XRT), even for
|
|
202
|
+
free RWS calls. `client.system.exists(a)` tells beforehand. It is true when the
|
|
203
|
+
account has providers or sufficients — not the `free or nonce` test `rrs-admin`
|
|
204
|
+
used, which misses reserved-only accounts.
|
|
205
|
+
8. **`ExtrinsicOutcomeUnknown` is not a failure.** The extrinsic was sent and
|
|
206
|
+
may still land; look it up by `.extrinsic_hash` before sending again.
|
|
207
|
+
9. **Mortal era by default.** Extrinsics are valid for ~64 blocks, so a stuck
|
|
208
|
+
one cannot land an hour later, nor be replayed if the account is ever reaped.
|
|
209
|
+
10. **`Keypair.from_secret` refuses derivation paths** (`//Alice`); use
|
|
210
|
+
`Keypair.from_uri` in tests. `Keypair.sign(str)` signs UTF-8; the 2.x stack
|
|
211
|
+
decoded a `str` starting with `0x` as hex first.
|
|
212
|
+
11. **Keypairs keep secrets to themselves:** `repr()` shows the address, errors
|
|
213
|
+
never include the input, pickling raises `TypeError`.
|
|
214
|
+
12. **One key type.** Accounts must be ED25519 — which report encryption
|
|
215
|
+
already required. A mnemonic that was used with sr25519 gives a different
|
|
216
|
+
address here.
|
|
217
|
+
|
|
218
|
+
## 7. Per project
|
|
219
|
+
|
|
220
|
+
### rrs-connector
|
|
221
|
+
|
|
222
|
+
- `pyproject.toml`: drop `substrate-interface` and `websocket-client`; replace
|
|
223
|
+
`robonomics-interface>=2.0.0` with `==3.0.0rc1`.
|
|
224
|
+
- `robonomics/datalog_reader.py`: rebuild on `RobonomicsSync`, keeping
|
|
225
|
+
`DatalogRecord`, `DatalogIndexRange`, `DatalogScan` and the cursor logic
|
|
226
|
+
(`list_new_records`, `list_last_records`) as they are:
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
class DatalogReader:
|
|
230
|
+
def __init__(self, wss_endpoints, request_timeout_seconds, max_attempts=1, backoff_seconds=0):
|
|
231
|
+
if not wss_endpoints:
|
|
232
|
+
raise ValueError("At least one WSS endpoint is required")
|
|
233
|
+
self.client = RobonomicsSync(
|
|
234
|
+
list(wss_endpoints),
|
|
235
|
+
timeout=request_timeout_seconds,
|
|
236
|
+
retries=max(max_attempts - 1, 0),
|
|
237
|
+
).connect()
|
|
238
|
+
|
|
239
|
+
def get_window_size(self) -> int:
|
|
240
|
+
return self.client.datalog.window_size()
|
|
241
|
+
|
|
242
|
+
def get_index_range(self, sender_address):
|
|
243
|
+
index = self.client.datalog.index(sender_address)
|
|
244
|
+
return DatalogIndexRange(index.start, index.end)
|
|
245
|
+
|
|
246
|
+
def get_item(self, sender_address, datalog_index):
|
|
247
|
+
item = self.client.datalog.item(sender_address, datalog_index)
|
|
248
|
+
if item is None or item.text is None:
|
|
249
|
+
return None
|
|
250
|
+
return DatalogRecord(sender_address, item.index, item.timestamp_ms, payload=item.text)
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
`_interface`, `_reconnect`, the socket timeout and the direct
|
|
254
|
+
`chainstate_query` all go. `ring_buffer_indices` can stay (it matches
|
|
255
|
+
`DatalogIndex.positions`) or be replaced; `client.datalog.items(a)` reads all
|
|
256
|
+
records in one request, which is cheaper than one `get_item` per slot. Add a
|
|
257
|
+
`close()` and call it at the end of a run.
|
|
258
|
+
- `robonomics/retry.py`: `TRANSIENT_ERRORS = (TransportError,)`; with the
|
|
259
|
+
client retrying across endpoints, `before_retry=_reconnect` is no longer needed.
|
|
260
|
+
- `keygen.py`: `mnemonic = generate_mnemonic()`, `Keypair.from_mnemonic(...)`,
|
|
261
|
+
`.address`. The read-back check stays.
|
|
262
|
+
- `config.py`: `is_valid_address(address, ss58_format=None)` to keep accepting
|
|
263
|
+
any format — or the default, to require Robonomics addresses (pitfall 4).
|
|
264
|
+
- `proton_pass.py`: `load_integrator_account` returns
|
|
265
|
+
`Keypair.from_secret(seed)` and compares `.address`.
|
|
266
|
+
- `pipeline.py`, `reports/recipients.py`: the `Account` type becomes `Keypair`
|
|
267
|
+
(`AccountLoader = Callable[[str], Keypair]`).
|
|
268
|
+
- `reports/decryptor.py`: `multi_envelope_decrypt_data` becomes a thin wrapper
|
|
269
|
+
over `decrypt_package(package, recipient_keypair, sender_address)` that maps
|
|
270
|
+
`PackageError`/`PayloadError` to `ReportDecryptionError`; `decrypt_msg` and
|
|
271
|
+
the `substrateinterface` imports go. `parse_decrypted` can come from the library.
|
|
272
|
+
- Tests: `tests/test_decryptor.py`, `test_keygen.py`, `test_retry.py`,
|
|
273
|
+
`conftest.py` import `substrateinterface`; switch them to the library. Keep
|
|
274
|
+
any fixture ciphertext produced by the old stack: it is the compatibility check.
|
|
275
|
+
|
|
276
|
+
### rrs-admin
|
|
277
|
+
|
|
278
|
+
- `pyproject.toml`: drop `substrate-interface`; add `robonomics-interface==3.0.0rc1`.
|
|
279
|
+
- Delete `src/rrs_admin/chain/` (the third copy of the integration's key code);
|
|
280
|
+
import from `robonomicsinterface`: `Keypair.create_from_mnemonic` →
|
|
281
|
+
`Keypair.from_mnemonic`, `create_from_secret` → `from_secret`, `.ss58_address`
|
|
282
|
+
→ `.address`, `ss58_decode` → `decode_address`, `SS58Error` → `InvalidAddress`,
|
|
283
|
+
`validate_mnemonic` unchanged, `MnemonicError` → `InvalidMnemonic`.
|
|
284
|
+
Files: `pools.py`, `sites.py`.
|
|
285
|
+
- `rws.py`:
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
def read_subscription(url, pool) -> Subscription:
|
|
289
|
+
with RobonomicsSync(url) as client:
|
|
290
|
+
devices = client.rws.devices(pool)
|
|
291
|
+
ledger = client.rws.ledger(pool)
|
|
292
|
+
account = client.system.account(pool)
|
|
293
|
+
... # Subscription(kind=ledger.kind, issued=ledger.issued_at,
|
|
294
|
+
# expires=ledger.expires_at, free_weight=ledger.free_weight,
|
|
295
|
+
# balance=account.free / XRT)
|
|
296
|
+
|
|
297
|
+
def account_exists(url, address) -> bool:
|
|
298
|
+
with RobonomicsSync(url) as client:
|
|
299
|
+
return client.system.exists(address)
|
|
300
|
+
|
|
301
|
+
def set_devices(url, pool_seed, devices) -> str:
|
|
302
|
+
REDACT.add(pool_seed)
|
|
303
|
+
pool = Keypair.from_mnemonic(pool_seed)
|
|
304
|
+
planned = list(dict.fromkeys(encode_address(decode_address(d)) for d in devices))
|
|
305
|
+
with RobonomicsSync(url) as client:
|
|
306
|
+
try:
|
|
307
|
+
result = client.rws.set_devices(pool, planned)
|
|
308
|
+
except TransactionError as e:
|
|
309
|
+
raise ChainError(REDACT(f"set_devices failed: {e}")) from e
|
|
310
|
+
if client.rws.devices(pool) != planned:
|
|
311
|
+
raise ChainError("the chain holds a different device list than planned")
|
|
312
|
+
return result.block_hash
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
`_addresses_in` and the `[devices]` wrapping go (pitfall 5). Reading the list
|
|
316
|
+
back after inclusion is a stronger check than decoding the call before signing.
|
|
317
|
+
`Subscription.kind` changes from `"Daily 30 days"` formatting done by hand to
|
|
318
|
+
`ledger.kind`/`ledger.days`.
|
|
319
|
+
|
|
320
|
+
### rrs-ha-integration
|
|
321
|
+
|
|
322
|
+
- `manifest.json` `requirements`: `["robonomics-interface==3.0.0rc1", "pinatapy-vourhey==0.1.9"]`.
|
|
323
|
+
- Delete `custom_components/robonomics_report_service/chain/` and its tests
|
|
324
|
+
(`tests/test_chain.py`, `tests/test_chain_client.py`): the library carries
|
|
325
|
+
them, byte-for-byte vectors included. Keep `tests/fixtures/substrate_vectors.json`
|
|
326
|
+
in a test that runs the library against it, as the project's own contract.
|
|
327
|
+
- `robonomics.py`:
|
|
328
|
+
- one `RobonomicsClient(POLKADOT_ENDPOINTS, ssl=client_context())` (from
|
|
329
|
+
`homeassistant.util.ssl`) per config entry, created in `async_setup_entry`,
|
|
330
|
+
closed on unload; remove
|
|
331
|
+
`_clients`, `current_wss`, `change_current_wss` and the endpoint loop in
|
|
332
|
+
`_send_datalog`;
|
|
333
|
+
- publishing: `await client.datalog.record(self.sender_keypair, data, subscription_owner=self._owner_address or self.sender_address)`;
|
|
334
|
+
- errors: `ExtrinsicFailed` and `InvalidTransaction` → the integration's
|
|
335
|
+
`RobonomicsError` with the library's message (it already explains
|
|
336
|
+
`Payment`, `NotLinkedDevice` and `FreeWeightIsNotEnough`); `TransportError` →
|
|
337
|
+
retry later; `ExtrinsicOutcomeUnknown` → do not unpin the files from Pinata
|
|
338
|
+
yet, the record may have landed.
|
|
339
|
+
- Kusama is legacy and shutting down: drop `NETWORK_KUSAMA` from `const.py` and
|
|
340
|
+
the network choice from the config flow, and move existing Kusama entries to
|
|
341
|
+
Polkadot — or, until the network stops, keep the option labelled "legacy — switch
|
|
342
|
+
to Polkadot" and create its client with `genesis_hash=None`.
|
|
343
|
+
- `config_flow.py`: `Keypair.from_secret`, `generate_mnemonic()`,
|
|
344
|
+
`decode_address` (raises `InvalidAddress`, a `ValueError`, so the existing
|
|
345
|
+
`except ValueError` still works).
|
|
346
|
+
- `utils/encrypt_tools.py`, `utils/file_handler.py`: import `Keypair`,
|
|
347
|
+
`encrypt_for_recipients`, `decrypt_package`, `parse_decrypted` and the
|
|
348
|
+
envelope errors from `robonomicsinterface`; `encrypt_message`/`decrypt_message`
|
|
349
|
+
wrappers become `keypair.encrypt_message(...)` + hex.
|
|
350
|
+
- Nothing may block the event loop: use `RobonomicsClient` only, never
|
|
351
|
+
`RobonomicsSync` (which refuses to run there anyway).
|
|
352
|
+
|
|
353
|
+
## 8. Stable API
|
|
354
|
+
|
|
355
|
+
The Report Service projects build on the names below. They do not change between
|
|
356
|
+
release candidates and 3.0.0; if one ever has to, the change is written here, and
|
|
357
|
+
`tests/test_stable_api.py` — which pins this list — changes in the same commit.
|
|
358
|
+
|
|
359
|
+
- `RobonomicsClient(endpoints, genesis_hash=, ssl=, timeout=, retries=)`, `connect()`,
|
|
360
|
+
`close()`, `async with`; `RobonomicsSync`, the same without `await`.
|
|
361
|
+
- `client.datalog.items`, `.record(..., subscription_owner=)`, `.window_size`;
|
|
362
|
+
`client.rws.devices`, `.ledger`, `.set_devices`; `client.system.account`, `.exists`.
|
|
363
|
+
- `Keypair.from_secret`, `.from_mnemonic`, `.address`, `.public_key`, `.verify`,
|
|
364
|
+
`.encrypt_message`, `.decrypt_message`; `generate_mnemonic`, `is_valid_address`,
|
|
365
|
+
`decode_address`, `encode_address`, `address_format`.
|
|
366
|
+
- `encrypt_for_recipients`, `decrypt_package`, `parse_decrypted`.
|
|
367
|
+
- `DatalogItem(index, timestamp_ms, data, text)`, `Ledger(kind, days, issued_at,
|
|
368
|
+
expires_at, free_weight)`, `AccountInfo(free, exists)`, `XRT`, `ROBONOMICS_GENESIS_HASH`.
|
|
369
|
+
- Errors: `TransportError` (`ConnectionLost`, `RequestTimeout`, `ConnectionFailed`,
|
|
370
|
+
`AllEndpointsFailed`), `RpcError(method, code, message)`, `TransactionError`,
|
|
371
|
+
`ExtrinsicFailed(pallet, error)`, `ExtrinsicOutcomeUnknown(extrinsic_hash)`,
|
|
372
|
+
`ExtrinsicDropped`, `InvalidTransaction(kind, explanation)`, `RecipientError`,
|
|
373
|
+
`EnvelopeError`.
|
|
374
|
+
|
|
375
|
+
Changes since rc1: none to this list. rc2 adds the TLS fix, block hash checks and
|
|
376
|
+
the Kusama legacy handling (see CHANGELOG).
|
|
377
|
+
|
|
378
|
+
## 9. Checklist
|
|
379
|
+
|
|
380
|
+
- [ ] No `substrateinterface`, `websocket`, `robonomicsinterface.Account` or
|
|
381
|
+
`chain.` import left (`grep -rn "substrateinterface\|from websocket\|Account\b\|\.chain" src`).
|
|
382
|
+
- [ ] The same mnemonic gives the same address as before (the project's
|
|
383
|
+
fixtures, or `substrate_vectors.json`).
|
|
384
|
+
- [ ] A report encrypted by the old stack still decrypts, and a new one opens
|
|
385
|
+
with the connector.
|
|
386
|
+
- [ ] Datalog slot 0 reads as slot 0; a wrapped buffer reads 127 records, oldest first.
|
|
387
|
+
- [ ] `set_devices` with a flat list, read back from the chain.
|
|
388
|
+
- [ ] A missing account raises `InvalidTransaction` with `kind == "Payment"`; a
|
|
389
|
+
device outside the list raises `ExtrinsicFailed` `RWS.NotLinkedDevice`.
|
|
390
|
+
- [ ] Home Assistant: the integration installs in `python:3.13-alpine` on aarch64
|
|
391
|
+
and HA logs no "Detected blocking call".
|
|
392
|
+
- [ ] Integration tests against a development node, if the project has any:
|
|
393
|
+
`scripts/devchain.sh` in this repository starts one with funded ED25519
|
|
394
|
+
accounts.
|