1use std::fmt::{self, Display, Formatter};
2use std::path::{Path, PathBuf};
3
4use miden_protocol::Word;
5use miden_protocol::account::{AccountId, AccountIdPrefix, StorageMapKey, StorageSlotName};
6use miden_protocol::batch::BatchId;
7use miden_protocol::block::BlockNumber;
8use miden_protocol::note::{NoteId, Nullifier};
9use miden_protocol::transaction::TransactionId;
10use tracing::Value;
11
12const BOOLEAN_FIELD_NAMES: &[&str] = &[
13 "account.updated",
14 "funding_service.remote_prover",
15 "note.erased",
16 "note.id_resolved",
17 "panic",
18 "request.include_mmr_proof",
19 "request.include_proof",
20 "rpc.authentication.configured",
21];
22
23const NUMBER_FIELD_NAMES: &[&str] = &[
24 "account.id.length",
25 "account.index",
26 "asset.amount",
27 "asset.balance",
28 "asset.reserve",
29 "batch.expiration_height",
30 "batch.expires_at",
31 "batch.reference_block.number",
32 "batch.size",
33 "block.from",
34 "block.number",
35 "block.protocol.version",
36 "block.size",
37 "block.timestamp",
38 "block_range.from",
39 "block_range.to",
40 "counter.failures.consecutive",
41 "counter.latency.timeout_ms",
42 "counter.value.expected",
43 "counter.value.observed",
44 "counter.value.target",
45 "current_client_block_height",
46 "cutoff_block",
47 "db.account_state_forest.size",
48 "db.account_tree.size",
49 "db.block_store.size",
50 "db.nullifier_tree.size",
51 "db.sqlite.connection_pool_size",
52 "db.sqlite.size",
53 "db.sqlite.wal.size",
54 "dice_roll",
55 "failure_rate",
56 "fee.verification_base_fee",
57 "funding_service.max_amount",
58 "funding_service.max_notes_per_tx",
59 "funding_service.tx_expiration_delta",
60 "inputs_size",
61 "mempool.accounts",
62 "mempool.batches.proposed",
63 "mempool.batches.proven",
64 "mempool.nullifiers",
65 "mempool.output_notes",
66 "mempool.transactions.unbatched",
67 "mempool.transactions.uncommitted",
68 "note.committed",
69 "note.count",
70 "note.tag",
71 "ntx_builder.max_cycles",
72 "ntx_builder.tx_expiration_delta",
73 "port",
74 "pow.hash",
75 "pow.nonce",
76 "pow.target",
77 "pow.target.leading_zero_bits",
78 "prefix_len",
79 "proof_size",
80 "prover.capacity",
81 "prover.port",
82 "prover.proof_type.raw",
83 "reference_block.number",
84 "retry.attempt",
85 "retry.delay_ms",
86 "shutdown.grace_period_ms",
87 "snapshot.block_num",
88 "snapshot.lifetime_ms",
89 "snapshot.superseded_for_ms",
90 "snapshots.live",
91 "subscription.idle_ms",
92 "subscription.stall_timeout_ms",
93 "sync.block_gap",
94 "sync.ready_threshold",
95 "sync.upstream_block",
96 "timeout.ms",
97 "tip.number",
98 "tip.stale_duration_secs",
99 "transaction.expiration_delta",
100 "transaction.expires_at",
101 "transaction.reference_block.number",
102 "transaction.submitted_at",
103 "worker.status.raw",
104 "workers.active",
105 "workers.capacity",
106];
107
108const STRING_FIELD_NAMES: &[&str] = &[
109 "account.id",
110 "account.storage.kind",
111 "account.storage.map.entry.operation",
112 "account.storage.operation",
113 "admin.listen",
114 "asset.symbol",
115 "batch.interval",
116 "block.interval",
117 "dependency.endpoint",
118 "dependency.name",
119 "funding_service.listen",
120 "funding_service.poll_interval",
121 "genesis.source",
122 "genesis.source.kind",
123 "grpc.timeout",
124 "http.timeout",
125 "internal.listen",
126 "mempool.removal.reason",
127 "network_monitor.listen",
128 "node.role",
129 "note.execution_cycles",
130 "ntx_builder.endpoint",
131 "ntx_builder.idle_timeout",
132 "ntx_builder.listen",
133 "operation.name",
134 "path",
135 "pow.challenge.prefix",
136 "prover",
137 "prover.kind",
138 "prover.timeout",
139 "request.kind",
140 "rpc.endpoint",
141 "rpc.listen",
142 "rpc.timeout",
143 "sequencer.endpoint",
144 "service.name",
145 "service.readiness.reason",
146 "service.version",
147 "shutdown.signal",
148 "sync.block_source.endpoint",
149 "task.name",
150 "transaction.id",
151 "transaction.input_notes",
152 "transaction.output_notes",
153 "tx_prover.endpoint",
154 "tx_prover.timeout",
155 "validator.admin_listen",
156 "validator.endpoints",
157 "validator.listen",
158 "validator.signer",
159 "worker.name",
160];
161
162pub trait RecordAttribute {
170 const FIELD_NAMES: &'static [&'static str];
172
173 const PLURALIZE_FIELD_NAMES: bool = false;
175
176 fn record_attribute(&self) -> impl Value + '_;
178}
179
180#[doc(hidden)]
185pub const fn field_name_allowed(field_names: &[&str], field_name: &str, pluralize: bool) -> bool {
186 let mut index = 0;
187 while index < field_names.len() {
188 let allowed = if pluralize {
189 str_eq_with_s_suffix(field_names[index], field_name)
190 } else {
191 str_eq(field_names[index], field_name)
192 };
193 if allowed {
194 return true;
195 }
196 index += 1;
197 }
198 false
199}
200
201const fn str_eq_with_s_suffix(singular: &str, plural: &str) -> bool {
202 let singular = singular.as_bytes();
203 let plural = plural.as_bytes();
204 if plural.len() != singular.len() + 1 || plural[singular.len()] != b's' {
205 return false;
206 }
207
208 let mut index = 0;
209 while index < singular.len() {
210 if singular[index] != plural[index] {
211 return false;
212 }
213 index += 1;
214 }
215 true
216}
217
218const fn str_eq(left: &str, right: &str) -> bool {
219 let left = left.as_bytes();
220 let right = right.as_bytes();
221 if left.len() != right.len() {
222 return false;
223 }
224
225 let mut index = 0;
226 while index < left.len() {
227 if left[index] != right[index] {
228 return false;
229 }
230 index += 1;
231 }
232 true
233}
234
235#[doc(hidden)]
240pub fn record_attribute<T: RecordAttribute + ?Sized>(value: &T) -> impl Value + '_ {
241 value.record_attribute()
242}
243
244macro_rules! impl_scalar_attribute {
245 ($field_names:expr; $($ty:ty),* $(,)?) => {
246 $(
247 impl RecordAttribute for $ty {
248 const FIELD_NAMES: &'static [&'static str] = $field_names;
249
250 fn record_attribute(&self) -> impl Value + '_ {
251 *self
252 }
253 }
254 )*
255 };
256}
257
258impl_scalar_attribute!(BOOLEAN_FIELD_NAMES; bool);
259impl_scalar_attribute!(
260 NUMBER_FIELD_NAMES;
261 f32,
262 f64,
263 i8,
264 i16,
265 i32,
266 i64,
267 i128,
268 isize,
269 u8,
270 u16,
271 u64,
272 u128,
273 usize,
274);
275impl_scalar_attribute!(NUMBER_FIELD_NAMES; u32);
276
277impl RecordAttribute for str {
278 const FIELD_NAMES: &'static [&'static str] = STRING_FIELD_NAMES;
279
280 fn record_attribute(&self) -> impl Value + '_ {
281 self
282 }
283}
284
285impl RecordAttribute for String {
286 const FIELD_NAMES: &'static [&'static str] = <str as RecordAttribute>::FIELD_NAMES;
287
288 fn record_attribute(&self) -> impl Value + '_ {
289 self.as_str()
290 }
291}
292
293impl<T: RecordAttribute + ?Sized> RecordAttribute for &T {
294 const FIELD_NAMES: &'static [&'static str] = T::FIELD_NAMES;
295 const PLURALIZE_FIELD_NAMES: bool = T::PLURALIZE_FIELD_NAMES;
296
297 fn record_attribute(&self) -> impl Value + '_ {
298 (*self).record_attribute()
299 }
300}
301
302impl<T: RecordAttribute> RecordAttribute for Option<T> {
303 const FIELD_NAMES: &'static [&'static str] = T::FIELD_NAMES;
304 const PLURALIZE_FIELD_NAMES: bool = T::PLURALIZE_FIELD_NAMES;
305
306 fn record_attribute(&self) -> impl Value + '_ {
307 self.as_ref().map(RecordAttribute::record_attribute)
308 }
309}
310
311impl RecordAttribute for Path {
312 const FIELD_NAMES: &'static [&'static str] =
313 &["account.file", "data.directory", "genesis.file", "path"];
314
315 fn record_attribute(&self) -> impl Value + '_ {
316 tracing::field::display(self.display())
317 }
318}
319
320impl RecordAttribute for PathBuf {
321 const FIELD_NAMES: &'static [&'static str] = <Path as RecordAttribute>::FIELD_NAMES;
322
323 fn record_attribute(&self) -> impl Value + '_ {
324 self.as_path().record_attribute()
325 }
326}
327
328impl RecordAttribute for BlockNumber {
329 const FIELD_NAMES: &'static [&'static str] = &[
330 "batch.expiration_height",
331 "batch.expires_at",
332 "batch.reference_block.number",
333 "block.from",
334 "block.number",
335 "block_range.from",
336 "block_range.to",
337 "cutoff_block",
338 "current_client_block_height",
339 "reference_block.number",
340 "snapshot.block_num",
341 "sync.upstream_block",
342 "tip.number",
343 "transaction.expires_at",
344 "transaction.reference_block.number",
345 "transaction.submitted_at",
346 ];
347
348 fn record_attribute(&self) -> impl Value + '_ {
349 self.as_u64()
350 }
351}
352
353macro_rules! impl_display_attribute {
354 ($ty:ty, $field_names:expr $(,)?) => {
355 impl RecordAttribute for $ty {
356 const FIELD_NAMES: &'static [&'static str] = $field_names;
357
358 fn record_attribute(&self) -> impl Value + '_ {
359 tracing::field::display(self)
360 }
361 }
362 };
363}
364
365impl_display_attribute!(
366 AccountId,
367 &[
368 "account.id",
369 "asset.faucet_id",
370 "counter.account.id.new",
371 "counter.account.id.old",
372 "note.sender",
373 "wallet.account.id.new",
374 "wallet.account.id.old",
375 ],
376);
377impl_display_attribute!(AccountIdPrefix, &["account.id.network_prefix"]);
378impl_display_attribute!(StorageMapKey, &["account.storage.map.key"]);
379impl_display_attribute!(StorageSlotName, &["account.storage.slot"]);
380impl_display_attribute!(BatchId, &["batch.id", "block.batch.id"]);
381impl_display_attribute!(NoteId, &["note.id"]);
382impl_display_attribute!(Nullifier, &["note.nullifier"]);
383impl_display_attribute!(TransactionId, &["block.transaction.id", "transaction.id"]);
384impl_display_attribute!(
385 Word,
386 &[
387 "account.final_state.commitment",
388 "account.initial_state.commitment",
389 "account.storage.value",
390 "batch.reference_block.commitment",
391 "block.commitment",
392 "block.commitments.account",
393 "block.commitments.chain",
394 "block.commitments.kernel",
395 "block.commitments.note",
396 "block.commitments.nullifier",
397 "block.commitments.transaction",
398 "block.prev_block_commitment",
399 "block.sub_commitment",
400 "genesis.commitment",
401 "script.root",
402 "transaction.reference_block.commitment",
403 ],
404);
405
406struct AttributeList<'a, T>(&'a [T]);
411
412impl<T: Display> Display for AttributeList<'_, T> {
413 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
414 let mut values = self.0.iter();
415 let Some(first) = values.next() else {
416 return f.write_str("None");
417 };
418
419 write!(f, "[{first}")?;
420 for value in values {
421 write!(f, ", {value}")?;
422 }
423 f.write_str("]")
424 }
425}
426
427impl<T: Display + RecordAttribute> RecordAttribute for [T] {
428 const FIELD_NAMES: &'static [&'static str] = T::FIELD_NAMES;
429 const PLURALIZE_FIELD_NAMES: bool = true;
430
431 fn record_attribute(&self) -> impl Value + '_ {
432 tracing::field::display(AttributeList(self))
433 }
434}
435
436impl<T: Display + RecordAttribute, const N: usize> RecordAttribute for [T; N] {
437 const FIELD_NAMES: &'static [&'static str] = T::FIELD_NAMES;
438 const PLURALIZE_FIELD_NAMES: bool = true;
439
440 fn record_attribute(&self) -> impl Value + '_ {
441 self.as_slice().record_attribute()
442 }
443}
444
445impl<T: Display + RecordAttribute> RecordAttribute for Vec<T> {
446 const FIELD_NAMES: &'static [&'static str] = T::FIELD_NAMES;
447 const PLURALIZE_FIELD_NAMES: bool = true;
448
449 fn record_attribute(&self) -> impl Value + '_ {
450 self.as_slice().record_attribute()
451 }
452}
453
454#[cfg(test)]
455mod tests {
456 use miden_protocol::account::AccountId;
457
458 use super::{AttributeList, RecordAttribute, field_name_allowed};
459
460 #[test]
461 fn lists_use_the_canonical_format() {
462 assert_eq!(AttributeList::<u32>(&[]).to_string(), "None");
463 assert_eq!(AttributeList(&[1, 2, 3]).to_string(), "[1, 2, 3]");
464 }
465
466 #[test]
467 fn references_are_approved_when_the_referenced_type_is_approved() {
468 fn assert_record_attribute(_: &impl RecordAttribute) {}
469
470 let value = "attribute";
471 assert_record_attribute(&value);
472 assert_record_attribute(&&value);
473 assert_record_attribute(&Some(value));
474 assert_record_attribute(&None::<&str>);
475 }
476
477 #[test]
478 fn field_names_are_specific_to_the_attribute_type() {
479 assert!(field_name_allowed(
480 AccountId::FIELD_NAMES,
481 "account.id",
482 AccountId::PLURALIZE_FIELD_NAMES,
483 ));
484 assert!(!field_name_allowed(
485 AccountId::FIELD_NAMES,
486 "account.ids",
487 AccountId::PLURALIZE_FIELD_NAMES,
488 ));
489 assert!(field_name_allowed(
490 <[AccountId]>::FIELD_NAMES,
491 "account.ids",
492 <[AccountId]>::PLURALIZE_FIELD_NAMES,
493 ));
494 assert!(!field_name_allowed(
495 <[AccountId]>::FIELD_NAMES,
496 "account.id",
497 <[AccountId]>::PLURALIZE_FIELD_NAMES,
498 ));
499 }
500}