Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
084505d
feat(replication): stop penalising a peer for not holding a close-gro…
grumbach Aug 25, 2026
0edc96f
fix(replication): make the audit-type label available without the log…
grumbach Aug 25, 2026
ac68e59
fix(replication): keep charging a responder whose own storage read fa…
grumbach Aug 25, 2026
1fdae2a
docs(adr): renumber the file-based chunk store ADR to 0014
grumbach Aug 27, 2026
f6704fb
feat(storage): replace the LMDB chunk store with one file per chunk, …
grumbach Aug 25, 2026
aa4910d
test(e2e): make trust-penalty assertions state which policy they are …
grumbach Aug 25, 2026
107ac07
fix(storage): keep the deliberately-async key read quiet on newer clippy
grumbach Aug 25, 2026
711e36a
fix(storage): make the Windows retirement guard a configuration field
grumbach Aug 25, 2026
e82ce15
fix(storage): spawn the migration, and repair three gates the review …
grumbach Aug 25, 2026
84285fc
test(storage): prove the migration actually runs, end to end
grumbach Aug 25, 2026
391517b
fix(node): make a migration that never started impossible to miss
grumbach Aug 25, 2026
e4a51da
fix(storage): close three ways a node could destroy a chunk's last copy
grumbach Aug 25, 2026
ac24bc4
fix(storage): make the migration work on Windows instead of refusing …
grumbach Aug 25, 2026
0793463
fix(storage): close the remaining safety gaps in the LMDB migration
grumbach Aug 25, 2026
0ac6e64
fix(storage): make the repair path durable off Unix and fix the Windo…
grumbach Aug 25, 2026
742fd5d
fix(storage): turn retirement on and close the destructive-path findings
grumbach Aug 25, 2026
a9c3646
fix(storage): make retirement wait for readers instead of racing them
grumbach Aug 25, 2026
b71f413
fix(storage): do not delete the tombstone if its rename is not on disk
grumbach Aug 25, 2026
5385118
test(storage): drive the end-to-end migration from the shipped defaults
grumbach Aug 25, 2026
b0e1446
fix(storage): close the third-round findings on the destructive path
grumbach Aug 25, 2026
4c86197
fix(storage): let the node serve again before the legacy directory is…
grumbach Aug 25, 2026
c548e44
fix(storage): close the fourth-round findings, mostly on the previous…
grumbach Aug 25, 2026
1135e61
perf(storage): do not scan the legacy key set twice at startup
grumbach Aug 25, 2026
de456c7
fix(storage): put the retirement mark inside the directory it describes
grumbach Aug 25, 2026
9dbb027
fix(storage): keep every recursive delete off the startup path
grumbach Aug 25, 2026
bb48b14
fix(storage): make the retirement mark decide every deletion, and mak…
grumbach Aug 25, 2026
9e2e938
fix(storage): handle every read answer, and take the mark away last
grumbach Aug 25, 2026
34c606d
fix(storage): never delete through a link, and keep trying a stuck cl…
grumbach Aug 25, 2026
5c479b2
fix(storage): protect a chunk that has fallen out of both views
grumbach Aug 25, 2026
c8663b7
fix(storage): gate the permission-based tests to Unix
grumbach Aug 25, 2026
ab14295
fix(storage): stop a transient read error stranding a node or being a…
grumbach Aug 25, 2026
7481474
fix(storage): finish wiring the suspect state, and keep it out of the…
grumbach Aug 25, 2026
d709713
fix(storage): make a verification proof expire when the store changes
grumbach Aug 25, 2026
1d2b220
fix(storage): close the races around the health count and the legacy …
grumbach Aug 25, 2026
db30b4a
fix(storage): separate the in-flight note from what the node claims t…
grumbach Aug 25, 2026
ac00498
docs(storage): drop a link to a private item from public documentation
grumbach Aug 25, 2026
3c08358
fix(storage): make the in-flight journal hold at the boundary that ma…
grumbach Aug 25, 2026
57b6326
fix(storage): a delete must outlast both halves of a write, not one
grumbach Aug 25, 2026
7cca2b5
docs(adr): record the mechanisms adversarial review added
grumbach Aug 25, 2026
64617f9
fix(storage): let the file store answer what it is writing
grumbach Aug 25, 2026
e12fb45
fix(storage): count writes per key, and let the worker own what it pr…
grumbach Aug 25, 2026
8e67238
fix(storage): stop the two stores spending the same disk twice
grumbach Aug 25, 2026
b970c7f
docs(adr): correct a consequence the review no longer accepts
grumbach Aug 26, 2026
f1c465d
test(storage): prove the migration's claims in CI instead of assertin…
grumbach Aug 26, 2026
713e90e
test(storage): make the harnesses prove what they claim
grumbach Aug 26, 2026
1966ed9
test(storage): close the harness findings the second review raised
grumbach Aug 26, 2026
7271d57
test(storage): make the harnesses hold on CI's machines, not just thi…
grumbach Aug 26, 2026
6abdb28
test(storage): measure the end state against the file store, not the …
grumbach Aug 26, 2026
00177e7
test(storage): run the storage harnesses before the e2e suite, and of…
grumbach Aug 26, 2026
cd84974
test(storage): make the publish failpoint mean the same thing on ever…
grumbach Aug 26, 2026
82c5ddd
test(storage): prove the volume lock covers retirement, not only copying
grumbach Aug 26, 2026
0b7b461
ci: pass the feature the shared-volume harness needs, and fail loudly…
grumbach Aug 26, 2026
6c66cb3
docs(adr): record what CI now proves, and narrow the fleet gates to w…
grumbach Aug 26, 2026
0ca8c91
test(storage): close the gaps a fourth review found in the harnesses
grumbach Aug 26, 2026
2cf5d88
docs(adr): the Windows retirement position is now a mechanism, not a …
grumbach Aug 26, 2026
49ef456
fix(storage): an unreadable retirement mark is not permission to do a…
grumbach Aug 26, 2026
fcd89bd
test(storage): steady the scan comparison, tighten the memory gate, c…
grumbach Aug 26, 2026
3cc21b8
fix(storage): say which of the two is wrong when an environment canno…
grumbach Aug 26, 2026
e7568fa
fix(storage): finish the three-way answer at the two places that stil…
grumbach Aug 26, 2026
7507d10
fix(storage): ask the mark alone, so a missing answer cannot skip the…
grumbach Aug 26, 2026
82b29af
fix(storage): stop taking the mark's name as the mark, and gate on it…
grumbach Aug 26, 2026
c4a8a0a
test(storage): measure the index in a process of its own, because it …
grumbach Aug 26, 2026
d27fc6d
fix(storage): stop claiming, charging and recording things that were …
grumbach Aug 27, 2026
211ff96
fix(deploy): give the nodes on a host a lock they can all see
grumbach Aug 27, 2026
706b74e
ci(adr): fail a decision record whose number is already used on the b…
grumbach Aug 27, 2026
b0933ee
docs(adr): correct two claims the record makes that the code does not
grumbach Aug 27, 2026
90f9b54
feat(replication): penalise a peer again for not holding a close-grou…
grumbach Aug 28, 2026
f82f889
refactor(storage)!: delete the LMDB chunk store and the migration tha…
grumbach Aug 28, 2026
3464eaa
fix(storage): close what an independent review found in this removal
grumbach Aug 28, 2026
20e361a
fix(storage): a node root that does not exist yet is not a refusal
grumbach Aug 28, 2026
f73012b
fix(storage): restore two orderings the facade owned, and stop refusi…
grumbach Aug 28, 2026
96ce08c
fix(storage): order a put against a delete, and put back coverage tha…
grumbach Aug 28, 2026
96e2bff
test(storage): make three claims provable, and finish the rename's prose
grumbach Aug 28, 2026
ceedae5
docs: fix a doubled word left by the rename
grumbach Aug 28, 2026
1f5db84
fix(storage): remove two helpers the retirement path took with it
grumbach Aug 28, 2026
ea50837
test(storage): keep the test hooks out of the builds that are not tes…
grumbach Aug 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,24 @@ jobs:
version: ${{ env.FOUNDRY_VERSION }}
- name: Run unit tests
run: cargo test --lib --features test-utils
- name: Kill a node mid-write and check what survived
shell: bash
run: |
set -euo pipefail
cargo test --test chunk_store_crash_safety --features test-utils -- --test-threads=1 2>&1 | tee /tmp/crash_safety.log
grep -qE 'test result: ok\. [1-9]' /tmp/crash_safety.log \
|| { echo 'chunk_store_crash_safety ran no tests'; exit 1; }
- name: Startup scan, index memory and inode cost at scale
if: runner.os == 'Linux'
shell: bash
run: |
set -euo pipefail
cargo test --test storage_scale --features test-utils -- --nocapture --test-threads=1 2>&1 | tee /tmp/scale.log
# A target whose required features are not passed is skipped with a
# warning and a zero exit, so a harness can stop running without anyone
# noticing. This is what makes that loud.
grep -qE 'test result: ok\. [1-9]' /tmp/scale.log \
|| { echo 'scale ran no tests'; exit 1; }
- name: Run e2e tests
run: cargo test --test e2e --features test-utils -- --test-threads=1
- name: Run v12 storage-bound audit attack PoCs
Expand All @@ -59,6 +77,54 @@ jobs:
- name: Run bootstrap-stall PoC regression marker
run: cargo test --test poc_bootstrap_stall --features test-utils

filesystems:
name: Storage on ${{ matrix.fs }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
fs: [ext4, xfs, btrfs]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Install the filesystem tools
run: sudo apt-get update && sudo apt-get install -y xfsprogs btrfs-progs
- name: Make a ${{ matrix.fs }} volume and mount it
shell: bash
run: |
set -euo pipefail
# A loopback image, so these run on a filesystem of the right kind rather than
# on whatever the runner happens to give us. ext4 is what most of the fleet is
# on; XFS and btrfs are the two the design reasons about separately, btrfs
# because it has been observed reordering writes around a rename.
# 3 GiB is ample: these tests use tens of MiB.
truncate -s 3G /tmp/${{ matrix.fs }}.img
mkfs.${{ matrix.fs }} -q /tmp/${{ matrix.fs }}.img
sudo mkdir -p /mnt/antfs
sudo mount -o loop /tmp/${{ matrix.fs }}.img /mnt/antfs
sudo chown "$USER" /mnt/antfs
df -hT /mnt/antfs
# TMPDIR is what `TempDir::new` uses, so this puts every temporary store these tests
# build on the mounted filesystem rather than on the runner's root.
#
# These used to be the migration harnesses. The migration is gone, and what is left
# worth asking of a filesystem is what the store itself does on it: publish a chunk
# through a temporary and a rename, flush the directory, unlink it again, and rebuild
# an index from the names afterwards. The storage tests do all of that, and running
# them here is what keeps ext4, XFS and btrfs covered now that the harnesses that used
# to cover them have been deleted. They do not measure free space before and after;
# the harness that did was about the migration and went with it.
- name: Storage behaviour on ${{ matrix.fs }}
env:
TMPDIR: /mnt/antfs
shell: bash
run: |
set -euo pipefail
cargo test --lib --features test-utils storage:: 2>&1 | tee /tmp/storage.log
grep -qE 'test result: ok\. [1-9]' /tmp/storage.log \
|| { echo 'the storage tests ran nothing'; exit 1; }

doc:
name: Documentation
runs-on: ubuntu-latest
Expand All @@ -84,6 +150,29 @@ jobs:
- uses: Swatinem/rust-cache@v2
- name: Build release (no logging)
run: cargo build --release --no-default-features
# The crash harness drives the store through a failpoint that parks the process
# forever on an environment variable, and the store's own tests use the same one. It
# is compiled only under `test-utils`, which
# is not a default feature and is not passed by the release workflow, so a shipped
# binary does not contain it. This proves that rather than trusting it: the variable
# name is a string literal, so it survives into the binary whenever the code that
# reads it is compiled, and its absence is the absence of the failpoint.
- name: A shipped binary carries no failpoint
if: runner.os == 'Linux'
shell: bash
run: |
set -euo pipefail
cargo build --bin ant-node
found=$(strings -a target/debug/ant-node | grep -c 'ANT_HALT_' || true)
# With --features test-utils this count is not zero, which is what makes a zero
# here evidence rather than an accident of how the binary was stripped. The exact
# number is one per failpoint and is deliberately not asserted, so that adding a
# failpoint does not fail this check.
if [ "$found" != "0" ]; then
echo "the publish failpoint is compiled into a default-feature build"
exit 1
fi
echo "no failpoint in a default-feature build"

test-no-logging:
name: Test (no logging)
Expand Down
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 17 additions & 12 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ xor_name = "5"
lru = "0.16.3"
parking_lot = "0.12" # Efficient mutex for cache

# Storage - LMDB via heed for content-addressed chunk store
# LMDB via heed for the paid-key list. The chunk store is one file per chunk and does
# not use it.
heed = "0.22"

blake3 = "1"
Expand Down Expand Up @@ -106,8 +107,6 @@ sha2 = "0.10"
# Cross-platform file locking for upgrade caches
fs2 = "0.4"

# System page size (for LMDB map alignment during resize)
page_size = "0.6"

# Protocol serialization
postcard = { version = "1.1.3", features = ["use-std"] }
Expand All @@ -129,6 +128,20 @@ proptest = "1"
alloy = { version = "1", features = ["node-bindings"] }
serial_test = "3"

# Startup scan time, index memory and inode cost at scale. Regression gates, not
# benchmarks; ANT_SCALE_KEYS raises the count for a deliberate larger run.
[[test]]
name = "storage_scale"
path = "tests/storage_scale.rs"
required-features = ["test-utils"]

# A process killed part-way through a write: the store is whole or absent, never
# half-indexed, and what the interrupted write left behind is swept.
[[test]]
name = "chunk_store_crash_safety"
path = "tests/chunk_store_crash_safety.rs"
required-features = ["test-utils"]

# E2E test infrastructure (run with --features test-utils)
[[test]]
name = "e2e"
Expand All @@ -145,7 +158,7 @@ path = "tests/poc_commitment_audit_attacks.rs"
required-features = ["test-utils"]

# Live responder-handler tests for the v12 audit. Use
# LmdbStorageConfig::test_default(), gated on test-utils.
# ChunkStoreConfig::test_default(), gated on test-utils.
[[test]]
name = "poc_audit_handler_live"
path = "tests/poc_audit_handler_live.rs"
Expand All @@ -166,14 +179,6 @@ name = "poc_price_floor_live"
path = "tests/poc_price_floor_live.rs"
required-features = ["test-utils"]

# Shutdown/LMDB-drain regression: `ReplicationEngine::shutdown()` must not
# return while a detached LMDB blocking op is still running. Uses the
# test-only storage put gate, so it requires the test-utils feature.
[[test]]
name = "poc_shutdown_lmdb_drain"
path = "tests/poc_shutdown_lmdb_drain.rs"
required-features = ["test-utils"]

[features]
default = ["logging"]
# Enable tracing/logging infrastructure.
Expand Down
3 changes: 0 additions & 3 deletions config/production.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,6 @@ enabled = true
# Verify content hash on read
verify_on_read = true

# Maximum LMDB database size in GiB (0 = default 32 GiB)
db_size_gb = 0

# --- Upgrade ---
[upgrade]
enabled = false
Expand Down
Loading
Loading