From 4c3cfabe6c74fe839c826f508b6fa0fa5a2982ac Mon Sep 17 00:00:00 2001 From: E M <5089238+emizzle@users.noreply.github.com> Date: Mon, 23 Feb 2026 22:45:46 +1100 Subject: [PATCH 1/4] fix: prevent underflow The compiler should not allow this totalBlocks is a Natural, but... --- storage/stores/repostore/operations.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/storage/stores/repostore/operations.nim b/storage/stores/repostore/operations.nim index f2e67094..a6fdb992 100644 --- a/storage/stores/repostore/operations.nim +++ b/storage/stores/repostore/operations.nim @@ -94,9 +94,9 @@ proc updateTotalBlocksCount*( proc(maybeCurrCount: ?Natural): Future[?Natural] {.async.} = let count: Natural = if currCount =? maybeCurrCount: - currCount + plusCount - minusCount + max(0, currCount + plusCount - minusCount) else: - plusCount - minusCount + max(0, plusCount - minusCount) self.totalBlocks = count storage_repostore_blocks.set(count.int64) From 8e337478f2943c63eefab4cd45ecc23f4b1e7217 Mon Sep 17 00:00:00 2001 From: E M <5089238+emizzle@users.noreply.github.com> Date: Wed, 25 Feb 2026 20:13:54 +1100 Subject: [PATCH 2/4] Prevent further underflow when deleting blocks --- storage/stores/repostore/operations.nim | 2 +- storage/stores/repostore/store.nim | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/storage/stores/repostore/operations.nim b/storage/stores/repostore/operations.nim index a6fdb992..d83c46b4 100644 --- a/storage/stores/repostore/operations.nim +++ b/storage/stores/repostore/operations.nim @@ -157,7 +157,7 @@ proc updateBlockMetadata*( BlockMetadata( size: currBlockMd.size, expiry: max(currBlockMd.expiry, minExpiry), - refCount: currBlockMd.refCount + plusRefCount - minusRefCount, + refCount: max(0, currBlockMd.refCount + plusRefCount - minusRefCount), ).some else: raise newException( diff --git a/storage/stores/repostore/store.nim b/storage/stores/repostore/store.nim index feec3807..6864e0bf 100644 --- a/storage/stores/repostore/store.nim +++ b/storage/stores/repostore/store.nim @@ -272,7 +272,8 @@ method delBlock*( error "Failed to delete leaf metadata, block will remain on disk.", err = err.msg return failure(err) - if err =? + if leafMd.blkCid.mcodec == BlockCodec and + err =? (await self.updateBlockMetadata(leafMd.blkCid, minusRefCount = 1)).errorOption: if not (err of BlockNotFoundError): return failure(err) From 9d7cbf5783ef2099427749eccbc65148e22534f1 Mon Sep 17 00:00:00 2001 From: E M <5089238+emizzle@users.noreply.github.com> Date: Wed, 25 Feb 2026 22:11:52 +1100 Subject: [PATCH 3/4] formatting --- storage/conf.nim | 3 +-- storage/stores/repostore/store.nim | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/storage/conf.nim b/storage/conf.nim index db9dbc27..062eec0b 100644 --- a/storage/conf.nim +++ b/storage/conf.nim @@ -136,8 +136,7 @@ type .}: OutDir listenIp* {. - desc: - "IP address to listen on for remote peer connections, can be ipv4 or ipv6", + desc: "IP address to listen on for remote peer connections, can be ipv4 or ipv6", defaultValue: "0.0.0.0".parseIpAddress, defaultValueDesc: "Listens on all addresses.", abbr: "i", diff --git a/storage/stores/repostore/store.nim b/storage/stores/repostore/store.nim index 6864e0bf..f4cfa9e3 100644 --- a/storage/stores/repostore/store.nim +++ b/storage/stores/repostore/store.nim @@ -272,8 +272,8 @@ method delBlock*( error "Failed to delete leaf metadata, block will remain on disk.", err = err.msg return failure(err) - if leafMd.blkCid.mcodec == BlockCodec and - err =? + if leafMd.blkCid.mcodec == BlockCodec and + err =? (await self.updateBlockMetadata(leafMd.blkCid, minusRefCount = 1)).errorOption: if not (err of BlockNotFoundError): return failure(err) From e8ee59ee003584a531a37dfc427735263eae3947 Mon Sep 17 00:00:00 2001 From: E M <5089238+emizzle@users.noreply.github.com> Date: Thu, 26 Feb 2026 22:13:14 +1100 Subject: [PATCH 4/4] add test for hitting the logic path that panics --- tests/storage/stores/testrepostore.nim | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/storage/stores/testrepostore.nim b/tests/storage/stores/testrepostore.nim index 0af4e742..30617c08 100644 --- a/tests/storage/stores/testrepostore.nim +++ b/tests/storage/stores/testrepostore.nim @@ -435,6 +435,25 @@ asyncchecksuite "RepoStore": (await repo.delBlock(treeCid2, 0.Natural)).tryGet() check not (await sharedBlock.cid in repo) + test "should not panic (underflow) when deleting a manifest as a leaf": + let + repo = RepoStore.new(repoDs, metaDs, clock = mockClock, quotaMaxBytes = + 1000'nb) + (_, tree, manifest) = makeDataset( + await makeRandomBlocks(datasetSize = 2 * 256, blockSize = 256'nb) + ).tryGet() + treeCid = tree.rootCid.tryGet() + proof = tree.getProof(1).tryGet() + + let encodedVerifiable = manifest.encode().tryGet + let blk = bt.Block.new(data = encodedVerifiable, codec = ManifestCodec).tryGet + + (await repo.putCidAndProof(treeCid, 0.Natural, blk.cid, proof)).tryGet() + + (await repo.delBlock(treeCid, 0.Natural)).tryGet + + check not (await blk.cid in repo) + test "should clear leaf metadata when block is deleted from dataset": let repo = RepoStore.new(repoDs, metaDs, clock = mockClock, quotaMaxBytes =