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