1use anyhow::anyhow;
7use consensus_core::CommitIndex;
8use std::fmt;
9use sui_types::{
10 base_types::EpochId,
11 digests::{
12 CheckpointContentsDigest, CheckpointDigest, TransactionDigest, TransactionEffectsDigest,
13 },
14 messages_checkpoint::CheckpointSequenceNumber,
15};
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub enum VfsPath {
19 Root,
20 Epochs,
21 Epoch(EpochId),
22 EpochFirstCheckpoint(EpochId),
23 EpochLastCheckpoint(EpochId),
24 EpochCommittee(EpochId),
25 EpochCheckpoints(EpochId),
26 EpochCheckpointBySeq(EpochId, CheckpointSequenceNumber),
27 EpochCheckpointByDigest(EpochId, CheckpointDigest),
28 CheckpointsRoot,
29 CheckpointsSeqRoot,
30 CheckpointsBySeq(CheckpointSequenceNumber),
33 CheckpointSeqSummary(CheckpointSequenceNumber),
34 CheckpointSeqContents(CheckpointSequenceNumber),
35 CheckpointSeqContentsShort(CheckpointSequenceNumber),
36 CheckpointsDigestRoot,
37 CheckpointsByDigest(CheckpointDigest),
38 CheckpointDigestSummary(CheckpointDigest),
39 CheckpointDigestContents(CheckpointDigest),
40 CheckpointDigestContentsShort(CheckpointDigest),
41 CheckpointContentsRoot,
42 CheckpointContentsEntry(CheckpointContentsDigest),
43 TransactionsRoot,
44 TransactionEntry(TransactionDigest),
46 TransactionEffectsEntry(TransactionDigest, TransactionEffectsDigest),
48 ConsensusRoot,
49 ConsensusLatest,
51 ConsensusCommitsRoot,
52 ConsensusCommitDir(CommitIndex),
54 ConsensusCommitSummary(CommitIndex),
55}
56
57impl VfsPath {
58 pub fn is_dir(&self) -> bool {
59 matches!(
60 self,
61 VfsPath::Root
62 | VfsPath::Epochs
63 | VfsPath::Epoch(_)
64 | VfsPath::EpochCheckpoints(_)
65 | VfsPath::CheckpointsRoot
66 | VfsPath::CheckpointsSeqRoot
67 | VfsPath::CheckpointsBySeq(_)
68 | VfsPath::CheckpointsDigestRoot
69 | VfsPath::CheckpointsByDigest(_)
70 | VfsPath::CheckpointContentsRoot
71 | VfsPath::TransactionsRoot
72 | VfsPath::ConsensusRoot
73 | VfsPath::ConsensusCommitsRoot
74 | VfsPath::ConsensusCommitDir(_)
75 )
76 }
77
78 pub fn is_ls_cursor(&self) -> bool {
81 matches!(
82 self,
83 VfsPath::CheckpointsBySeq(_)
84 | VfsPath::CheckpointsByDigest(_)
85 | VfsPath::EpochCheckpointBySeq(_, _)
86 | VfsPath::CheckpointContentsEntry(_)
87 | VfsPath::TransactionEntry(_)
88 | VfsPath::ConsensusCommitDir(_)
89 )
90 }
91
92 pub fn parent(&self) -> Option<VfsPath> {
94 match self {
95 VfsPath::Root => None,
96 VfsPath::Epochs
97 | VfsPath::CheckpointsRoot
98 | VfsPath::CheckpointContentsRoot
99 | VfsPath::TransactionsRoot
100 | VfsPath::ConsensusRoot => Some(VfsPath::Root),
101 VfsPath::Epoch(_) => Some(VfsPath::Epochs),
102 VfsPath::EpochFirstCheckpoint(e)
103 | VfsPath::EpochLastCheckpoint(e)
104 | VfsPath::EpochCommittee(e)
105 | VfsPath::EpochCheckpoints(e) => Some(VfsPath::Epoch(*e)),
106 VfsPath::EpochCheckpointBySeq(e, _) | VfsPath::EpochCheckpointByDigest(e, _) => {
107 Some(VfsPath::EpochCheckpoints(*e))
108 }
109 VfsPath::CheckpointsSeqRoot | VfsPath::CheckpointsDigestRoot => {
110 Some(VfsPath::CheckpointsRoot)
111 }
112 VfsPath::CheckpointsBySeq(_) => Some(VfsPath::CheckpointsSeqRoot),
113 VfsPath::CheckpointSeqSummary(s)
114 | VfsPath::CheckpointSeqContents(s)
115 | VfsPath::CheckpointSeqContentsShort(s) => Some(VfsPath::CheckpointsBySeq(*s)),
116 VfsPath::CheckpointsByDigest(_) => Some(VfsPath::CheckpointsDigestRoot),
117 VfsPath::CheckpointDigestSummary(d)
118 | VfsPath::CheckpointDigestContents(d)
119 | VfsPath::CheckpointDigestContentsShort(d) => Some(VfsPath::CheckpointsByDigest(*d)),
120 VfsPath::CheckpointContentsEntry(_) => Some(VfsPath::CheckpointContentsRoot),
121 VfsPath::TransactionEntry(_) | VfsPath::TransactionEffectsEntry(_, _) => {
122 Some(VfsPath::TransactionsRoot)
123 }
124 VfsPath::ConsensusLatest => Some(VfsPath::ConsensusRoot),
125 VfsPath::ConsensusCommitsRoot => Some(VfsPath::ConsensusRoot),
126 VfsPath::ConsensusCommitDir(_) => Some(VfsPath::ConsensusCommitsRoot),
127 VfsPath::ConsensusCommitSummary(i) => Some(VfsPath::ConsensusCommitDir(*i)),
128 }
129 }
130}
131
132impl fmt::Display for VfsPath {
133 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134 match self {
135 VfsPath::Root => write!(f, "/"),
136 VfsPath::Epochs => write!(f, "/epochs"),
137 VfsPath::Epoch(e) => write!(f, "/epochs/{e}"),
138 VfsPath::EpochFirstCheckpoint(e) => write!(f, "/epochs/{e}/first-checkpoint"),
139 VfsPath::EpochLastCheckpoint(e) => write!(f, "/epochs/{e}/last-checkpoint"),
140 VfsPath::EpochCommittee(e) => write!(f, "/epochs/{e}/committee"),
141 VfsPath::EpochCheckpoints(e) => write!(f, "/epochs/{e}/checkpoints"),
142 VfsPath::EpochCheckpointBySeq(e, s) => write!(f, "/epochs/{e}/checkpoints/{s}"),
143 VfsPath::EpochCheckpointByDigest(e, d) => write!(f, "/epochs/{e}/checkpoints/{d}"),
144 VfsPath::CheckpointsRoot => write!(f, "/checkpoints"),
145 VfsPath::CheckpointsSeqRoot => write!(f, "/checkpoints/seq"),
146 VfsPath::CheckpointsBySeq(s) => write!(f, "/checkpoints/seq/{s}"),
147 VfsPath::CheckpointSeqSummary(s) => write!(f, "/checkpoints/seq/{s}/summary"),
148 VfsPath::CheckpointSeqContents(s) => write!(f, "/checkpoints/seq/{s}/contents"),
149 VfsPath::CheckpointSeqContentsShort(s) => {
150 write!(f, "/checkpoints/seq/{s}/contents-short")
151 }
152 VfsPath::CheckpointsDigestRoot => write!(f, "/checkpoints/digest"),
153 VfsPath::CheckpointsByDigest(d) => write!(f, "/checkpoints/digest/{d}"),
154 VfsPath::CheckpointDigestSummary(d) => write!(f, "/checkpoints/digest/{d}/summary"),
155 VfsPath::CheckpointDigestContents(d) => write!(f, "/checkpoints/digest/{d}/contents"),
156 VfsPath::CheckpointDigestContentsShort(d) => {
157 write!(f, "/checkpoints/digest/{d}/contents-short")
158 }
159 VfsPath::CheckpointContentsRoot => write!(f, "/checkpoint-contents"),
160 VfsPath::CheckpointContentsEntry(d) => write!(f, "/checkpoint-contents/{d}"),
161 VfsPath::TransactionsRoot => write!(f, "/transactions"),
162 VfsPath::TransactionEntry(d) => write!(f, "/transactions/{d}"),
163 VfsPath::TransactionEffectsEntry(tx, fx) => {
164 write!(f, "/transactions/{tx}.fx-{fx}")
165 }
166 VfsPath::ConsensusRoot => write!(f, "/consensus"),
167 VfsPath::ConsensusLatest => write!(f, "/consensus/latest"),
168 VfsPath::ConsensusCommitsRoot => write!(f, "/consensus/commits"),
169 VfsPath::ConsensusCommitDir(i) => write!(f, "/consensus/commits/{i}"),
170 VfsPath::ConsensusCommitSummary(i) => write!(f, "/consensus/commits/{i}/summary"),
171 }
172 }
173}
174
175pub fn parse_path(s: &str) -> anyhow::Result<VfsPath> {
176 let parts: Vec<&str> = s
177 .trim_start_matches('/')
178 .split('/')
179 .filter(|p| !p.is_empty())
180 .collect();
181
182 let v = match parts.as_slice() {
183 [] => VfsPath::Root,
184 ["epochs"] => VfsPath::Epochs,
185 ["epochs", e] => VfsPath::Epoch(e.parse().map_err(|_| anyhow!("invalid epoch: '{e}'"))?),
186 ["epochs", e, "first-checkpoint"] => {
187 VfsPath::EpochFirstCheckpoint(e.parse().map_err(|_| anyhow!("invalid epoch: '{e}'"))?)
188 }
189 ["epochs", e, "last-checkpoint"] => {
190 VfsPath::EpochLastCheckpoint(e.parse().map_err(|_| anyhow!("invalid epoch: '{e}'"))?)
191 }
192 ["epochs", e, "committee"] => {
193 VfsPath::EpochCommittee(e.parse().map_err(|_| anyhow!("invalid epoch: '{e}'"))?)
194 }
195 ["epochs", e, "checkpoints"] => {
196 VfsPath::EpochCheckpoints(e.parse().map_err(|_| anyhow!("invalid epoch: '{e}'"))?)
197 }
198 ["epochs", e, "checkpoints", ref_str] => {
199 let epoch = e.parse().map_err(|_| anyhow!("invalid epoch: '{e}'"))?;
200 if let Ok(seq) = ref_str.parse::<CheckpointSequenceNumber>() {
201 VfsPath::EpochCheckpointBySeq(epoch, seq)
202 } else {
203 let digest: CheckpointDigest = ref_str
204 .parse()
205 .map_err(|_| anyhow!("invalid checkpoint ref: '{ref_str}'"))?;
206 VfsPath::EpochCheckpointByDigest(epoch, digest)
207 }
208 }
209 ["checkpoints"] => VfsPath::CheckpointsRoot,
210 ["checkpoints", "seq"] => VfsPath::CheckpointsSeqRoot,
211 ["checkpoints", "seq", s] => VfsPath::CheckpointsBySeq(
212 s.parse()
213 .map_err(|_| anyhow!("invalid sequence number: '{s}'"))?,
214 ),
215 ["checkpoints", "seq", s, "summary"] => VfsPath::CheckpointSeqSummary(
216 s.parse()
217 .map_err(|_| anyhow!("invalid sequence number: '{s}'"))?,
218 ),
219 ["checkpoints", "seq", s, "contents"] => VfsPath::CheckpointSeqContents(
220 s.parse()
221 .map_err(|_| anyhow!("invalid sequence number: '{s}'"))?,
222 ),
223 ["checkpoints", "seq", s, "contents-short"] => VfsPath::CheckpointSeqContentsShort(
224 s.parse()
225 .map_err(|_| anyhow!("invalid sequence number: '{s}'"))?,
226 ),
227 ["checkpoints", "digest"] => VfsPath::CheckpointsDigestRoot,
228 ["checkpoints", "digest", d] => VfsPath::CheckpointsByDigest(
229 d.parse()
230 .map_err(|_| anyhow!("invalid checkpoint digest: '{d}'"))?,
231 ),
232 ["checkpoints", "digest", d, "summary"] => VfsPath::CheckpointDigestSummary(
233 d.parse()
234 .map_err(|_| anyhow!("invalid checkpoint digest: '{d}'"))?,
235 ),
236 ["checkpoints", "digest", d, "contents"] => VfsPath::CheckpointDigestContents(
237 d.parse()
238 .map_err(|_| anyhow!("invalid checkpoint digest: '{d}'"))?,
239 ),
240 ["checkpoints", "digest", d, "contents-short"] => VfsPath::CheckpointDigestContentsShort(
241 d.parse()
242 .map_err(|_| anyhow!("invalid checkpoint digest: '{d}'"))?,
243 ),
244 ["checkpoint-contents"] => VfsPath::CheckpointContentsRoot,
245 ["checkpoint-contents", d] => VfsPath::CheckpointContentsEntry(
246 d.parse()
247 .map_err(|_| anyhow!("invalid contents digest: '{d}'"))?,
248 ),
249 ["transactions"] => VfsPath::TransactionsRoot,
250 ["transactions", seg] => parse_transaction_seg(seg)?,
251 ["consensus"] => VfsPath::ConsensusRoot,
252 ["consensus", "latest"] => VfsPath::ConsensusLatest,
253 ["consensus", "commits"] => VfsPath::ConsensusCommitsRoot,
254 ["consensus", "commits", i] => VfsPath::ConsensusCommitDir(
255 i.parse()
256 .map_err(|_| anyhow!("invalid commit index: '{i}'"))?,
257 ),
258 ["consensus", "commits", i, "summary"] => VfsPath::ConsensusCommitSummary(
259 i.parse()
260 .map_err(|_| anyhow!("invalid commit index: '{i}'"))?,
261 ),
262 _ => return Err(anyhow!("unknown path: '{s}'")),
263 };
264 Ok(v)
265}
266
267fn parse_transaction_seg(seg: &str) -> anyhow::Result<VfsPath> {
268 if let Some((tx_str, fx_str)) = seg.split_once(".fx-") {
269 let tx: TransactionDigest = tx_str
270 .parse()
271 .map_err(|_| anyhow!("invalid transaction digest: '{tx_str}'"))?;
272 let fx: TransactionEffectsDigest = fx_str
273 .parse()
274 .map_err(|_| anyhow!("invalid effects digest: '{fx_str}'"))?;
275 Ok(VfsPath::TransactionEffectsEntry(tx, fx))
276 } else {
277 let tx: TransactionDigest = seg
278 .parse()
279 .map_err(|_| anyhow!("invalid transaction digest: '{seg}'"))?;
280 Ok(VfsPath::TransactionEntry(tx))
281 }
282}
283
284pub fn resolve_path(cwd: &VfsPath, path: &str) -> anyhow::Result<VfsPath> {
286 if path.starts_with('/') {
287 return parse_path(path);
288 }
289 let cwd_str = cwd.to_string();
290 let mut parts: Vec<&str> = cwd_str
291 .trim_start_matches('/')
292 .split('/')
293 .filter(|p| !p.is_empty())
294 .collect();
295 for component in path.split('/') {
296 match component {
297 "" | "." => {}
298 ".." => {
299 parts.pop();
300 }
301 c => parts.push(c),
302 }
303 }
304 let absolute = format!("/{}", parts.join("/"));
305 parse_path(&absolute)
306}