Skip to content

Commit bfc466c

Browse files
committed
feat(relayer): make register_relayer sudo/gov-only; refactor node RPC and service
pallet-relayer (v0.2.2 → v0.3.0): - register_relayer: change from self-service (ensure_signed + IsValidator) to ManageOrigin (sudo/gov) with explicit `who: AccountId` parameter - Remove T::IsValidator config trait and Error::NotValidator - Benchmark: replace manual #[block] storage-insert with #[extrinsic_call] using RawOrigin::Root — weights now reflect real dispatch path - Tests: rewrite registry_tests for RuntimeOrigin::root(); add dispatch_info_tests (Pays::Yes, DispatchClass::Normal) - mock.rs: remove MockValidators node (template/node/): - relayer_register.rs: rewrite auto_register as log-only — derives EVM address from Aura key and prints a log box at block #1 instructing sudo to call registerRelayer; removes all extrinsic submission logic - relayer_author.rs: rewrite RelayerAuthorApi as informational RPC — returns {substrate_account, evm_address} from keystore; no longer submits transactions; remove generic C/B params - rpc/mod.rs: update RelayerAuthor::new(keystore) — drop client arg - service.rs: remove transaction pool from auto_register call; add ValidatorSet + Session pallets to service setup - node/Cargo.toml: remove unused pallet-relayer direct dependency runtime (template/runtime/): - genesis_config_preset.rs: add session + validator_set genesis config
1 parent e139d80 commit bfc466c

18 files changed

Lines changed: 646 additions & 302 deletions

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frame/relayer/CHANGELOG.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,41 @@
22

33
All notable changes to `pallet-relayer` will be documented in this file.
44

5+
## [0.3.0] - 2026-06-03
6+
7+
### Changed
8+
9+
- **`register_relayer` is now sudo/governance-only** (`ManageOrigin`). Previously it was
10+
a self-service call restricted to validator nodes via `T::IsValidator`. The new signature
11+
adds an explicit `who: T::AccountId` argument so the privileged origin can register any
12+
account:
13+
```
14+
// before: register_relayer(origin, evm_address) — signed by validator
15+
// after: register_relayer(origin, who, evm_address) — ManageOrigin (sudo/gov)
16+
```
17+
- **Benchmark** for `register_relayer` updated to use `#[extrinsic_call]` with
18+
`RawOrigin::Root` instead of the previous manual `#[block]` storage-insert approach.
19+
Measured weights now reflect the real extrinsic dispatch path.
20+
- Event doc comments updated: `RelayerRegistered` and `RelayerUnregistered` now note
21+
sudo/governance as the acting origin.
22+
- Module-level doc updated: registry is "managed exclusively by sudo/governance".
23+
24+
### Removed
25+
26+
- `T::IsValidator: Contains<AccountId>` — config trait no longer needed. Validator gating
27+
is handled upstream by `pallet-validator-set`.
28+
- `Error::NotValidator` — removed alongside `IsValidator`.
29+
- `MockValidators` from `mock.rs` — no longer meaningful.
30+
31+
### Migration
32+
33+
Update runtime `Config` impl and any `register_relayer` call sites:
34+
- Remove `type IsValidator = ...` from the `pallet_relayer::Config` impl.
35+
- Change origin from a signed validator to `ManageOrigin` (e.g. `sudo`).
36+
- Add `who: AccountId` as the first call argument before `evm_address`.
37+
38+
---
39+
540
## [0.2.2] - 2026-06-01
641

742
### Changed

frame/relayer/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "pallet-relayer"
3-
version = "0.2.2"
3+
version = "0.3.0"
44
description = "On-chain relay configuration, relayer registry and fee accounting."
55
authors = { workspace = true }
66
license = "GPL-3.0-or-later"

frame/relayer/README.md

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -65,22 +65,18 @@ Emits: `MinRelayFeeUpdated { new_fee }`.
6565
Replaces the ABI selector whitelist. Pass `vec![]` to restore the Runtime API defaults.
6666
Bounded by `T::MaxAllowedSelectors`. Emits: `AllowedSelectorsUpdated { count }`.
6767

68-
### `register_relayer(evm_address: H160)` — origin: `Signed`
69-
Binds the caller's EVM address to their substrate AccountId.
70-
Fails if the EVM address is already claimed by another account (`AlreadyRegistered`).
68+
### `register_relayer(who: AccountId, evm_address: H160)` — origin: `ManageOrigin`
69+
Registers an EVM address for the given `who` account. Must be called by sudo or governance.
70+
Fails if the EVM address is already claimed (`AlreadyRegistered`) or the account already has a binding (`AccountAlreadyRegistered`).
7171
Emits: `RelayerRegistered { evm_address, account }`.
7272

7373
### `unregister_relayer()` — origin: `Signed`
7474
Removes the caller's EVM binding. Clears both forward and reverse maps.
7575
Fails if the caller has no binding (`NotRegistered`).
7676
Emits: `RelayerUnregistered { evm_address, account }`.
7777

78-
### `claim_relay_fees(asset_id: u32, amount: u128)` — origin: `Signed`
79-
Decrements `amount` from the caller's pending fee balance (accounting only — no token transfer).
80-
Emits: `RelayFeesConsumed { relayer, asset_id, amount }`.
81-
82-
> **Note:** This extrinsic only updates the `PendingRelayerFees` counter. To receive real tokens, the validator must call one of the two claim paths in `pallet-shielded-pool`:
83-
> - `claim_shielded_fees` — receives the fee as a private ZK note (requires a value_proof ZK proof)
78+
> **Claiming fees:** To receive accumulated relay fees, the validator calls one of the two claim paths in `pallet-shielded-pool` (not in this pallet):
79+
> - `claim_shielded_fees` — receives fees as a private ZK note (requires a value_proof ZK proof)
8480
> - `claim_relay_fees_to_evm` — transfers public ORB directly to the H160 mirror AccountId (no proof required; ideal for refilling the relayer's EVM gas wallet)
8581
8682
---
@@ -94,7 +90,7 @@ Each node has **two separate identities**:
9490
| `AccountId` (sr25519/Aura) | Substrate | Aura key | Receives and accumulates fees in `PendingRelayerFees` |
9591
| `H160` (ECDSA) | EVM | `--evm-relayer-key` | Signs EVM transactions, pays gas |
9692

97-
Both are linked at startup via `register_relayer(evm_address)`, which writes both indexes (`RelayerRegistry` and `RelayerByAccount`).
93+
Both are linked by sudo/governance via `register_relayer(who, evm_address)`, which writes both indexes (`RelayerRegistry` and `RelayerByAccount`). The node derives and logs the EVM address automatically at block #1 (see [Validator registration flow](#validator-registration-flow) below).
9894

9995
```
10096
1. shield(1000)
@@ -162,6 +158,37 @@ In `pallet-shielded-pool` unit tests: a lightweight mock struct in `mock.rs` bac
162158

163159
---
164160

161+
## Validator registration flow
162+
163+
A new validator node does not call `register_relayer` directly — it is a privileged call. The intended flow is:
164+
165+
1. **Insert Aura key** into the node keystore:
166+
```bash
167+
curl -s -X POST http://localhost:9944 -H 'Content-Type: application/json' \
168+
-d '{"jsonrpc":"2.0","id":1,"method":"author_insertKey",
169+
"params":["aura","<mnemonic>","<public_key_hex>"]}'
170+
```
171+
2. **Restart the node.** At block #1 `auto_register` (in `template/node/src/relayer_register.rs`) reads the Aura key from the keystore, derives the EVM address, checks `RelayerRegistry`, and if not registered **prints a log box**:
172+
```
173+
╔══════════════════════════════════════════════════╗
174+
║ EVM relay key detected — register via sudo ║
175+
╠══════════════════════════════════════════════════╣
176+
║ Substrate : 5GrwvaEF5...
177+
║ EVM addr : 0xd43593c7...
178+
╠══════════════════════════════════════════════════╣
179+
║ relayer.registerRelayer(who, evmAddress) ║
180+
╚══════════════════════════════════════════════════╝
181+
```
182+
3. **Sudo** calls `relayer → registerRelayer(who, evmAddress)` on-chain with the values from the log.
183+
4. The validator can now complete the `pallet-validator-set` registration:
184+
```
185+
session → setKeys(aura + grandpa keys, proof)
186+
validatorSet → registerValidator() // has_relayer check now passes
187+
```
188+
5. **Governance** approves: `validatorSet → approveValidator(who)`.
189+
190+
---
191+
165192
## Runtime integration
166193

167194
```rust
@@ -170,9 +197,10 @@ In `pallet-shielded-pool` unit tests: a lightweight mock struct in `mock.rs` bac
170197
impl pallet_relayer::Config for Runtime {
171198
type BlockAuthor = RelayerBlockAuthor; // wraps pallet_authorship
172199
type DefaultMinRelayFee = ConstU128<1_000_000_000_000_000>; // 1e15 planck = 0.001 ORB
173-
type ManageOrigin = EnsureRoot<AccountId>;
200+
type ManageOrigin = EnsureRoot<AccountId>; // sudo/governance only
174201
type MaxAllowedSelectors = ConstU32<16>;
175202
type WeightInfo = pallet_relayer::weights::SubstrateWeight<Runtime>;
203+
// Note: IsValidator removed in v0.3.0 — validator gating is handled by pallet-validator-set
176204
}
177205

178206
impl pallet_shielded_pool::Config for Runtime {
@@ -194,7 +222,6 @@ Weights in `src/weights.rs` were generated with Substrate Benchmark CLI v49.1.0
194222
| `set_allowed_selectors(n)` | linear in `n` ||
195223
| `register_relayer` | ~10 ms | 2.5 KB |
196224
| `unregister_relayer` | ~10 ms | 2.5 KB |
197-
| `claim_relay_fees` | ~8 ms | 2.5 KB |
198225

199226
To regenerate after modifying the pallet:
200227

@@ -218,8 +245,9 @@ cargo build --release --features runtime-benchmarks
218245
```
219246
src/tests/
220247
├── config_tests.rs — MinRelayFee and AllowedSelectors (governance gating, storage, events, capacity limits)
221-
├── registry_tests.rs — register/unregister + registered_evm_address (duplicate guard, reverse index, signed-only, reverse lookup)
222-
└── fees_tests.rs — accumulate, pending query, consume, claim (saturation, insufficient, events, per-account isolation)
248+
├── registry_tests.rs — register/unregister + registered_evm_address (duplicate guard, reverse index, sudo-only, reverse lookup)
249+
└── dispatch_info_tests.rs — call dispatch info (Pays::Yes, DispatchClass::Normal for register_relayer)
250+
fees_tests.rs — accumulate, pending query, consume, claim (saturation, insufficient, events, per-account isolation)
223251
```
224252

225253
```bash

frame/relayer/src/benchmarking.rs

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -55,28 +55,19 @@ mod benchmarks {
5555

5656
// ── register_relayer ─────────────────────────────────────────────────────
5757

58-
/// Benchmark the storage write path for relayer registration.
58+
/// Worst case: two storage writes + event deposit.
5959
///
60-
/// `register_relayer` is gated by `T::IsValidator`, which is a runtime
61-
/// trait that cannot be generically seeded in benchmarks. We measure the
62-
/// two storage inserts + event deposit directly — the validator check is
63-
/// a cheap in-memory read and does not contribute meaningful weight.
60+
/// `register_relayer` is gated by `ManageOrigin` (sudo/governance).
61+
/// We call it with `RawOrigin::Root` which always satisfies that bound.
6462
#[benchmark]
6563
fn register_relayer() {
66-
let caller: T::AccountId = whitelisted_caller();
64+
let who: T::AccountId = whitelisted_caller();
6765
let evm = evm_address_for(0xA11CE);
6866

69-
#[block]
70-
{
71-
RelayerRegistry::<T>::insert(evm, caller.clone());
72-
RelayerByAccount::<T>::insert(caller.clone(), evm);
73-
Pallet::<T>::deposit_event(Event::RelayerRegistered {
74-
evm_address: evm,
75-
account: caller.clone(),
76-
});
77-
}
78-
79-
assert_eq!(RelayerRegistry::<T>::get(evm), Some(caller));
67+
#[extrinsic_call]
68+
register_relayer(RawOrigin::Root, who.clone(), evm);
69+
70+
assert_eq!(RelayerRegistry::<T>::get(evm), Some(who));
8071
}
8172

8273
// ── unregister_relayer ───────────────────────────────────────────────────

frame/relayer/src/lib.rs

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
//! `ShieldedPoolRuntimeApi::relay_config()`.
1010
//! - **Registry**: EVM address → AccountId binding so fee attribution is
1111
//! unambiguous even when the EVM and substrate keys differ.
12-
//! Only validator nodes (as determined by `T::IsValidator`) may register.
12+
//! Managed exclusively by sudo/governance via `register_relayer`.
1313
//! - **Fee accounting**: `PendingRelayerFees` tracks accrued relay fees per
1414
//! (AccountId, asset_id). Other pallets (pallet-shielded-pool) call
1515
//! `T::Relayer::accumulate_relay_fee()` and `T::Relayer::consume_relay_fee()`
@@ -57,7 +57,7 @@ mod tests;
5757
#[frame_support::pallet]
5858
pub mod pallet {
5959
use super::{RelayerInterface, WeightInfo};
60-
use frame_support::{dispatch::DispatchResult, pallet_prelude::*, traits::Contains};
60+
use frame_support::{dispatch::DispatchResult, pallet_prelude::*};
6161
use frame_system::pallet_prelude::*;
6262
use sp_core::H160;
6363
use sp_std::vec::Vec;
@@ -74,12 +74,6 @@ pub mod pallet {
7474
#[pallet::constant]
7575
type DefaultMinRelayFee: Get<u128>;
7676

77-
/// Returns whether an account is currently a validator node.
78-
///
79-
/// Only accounts recognised as validators may call `register_relayer`.
80-
/// Wire this to a session-validator or Aura-authority check in the runtime.
81-
type IsValidator: frame_support::traits::Contains<Self::AccountId>;
82-
8377
/// Origin allowed to update relay configuration (fee, selectors).
8478
/// Use `EnsureRoot` for testnets; a governance pallet for mainnet.
8579
type ManageOrigin: EnsureOrigin<Self::RuntimeOrigin>;
@@ -112,7 +106,6 @@ pub mod pallet {
112106
StorageValue<_, BoundedVec<[u8; 4], T::MaxAllowedSelectors>, ValueQuery>;
113107

114108
/// On-chain registry: EVM address → substrate AccountId.
115-
/// Only validator nodes may have entries here.
116109
#[pallet::storage]
117110
pub type RelayerRegistry<T: Config> =
118111
StorageMap<_, Blake2_128Concat, H160, T::AccountId, OptionQuery>;
@@ -143,12 +136,12 @@ pub mod pallet {
143136
MinRelayFeeUpdated { new_fee: u128 },
144137
/// `ManageOrigin` updated the allowed selector whitelist.
145138
AllowedSelectorsUpdated { count: u32 },
146-
/// A validator node registered an EVM address.
139+
/// sudo/governance registered a relayer mapping.
147140
RelayerRegistered {
148141
evm_address: H160,
149142
account: T::AccountId,
150143
},
151-
/// A validator node unregistered its EVM address.
144+
/// sudo/governance removed a relayer mapping.
152145
RelayerUnregistered {
153146
evm_address: H160,
154147
account: T::AccountId,
@@ -171,8 +164,6 @@ pub mod pallet {
171164

172165
#[pallet::error]
173166
pub enum Error<T> {
174-
/// Caller is not a validator node. Only validators may register as relayers.
175-
NotValidator,
176167
/// No EVM address registered for this account.
177168
NotRegistered,
178169
/// The EVM address already has a registered AccountId.
@@ -227,15 +218,23 @@ pub mod pallet {
227218
Ok(())
228219
}
229220

230-
/// Register a substrate account as the owner of an EVM relay address.
221+
/// Register an EVM address for a substrate account.
231222
///
232-
/// Only validator nodes (as determined by `T::IsValidator`) may call this.
233-
/// Non-validator accounts are rejected with `NotValidator`.
223+
/// Requires `ManageOrigin` (sudo / governance). The `who` account is
224+
/// registered as the owner of `evm_address`. One registration per account
225+
/// (`AccountAlreadyRegistered`) and one per EVM address (`AlreadyRegistered`)
226+
/// are enforced.
227+
///
228+
/// Intended to be called after a validator inserts their Aura key and the
229+
/// node derives and logs the corresponding EVM relay address.
234230
#[pallet::call_index(2)]
235231
#[pallet::weight(T::WeightInfo::register_relayer())]
236-
pub fn register_relayer(origin: OriginFor<T>, evm_address: H160) -> DispatchResult {
237-
let who = ensure_signed(origin)?;
238-
ensure!(T::IsValidator::contains(&who), Error::<T>::NotValidator);
232+
pub fn register_relayer(
233+
origin: OriginFor<T>,
234+
who: T::AccountId,
235+
evm_address: H160,
236+
) -> DispatchResult {
237+
T::ManageOrigin::ensure_origin(origin)?;
239238
ensure!(
240239
!RelayerRegistry::<T>::contains_key(evm_address),
241240
Error::<T>::AlreadyRegistered
@@ -253,9 +252,9 @@ pub mod pallet {
253252
Ok(())
254253
}
255254

256-
/// Remove the caller's EVM address from the relay registry.
255+
/// Remove an EVM relay address mapping.
257256
///
258-
/// The caller must be a registered validator node.
257+
/// The caller (account owner) may remove their own registration.
259258
#[pallet::call_index(3)]
260259
#[pallet::weight(T::WeightInfo::unregister_relayer())]
261260
pub fn unregister_relayer(origin: OriginFor<T>) -> DispatchResult {

frame/relayer/src/mock.rs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,9 @@ impl frame_support::traits::Get<Option<u64>> for MockBlockAuthor {
3131
}
3232
}
3333

34-
/// Accounts with ID >= 100 are treated as validator nodes in tests.
35-
pub struct MockValidators;
36-
impl frame_support::traits::Contains<u64> for MockValidators {
37-
fn contains(who: &u64) -> bool {
38-
*who >= 100
39-
}
40-
}
41-
4234
impl pallet_relayer::Config for Test {
4335
type BlockAuthor = MockBlockAuthor;
4436
type DefaultMinRelayFee = DefaultMinRelayFee;
45-
type IsValidator = MockValidators;
4637
type ManageOrigin = frame_system::EnsureRoot<u64>;
4738
type MaxAllowedSelectors = MaxAllowedSelectors;
4839
type WeightInfo = ();

0 commit comments

Comments
 (0)