Skip to content

Commit 5cca497

Browse files
committed
fix: skip rewriting CAR blocks that are already stored
1 parent 9335bfe commit 5cca497

2 files changed

Lines changed: 47 additions & 1 deletion

File tree

internal/datastore/blockstore_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"testing"
1616
"time"
1717

18+
blocks "github.com/ipfs/go-block-format"
1819
"github.com/stretchr/testify/require"
1920

2021
"github.com/sourcenetwork/corekv/memory"
@@ -80,6 +81,48 @@ func TestIsMerged_DeletedBlockIsNotMerged(t *testing.T) {
8081
require.False(t, merged)
8182
}
8283

84+
// A CAR import re-puts blocks the node already holds. Storing one unconditionally stamps a fresh
85+
// to-merge marker over a block that already merged, which reads as an abandoned fetch and leaves
86+
// the orphan sweep free to delete a block a live document still links to.
87+
func TestIsMerged_ReputtingAMergedBlockLeavesItMerged(t *testing.T) {
88+
for _, tc := range []struct {
89+
name string
90+
put func(context.Context, Blockstore, blocks.Block) error
91+
}{
92+
{
93+
name: "Put",
94+
put: func(ctx context.Context, bs Blockstore, b blocks.Block) error { return bs.Put(ctx, b) },
95+
},
96+
{
97+
name: "PutMany",
98+
put: func(ctx context.Context, bs Blockstore, b blocks.Block) error {
99+
return bs.PutMany(ctx, []blocks.Block{b})
100+
},
101+
},
102+
} {
103+
t.Run(tc.name, func(t *testing.T) {
104+
ctx := context.Background()
105+
rootstore := memory.NewDatastore(ctx)
106+
107+
blockNS := namespace.Wrap(rootstore, []byte{blockStoreKey})
108+
block := putMarkedBlock(t, ctx, blockNS, []byte("merged then re-imported"),
109+
newToMergeValue(time.Unix(1_700_000_000, 0)))
110+
111+
blockstore := BlockstoreFrom(rootstore, immutable.None[int]())
112+
require.NoError(t, blockstore.MarkAsMerged(ctx, block.Cid()))
113+
merged, err := blockstore.IsMerged(ctx, block.Cid())
114+
require.NoError(t, err)
115+
require.True(t, merged)
116+
117+
require.NoError(t, tc.put(ctx, P2PBlockstoreFrom(rootstore, immutable.None[int]()), block))
118+
119+
merged, err = blockstore.IsMerged(ctx, block.Cid())
120+
require.NoError(t, err)
121+
require.True(t, merged, "storing a block the node already holds must not unmerge it")
122+
})
123+
}
124+
}
125+
83126
func TestIsMerged_UnmergedAndAbsentBlocks(t *testing.T) {
84127
ctx := context.Background()
85128
rootstore := memory.NewDatastore(ctx)

internal/db/p2p/car.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,10 @@ func (p *P2P) importCAR(ctx context.Context, carData []byte) (*coreblock.Block,
224224
return nil, p.carImportFailure(reasonNoRoots, written, ErrEmptyCARRoots)
225225
}
226226

227-
bstore := datastore.BlindWriteP2PBlockstoreFrom(p.db.Rootstore(), immutable.None[int]())
227+
// Content-addressed field blocks recur across documents that share a field value, so a
228+
// CAR routinely contains blocks already stored. The guarded store skips those instead of
229+
// rewriting the block and re-stamping a to-merge marker on one that already merged.
230+
bstore := datastore.P2PBlockstoreFrom(p.db.Rootstore(), immutable.None[int]())
228231
encStore := datastore.EncstoreFrom(p.db.Rootstore())
229232
var rootBlock *coreblock.Block
230233

0 commit comments

Comments
 (0)