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