Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
442a44d
fix(provider): purge keystore datastore after reset
guillaumemichel Feb 13, 2026
b4a877e
changelog
guillaumemichel Feb 13, 2026
5ba7359
use MapDatastore if no datastore is configured
guillaumemichel Feb 13, 2026
76916a6
bump kad-dht to latest commit
guillaumemichel Feb 17, 2026
0f1d237
purge orphaned keystore migration
guillaumemichel Feb 17, 2026
50d6a9f
Merge branch 'master' into fix/keystore-datastore-compaction
guillaumemichel Feb 17, 2026
6ef1b99
bump kad-dht
guillaumemichel Feb 17, 2026
86d814b
use main datastore for keystore "meta" store
guillaumemichel Feb 17, 2026
d8e7d78
add provider/keystore/0 and /1 to ipfs diag command
guillaumemichel Feb 17, 2026
4c0732a
fix(provider): reject unexpected keystore suffix to prevent stray del…
lidel Mar 18, 2026
727df42
fix(provider): close opened datastores when mounting partially fails
lidel Mar 18, 2026
813f77e
fix(provider): defer batch creation in orphan purge until keys are found
lidel Mar 18, 2026
a47cba0
fix(provider): warn on unrecognized datastore wrapper types
lidel Mar 18, 2026
b7b8286
docs: document keystore migration behavior on upgrade and downgrade
lidel Mar 18, 2026
08f4e7c
Merge branch 'master' into fix/keystore-datastore-compaction
lidel Mar 18, 2026
c22c16d
chore(deps): bump go-libp2p-kad-dht to latest keystore factory commit
lidel Mar 18, 2026
188addc
fix(provider): harden keystore migration and spec handling
lidel Mar 18, 2026
9dd5898
test(provider): add migration purge test and diag datastore put command
lidel Mar 18, 2026
0da88af
chore(deps): bump go-libp2p-kad-dht to 1bede74b8246
lidel Mar 18, 2026
010e0c8
fix(provider): log keystore datastore create and destroy operations
lidel Mar 18, 2026
a16ddc0
docs: rewrite provider keystore changelog to focus on user impact
lidel Mar 18, 2026
72f4a5f
bump kad-dht@master
guillaumemichel Mar 19, 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
1 change: 1 addition & 0 deletions core/commands/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ func TestCommands(t *testing.T) {
"/diag/datastore",
"/diag/datastore/count",
"/diag/datastore/get",
"/diag/datastore/put",
"/diag/profile",
"/diag/sys",
"/files",
Expand Down
100 changes: 89 additions & 11 deletions core/commands/diag.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import (
"io"

"github.com/ipfs/go-datastore"
"github.com/ipfs/go-datastore/mount"
"github.com/ipfs/go-datastore/query"
cmds "github.com/ipfs/go-ipfs-cmds"
oldcmds "github.com/ipfs/kubo/commands"
node "github.com/ipfs/kubo/core/node"
fsrepo "github.com/ipfs/kubo/repo/fsrepo"
)

Expand Down Expand Up @@ -41,7 +43,11 @@ in production workflows. The datastore format may change between versions.

The daemon must not be running when calling these commands.

EXAMPLE
When the provider keystore datastores exist on disk (nodes with
Provide.DHT.SweepEnabled=true), they are automatically mounted into the
datastore view under /provider/keystore/0/ and /provider/keystore/1/.

EXAMPLES

Inspecting pubsub seqno validator state:

Expand All @@ -51,10 +57,20 @@ Inspecting pubsub seqno validator state:
Key: /pubsub/seqno/12D3KooW...
Hex Dump:
00000000 18 81 81 c8 91 c0 ea f6 |........|

Writing a test key (debugging only):

$ ipfs diag datastore put /test/mykey "hello"

Inspecting provider keystore (requires SweepEnabled):

$ ipfs diag datastore count /provider/keystore/0/
$ ipfs diag datastore count /provider/keystore/1/
`,
},
Subcommands: map[string]*cmds.Command{
"get": diagDatastoreGetCmd,
"put": diagDatastorePutCmd,
"count": diagDatastoreCountCmd,
},
}
Expand All @@ -67,6 +83,36 @@ type diagDatastoreGetResult struct {
HexDump string `json:"hex_dump,omitempty"`
}

// openDiagDatastore opens the repo datastore and conditionally mounts any
// provider keystore datastores that exist on disk. It returns the composite
// datastore and a cleanup function that must be called when done.
func openDiagDatastore(env cmds.Environment) (datastore.Datastore, func(), error) {
cctx := env.(*oldcmds.Context)
repo, err := fsrepo.Open(cctx.ConfigRoot)
if err != nil {
return nil, nil, fmt.Errorf("failed to open repo: %w", err)
}

extraMounts, extraCloser, err := node.MountKeystoreDatastores(repo)
if err != nil {
repo.Close()
return nil, nil, err
}

closer := func() {
extraCloser()
repo.Close()
}

if len(extraMounts) == 0 {
return repo.Datastore(), closer, nil
}

mounts := []mount.Mount{{Prefix: datastore.NewKey("/"), Datastore: repo.Datastore()}}
mounts = append(mounts, extraMounts...)
return mount.New(mounts), closer, nil
}

var diagDatastoreGetCmd = &cmds.Command{
Status: cmds.Experimental,
Helptext: cmds.HelpText{
Expand All @@ -89,16 +135,14 @@ WARNING: FOR DEBUGGING/TESTING ONLY
NoRemote: true,
PreRun: DaemonNotRunning,
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
cctx := env.(*oldcmds.Context)
repo, err := fsrepo.Open(cctx.ConfigRoot)
ds, closer, err := openDiagDatastore(env)
if err != nil {
return fmt.Errorf("failed to open repo: %w", err)
return err
}
defer repo.Close()
defer closer()

keyStr := req.Arguments[0]
key := datastore.NewKey(keyStr)
ds := repo.Datastore()

val, err := ds.Get(req.Context, key)
if err != nil {
Expand Down Expand Up @@ -133,6 +177,42 @@ WARNING: FOR DEBUGGING/TESTING ONLY
},
}

var diagDatastorePutCmd = &cmds.Command{
Status: cmds.Experimental,
Helptext: cmds.HelpText{
Tagline: "Write a raw key-value pair to the datastore.",
ShortDescription: `
Stores the given value at the specified datastore key.

The daemon must not be running when using this command.

WARNING: FOR DEBUGGING/TESTING ONLY
`,
},
Arguments: []cmds.Argument{
cmds.StringArg("key", true, false, "Datastore key (e.g., /test/mykey)"),
cmds.StringArg("value", true, false, "Value to store (as a string)"),
},
NoRemote: true,
PreRun: DaemonNotRunning,
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
ds, closer, err := openDiagDatastore(env)
if err != nil {
return err
}
defer closer()

key := datastore.NewKey(req.Arguments[0])
if err := ds.Put(req.Context, key, []byte(req.Arguments[1])); err != nil {
return fmt.Errorf("failed to put key: %w", err)
}
if err := ds.Sync(req.Context, key); err != nil {
return fmt.Errorf("failed to sync: %w", err)
}
return nil
},
}

type diagDatastoreCountResult struct {
Prefix string `json:"prefix"`
Count int64 `json:"count"`
Expand All @@ -156,15 +236,13 @@ WARNING: FOR DEBUGGING/TESTING ONLY
NoRemote: true,
PreRun: DaemonNotRunning,
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
cctx := env.(*oldcmds.Context)
repo, err := fsrepo.Open(cctx.ConfigRoot)
ds, closer, err := openDiagDatastore(env)
if err != nil {
return fmt.Errorf("failed to open repo: %w", err)
return err
}
defer repo.Close()
defer closer()

prefix := req.Arguments[0]
ds := repo.Datastore()

q := query.Query{
Prefix: prefix,
Expand Down
Loading
Loading