Oregami
Repositories/oxedyne/ore

oxedyne/ore/cli/tests/relay.rs

72.6 KiB, 42 runs

created by r2848102244:134, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! `ore sync <url>`: a repository and a relay, over a socket.
2//!
3//! Every test here starts a real `ore-relay` process on a loopback port and
4//! drives the real `ore` binary against it, so what is exercised is the wire, the
5//! signed request, the access list and the store, and not a shortcut around any
6//! of them.
7//!
8//! # The case the whole rung exists for
9//!
10//! Two replicas that are never awake at the same time. A pushes on one day and
11//! goes away; B, which has never met A and never will, pulls on the next and ends
12//! holding A's history, A's files and A's key binding. No filesystem is shared
13//! and no session spans the two. That is
14//! [`a_relay_converges_two_replicas_that_never_meet`], and it is the thing
15//! `ore sync <path>` cannot do.
16//!
17//! # And the case that keeps the relay honest
18//!
19//! The relay stores what it is given and verifies none of it, so a relay that
20//! has been tampered with serves a forgery quite happily. It gets refused at the
21//! client, by signature, by name --
22//! [`a_tampered_relay_cannot_alter_an_operation`]. That is the whole of "never an
23//! authority" made testable: the relay can withhold, and it cannot lie.
24
25mod support;
26
27use support::{
28 counted,
29 forge_payload,
30 key_in,
31 ore,
32 write,
33 Ran,
34 Scratch,
35};
36
37use oxedyne_fe2o3_core::prelude::*;
38
39use std::io::{
40 BufRead,
41 BufReader,
42 Read,
43 Write as IoWrite,
44};
45use std::net::TcpStream;
46use std::path::{
47 Path,
48 PathBuf,
49};
50use std::process::{
51 Child,
52 Command,
53 Stdio,
54};
55
56
57/// Returns the `ore-relay` binary, which sits beside the `ore` one cargo built.
58fn relay_binary()
59 -> Outcome<PathBuf>
60{
61 let ore = PathBuf::from(env!("CARGO_BIN_EXE_ore"));
62 let dir = match ore.parent() {
63 Some(d) => d,
64 None => return Err(err!(
65 "The ore binary {:?} has no directory.", ore; Test, Missing)),
66 };
67 let path = dir.join("ore-relay");
68 if !path.is_file() {
69 return Err(err!(
70 "{:?} is not there. Run `cargo build --workspace` first: `cargo test` \
71 compiles the relay's own test target and does not put its binary here.", path;
72 Test, Missing));
73 }
74 // AND IT IS THE ONE THE SOURCES SAY IT IS. `cargo test --workspace` does not
75 // refresh this file -- it builds `ore_relay`'s own test target and leaves the
76 // binary as whatever last wrote it -- so a change to `relay/src/` is not in
77 // the relay these tests drive until somebody builds. On 2026-08-22 that ran a
78 // whole suite against a relay built from a deliberately broken tree half an
79 // hour earlier, and one test passed for that reason and no other. The comment
80 // this replaces asserted the opposite, in as many words.
81 let built = res!(res!(std::fs::metadata(&path)).modified());
82 let mut newest: Option<(std::time::SystemTime, PathBuf)> = None;
83 // From the manifest and not from the binary: a target directory is often
84 // somewhere else entirely, and walking up from the artefact finds a cache
85 // rather than the tree, which is a guard that quietly never fires.
86 let src = match PathBuf::from(env!("CARGO_MANIFEST_DIR")).parent() {
87 Some(root) => root.join("relay").join("src"),
88 None => return Err(err!(
89 "The relay sources could not be found beside {:?}.",
90 env!("CARGO_MANIFEST_DIR"); Test, Missing)),
91 };
92 if !src.is_dir() {
93 return Err(err!(
94 "{:?} is not there, so nothing checks that the relay these tests drive \
95 is the one in the tree.", src; Test, Missing));
96 }
97 let mut stack = vec![src];
98 while let Some(at) = stack.pop() {
99 if !at.is_dir() {
100 continue;
101 }
102 for entry in res!(std::fs::read_dir(&at)) {
103 let entry = res!(entry);
104 let found = entry.path();
105 if found.is_dir() {
106 stack.push(found);
107 } else if found.extension().and_then(|e| e.to_str()) == Some("rs") {
108 let when = res!(res!(entry.metadata()).modified());
109 if newest.as_ref().map(|(w, _)| when > *w) != Some(false) {
110 newest = Some((when, found));
111 }
112 }
113 }
114 }
115 if let Some((when, which)) = newest {
116 if when > built {
117 return Err(err!(
118 "{:?} was built before {:?} was last written, so these tests would \
119 drive a relay that is not the one in the tree. Run `cargo build \
120 --workspace` and try again.", path, which;
121 Test, Mismatch));
122 }
123 }
124 Ok(path)
125}
126
127/// A relay process listening on a port of its own, stopped however the test
128/// ends.
129struct Relay {
130 /// The process.
131 child: Child,
132 /// The port it bound, which it chose and then said.
133 port: u16,
134 /// Where its repositories live.
135 data: PathBuf,
136}
137
138impl Relay {
139
140 /// Starts a relay over a data directory, on a port the operating system
141 /// picks.
142 ///
143 /// The port is read back from the relay's own first line rather than guessed
144 /// at, so two tests running at once cannot land on one port.
145 fn start(data: &Path)
146 -> Outcome<Self>
147 {
148 Self::start_with(data, &[])
149 }
150
151 /// Starts a relay with further arguments of the caller's choosing.
152 fn start_with(data: &Path, extra: &[&str])
153 -> Outcome<Self>
154 {
155 let mut child = match Command::new(res!(relay_binary()))
156 .arg("serve")
157 .arg(fmt!("{}", data.display()))
158 .arg("--port").arg("0")
159 .args(extra)
160 .stdout(Stdio::piped())
161 .stderr(Stdio::piped())
162 .spawn()
163 {
164 Ok(c) => c,
165 Err(e) => return Err(err!(e,
166 "The relay could not be started over {:?}.", data; Test, IO)),
167 };
168 let out = match child.stdout.take() {
169 Some(o) => o,
170 None => return Err(err!("The relay was started with no output."; Test, Missing)),
171 };
172 let mut line = String::new();
173 if let Err(e) = BufReader::new(out).read_line(&mut line) {
174 return Err(err!(e, "The relay said nothing about where it is listening.";
175 Test, IO));
176 }
177 // "relay listening on 127.0.0.1:44321, holding ..."
178 let port = match line.split(':').nth(1).and_then(|rest| {
179 rest.split(',').next().and_then(|n| n.trim().parse::<u16>().ok())
180 }) {
181 Some(p) => p,
182 None => return Err(err!(
183 "The relay's first line names no port: {:?}", line; Test, Mismatch)),
184 };
185 Ok(Self { child, port, data: data.to_path_buf() })
186 }
187
188 /// Starts a relay that will not take more than `bytes` in one request body.
189 ///
190 /// What a reverse proxy in front of a relay decides, and what the deployment
191 /// this exists for sets to eight mebibytes. Small here so an operation over it
192 /// costs a test kilobytes rather than megabytes; the boundary is the same
193 /// boundary.
194 fn start_capped(data: &Path, bytes: usize)
195 -> Outcome<Self>
196 {
197 Self::start_with(data, &["--post-bytes", &fmt!("{}", bytes)])
198 }
199
200 /// Posts a body with no credential at all, and returns the whole answer.
201 fn unsigned_post(&self, path: &str, body: &[u8])
202 -> Outcome<String>
203 {
204 let mut stream = match TcpStream::connect(("127.0.0.1", self.port)) {
205 Ok(s) => s,
206 Err(e) => return Err(err!(e, "The relay would not take a connection."; Test, IO)),
207 };
208 let head = fmt!(
209 "POST {} HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: close\r\n\
210 Content-Type: application/octet-stream\r\nContent-Length: {}\r\n\r\n",
211 path, body.len(),
212 );
213 res!(stream.write_all(head.as_bytes()));
214 res!(stream.write_all(body));
215 let mut said = String::new();
216 res!(stream.read_to_string(&mut said));
217 Ok(said)
218 }
219
220 /// Starts a relay that will not put more than `bytes` in one reply.
221 ///
222 /// The default is six mebibytes, and pushing that much through a test to reach
223 /// the boundary would cost minutes; the boundary is the same boundary at any
224 /// size.
225 fn start_bounded(data: &Path, bytes: usize)
226 -> Outcome<Self>
227 {
228 Self::start_with(data, &["--reply-bytes", &fmt!("{}", bytes)])
229 }
230
231 /// Returns the URL of one hosted repository.
232 fn url(&self, account: &str, name: &str) -> String {
233 fmt!("http://127.0.0.1:{}/{}/{}", self.port, account, name)
234 }
235
236 /// Returns the path of a hosted repository's first segment.
237 fn segment(&self, account: &str, name: &str) -> PathBuf {
238 self.data.join(account).join(name).join("log").join("000000.seg")
239 }
240
241 /// Returns every byte of a hosted repository's log, segments run together.
242 ///
243 /// What is on the relay's disk, asked of the disk. The relay's own API would
244 /// answer the same question and is exactly what a test of what the relay holds
245 /// must not ask, since it is the thing under test.
246 fn stored(&self, account: &str, name: &str)
247 -> Outcome<Vec<u8>>
248 {
249 let dir = self.data.join(account).join(name).join("log");
250 let mut paths: Vec<PathBuf> = Vec::new();
251 for entry in res!(std::fs::read_dir(&dir)) {
252 paths.push(res!(entry).path());
253 }
254 paths.sort();
255 let mut out = Vec::new();
256 for path in paths {
257 out.extend(res!(std::fs::read(&path)));
258 }
259 Ok(out)
260 }
261
262 /// Fetches a path with no credential at all, the way a stranger would.
263 fn unsigned_get(&self, path: &str)
264 -> Outcome<String>
265 {
266 let mut stream = match TcpStream::connect(("127.0.0.1", self.port)) {
267 Ok(s) => s,
268 Err(e) => return Err(err!(e, "The relay would not take a connection."; Test, IO)),
269 };
270 let request = fmt!(
271 "GET {} HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: close\r\n\
272 Content-Length: 0\r\n\r\n", path,
273 );
274 res!(stream.write_all(request.as_bytes()));
275 let mut said = String::new();
276 res!(stream.read_to_string(&mut said));
277 Ok(said)
278 }
279}
280
281impl Drop for Relay {
282 fn drop(&mut self) {
283 let _ = self.child.kill();
284 let _ = self.child.wait();
285 }
286}
287
288
289/// Runs `ore-relay` once and fails the test if it did not succeed.
290fn relay_cmd(args: &[&str])
291 -> Outcome<String>
292{
293 let out = match Command::new(res!(relay_binary())).args(args).output() {
294 Ok(o) => o,
295 Err(e) => return Err(err!(e,
296 "`ore-relay {}` could not be run.", args.join(" "); Test, IO)),
297 };
298 if !out.status.success() {
299 return Err(err!(
300 "`ore-relay {}` failed: {}{}", args.join(" "),
301 String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr);
302 Test, IO));
303 }
304 Ok(fmt!("{}", String::from_utf8_lossy(&out.stdout)))
305}
306
307
308/// Makes a signed repository and returns where it is and what its public key is.
309fn replica(scratch: &Scratch, name: &str)
310 -> Outcome<(PathBuf, String)>
311{
312 let dir = res!(scratch.sub(name));
313 let said = res!(ore(&dir, &["init"]));
314 let key = res!(key_in(res!(said.good("init"))));
315 Ok((dir, key))
316}
317
318/// Runs `ore sync <url>` and returns what it said.
319fn sync(dir: &Path, url: &str)
320 -> Outcome<Ran>
321{
322 ore(dir, &["sync", url])
323}
324
325/// Runs `ore sync <url>` and fails the test if it did not succeed.
326fn synced(dir: &Path, url: &str)
327 -> Outcome<String>
328{
329 let out = res!(sync(dir, url));
330 Ok(fmt!("{}", res!(out.good("sync <url>"))))
331}
332
333/// Runs `ore sync <url> --pull-only` in a directory and returns what it said.
334fn pull_only(dir: &Path, url: &str)
335 -> Outcome<Ran>
336{
337 ore(dir, &["sync", url, "--pull-only"])
338}
339
340/// Runs `ore sync <url> --dry-run` in a directory and returns what it said.
341fn dry_run(dir: &Path, url: &str)
342 -> Outcome<Ran>
343{
344 ore(dir, &["sync", url, "--dry-run"])
345}
346
347/// What `ore log` says a repository holds: how many operations, and the
348/// frontier.
349///
350/// The frontier is the part that matters when two replicas are compared. A count
351/// can match by coincidence; a frontier names the operations themselves, and two
352/// repositories printing the same one hold the same history.
353fn history(dir: &Path)
354 -> Outcome<(usize, String)>
355{
356 let out = res!(ore(dir, &["log"]));
357 let text = fmt!("{}", res!(out.good("log")));
358 let mut count: Option<usize> = None;
359 let mut frontier: Option<String> = None;
360 for line in text.lines() {
361 if let Some(rest) = line.strip_prefix("frontier ") {
362 frontier = Some(fmt!("{}", rest.trim()));
363 continue;
364 }
365 if !line.contains(" in ") || !line.contains(" on replica ") {
366 continue;
367 }
368 let first = match line.split_whitespace().next() {
369 Some(f) => f,
370 None => continue,
371 };
372 count = match first.parse::<usize>() {
373 Ok(n) => Some(n),
374 Err(_) => continue,
375 };
376 }
377 match (count, frontier) {
378 (Some(n), Some(f)) => Ok((n, f)),
379 _ => Err(err!(
380 "`ore log` in {:?} said neither how many operations nor which frontier: {}",
381 dir, text;
382 Test, Missing)),
383 }
384}
385
386
387/// The two halves of the traffic the summary reports: bytes up, then bytes down.
388///
389/// Two numbers and never their sum. The defect they exist for is invisible in
390/// one: a loose frontier walk hands a peer a log that peer already holds, which
391/// is upload and nothing else, and a clone that took 87 MB down was offering
392/// several hundred back up while it did so.
393fn traffic(said: &str)
394 -> Outcome<(usize, usize)>
395{
396 for line in said.lines() {
397 if !line.contains("this relay publishes") {
398 continue;
399 }
400 // "<up> bytes up, <down> down, largest request body <n> against the <m> ..."
401 let words: Vec<&str> = line.split_whitespace().collect();
402 let read = |at: usize| -> Option<usize> {
403 words.get(at).and_then(|w| w.parse::<usize>().ok())
404 };
405 return match (read(0), read(3)) {
406 (Some(up), Some(down)) => Ok((up, down)),
407 _ => Err(err!(
408 "The traffic line names no two numbers: {:?}", line; Test, Mismatch)),
409 };
410 }
411 Err(err!(
412 "The output says nothing about what crossed: {}", said; Test, Missing))
413}
414
415
416/// A pushes, goes away, and B -- which has never met A -- pulls the whole
417/// history, the files and the key that signed them.
418///
419/// This is the service the rung exists to provide, and the one a filesystem
420/// transport cannot: no directory is shared between the two working copies, and
421/// no session spans them.
422#[test]
423fn a_relay_converges_two_replicas_that_never_meet() -> Outcome<()> {
424 let scratch = res!(Scratch::new("relay_apart"));
425 let data = res!(scratch.sub("data"));
426 let (a, key_a) = res!(replica(&scratch, "a"));
427 let (b, key_b) = res!(replica(&scratch, "b"));
428 let data_arg = fmt!("{}", data.display());
429 res!(relay_cmd(&["create", "oxedyne/ore", "--owner", &key_a, "--data", &data_arg]));
430 res!(relay_cmd(&["grant", "oxedyne/ore", &key_b, "push", "--data", &data_arg]));
431 let relay = res!(Relay::start(&data));
432 let url = relay.url("oxedyne", "ore");
433
434 // Monday: A writes and pushes.
435 res!(write(&a, "f.txt", b"alpha\nbeta\ngamma\n"));
436 res!(write(&a, "notes.md", b"shared notes\n"));
437 res!(res!(ore(&a, &["mark", "base"])).good("mark"));
438 let said = res!(synced(&a, &url));
439 assert!(said.contains("sent 5 operations the relay did not hold"),
440 "the whole history goes up: {}", said);
441 assert!(said.contains("received 0 operations"), "and nothing comes back: {}", said);
442 // One session, and the requests are counted separately from it. Until
443 // 2026-08-20 this line said "2 round trips" and meant two turns of the state
444 // machine, while a push could go out as a dozen HTTP requests.
445 assert!(said.contains("over 1 session"), "in one session: {}", said);
446 assert!(said.contains(" request"), "and the requests are counted: {}", said);
447
448 // Tuesday: B, which has never met A, pulls.
449 let said = res!(synced(&b, &url));
450 assert!(said.contains("received 5 operations this repository did not hold"),
451 "the whole history comes down: {}", said);
452 assert_eq!(res!(std::fs::read(b.join("f.txt"))), b"alpha\nbeta\ngamma\n".to_vec());
453 assert_eq!(res!(std::fs::read(b.join("notes.md"))), b"shared notes\n".to_vec());
454 assert_eq!(res!(history(&a)), res!(history(&b)),
455 "the two replicas hold different histories");
456
457 // A's key crossed with the operations, so B can say who wrote them rather
458 // than merely that somebody did.
459 assert!(said.contains("1 learned now"), "B learned A's binding: {}", said);
460 let listing = fmt!("{}", res!(res!(ore(&b, &["log"])).good("log")));
461 assert!(listing.contains("5 signed and verified, 0 signed by an unknown key"),
462 "every operation is attributable at B: {}", listing);
463
464 // Neither working copy is left behind its own log, so there is no marker to
465 // leave: the relay holds no working copy for a marker to protect.
466 assert!(!a.join(".ore").join("pending").is_file(), "a marker was left at A");
467 assert!(!b.join(".ore").join("pending").is_file(), "a marker was left at B");
468
469 // A second sync either way has nothing to carry.
470 let said = res!(synced(&b, &url));
471 assert!(said.contains("nothing to exchange"), "the second visit is empty: {}", said);
472 Ok(())
473}
474
475/// Two replicas that edited the same line converge through the relay, and both
476/// arbitrate it the same way and say so in the same words.
477///
478/// Two concurrent replacements of one line are one overlap group. The member
479/// highest in op order prevails and the other yields, its insertion buried whole,
480/// so each working copy holds one writer's line rather than both writers'
481/// fragments; and the arbitration is a function of the operation set, so the two
482/// replicas reach it independently and render the same bytes.
483#[test]
484fn a_divergence_converges_through_the_relay() -> Outcome<()> {
485 let scratch = res!(Scratch::new("relay_diverge"));
486 let data = res!(scratch.sub("data"));
487 let (a, key_a) = res!(replica(&scratch, "a"));
488 let (b, key_b) = res!(replica(&scratch, "b"));
489 let data_arg = fmt!("{}", data.display());
490 res!(relay_cmd(&["create", "oxedyne/ore", "--owner", &key_a, "--data", &data_arg]));
491 res!(relay_cmd(&["grant", "oxedyne/ore", &key_b, "push", "--data", &data_arg]));
492 let relay = res!(Relay::start(&data));
493 let url = relay.url("oxedyne", "ore");
494
495 res!(write(&a, "f.txt", b"alpha\nbeta\ngamma\n"));
496 res!(res!(ore(&a, &["mark", "base"])).good("mark"));
497 res!(synced(&a, &url));
498 res!(synced(&b, &url));
499
500 // Each writes over the same middle line, knowing nothing of the other. That
501 // last part is the genuine concurrency.
502 res!(write(&a, "f.txt", b"alpha\nBETA from A\ngamma\n"));
503 res!(write(&a, "only-a.txt", b"a line only A wrote\n"));
504 res!(write(&b, "f.txt", b"alpha\nBETA from B\ngamma\n"));
505 res!(write(&b, "only-b.txt", b"a line only B wrote\n"));
506
507 // Three visits, none of them at the same time as another.
508 res!(synced(&a, &url));
509 res!(synced(&b, &url));
510 res!(synced(&a, &url));
511
512 let here = res!(history(&a));
513 assert_eq!(res!(history(&b)), here, "the two replicas hold different histories");
514
515 let merged = res!(std::fs::read(a.join("f.txt")));
516 let shown = fmt!("{}", String::from_utf8_lossy(&merged));
517 assert!(shown == "alpha\nBETA from A\ngamma\n" || shown == "alpha\nBETA from B\ngamma\n",
518 "the contended line is one writer's, whole: {:?}", shown);
519 assert_eq!(res!(std::fs::read(b.join("f.txt"))), merged,
520 "and the other working copy renders the same bytes");
521 // Neither writer's uncontended file is touched by any of it.
522 assert_eq!(res!(std::fs::read(a.join("only-b.txt"))), b"a line only B wrote\n".to_vec());
523 assert_eq!(res!(std::fs::read(b.join("only-a.txt"))), b"a line only A wrote\n".to_vec());
524
525 // The concurrent edit is flagged, and flagged identically, at both ends: the
526 // raw overlap, and what the renderer then did about it.
527 let mine = fmt!("{}", res!(res!(ore(&a, &["flags"])).good("flags")));
528 let theirs = fmt!("{}", res!(res!(ore(&b, &["flags"])).good("flags")));
529 assert_eq!(mine, theirs, "the two replicas flag different things");
530 assert!(mine.contains("overlap"), "the raw fact is reported: {}", mine);
531 assert!(mine.contains("yielded") && mine.contains("prevailed"),
532 "and so is the arbitration: {}", mine);
533 assert!(mine.contains("2 flags in all"), "and nothing else is: {}", mine);
534 assert!(mine.contains("f.txt"), "and which file it was in: {}", mine);
535
536 // The buried version is spilled beside each working copy, identically, so a
537 // reviewer with no forge at either end has `diff` and needs nothing else.
538 let one = res!(spilled(&a));
539 assert_eq!(one, res!(spilled(&b)), "the two spills differ");
540 assert_eq!(one.len(), 1, "one version yielded, so one is spilled");
541 // What is spilled is the whole of what the yielding operation wrote, which is
542 // the hunk the diff found rather than the line it sits in: the newline was
543 // never contended and is still the base's.
544 let held = fmt!("{}", String::from_utf8_lossy(&one[0]));
545 assert!(held == "BETA from A" || held == "BETA from B",
546 "the spill holds the buried author's whole insertion: {:?}", held);
547 assert!(!shown.contains(held.trim_end_matches('\n')),
548 "which is the line the file does not hold: {:?} against {:?}", held, shown);
549 Ok(())
550}
551
552/// The buried versions a repository has spilled, in name order.
553fn spilled(root: &Path)
554 -> Outcome<Vec<Vec<u8>>>
555{
556 let dir = root.join(".ore").join("collisions");
557 if !dir.is_dir() {
558 return Ok(Vec::new());
559 }
560 let mut names: Vec<std::path::PathBuf> = Vec::new();
561 for entry in res!(std::fs::read_dir(&dir)) {
562 names.push(res!(entry).path());
563 }
564 names.sort();
565 let mut out: Vec<Vec<u8>> = Vec::new();
566 for at in names {
567 out.push(res!(std::fs::read(at)));
568 }
569 Ok(out)
570}
571
572/// A relay cannot alter an operation, because the client checks the signature
573/// and the relay cannot produce one.
574///
575/// The forgery is the competent version: the relay's stored segment is rewritten
576/// whole, digests and all, so everything about the file is consistent afterwards
577/// except the one thing nobody but the key holder can make. Somebody who owns the
578/// relay's disk owns the digests beside the records, which is why the digest is
579/// not what this test rests on.
580#[test]
581fn a_tampered_relay_cannot_alter_an_operation() -> Outcome<()> {
582 let scratch = res!(Scratch::new("relay_tamper"));
583 let data = res!(scratch.sub("data"));
584 let (a, key_a) = res!(replica(&scratch, "a"));
585 let (c, key_c) = res!(replica(&scratch, "c"));
586 let data_arg = fmt!("{}", data.display());
587 res!(relay_cmd(&["create", "oxedyne/ore", "--owner", &key_a, "--data", &data_arg]));
588 res!(relay_cmd(&["grant", "oxedyne/ore", &key_c, "pull", "--data", &data_arg]));
589
590 let altered = {
591 let relay = res!(Relay::start(&data));
592 let url = relay.url("oxedyne", "ore");
593 res!(write(&a, "f.txt", b"alpha\nbeta\ngamma\n"));
594 res!(res!(ore(&a, &["mark", "base"])).good("mark"));
595 res!(synced(&a, &url));
596 // The relay is stopped while its disk is rewritten, which is the position
597 // somebody who owns the machine is in.
598 res!(forge_payload(&relay.segment("oxedyne", "ore"), 0))
599 };
600
601 let relay = res!(Relay::start(&data));
602 let url = relay.url("oxedyne", "ore");
603 let out = res!(sync(&c, &url));
604 assert!(!out.ok, "a client absorbed an operation the relay had altered");
605 assert!(out.err.contains("does not verify against the public key it carries"),
606 "and the reason is the signature: {}", out.err);
607 assert!(out.err.contains(&altered),
608 "and it names the operation {}: {}", altered, out.err);
609 assert!(out.err.contains(&url),
610 "and where it came from: {}", out.err);
611
612 // Nothing was absorbed: the batch is refused whole, so the client's history
613 // is exactly what it was.
614 let (count, _) = res!(history(&c));
615 assert_eq!(count, 0, "a refused batch left {} operations behind", count);
616 assert!(!c.join("f.txt").is_file(), "and wrote no files");
617 Ok(())
618}
619
620/// A key the access list does not name is refused, and so is a repository
621/// nobody made.
622#[test]
623fn a_relay_answers_only_the_keys_its_list_names() -> Outcome<()> {
624 let scratch = res!(Scratch::new("relay_acl"));
625 let data = res!(scratch.sub("data"));
626 let (a, key_a) = res!(replica(&scratch, "a"));
627 let (stranger, _) = res!(replica(&scratch, "stranger"));
628 let (reader, key_reader) = res!(replica(&scratch, "reader"));
629 let data_arg = fmt!("{}", data.display());
630 res!(relay_cmd(&["create", "oxedyne/ore", "--owner", &key_a, "--data", &data_arg]));
631 res!(relay_cmd(&["grant", "oxedyne/ore", &key_reader, "pull", "--data", &data_arg]));
632 let relay = res!(Relay::start(&data));
633 let url = relay.url("oxedyne", "ore");
634
635 res!(write(&a, "f.txt", b"alpha\n"));
636 res!(synced(&a, &url));
637
638 // A key nobody granted anything to is refused at the first thing it asks,
639 // which is the bindings, and is told it may not even read.
640 let out = res!(sync(&stranger, &url));
641 assert!(!out.ok, "a stranger was served");
642 assert!(out.err.contains("holds no pull"), "and told why: {}", out.err);
643 assert!(out.err.contains("ore-relay grant"),
644 "and how access is given: {}", out.err);
645
646 // A key granted a pull may take and may not give.
647 let said = res!(synced(&reader, &url));
648 assert!(said.contains("received "), "a pull key pulls: {}", said);
649 res!(write(&reader, "mine.txt", b"something of my own\n"));
650 let out = res!(sync(&reader, &url));
651 assert!(!out.ok, "a pull key pushed");
652 assert!(out.err.contains("holds no push"), "and told why: {}", out.err);
653 // What it wrote is still its own, in its own history: a refused push costs
654 // convergence and nothing else.
655 assert_eq!(res!(std::fs::read(reader.join("mine.txt"))), b"something of my own\n".to_vec());
656
657 // And a repository nobody made is not made by pushing to it.
658 let out = res!(sync(&a, &relay.url("oxedyne", "nothing")));
659 assert!(!out.ok, "a repository was created by pushing to it");
660 assert!(out.err.contains("does not hold oxedyne/nothing"),
661 "and told why: {}", out.err);
662 assert!(out.err.contains("ore-relay create"), "and how one is made: {}", out.err);
663 Ok(())
664}
665
666/// A relay that is not there costs the capture and nothing else.
667#[test]
668fn an_unreachable_relay_changes_nothing_but_the_capture() -> Outcome<()> {
669 let scratch = res!(Scratch::new("relay_absent"));
670 let data = res!(scratch.sub("data"));
671 let (a, key_a) = res!(replica(&scratch, "a"));
672 let data_arg = fmt!("{}", data.display());
673 res!(relay_cmd(&["create", "oxedyne/ore", "--owner", &key_a, "--data", &data_arg]));
674 // Started only to learn a port, and stopped before anything is asked of it.
675 //
676 // The port is then CHECKED to be dead rather than assumed to be. The operating
677 // system hands a freed port straight back out, and this suite starts several
678 // relays at once, so another test's relay can be listening on it by the time
679 // this one connects -- which fails here as a 403 from a relay that was never
680 // supposed to answer, on a test whose name says the relay is absent. Seen once
681 // in the whole suite and never when this test ran alone, which is what a race
682 // looks like from the outside.
683 let mut dead = fmt!("");
684 for _ in 0..8 {
685 let relay = res!(Relay::start(&data));
686 let url = relay.url("oxedyne", "ore");
687 let at = fmt!("127.0.0.1:{}", relay.port);
688 drop(relay);
689 if TcpStream::connect(&at).is_err() {
690 dead = url;
691 break;
692 }
693 }
694 if dead.is_empty() {
695 return Err(err!(
696 "No freed port stayed free long enough to point at nothing."; Test, IO));
697 }
698
699 res!(write(&a, "f.txt", b"alpha\n"));
700 let out = res!(sync(&a, &dead));
701 assert!(!out.ok, "a sync with nothing at the other end succeeded");
702 assert!(out.err.contains("could not be reached"), "and said so: {}", out.err);
703 assert!(out.err.contains(&dead), "naming the relay: {}", out.err);
704 assert!(out.err.contains("running the command again is the whole of the retry"),
705 "and what to do about it: {}", out.err);
706
707 // The capture happened, which is wanted regardless, and nothing else did.
708 assert!(out.out.contains("created f.txt"), "the capture ran first: {}", out.out);
709 let (count, _) = res!(history(&a));
710 assert!(count > 0, "the capture reached the history");
711 assert!(!a.join(".ore").join("pending").is_file(), "a marker was left behind");
712 assert_eq!(res!(std::fs::read(a.join("f.txt"))), b"alpha\n".to_vec());
713 Ok(())
714}
715
716/// A small divergence over a large shared history goes by sketch over the wire,
717/// and the sketch holds.
718///
719/// The relay sizes its own answer from the shape the client's sketch message
720/// states, so the saving runs in both directions rather than only up.
721#[test]
722fn a_small_divergence_over_a_large_history_uses_the_sketch() -> Outcome<()> {
723 let scratch = res!(Scratch::new("relay_sketch"));
724 let data = res!(scratch.sub("data"));
725 let (a, key_a) = res!(replica(&scratch, "a"));
726 let (b, key_b) = res!(replica(&scratch, "b"));
727 let data_arg = fmt!("{}", data.display());
728 res!(relay_cmd(&["create", "oxedyne/ore", "--owner", &key_a, "--data", &data_arg]));
729 res!(relay_cmd(&["grant", "oxedyne/ore", &key_b, "push", "--data", &data_arg]));
730 let relay = res!(Relay::start(&data));
731 let url = relay.url("oxedyne", "ore");
732
733 for i in 0..60 {
734 res!(write(&a, &fmt!("f{:02}.txt", i), fmt!("file number {}\n", i).as_bytes()));
735 }
736 res!(res!(ore(&a, &["mark", "base"])).good("mark"));
737 let said = res!(synced(&a, &url));
738 assert!(said.contains("sent 121 operations"),
739 "the clone carries the whole history: {}", said);
740 let said = res!(synced(&b, &url));
741 assert!(said.contains("received 121 operations"),
742 "and B takes it whole: {}", said);
743
744 // One line at each end, and the two are two operations apart.
745 res!(write(&a, "f00.txt", b"file number 0\na tail from A\n"));
746 let said = res!(synced(&a, &url));
747 assert!(said.contains("mode sketch"),
748 "a small difference over a large history is what a sketch is for: {}", said);
749 assert!(!said.contains("fell back"),
750 "and the estimate held, so no round trip was spent on the walk: {}", said);
751 res!(write(&b, "f01.txt", b"file number 1\na tail from B\n"));
752 res!(synced(&b, &url));
753 res!(synced(&a, &url));
754 assert_eq!(res!(history(&a)), res!(history(&b)));
755 Ok(())
756}
757
758/// A relay says which versions it speaks, to anybody, before anything is signed.
759///
760/// An old client then fails with a sentence naming both sides rather than a
761/// decode error part way through an exchange.
762#[test]
763fn a_relay_says_which_versions_it_speaks() -> Outcome<()> {
764 let scratch = res!(Scratch::new("relay_versions"));
765 let data = res!(scratch.sub("data"));
766 let relay = res!(Relay::start(&data));
767
768 let said = res!(relay.unsigned_get("/ore"));
769 assert!(said.contains("200"), "the version endpoint needs no credential: {}", said);
770 assert!(said.contains("\"transport\""), "it names the transport: {}", said);
771 assert!(said.contains("\"v1\""), "and its version: {}", said);
772 assert!(said.contains("\"oresyn\""), "and the message format: {}", said);
773 assert!(said.contains("\"oreseg\""), "and the segment format: {}", said);
774
775 // A path of a version this relay does not speak is refused by name rather
776 // than guessed at.
777 let said = res!(relay.unsigned_get("/ore/v2/oxedyne/ore/sync"));
778 assert!(said.contains("404"), "an unknown transport version is not served: {}", said);
779 assert!(said.contains("which versions this relay speaks"),
780 "and the answer says where to ask: {}", said);
781 Ok(())
782}
783
784/// A marker an earlier local sync left is settled before a relay sync runs, so
785/// the two transports do not tread on each other.
786#[test]
787fn a_relay_sync_settles_what_a_local_sync_left() -> Outcome<()> {
788 let scratch = res!(Scratch::new("relay_pending"));
789 let data = res!(scratch.sub("data"));
790 let (a, key_a) = res!(replica(&scratch, "a"));
791 let (b, key_b) = res!(replica(&scratch, "b"));
792 let data_arg = fmt!("{}", data.display());
793 res!(relay_cmd(&["create", "oxedyne/ore", "--owner", &key_a, "--data", &data_arg]));
794 res!(relay_cmd(&["grant", "oxedyne/ore", &key_b, "push", "--data", &data_arg]));
795 let relay = res!(Relay::start(&data));
796 let url = relay.url("oxedyne", "ore");
797
798 // A local sync writes A's working copy and leaves the marker at B.
799 res!(write(&a, "f.txt", b"alpha\nbeta\ngamma\n"));
800 res!(res!(ore(&a, &["sync", &fmt!("{}", b.display())])).good("sync <path>"));
801 assert!(b.join(".ore").join("pending").is_file(),
802 "a local sync leaves the far end a marker");
803 assert!(!b.join("f.txt").is_file(), "and does not write its files");
804
805 // The next verb at B is a relay sync, and it settles the marker first, as
806 // every verb there does.
807 let said = res!(synced(&b, &url));
808 assert!(said.contains("the working copy is now the merged state"),
809 "the marker was settled before the exchange: {}", said);
810 assert!(!b.join(".ore").join("pending").is_file(), "and cleared");
811 assert_eq!(res!(std::fs::read(b.join("f.txt"))), b"alpha\nbeta\ngamma\n".to_vec());
812
813 // And the relay took what B had, so a third replica would find it there.
814 assert!(said.contains("sent "), "B pushed what the local sync gave it: {}", said);
815 let listed = res!(relay_cmd(&["list", &data_arg]));
816 assert!(listed.contains("oxedyne/ore"), "the relay lists what it holds: {}", listed);
817 Ok(())
818}
819
820
821/// A pull grant reads for a replica that holds work of its own, but only when it
822/// offers nothing.
823///
824/// A relay asks for push over the operations it lacks of what a request offers,
825/// and refuses **the whole request** rather than its push half. So one operation
826/// of its own locks a `pull` voice out of reading altogether. That is what
827/// `--pull-only` is for: the messages go out emptied of what they would hand
828/// over, so the request is the read it always was.
829#[test]
830fn a_pull_grant_reads_for_a_replica_holding_its_own_work() -> Outcome<()> {
831 let scratch = res!(Scratch::new("relay_pull_only"));
832 let data = res!(scratch.sub("data"));
833 let (owner, key_owner) = res!(replica(&scratch, "owner"));
834 let (reader, key_reader) = res!(replica(&scratch, "reader"));
835 let data_arg = fmt!("{}", data.display());
836 res!(relay_cmd(&["create", "oxedyne/ore", "--owner", &key_owner, "--data", &data_arg]));
837 res!(relay_cmd(&["grant", "oxedyne/ore", &key_reader, "pull", "--data", &data_arg]));
838 let relay = res!(Relay::start(&data));
839 let url = relay.url("oxedyne", "ore");
840
841 res!(write(&owner, "theirs.txt", b"the owner's work\n"));
842 res!(synced(&owner, &url));
843
844 // Work of its own is all it takes: the ordinary sync now carries an operation,
845 // so the request is a write and the read goes down with it.
846 res!(write(&reader, "mine.txt", b"work of my own\n"));
847 let out = res!(sync(&reader, &url));
848 assert!(!out.ok, "a pull voice holding its own work was served an ordinary sync");
849 assert!(out.err.contains("holds no push"), "and told why: {}", out.err);
850
851 // Offering nothing, the same voice reads.
852 let said = fmt!("{}", res!(res!(pull_only(&reader, &url)).good("sync --pull-only")));
853 assert!(said.contains("offered nothing, by request"),
854 "the sync says it handed nothing over: {}", said);
855 assert!(said.contains("kept back"),
856 "and says what stayed behind, the two ends not being left agreeing: {}", said);
857 assert_eq!(res!(std::fs::read(reader.join("theirs.txt"))), b"the owner's work\n".to_vec(),
858 "the owner's work arrived");
859 assert_eq!(res!(std::fs::read(reader.join("mine.txt"))), b"work of my own\n".to_vec(),
860 "and the reader's own work is untouched");
861
862 // Nothing of the reader's reached the relay, which is the point rather than a
863 // side effect: the owner, who takes everything the relay holds, never sees it.
864 res!(synced(&owner, &url));
865 assert!(!owner.join("mine.txt").exists(),
866 "the reader's work reached the relay and came back out of it");
867 Ok(())
868}
869
870
871/// A pull grant fetches, and fetches again once the author has moved on, with no
872/// option and no ceremony.
873///
874/// The walk is loose, so a replica that cannot subtract the author's new tip
875/// offers its whole log back -- every operation of which came off this relay in
876/// the first place. Read off the offer alone that is a push, and somebody told to
877/// fetch a public repository gets exactly one successful command: the next is
878/// refused for something it was not doing, which is the first thing an outside
879/// reader meets.
880#[test]
881fn a_pull_grant_fetches_again_after_the_author_moves_on() -> Outcome<()> {
882 let scratch = res!(Scratch::new("relay_pull_again"));
883 let data = res!(scratch.sub("data"));
884 let (owner, key_owner) = res!(replica(&scratch, "owner"));
885 let (reader, key_reader) = res!(replica(&scratch, "reader"));
886 let data_arg = fmt!("{}", data.display());
887 res!(relay_cmd(&["create", "oxedyne/ore", "--owner", &key_owner, "--data", &data_arg]));
888 res!(relay_cmd(&["grant", "oxedyne/ore", &key_reader, "pull", "--data", &data_arg]));
889 let relay = res!(Relay::start(&data));
890 let url = relay.url("oxedyne", "ore");
891
892 res!(write(&owner, "readme.txt", b"the first word\n"));
893 res!(synced(&owner, &url));
894
895 // The clone. This much always worked: a replica holding nothing offers nothing.
896 let said = res!(synced(&reader, &url));
897 assert!(said.contains("received "), "the clone arrives: {}", said);
898 assert_eq!(res!(std::fs::read(reader.join("readme.txt"))), b"the first word\n".to_vec());
899
900 // The author moves on, so the reader's next visit meets a head it has never
901 // seen and offers back everything it holds -- which is the clone.
902 res!(write(&owner, "readme.txt", b"the first word\nand a second\n"));
903 res!(synced(&owner, &url));
904
905 let said = res!(synced(&reader, &url));
906 assert!(said.contains("received "), "and the next visit arrives too: {}", said);
907 assert_eq!(res!(std::fs::read(reader.join("readme.txt"))),
908 b"the first word\nand a second\n".to_vec(),
909 "what the author wrote after the clone did not reach the reader");
910 assert_eq!(res!(history(&reader)), res!(history(&owner)),
911 "the reader is not where the author is");
912
913 // And the half that must not move: an operation the relay genuinely lacks is a
914 // push, and a pull grant does not cover one.
915 res!(write(&reader, "mine.txt", b"work of my own\n"));
916 let out = res!(sync(&reader, &url));
917 assert!(!out.ok, "a pull grant handed over work the relay did not hold");
918 assert!(out.err.contains("holds no push"), "and told why: {}", out.err);
919 Ok(())
920}
921
922
923/// A rehearsal says what a sync would bring and leaves the repository as it was.
924///
925/// It has to absorb to know: what would arrive is only knowable by taking it,
926/// verifying it under the keys this end knows and placing it. So the test is not
927/// that nothing crossed, which would be a weaker claim about a slower command,
928/// but that nothing was *kept* -- the log is the length it was, the working copy
929/// holds no file that arrived, and `--arrived` afterwards still speaks of the
930/// sync before this one rather than of the rehearsal.
931#[test]
932fn a_rehearsal_says_what_would_arrive_and_keeps_none_of_it() -> Outcome<()> {
933 let scratch = res!(Scratch::new("relay_dry_run"));
934 let data = res!(scratch.sub("data"));
935 let (owner, key_owner) = res!(replica(&scratch, "owner"));
936 let (other, key_other) = res!(replica(&scratch, "other"));
937 let data_arg = fmt!("{}", data.display());
938 res!(relay_cmd(&["create", "oxedyne/ore", "--owner", &key_owner, "--data", &data_arg]));
939 res!(relay_cmd(&["grant", "oxedyne/ore", &key_other, "push", "--data", &data_arg]));
940 let relay = res!(Relay::start(&data));
941 let url = relay.url("oxedyne", "ore");
942
943 res!(write(&owner, "shared.txt", b"the beginning\n"));
944 res!(synced(&owner, &url));
945 res!(synced(&other, &url));
946
947 // The other writes something and puts it on the relay. The owner has not
948 // taken it and is about to ask what taking it would come to.
949 res!(write(&other, "theirs.txt", b"work from the other end\n"));
950 res!(synced(&other, &url));
951
952 let (held, frontier) = res!(history(&owner));
953 let said = fmt!("{}", res!(res!(dry_run(&owner, &url)).good("sync --dry-run")));
954
955 // It answers the question. How much is read out rather than written down: what
956 // the relay holds that this end does not depends on how many commands were run
957 // at the other end, each of which ends by naming the point it reached and
958 // pushing that name. The claim worth making is that the two ways the rehearsal
959 // says it agree with each other, and that the sync at the end of this test
960 // brings that many.
961 let would = res!(counted(&said, "would take "));
962 assert!(would > 0, "the rehearsal says how much would arrive: {}", said);
963 assert_eq!(res!(counted(&said, "it would bring ")), would,
964 "the rehearsal counts one way in the summary and another in the description: \
965 {}", said);
966 assert!(said.contains("theirs.txt"),
967 "naming the file that would appear: {}", said);
968 assert!(said.contains("nothing was written: this was a rehearsal"),
969 "and says plainly that it kept none of it: {}", said);
970 assert!(said.contains("offered nothing"),
971 "a rehearsal hands nothing over either: {}", said);
972
973 // And keeps none of it. The log is where it was, to the frontier.
974 assert_eq!(res!(history(&owner)), (held, frontier),
975 "the rehearsal left operations in the repository");
976 assert!(!owner.join("theirs.txt").exists(),
977 "the rehearsal wrote a file into the working copy");
978
979 // Nor did it leave a record behind it: `--arrived` still speaks of the sync
980 // that really happened, which is the first one.
981 let told = fmt!("{}", res!(res!(ore(&owner, &["log", "--arrived"])).good("log --arrived")));
982 assert!(!told.contains("theirs.txt"),
983 "the rehearsal was recorded as an arrival: {}", told);
984
985 // The real thing afterwards brings exactly what the rehearsal said it would,
986 // in name and in number.
987 let ran = res!(synced(&owner, &url));
988 assert!(ran.contains(&fmt!("received {} operation", would)),
989 "the rehearsal said {} operations would arrive and the sync brought \
990 something else: {}", would, ran);
991 assert_eq!(res!(std::fs::read(owner.join("theirs.txt"))), b"work from the other end\n".to_vec(),
992 "what the rehearsal described did not arrive when the sync ran");
993 Ok(())
994}
995
996/// The claim, end to end: two replicas exchange a private repository through a
997/// relay, both read it, and the relay's disk holds none of it.
998///
999/// This is the one test the veiled form exists for, and every assertion in it is
1000/// aimed at the relay's own bytes rather than at anything the relay says about
1001/// itself. A relay that had been quietly reading the history would pass any
1002/// question put to its API and fail the search below.
1003///
1004/// The search is for the content by name: the file names, the text in the files,
1005/// and the name of a mark. Each of those is a string a plaintext segment holds
1006/// literally -- `a_relay_converges_two_replicas_that_never_meet` walks the same
1007/// path unveiled, and the same search finds all of them there.
1008#[test]
1009fn a_veiled_repository_crosses_a_relay_that_cannot_read_it() -> Outcome<()> {
1010 let scratch = res!(Scratch::new("relay_veiled"));
1011 let data = res!(scratch.sub("data"));
1012 let (a, key_a) = res!(replica(&scratch, "a"));
1013 let (b, key_b) = res!(replica(&scratch, "b"));
1014 let data_arg = fmt!("{}", data.display());
1015 res!(relay_cmd(&["create", "oxedyne/ore", "--owner", &key_a, "--data", &data_arg]));
1016 res!(relay_cmd(&["grant", "oxedyne/ore", &key_b, "push", "--data", &data_arg]));
1017 let relay = res!(Relay::start(&data));
1018 let url = relay.url("oxedyne", "ore");
1019
1020 // A veils, and is told the key. B is given the same key by hand, which is the
1021 // whole of how a content key travels: not through the relay.
1022 let said = fmt!("{}", res!(res!(ore(&a, &["key", "--veil"])).good("key --veil")));
1023 assert!(said.contains("this repository is now veiled"), "{}", said);
1024 let content_key = match said.lines().find(|l| l.starts_with(" ")) {
1025 Some(l) => fmt!("{}", l.trim()),
1026 None => return Err(err!(
1027 "`ore key --veil` printed no key: {}", said; Test, Missing)),
1028 };
1029 let said = fmt!("{}", res!(res!(ore(&b, &["key", "--veil", &content_key]))
1030 .good("key --veil <key>")));
1031 assert!(said.contains("installed a content key"), "{}", said);
1032
1033 // A writes something worth hiding and pushes it.
1034 let secret: &[u8] = b"the merger closes on Friday at eleven\n";
1035 res!(write(&a, "board-minutes.md", secret));
1036 res!(write(&a, "figures.csv", b"quarter,revenue\nQ3,4180000\n"));
1037 res!(res!(ore(&a, &["mark", "before-the-announcement"])).good("mark"));
1038 let said = res!(synced(&a, &url));
1039 assert!(said.contains("sent "), "the history goes up: {}", said);
1040
1041 // What the relay wrote down. Nothing of the content is in it.
1042 let held = res!(relay.stored("oxedyne", "ore"));
1043 for hidden in [
1044 &secret[..],
1045 b"board-minutes.md",
1046 b"figures.csv",
1047 b"before-the-announcement",
1048 b"4180000",
1049 ] {
1050 assert!(
1051 !held.windows(hidden.len()).any(|w| w == hidden),
1052 "the relay's {} bytes on disk contain {:?}",
1053 held.len(), String::from_utf8_lossy(hidden),
1054 );
1055 }
1056 // And what is there is veiled rather than merely absent: every record of the
1057 // log is tagged kind 3. The header is eight bytes, and a record's kind is its
1058 // first, so the byte after the header says what the first record is.
1059 assert_eq!(held[6], 4, "the segment declares the current format version");
1060 assert!(held.len() > 8, "the relay wrote nothing at all");
1061 assert_eq!(held[8], 3, "the first record the relay holds is not a veiled one");
1062
1063 // B, which has never met A, pulls, and reads every byte of it.
1064 let said = res!(synced(&b, &url));
1065 assert!(said.contains("received "), "the history comes down: {}", said);
1066 assert_eq!(res!(std::fs::read(b.join("board-minutes.md"))), secret.to_vec(),
1067 "the replica holding the key did not get the content back");
1068 assert_eq!(res!(std::fs::read(b.join("figures.csv"))), b"quarter,revenue\nQ3,4180000\n".to_vec());
1069 let told = fmt!("{}", res!(res!(ore(&b, &["log"])).good("log")));
1070 assert!(told.contains("before-the-announcement"),
1071 "the mark A named did not survive the hop: {}", told);
1072 assert!(told.contains('+'),
1073 "the operations arrived unverified, so the signature did not survive the \
1074 veil: {}", told);
1075
1076 // B writes back, veiled in its turn, and A takes it.
1077 res!(write(&b, "reply.md", b"agreed, and the lawyers are told\n"));
1078 res!(res!(ore(&b, &["mark", "answered"])).good("mark"));
1079 res!(synced(&b, &url));
1080 res!(synced(&a, &url));
1081 assert_eq!(res!(std::fs::read(a.join("reply.md"))), b"agreed, and the lawyers are told\n".to_vec());
1082 assert_eq!(res!(history(&a)), res!(history(&b)),
1083 "the two replicas hold different histories");
1084
1085 // The relay still holds none of it, the second replica's work included.
1086 let held = res!(relay.stored("oxedyne", "ore"));
1087 for hidden in [
1088 &secret[..],
1089 b"agreed, and the lawyers are told",
1090 b"answered",
1091 b"reply.md",
1092 ] {
1093 assert!(
1094 !held.windows(hidden.len()).any(|w| w == hidden),
1095 "the relay's {} bytes on disk contain {:?} after the second exchange",
1096 held.len(), String::from_utf8_lossy(hidden),
1097 );
1098 }
1099 Ok(())
1100}
1101
1102/// A replica that is not given the content key is refused by name, and absorbs
1103/// nothing.
1104///
1105/// The relay serves it happily, because a relay cannot tell one client from
1106/// another and holds no key to check anything with. Everything that keeps the
1107/// repository private is at the reader's end, and this is what that end says when
1108/// it is not one of the readers.
1109#[test]
1110fn a_replica_without_the_content_key_is_refused_by_name() -> Outcome<()> {
1111 let scratch = res!(Scratch::new("relay_no_key"));
1112 let data = res!(scratch.sub("data"));
1113 let (a, key_a) = res!(replica(&scratch, "a"));
1114 let (c, key_c) = res!(replica(&scratch, "c"));
1115 let data_arg = fmt!("{}", data.display());
1116 res!(relay_cmd(&["create", "oxedyne/ore", "--owner", &key_a, "--data", &data_arg]));
1117 res!(relay_cmd(&["grant", "oxedyne/ore", &key_c, "pull", "--data", &data_arg]));
1118 let relay = res!(Relay::start(&data));
1119 let url = relay.url("oxedyne", "ore");
1120
1121 res!(res!(ore(&a, &["key", "--veil"])).good("key --veil"));
1122 res!(write(&a, "board-minutes.md", b"the merger closes on Friday\n"));
1123 res!(res!(ore(&a, &["mark", "base"])).good("mark"));
1124 res!(synced(&a, &url));
1125
1126 let out = res!(pull_only(&c, &url));
1127 assert!(!out.ok, "a replica with no content key read a veiled repository");
1128 // The whole phrase, and not the word "veiled" on its own. The engine says
1129 // that much when anything asks a veiled entry what operation it is, so an
1130 // assertion on the word alone would hold whether or not this repository ever
1131 // worked out that the key is what it is missing.
1132 assert!(out.err.contains("is veiled, and this repository holds no content key"),
1133 "the refusal does not say what is missing: {}", out.err);
1134 assert!(out.err.contains("ore key --veil"),
1135 "nor what would answer it: {}", out.err);
1136 assert!(out.err.contains("The operation r"),
1137 "nor which operation it met: {}", out.err);
1138 // Nothing was taken. The working copy is as it was, and so is the log.
1139 assert!(!c.join("board-minutes.md").exists(),
1140 "a repository that cannot read the history wrote a file out of it");
1141 Ok(())
1142}
1143
1144/// A second replica is let into a veiled repository without the content key ever
1145/// being read out loud, and the relay carries the wrap that does it.
1146///
1147/// The story end to end, in the order a person would live it. B cannot read the
1148/// repository, publishes a veil key, and is refused by name. A learns that key,
1149/// wraps the content key to it and pushes the wrap. B syncs again and reads
1150/// every byte. Nothing crossed but the relay, and the relay still holds nothing.
1151///
1152/// The assertion that carries the claim is not that B ends up reading -- it is
1153/// that the relay's own bytes hold neither the content nor the content key while
1154/// B does. A relay that had quietly kept the key would pass every other check
1155/// here.
1156#[test]
1157fn a_wrap_lets_a_second_replica_in_without_the_key_being_said_aloud() -> Outcome<()> {
1158 let scratch = res!(Scratch::new("relay_wrap"));
1159 let data = res!(scratch.sub("data"));
1160 let (a, key_a) = res!(replica(&scratch, "a"));
1161 let (b, key_b) = res!(replica(&scratch, "b"));
1162 let data_arg = fmt!("{}", data.display());
1163 res!(relay_cmd(&["create", "oxedyne/ore", "--owner", &key_a, "--data", &data_arg]));
1164 res!(relay_cmd(&["grant", "oxedyne/ore", &key_b, "push", "--data", &data_arg]));
1165 let relay = res!(Relay::start(&data));
1166 let url = relay.url("oxedyne", "ore");
1167
1168 // A veils and pushes. The content key is minted here and is never printed to
1169 // anybody: this test never runs `ore key --veil <key>`, which is the whole
1170 // point of it.
1171 let said = fmt!("{}", res!(res!(ore(&a, &["key", "--veil"])).good("key --veil")));
1172 assert!(said.contains("this repository is now veiled"), "{}", said);
1173 let content_key = match said.lines().find(|l| l.starts_with(" ")) {
1174 Some(l) => fmt!("{}", l.trim()),
1175 None => return Err(err!(
1176 "`ore key --veil` printed no key: {}", said; Test, Missing)),
1177 };
1178 let secret: &[u8] = b"the merger closes on Friday at eleven\n";
1179 res!(write(&a, "board-minutes.md", secret));
1180 res!(res!(ore(&a, &["mark", "before-the-announcement"])).good("mark"));
1181 res!(synced(&a, &url));
1182
1183 // B mints a veil key and says which replica it belongs to.
1184 let said = fmt!("{}", res!(res!(ore(&b, &["key", "--veil-key"])).good("key --veil-key")));
1185 assert!(said.contains("minted a veil key with X25519"), "{}", said);
1186 let replica_b = match said.split("for replica ").nth(1) {
1187 Some(rest) => fmt!("{}", rest.lines().next().unwrap_or("").trim()),
1188 None => return Err(err!(
1189 "`ore key --veil-key` did not say which replica: {}", said; Test, Missing)),
1190 };
1191 assert!(b.join(".ore").join("veilkey").is_file(), "no veil key was written");
1192 assert!(!b.join(".ore").join("veil").is_file(),
1193 "a veil key is not a content key, and minting one gave this replica one");
1194
1195 // B syncs, and is refused by name: the veil binding is deposited before the
1196 // operations cross, so publishing it costs nothing even though the sync then
1197 // fails on the first thing it cannot read.
1198 let out = res!(sync(&b, &url));
1199 assert!(!out.ok, "a replica with no content key read a veiled repository");
1200 assert!(out.err.contains("is veiled, and this repository holds no content key"),
1201 "the refusal does not say what is missing: {}", out.err);
1202 assert!(!b.join("board-minutes.md").exists(),
1203 "a replica that cannot read the history wrote a file out of it");
1204
1205 // A learns the veil key, and cannot wrap to a replica it has not met.
1206 let out = res!(ore(&a, &["key", "--wrap", &replica_b]));
1207 assert!(!out.ok, "A wrapped to a replica whose veil key it had never seen");
1208 assert!(out.err.contains("knows no veil key for replica"),
1209 "the refusal does not say what is missing: {}", out.err);
1210 res!(synced(&a, &url));
1211 let said = fmt!("{}", res!(res!(ore(&a, &["key", "--wrap", &replica_b]))
1212 .good("key --wrap")));
1213 assert!(said.contains(&fmt!("content key for replica {}", replica_b.trim_start_matches('r'))),
1214 "the wrap was not made for the replica that asked: {}", said);
1215 res!(synced(&a, &url));
1216
1217 // The wrap is on the relay, and it carries neither the content key nor the
1218 // content. This is the sentence the design rests on made testable: the relay
1219 // may hand the wrap to anybody, because the wrap says nothing.
1220 let carried = res!(std::fs::read(data.join("oxedyne").join("ore").join("wraps")));
1221 assert!(!carried.is_empty(), "the relay kept no wrap");
1222 for hidden in [content_key.as_bytes(), secret] {
1223 assert!(
1224 !carried.windows(hidden.len()).any(|w| w == hidden),
1225 "the relay\'s wrap file holds {:?} in clear",
1226 String::from_utf8_lossy(hidden),
1227 );
1228 }
1229
1230 // B syncs again, takes the wrap, and reads the lot.
1231 let said = res!(synced(&b, &url));
1232 assert!(said.contains("a wrap addressed to this replica was opened"),
1233 "the sync did not say it had been let in: {}", said);
1234 assert!(b.join(".ore").join("veil").is_file(),
1235 "the content key was not written where the next command will find it");
1236 assert_eq!(res!(std::fs::read(b.join("board-minutes.md"))), secret.to_vec(),
1237 "the replica that was let in did not get the content");
1238 let told = fmt!("{}", res!(res!(ore(&b, &["log"])).good("log")));
1239 assert!(told.contains("before-the-announcement"),
1240 "the mark A named did not survive the hop: {}", told);
1241 assert!(told.contains('+'),
1242 "the operations arrived unverified, so the signature did not survive the \
1243 veil: {}", told);
1244
1245 // B is a full member from here: it writes back veiled under the same key and
1246 // A reads it.
1247 res!(write(&b, "reply.md", b"agreed, and the lawyers are told\n"));
1248 res!(res!(ore(&b, &["mark", "answered"])).good("mark"));
1249 res!(synced(&b, &url));
1250 res!(synced(&a, &url));
1251 assert_eq!(res!(std::fs::read(a.join("reply.md"))),
1252 b"agreed, and the lawyers are told\n".to_vec());
1253
1254 // And the relay still holds none of it.
1255 let held = res!(relay.stored("oxedyne", "ore"));
1256 for hidden in [
1257 &secret[..],
1258 b"board-minutes.md",
1259 b"before-the-announcement",
1260 b"agreed, and the lawyers are told",
1261 content_key.as_bytes(),
1262 ] {
1263 assert!(
1264 !held.windows(hidden.len()).any(|w| w == hidden),
1265 "the relay\'s {} bytes on disk contain {:?}",
1266 held.len(), String::from_utf8_lossy(hidden),
1267 );
1268 }
1269 Ok(())
1270}
1271
1272/// A veil binding the relay has been edited to change is dropped, so nobody
1273/// wraps a content key to a key the relay put there.
1274///
1275/// The relay cannot mint a reading key any more than it can mint a signing key.
1276/// What is exercised here is a real relay whose file on disk has been altered
1277/// between one sync and the next: the binding stops holding, both ends drop it,
1278/// and the owner is told it knows no veil key for that replica rather than
1279/// quietly wrapping to the relay\'s choice. The second link on its own -- a
1280/// binding perfectly signed by a key that is not the replica\'s -- cannot be
1281/// produced through the tool at all, and is proved in `ore_store`.
1282#[test]
1283fn a_veil_binding_the_relay_has_altered_is_dropped() -> Outcome<()> {
1284 let scratch = res!(Scratch::new("relay_veil_chain"));
1285 let data = res!(scratch.sub("data"));
1286 let (a, key_a) = res!(replica(&scratch, "a"));
1287 let (b, key_b) = res!(replica(&scratch, "b"));
1288 let data_arg = fmt!("{}", data.display());
1289 res!(relay_cmd(&["create", "oxedyne/ore", "--owner", &key_a, "--data", &data_arg]));
1290 res!(relay_cmd(&["grant", "oxedyne/ore", &key_b, "push", "--data", &data_arg]));
1291 let relay = res!(Relay::start(&data));
1292 let url = relay.url("oxedyne", "ore");
1293
1294 res!(res!(ore(&a, &["key", "--veil"])).good("key --veil"));
1295 res!(write(&a, "board-minutes.md", b"the merger closes on Friday\n"));
1296 res!(res!(ore(&a, &["mark", "base"])).good("mark"));
1297 res!(synced(&a, &url));
1298
1299 let said = fmt!("{}", res!(res!(ore(&b, &["key", "--veil-key"])).good("key --veil-key")));
1300 let replica_b = match said.split("for replica ").nth(1) {
1301 Some(rest) => fmt!("{}", rest.lines().next().unwrap_or("").trim()),
1302 None => return Err(err!(
1303 "`ore key --veil-key` did not say which replica: {}", said; Test, Missing)),
1304 };
1305 // The sync fails on the first veiled operation it cannot read, and deposits
1306 // the binding on the way past.
1307 let out = res!(sync(&b, &url));
1308 assert!(!out.ok, "a replica with no content key read a veiled repository");
1309 let kept = data.join("oxedyne").join("ore").join("veils");
1310 let held = fmt!("{}", String::from_utf8_lossy(&res!(std::fs::read(&kept))));
1311 assert!(held.contains(&key_b),
1312 "the veil binding was not kept beside the signing key that vouched for it: {}",
1313 held);
1314
1315 // Altered on the relay\'s own disk: the signature stops covering what the
1316 // binding says, so it stops holding.
1317 let (_, key_c) = res!(replica(&scratch, "c"));
1318 res!(std::fs::write(&kept, held.replace(&key_b, &key_c)));
1319 res!(synced(&a, &url));
1320 let out = res!(ore(&a, &["key", "--wrap", &replica_b]));
1321 assert!(!out.ok, "a content key was wrapped to a veil key the relay had altered");
1322 assert!(out.err.contains("knows no veil key for replica"),
1323 "the refusal does not say why: {}", out.err);
1324
1325 // Put back, and the same command works, so what was refused was the alteration
1326 // and not the route.
1327 res!(std::fs::write(&kept, &held));
1328 res!(synced(&a, &url));
1329 let said = fmt!("{}", res!(res!(ore(&a, &["key", "--wrap", &replica_b]))
1330 .good("key --wrap")));
1331 assert!(said.contains("content key for replica"),
1332 "the unaltered binding was refused too: {}", said);
1333 Ok(())
1334}
1335
1336/// The relay does the whole of its job on a repository it cannot read: the
1337/// frontier walk, the sketch, and a genuine divergence converging.
1338///
1339/// Blindness is not much of a claim if it costs the service. So this is the
1340/// sketch test run veiled, with a divergence in the middle: two replicas write
1341/// concurrently over a large shared history, each visits the relay once, and both
1342/// end holding the same frontier -- while the relay's disk holds none of the
1343/// sixty file names it reconciled.
1344#[test]
1345fn a_veiled_repository_still_walks_sketches_and_converges() -> Outcome<()> {
1346 let scratch = res!(Scratch::new("relay_veiled_sketch"));
1347 let data = res!(scratch.sub("data"));
1348 let (a, key_a) = res!(replica(&scratch, "a"));
1349 let (b, key_b) = res!(replica(&scratch, "b"));
1350 let data_arg = fmt!("{}", data.display());
1351 res!(relay_cmd(&["create", "oxedyne/ore", "--owner", &key_a, "--data", &data_arg]));
1352 res!(relay_cmd(&["grant", "oxedyne/ore", &key_b, "push", "--data", &data_arg]));
1353 let relay = res!(Relay::start(&data));
1354 let url = relay.url("oxedyne", "ore");
1355
1356 let said = fmt!("{}", res!(res!(ore(&a, &["key", "--veil"])).good("key --veil")));
1357 let content_key = match said.lines().find(|l| l.starts_with(" ")) {
1358 Some(l) => fmt!("{}", l.trim()),
1359 None => return Err(err!("`ore key --veil` printed no key: {}", said; Test, Missing)),
1360 };
1361 res!(res!(ore(&b, &["key", "--veil", &content_key])).good("key --veil <key>"));
1362
1363 for i in 0..60 {
1364 res!(write(&a, &fmt!("f{:02}.txt", i), fmt!("file number {}\n", i).as_bytes()));
1365 }
1366 res!(res!(ore(&a, &["mark", "base"])).good("mark"));
1367 // The walk carries the whole history up and down, veiled.
1368 let said = res!(synced(&a, &url));
1369 assert!(said.contains("mode frontier walk"),
1370 "a clone is what the walk is for: {}", said);
1371 assert!(said.contains("sent 121 operations"),
1372 "the whole history goes up veiled: {}", said);
1373 let said = res!(synced(&b, &url));
1374 assert!(said.contains("received 121 operations"),
1375 "and comes down again: {}", said);
1376
1377 // Now a real divergence: both write before either visits.
1378 res!(write(&a, "f00.txt", b"file number 0\na tail from A\n"));
1379 res!(write(&b, "f01.txt", b"file number 1\na tail from B\n"));
1380 let said = res!(synced(&a, &url));
1381 assert!(said.contains("mode sketch"),
1382 "a small difference over a large history is what the sketch is for, and \
1383 veiling does not change the shape it is sized from: {}", said);
1384 assert!(!said.contains("fell back"),
1385 "and the estimate held, so the sketch reconciled entries the relay could \
1386 not read: {}", said);
1387 let said = res!(synced(&b, &url));
1388 assert!(said.contains("received "), "B takes A's branch: {}", said);
1389 res!(synced(&a, &url));
1390 assert_eq!(res!(history(&a)), res!(history(&b)),
1391 "the divergence did not converge through a relay that could not read it");
1392
1393 // Both working copies hold both branches.
1394 assert_eq!(res!(std::fs::read(a.join("f01.txt"))), b"file number 1\na tail from B\n".to_vec());
1395 assert_eq!(res!(std::fs::read(b.join("f00.txt"))), b"file number 0\na tail from A\n".to_vec());
1396
1397 // And the relay reconciled all of it without ever holding a file name.
1398 let held = res!(relay.stored("oxedyne", "ore"));
1399 for i in 0..60 {
1400 let name = fmt!("f{:02}.txt", i);
1401 assert!(
1402 !held.windows(name.len()).any(|w| w == name.as_bytes()),
1403 "the relay's disk holds the file name {:?}", name,
1404 );
1405 }
1406 for hidden in [&b"a tail from A"[..], b"a tail from B", b"base"] {
1407 assert!(
1408 !held.windows(hidden.len()).any(|w| w == hidden),
1409 "the relay's disk holds {:?}", String::from_utf8_lossy(hidden),
1410 );
1411 }
1412 Ok(())
1413}
1414
1415/// A clone larger than one reply arrives whole, across several sessions, and the
1416/// summary never calls a partial exchange a finished one.
1417///
1418/// The failure this guards is not a refusal. Before 2026-08-20 `serve.rs` framed
1419/// the entire reply as one body, so a 58 MB clone had to be materialised whole in
1420/// the receiving process. Proxies cap requests and not responses, so nothing in
1421/// front of the relay would ever have refused it; it dies on the receiving end,
1422/// where a phone or a small VPS has nothing to raise.
1423///
1424/// This used to go on to say the clone was held "at about six times its size --
1425/// measured at 347,532 kB for 44,541 operations", as though the framing were what
1426/// cost that. It is not: the same clone across fourteen bounded replies peaked at
1427/// 346,612 kB, under one percent lower. The multiple is the engine's per-operation
1428/// cost of holding a history, and `ore log` pays it over no network at all. The
1429/// bound is still worth testing, for the whole-body materialisation above.
1430///
1431/// The relay here is bounded to a few hundred bytes so the boundary is reached in
1432/// a test rather than in a megabyte-scale fixture. The boundary is the same one.
1433#[test]
1434fn a_clone_larger_than_one_reply_still_arrives_whole() -> Outcome<()> {
1435 let scratch = res!(Scratch::new("relay_bounded"));
1436 let data = res!(scratch.sub("data"));
1437 let (a, key_a) = res!(replica(&scratch, "a"));
1438 let (b, key_b) = res!(replica(&scratch, "b"));
1439 let data_arg = fmt!("{}", data.display());
1440 res!(relay_cmd(&["create", "oxedyne/ore", "--owner", &key_a, "--data", &data_arg]));
1441 res!(relay_cmd(&["grant", "oxedyne/ore", &key_b, "push", "--data", &data_arg]));
1442
1443 // A pushes a history of some size against a relay with ordinary bounds, so
1444 // that what is being tested is the pull and not the push.
1445 let plain = res!(Relay::start(&data));
1446 let url = plain.url("oxedyne", "ore");
1447 for i in 0..12 {
1448 res!(write(&a, &fmt!("f{}.txt", i), fmt!("{}\n", "content line\n".repeat(40)).as_bytes()));
1449 res!(res!(ore(&a, &["mark", &fmt!("m{}", i)])).good("mark"));
1450 }
1451 res!(synced(&a, &url));
1452 let (there, frontier_a) = res!(history(&a));
1453 drop(plain);
1454
1455 // The same data, served by a relay that will not answer with more than a few
1456 // hundred bytes at a time.
1457 let relay = res!(Relay::start_bounded(&data, 400));
1458 let url = relay.url("oxedyne", "ore");
1459 let said = res!(synced(&b, &url));
1460
1461 // It took more than one session, or the bound was not reached and this test
1462 // proves nothing about the thing it is named for.
1463 assert!(!said.contains("over 1 session,"),
1464 "the reply bound was never reached, so nothing here was exercised: {}", said);
1465 // And it finished anyway.
1466 assert!(!said.contains("UNFINISHED"),
1467 "the clone stopped short of the history: {}", said);
1468 // A fresh replica keeps nothing back, however many sessions it took to fill.
1469 // The count is a fact about the state the exchange began in, and this one began
1470 // empty; summed across sessions instead it reads as hundreds of thousands of
1471 // operations withheld by a repository that had none.
1472 let (c, key_c) = res!(replica(&scratch, "c"));
1473 res!(relay_cmd(&["grant", "oxedyne/ore", &key_c, "push", "--data", &data_arg]));
1474 let told = res!(pull_only(&c, &url));
1475 let told = fmt!("{}", res!(told.good("sync --pull-only")));
1476 assert!(!told.contains("kept back"),
1477 "a replica that began empty reported withholding its own work: {}", told);
1478
1479 let (here, frontier_b) = res!(history(&b));
1480 assert_eq!(here, there, "the clone holds a different number of operations");
1481 assert_eq!(frontier_b, frontier_a, "the clone holds a different history");
1482 for i in 0..12 {
1483 assert!(b.join(fmt!("f{}.txt", i)).is_file(), "f{}.txt did not arrive", i);
1484 }
1485 Ok(())
1486}
1487
1488
1489/// An operation larger than one request body crosses, and comes back byte for
1490/// byte.
1491///
1492/// **The defect this exists for, at the size a test can afford.** fe2o3's git
1493/// history holds `fe2o3_steel/steel`, a 22,153,680 byte compiled binary added in
1494/// `7b6d603` and deleted in `d842b87`. The import reads git history, so it is one
1495/// operation in the Ore log, and one operation was the smallest thing the
1496/// transport had: `split` bounded a `Send` at four mebibytes of entries and
1497/// always kept at least one entry, `groups` always kept at least one message, so
1498/// it became a 22,153,955 byte request body. Steel's `http_max_body_bytes`
1499/// defaults to 8,388,608 and the deployed forge sets no override, so the proxy
1500/// closed the connection part way through the body and the client saw `Broken
1501/// pipe`. The push only ever succeeded through an `ssh -L` tunnel to the relay's
1502/// loopback port, which is not a deployment.
1503///
1504/// Raising the limit was refused, and rightly: it carries this operation and not
1505/// the next one. The operation crosses in pieces instead.
1506///
1507/// The relay here is capped at 64 kB rather than eight mebibytes so the boundary
1508/// is reached in a test rather than in a megabyte-scale fixture, and it is
1509/// enforced at the relay rather than by a proxy, which is a better refusal than
1510/// the one the deployment gives.
1511#[test]
1512fn an_operation_larger_than_one_request_body_crosses_and_comes_back() -> Outcome<()> {
1513 let scratch = res!(Scratch::new("relay_oversize"));
1514 let data = res!(scratch.sub("data"));
1515 let (a, key_a) = res!(replica(&scratch, "a"));
1516 let (b, key_b) = res!(replica(&scratch, "b"));
1517 let data_arg = fmt!("{}", data.display());
1518 res!(relay_cmd(&["create", "oxedyne/ore", "--owner", &key_a, "--data", &data_arg]));
1519 res!(relay_cmd(&["grant", "oxedyne/ore", &key_b, "push", "--data", &data_arg]));
1520
1521 // One file whose contents are one splice, several times what the relay will
1522 // take in a request body. The bytes vary, so a compressing carrier could not
1523 // make this fit and pass by accident.
1524 let mut big = Vec::with_capacity(400_000);
1525 let mut x: u32 = 0x1234_5678;
1526 while big.len() < 400_000 {
1527 x = x.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
1528 big.extend_from_slice(&x.to_le_bytes());
1529 }
1530 res!(write(&a, "steel", &big));
1531
1532 let relay = res!(Relay::start_capped(&data, 64 << 10));
1533 let url = relay.url("oxedyne", "ore");
1534 let said = res!(synced(&a, &url));
1535 assert!(said.contains("pieces "),
1536 "the operation did not cross in pieces, so this test proves nothing: {}", said);
1537 assert!(!said.contains("UNFINISHED"), "the push stopped short: {}", said);
1538 let (there, frontier_a) = res!(history(&a));
1539
1540 // And a replica that has never met the first one clones it back.
1541 let said = res!(synced(&b, &url));
1542 assert!(!said.contains("UNFINISHED"), "the clone stopped short: {}", said);
1543 let got = match std::fs::read(b.join("steel")) {
1544 Ok(g) => g,
1545 Err(e) => return Err(err!(e,
1546 "The large file did not arrive at the second replica."; Test, IO)),
1547 };
1548 assert_eq!(got.len(), big.len(), "the file that came back is a different length");
1549 assert_eq!(got, big, "the file that came back is not the file that went");
1550
1551 let (here, frontier_b) = res!(history(&b));
1552 assert_eq!(here, there, "the clone holds a different number of operations");
1553 assert_eq!(frontier_b, frontier_a, "the clone holds a different history");
1554 Ok(())
1555}
1556
1557/// A relay refuses a body over what it published, and says both numbers.
1558///
1559/// The half that was missing until 2026-08-22. A relay published a request limit
1560/// compiled into it and enforced nothing, so a client that ignored the number met
1561/// the proxy instead -- and a proxy closes the connection part way through the
1562/// body, which reads as the relay being down rather than as a limit being
1563/// reached.
1564#[test]
1565fn a_relay_refuses_a_body_over_what_it_published() -> Outcome<()> {
1566 let scratch = res!(Scratch::new("relay_postcap"));
1567 let data = res!(scratch.sub("data"));
1568 let (_a, key_a) = res!(replica(&scratch, "a"));
1569 let data_arg = fmt!("{}", data.display());
1570 res!(relay_cmd(&["create", "oxedyne/ore", "--owner", &key_a, "--data", &data_arg]));
1571
1572 let relay = res!(Relay::start_capped(&data, 4_096));
1573 // What it says it takes.
1574 let said = res!(relay.unsigned_get("/ore"));
1575 assert!(said.contains("4096"),
1576 "the relay does not publish the limit it was given: {}", said);
1577 // And what it does about a body over it.
1578 let answer = res!(relay.unsigned_post("/ore/v1/oxedyne/ore/sync", &vec![0u8; 8_192]));
1579 assert!(answer.contains("413"), "an oversized body was not refused: {}", answer);
1580 assert!(answer.contains("8192") && answer.contains("4096"),
1581 "the refusal does not name both numbers: {}", answer);
1582 Ok(())
1583}
1584
1585
1586/// A clone offers back nothing it was given, however many sessions it takes.
1587///
1588/// **The defect this exists for.** A relay bounds its reply, so a clone larger
1589/// than the bound is a run of sessions, and a session worked out what it owed
1590/// from the relay's frontier alone. Nothing in a frontier says "you handed me
1591/// this": the relay's heads are operations the puller has not reached yet, so
1592/// they subtract nothing, and every session offered back the whole prefix the
1593/// sessions before it had just delivered.
1594///
1595/// Measured on the clone of fe2o3 of 12026-08-22 -- 35,314 operations, sixteen
1596/// sessions -- 166,224 operations were offered, every one of them already at the
1597/// relay: **717,611,365 bytes up to take 87,505,248 down**, eight to one, on a
1598/// replica that had authored nothing at all. `--pull-only` cost 1,197 bytes up
1599/// over the same clone, because it suppresses the offer rather than computing a
1600/// smaller one.
1601///
1602/// The assertion is the property and not a threshold: a clone that uploads less
1603/// than it downloads did not offer its own log back even once.
1604#[test]
1605fn a_bounded_clone_does_not_offer_back_what_it_was_given() -> Outcome<()> {
1606 let scratch = res!(Scratch::new("relay_reoffer"));
1607 let data = res!(scratch.sub("data"));
1608 let (a, key_a) = res!(replica(&scratch, "a"));
1609 let (b, key_b) = res!(replica(&scratch, "b"));
1610 let data_arg = fmt!("{}", data.display());
1611 res!(relay_cmd(&["create", "oxedyne/ore", "--owner", &key_a, "--data", &data_arg]));
1612 res!(relay_cmd(&["grant", "oxedyne/ore", &key_b, "push", "--data", &data_arg]));
1613
1614 // A history of some size, pushed against ordinary bounds so that what is under
1615 // test is the pull.
1616 let plain = res!(Relay::start(&data));
1617 let url = plain.url("oxedyne", "ore");
1618 for i in 0..12 {
1619 res!(write(&a, &fmt!("f{}.txt", i),
1620 fmt!("{}", "content line\n".repeat(40)).as_bytes()));
1621 res!(res!(ore(&a, &["mark", &fmt!("m{}", i)])).good("mark"));
1622 }
1623 res!(synced(&a, &url));
1624 let (there, frontier_a) = res!(history(&a));
1625 drop(plain);
1626
1627 // The same history, from a relay that will not answer with more than a few
1628 // hundred bytes at a time, so the clone is a run of sessions.
1629 let relay = res!(Relay::start_bounded(&data, 400));
1630 let url = relay.url("oxedyne", "ore");
1631 let said = res!(synced(&b, &url));
1632 assert!(!said.contains("over 1 session,"),
1633 "the reply bound was never reached, so nothing here was exercised: {}", said);
1634 assert!(!said.contains("UNFINISHED"),
1635 "the clone stopped short of the history: {}", said);
1636
1637 let (up, down) = res!(traffic(&said));
1638 assert!(up < down,
1639 "a clone that authored nothing sent {} bytes to take {} -- it is offering \
1640 back what it was given: {}", up, down, said);
1641
1642 // And it is a clone: the history arrived whole.
1643 let (here, frontier_b) = res!(history(&b));
1644 assert_eq!(here, there, "the clone holds a different number of operations");
1645 assert_eq!(frontier_b, frontier_a, "the clone holds a different history");
1646 Ok(())
1647}
1648
1649/// And a replica that has written of its own still hands over every one of them,
1650/// however the exchange is cut up.
1651///
1652/// The other side of the same change, and the one that matters more: what a
1653/// session remembers is subtracted from what it offers, so a belief that is too
1654/// generous does not cost bytes -- it silently fails to deliver somebody's work.
1655/// The relay is bounded here so the exchange takes several sessions, which is
1656/// exactly when the remembering happens.
1657#[test]
1658fn a_bounded_exchange_hands_over_every_operation() -> Outcome<()> {
1659 let scratch = res!(Scratch::new("relay_reoffer_both"));
1660 let data = res!(scratch.sub("data"));
1661 let (a, key_a) = res!(replica(&scratch, "a"));
1662 let (b, key_b) = res!(replica(&scratch, "b"));
1663 let data_arg = fmt!("{}", data.display());
1664 res!(relay_cmd(&["create", "oxedyne/ore", "--owner", &key_a, "--data", &data_arg]));
1665 res!(relay_cmd(&["grant", "oxedyne/ore", &key_b, "push", "--data", &data_arg]));
1666
1667 let plain = res!(Relay::start(&data));
1668 let url = plain.url("oxedyne", "ore");
1669 for i in 0..12 {
1670 res!(write(&a, &fmt!("f{}.txt", i),
1671 fmt!("{}", "content line\n".repeat(40)).as_bytes()));
1672 res!(res!(ore(&a, &["mark", &fmt!("m{}", i)])).good("mark"));
1673 }
1674 res!(synced(&a, &url));
1675 drop(plain);
1676
1677 // B has never met the relay and has written four files of its own, so it is
1678 // behind on everything and ahead on something at the same time.
1679 for i in 0..4 {
1680 res!(write(&b, &fmt!("mine{}.txt", i),
1681 fmt!("{}", "b wrote this\n".repeat(40)).as_bytes()));
1682 res!(res!(ore(&b, &["mark", &fmt!("b{}", i)])).good("mark"));
1683 }
1684 let (mine, _) = res!(history(&b));
1685
1686 let relay = res!(Relay::start_bounded(&data, 400));
1687 let url = relay.url("oxedyne", "ore");
1688 let said = res!(synced(&b, &url));
1689 assert!(!said.contains("over 1 session,"),
1690 "the reply bound was never reached, so nothing here was exercised: {}", said);
1691 assert!(!said.contains("UNFINISHED"),
1692 "the exchange stopped short: {}", said);
1693
1694 // A visits again and finds B's work waiting, whole. The relay is asked nothing
1695 // about itself: this is read off a third replica's rendered files.
1696 res!(synced(&a, &url));
1697 for i in 0..4 {
1698 let at = a.join(fmt!("mine{}.txt", i));
1699 assert!(at.is_file(), "mine{}.txt never reached the relay: {}", i, said);
1700 assert_eq!(res!(std::fs::read(&at)), fmt!("{}", "b wrote this\n".repeat(40)).into_bytes(),
1701 "mine{}.txt arrived changed", i);
1702 }
1703 let (theirs, frontier_a) = res!(history(&a));
1704 let (here, frontier_b) = res!(history(&b));
1705 assert!(theirs >= mine, "the relay lost operations B had already written");
1706 assert_eq!(frontier_a, frontier_b, "the two replicas hold different histories");
1707 assert_eq!(theirs, here, "the two replicas hold different numbers of operations");
1708 Ok(())
1709}