Skip to content

Commit c3d1744

Browse files
committed
fix: change namespace char set
1 parent 366e6d0 commit c3d1744

File tree

9 files changed

+74
-27
lines changed

9 files changed

+74
-27
lines changed

.github/workflows/build-runtime.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,13 @@ jobs:
3737

3838
- name: Build runtime
3939
run: |
40-
cargo build --release --timings --package torus-runtime
40+
if [[ "${{ github.ref }}" == refs/tags/runtime/testnet* ]]; then
41+
echo "Building with testnet feature flag"
42+
cargo build --release --timings --package torus-runtime --features testnet
43+
else
44+
echo "Building without testnet feature flag"
45+
cargo build --release --timings --package torus-runtime
46+
fi
4147
4248
export SHA256SUM=$(sha256sum target/release/wbuild/torus-runtime/torus_runtime.compact.compressed.wasm | cut -d ' ' -f1)
4349
echo Hash of compact and compressed WASM: $SHA256SUM

docs/namespace.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ The path validation ensures consistency across the network:
2323
- Maximum 255 bytes total length
2424
- Maximum 10 segments (depth limitation)
2525
- Each segment between 1-63 characters
26-
- Valid characters: unicode alphanumerics, `-`, `_`
26+
- Segments must begin with alphanumerics
27+
- Valid characters: ASCII alphanumerics, `-`, `_`, and `+`
28+
29+
> Implementations MUST NOT assume the character set will be ASCII only forever. Paths are UTF-8 encoded strings.
2730
2831
This structure creates clear ownership: `agent.alice` owns all paths under that prefix, from `agent.alice.api` to `agent.alice.memory.twitter.v2`. The depth limitation prevents excessive nesting while still allowing meaningful organization.
2932

justfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ install-try-runtime:
5959
cargo install --git https://github.com/paritytech/try-runtime-cli --locked
6060

6161
try-runtime-upgrade-testnet:
62-
cargo build --release --features try-runtime
62+
cargo build --release --features try-runtime,testnet
6363
RUST_BACKTRACE=1 RUST_LOG=info try-runtime --runtime target/release/wbuild/torus-runtime/torus_runtime.compact.compressed.wasm on-runtime-upgrade --blocktime 8000 live --uri wss://api.testnet.torus.network
6464

6565
try-runtime-upgrade-mainnet:

pallets/torus0/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ runtime-benchmarks = [
2525
"pallet-torus0-api/runtime-benchmarks",
2626
]
2727
try-runtime = ["polkadot-sdk/try-runtime", "pallet-torus0-api/try-runtime"]
28-
28+
testnet = []
2929

3030
[dependencies]
3131
codec = { workspace = true, features = ["derive"] }

pallets/torus0/api/src/lib.rs

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,10 @@ pub trait Torus0Api<AccountId, Balance> {
6262
/// This might have to increase in the future, but is a good enough default value.
6363
/// If it ends up being formalized, the length can be described as a u8.
6464
pub const MAX_NAMESPACE_PATH_LENGTH: usize = 256;
65-
/// Number of total bytes a segment can contain.
66-
pub const MAX_SEGMENT_LENGTH: usize = 64;
67-
/// Max number of segments in a path.
65+
/// Number of total bytes a segment can contain. 63 plus dot.
66+
pub const MAX_SEGMENT_LENGTH: usize = 63;
67+
/// Max number of segments in a path. In the common prefix case, an agent will have
68+
/// up to 8 levels of depth to use, 2 being allocated to the agent prefix notation.
6869
pub const MAX_NAMESPACE_SEGMENTS: usize = 10;
6970

7071
pub const NAMESPACE_SEPARATOR: u8 = b'.';
@@ -103,19 +104,22 @@ impl NamespacePath {
103104
for segment in &segments {
104105
let segment = core::str::from_utf8(segment).map_err(|_| "path is invalid itf-8")?;
105106

106-
let first = segment.chars().next().ok_or("empty namespace segment")?;
107-
if !first.is_alphanumeric() {
108-
return Err("namespace segment must start with alphanumeric character");
109-
}
110-
111107
if segment.len() > MAX_SEGMENT_LENGTH {
112108
return Err("namespace segment too long");
113109
}
114110

115-
if segment
116-
.chars()
117-
.any(|c| !c.is_alphanumeric() && c != '-' && c != '_' && c != '+' && c != '=')
118-
{
111+
let first = segment.chars().next().ok_or("empty namespace segment")?;
112+
let last = segment.chars().last().ok_or("empty namespace segment")?;
113+
if !first.is_ascii_alphanumeric() || !last.is_ascii_alphanumeric() {
114+
return Err("namespace segment must start and end with alphanumeric characters");
115+
}
116+
117+
if segment.chars().any(|c| {
118+
!(c.is_ascii_digit() || c.is_ascii_alphabetic() && c.is_ascii_lowercase())
119+
&& c != '-'
120+
&& c != '_'
121+
&& c != '+'
122+
}) {
119123
return Err("invalid character in namespace segment");
120124
}
121125
}
@@ -224,17 +228,23 @@ mod tests {
224228
#[test]
225229
fn namespace_creation_validates_paths() {
226230
assert!(NamespacePath::new_agent(b"agent.alice").is_ok());
227-
assert!(NamespacePath::new_agent("agent.alice.tørûs".as_bytes()).is_ok());
228-
assert!(NamespacePath::new_agent(b"agent.alice_2.memory-1.key=val+1").is_ok());
231+
assert!(NamespacePath::new_agent(b"agent.alice_2.memory-1.func+1").is_ok());
232+
233+
assert!(NamespacePath::new_agent(format!("agent.alice.{:0<63}", 1).as_bytes()).is_ok());
234+
assert!(NamespacePath::new_agent(format!("agent.alice.{:0<64}", 1).as_bytes()).is_err());
229235

230236
assert!(NamespacePath::new_agent(b"").is_err());
231237
assert!(NamespacePath::new_agent(b"agent").is_err());
232238
assert!(NamespacePath::new_agent(b".agent").is_err());
233239
assert!(NamespacePath::new_agent(b"agent.").is_err());
240+
assert!(NamespacePath::new_agent(b"agent.Alice").is_err());
234241
assert!(NamespacePath::new_agent(b"agent..alice").is_err());
235242
assert!(NamespacePath::new_agent(b"agent.-alice").is_err());
243+
assert!(NamespacePath::new_agent(b"agent.alice-").is_err());
244+
assert!(NamespacePath::new_agent(b"agent.-alice-").is_err());
236245
assert!(NamespacePath::new_agent(b"agent.alice!").is_err());
237246
assert!(NamespacePath::new_agent(b"agent.alice memory").is_err());
247+
assert!(NamespacePath::new_agent("agent.alice.tørûs".as_bytes()).is_err());
238248
}
239249

240250
#[test]

pallets/torus0/src/lib.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -374,11 +374,6 @@ pub mod pallet {
374374
Error::<T>::NamespacesFrozen
375375
);
376376

377-
ensure!(
378-
Agents::<T>::contains_key(&owner),
379-
Error::<T>::AgentDoesNotExist
380-
);
381-
382377
let namespace_path =
383378
NamespacePath::new_agent(&path).map_err(|_| Error::<T>::InvalidNamespacePath)?;
384379

pallets/torus0/src/migrations.rs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,28 +139,51 @@ pub mod v5 {
139139
info!("created root agent namespace");
140140

141141
for (id, agent) in Agents::<T>::iter() {
142+
let old_name = agent.name.clone();
142143
let Ok(agent_name) = core::str::from_utf8(&agent.name) else {
143144
error!("agent name is not utf-8: {:?}", agent.name);
144145
continue;
145146
};
146147

148+
let agent_name = if cfg!(feature = "testnet") {
149+
agent_name.to_ascii_lowercase().replace(' ', "-")
150+
} else {
151+
agent_name.into()
152+
};
153+
154+
let Ok(bounded_name) = agent_name.as_bytes().to_vec().try_into() else {
155+
error!("cannot lower case agent {agent_name:?}");
156+
continue;
157+
};
158+
147159
let path: polkadot_sdk::sp_std::vec::Vec<_> =
148-
[NAMESPACE_AGENT_PREFIX, &agent.name].concat();
160+
[NAMESPACE_AGENT_PREFIX, agent_name.as_bytes()].concat();
149161
let path = match NamespacePath::new_agent(&path) {
150162
Ok(path) => path,
151163
Err(err) => {
152-
error!("cannot create path for agent {agent_name:?}: {err:?}");
164+
error!(
165+
"cannot create path for agent {agent_name:?} ({:?}): {err:?}",
166+
core::str::from_utf8(&path)
167+
);
153168
continue;
154169
}
155170
};
156171

172+
Agents::<T>::mutate_extant(id.clone(), |agent| {
173+
agent.name = bounded_name;
174+
});
175+
157176
#[allow(deprecated)]
158177
if let Err(err) = crate::namespace::create_namespace0::<T>(
159-
NamespaceOwnership::Account(id),
178+
NamespaceOwnership::Account(id.clone()),
160179
path.clone(),
161180
false,
162181
) {
163182
error!("cannot create namespace for agent {agent_name:?}: {err:?}");
183+
184+
Agents::<T>::mutate_extant(id.clone(), |agent| {
185+
agent.name = old_name;
186+
});
164187
} else {
165188
info!("created namespace entry for agent {agent_name:?}: {path:?}");
166189
}

pallets/torus0/src/namespace.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,16 @@ pub(crate) fn create_namespace0<T: Config>(
208208
Error::<T>::NamespaceAlreadyExists
209209
);
210210

211+
if let NamespaceOwnership::Account(owner) = &owner {
212+
let path_agent = path
213+
.segments()
214+
.nth(1)
215+
.ok_or(Error::<T>::InvalidNamespacePath)?;
216+
let agent = Agents::<T>::get(owner).ok_or(Error::<T>::AgentDoesNotExist)?;
217+
218+
ensure!(path_agent == *agent.name, Error::<T>::InvalidNamespacePath);
219+
}
220+
211221
let missing_paths = find_missing_paths::<T>(&owner, &path);
212222

213223
if charge {

runtime/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ try-runtime = [
5656
# Frontier
5757
"fp-self-contained/try-runtime",
5858
]
59-
testnet = ["pallet-faucet/testnet"]
59+
testnet = ["pallet-torus0/testnet", "pallet-faucet/testnet"]
6060

6161
[dependencies]
6262
serde_json = { workspace = true, features = ["alloc"] }

0 commit comments

Comments
 (0)