1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-12-16 23:10:09 +08:00

feat(ipns): support passing custom sequence number during publishing (#10851)

* feat(ipns): Add a parameter in name.publish to change the sequence number
* test: monotonic name publish --sequence
* docs: `name publish --sequence`
* chore: boxo main with PR 962

---------

Co-authored-by: Marcin Rataj <lidel@lidel.org>
This commit is contained in:
Sergey Gorbunov
2025-08-13 05:15:45 +03:00
committed by GitHub
parent 7250eb8786
commit d81f524cce
12 changed files with 120 additions and 10 deletions

View File

@@ -91,7 +91,7 @@ jobs:
steps:
- uses: actions/setup-node@v4
with:
node-version: 18.14.0
node-version: 20.x
- uses: actions/download-artifact@v4
with:
name: kubo

View File

@@ -27,6 +27,7 @@ const (
keyOptionName = "key"
quieterOptionName = "quieter"
v1compatOptionName = "v1compat"
sequenceOptionName = "sequence"
)
var PublishCmd = &cmds.Command{
@@ -66,6 +67,23 @@ Alternatively, publish an <ipfs-path> using a valid PeerID (as listed by
> ipfs name publish --key=QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy
Published to QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n: /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy
Notes:
The --ttl option specifies the time duration for caching IPNS records.
Lower values like '1m' enable faster updates but increase network load,
while the default of 1 hour reduces traffic but may delay propagation.
Gateway operators may override this with Ipns.MaxCacheTTL configuration.
The --sequence option sets a custom sequence number for the IPNS record.
The sequence number must be monotonically increasing (greater than the
current record's sequence). This is useful for manually coordinating
updates across multiple writers. If not specified, the sequence number
increments automatically.
For faster IPNS updates, consider:
- Using a lower --ttl value (e.g., '1m' for quick updates)
- Enabling PubSub via Ipns.UsePubsub in the config
`,
},
@@ -80,6 +98,7 @@ Alternatively, publish an <ipfs-path> using a valid PeerID (as listed by
cmds.BoolOption(quieterOptionName, "Q", "Write only final IPNS Name encoded as CIDv1 (for use in /ipns content paths)."),
cmds.BoolOption(v1compatOptionName, "Produce a backward-compatible IPNS Record by including fields for both V1 and V2 signatures.").WithDefault(true),
cmds.BoolOption(allowOfflineOptionName, "When --offline, save the IPNS record to the local datastore without broadcasting to the network (instead of failing)."),
cmds.Uint64Option(sequenceOptionName, "Set a custom sequence number for the IPNS record (must be higher than current)."),
ke.OptionIPNSBase,
},
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
@@ -114,6 +133,10 @@ Alternatively, publish an <ipfs-path> using a valid PeerID (as listed by
opts = append(opts, options.Name.TTL(d))
}
if sequence, found := req.Options[sequenceOptionName].(uint64); found {
opts = append(opts, options.Name.Sequence(sequence))
}
p, err := cmdutils.PathOrCidPath(req.Arguments[0])
if err != nil {
return err

View File

@@ -66,6 +66,10 @@ func (api *NameAPI) Publish(ctx context.Context, p path.Path, opts ...caopts.Nam
publishOptions = append(publishOptions, namesys.PublishWithTTL(*options.TTL))
}
if options.Sequence != nil {
publishOptions = append(publishOptions, namesys.PublishWithSequence(*options.Sequence))
}
err = api.namesys.Publish(ctx, k, p, publishOptions...)
if err != nil {
return ipns.Name{}, err

View File

@@ -16,6 +16,7 @@ type NamePublishSettings struct {
TTL *time.Duration
CompatibleWithV1 bool
AllowOffline bool
Sequence *uint64
}
type NameResolveSettings struct {
@@ -105,6 +106,15 @@ func (nameOpts) TTL(ttl time.Duration) NamePublishOption {
}
}
// Sequence is an option for Name.Publish which specifies the sequence number of
// a namesys record.
func (nameOpts) Sequence(seq uint64) NamePublishOption {
return func(settings *NamePublishSettings) error {
settings.Sequence = &seq
return nil
}
}
// CompatibleWithV1 is an option for [Name.Publish] which specifies if the
// created record should be backwards compatible with V1 IPNS Records.
func (nameOpts) CompatibleWithV1(compatible bool) NamePublishOption {

View File

@@ -13,8 +13,10 @@ This release was brought to you by the [Shipyard](https://ipshipyard.com/) team.
- [Clear provide queue when reprovide strategy changes](#clear-provide-queue-when-reprovide-strategy-changes)
- [🪵 Revamped `ipfs log level` command](#-revamped-ipfs-log-level-command)
- [📌 Named pins in `ipfs add` command](#-named-pins-in-ipfs-add-command)
- [Custom sequence numbers in `ipfs name publish`](#custom-sequence-numbers-in-ipfs-name-publish)
- [⚙️ `Reprovider.Strategy` is now consistently respected](#-reprovider-strategy-is-now-consistently-respected)
- [Removed unnecessary dependencies](#removed-unnecessary-dependencies)
- [Improved `ipfs cid`](#improved-ipfs-cid)
- [Deprecated `ipfs stats reprovide`](#deprecated-ipfs-stats-reprovide)
- [🔄 AutoRelay now uses all connected peers for relay discovery](#-autorelay-now-uses-all-connected-peers-for-relay-discovery)
- [📦️ Important dependency updates](#-important-dependency-updates)
@@ -93,6 +95,10 @@ Kubo has been cleaned up by removing unnecessary dependencies and packages:
These changes reduce the dependency footprint while improving code maintainability and following Go best practices.
#### Custom sequence numbers in `ipfs name publish`
Added `--sequence` flag to `ipfs name publish` for setting custom sequence numbers in IPNS records. This enables advanced use cases like manually coordinating updates across multiple nodes. See `ipfs name publish --help` for details.
#### Improved `ipfs cid`
Certain `ipfs cid` commands can now be run without a daemon or repository, and return correct exit code 1 on error, making it easier to perform CID conversion in scripts and CI/CD pipelines.

View File

@@ -7,7 +7,7 @@ go 1.24
replace github.com/ipfs/kubo => ./../../..
require (
github.com/ipfs/boxo v0.33.2-0.20250804224807-e5da058ebb08
github.com/ipfs/boxo v0.33.2-0.20250813013451-825361b44b4e
github.com/ipfs/kubo v0.0.0-00010101000000-000000000000
github.com/libp2p/go-libp2p v0.43.0
github.com/multiformats/go-multiaddr v0.16.1

View File

@@ -287,8 +287,8 @@ github.com/ipfs-shipyard/nopfs/ipfs v0.25.0 h1:OqNqsGZPX8zh3eFMO8Lf8EHRRnSGBMqcd
github.com/ipfs-shipyard/nopfs/ipfs v0.25.0/go.mod h1:BxhUdtBgOXg1B+gAPEplkg/GpyTZY+kCMSfsJvvydqU=
github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
github.com/ipfs/boxo v0.33.2-0.20250804224807-e5da058ebb08 h1:PtntQQtYOh7YTCRnrU1idTuOwxEi0ZmYM4u7ZfSAExY=
github.com/ipfs/boxo v0.33.2-0.20250804224807-e5da058ebb08/go.mod h1:KwlJTzv5fb1GLlA9KyMqHQmvP+4mrFuiE3PnjdrPJHs=
github.com/ipfs/boxo v0.33.2-0.20250813013451-825361b44b4e h1:A2zSzpyrerCtdN69iDxt9S9z27cD1R4Uw3l1ctLTxX0=
github.com/ipfs/boxo v0.33.2-0.20250813013451-825361b44b4e/go.mod h1:ehi6uM9NBRkAaB7Q7u2kZgGArXPfbNRe0X/CYTqUwq8=
github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk=

2
go.mod
View File

@@ -22,7 +22,7 @@ require (
github.com/hashicorp/go-version v1.7.0
github.com/ipfs-shipyard/nopfs v0.0.14
github.com/ipfs-shipyard/nopfs/ipfs v0.25.0
github.com/ipfs/boxo v0.33.2-0.20250804224807-e5da058ebb08
github.com/ipfs/boxo v0.33.2-0.20250813013451-825361b44b4e
github.com/ipfs/go-block-format v0.2.2
github.com/ipfs/go-cid v0.5.0
github.com/ipfs/go-cidutil v0.1.0

4
go.sum
View File

@@ -354,8 +354,8 @@ github.com/ipfs-shipyard/nopfs/ipfs v0.25.0 h1:OqNqsGZPX8zh3eFMO8Lf8EHRRnSGBMqcd
github.com/ipfs-shipyard/nopfs/ipfs v0.25.0/go.mod h1:BxhUdtBgOXg1B+gAPEplkg/GpyTZY+kCMSfsJvvydqU=
github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
github.com/ipfs/boxo v0.33.2-0.20250804224807-e5da058ebb08 h1:PtntQQtYOh7YTCRnrU1idTuOwxEi0ZmYM4u7ZfSAExY=
github.com/ipfs/boxo v0.33.2-0.20250804224807-e5da058ebb08/go.mod h1:KwlJTzv5fb1GLlA9KyMqHQmvP+4mrFuiE3PnjdrPJHs=
github.com/ipfs/boxo v0.33.2-0.20250813013451-825361b44b4e h1:A2zSzpyrerCtdN69iDxt9S9z27cD1R4Uw3l1ctLTxX0=
github.com/ipfs/boxo v0.33.2-0.20250813013451-825361b44b4e/go.mod h1:ehi6uM9NBRkAaB7Q7u2kZgGArXPfbNRe0X/CYTqUwq8=
github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk=

View File

@@ -263,4 +263,71 @@ func TestName(t *testing.T) {
require.NoError(t, err)
require.False(t, val.Validation.Valid)
})
t.Run("Publishing with custom sequence number", func(t *testing.T) {
t.Parallel()
node := makeDaemon(t, nil)
publishPath := "/ipfs/" + fixtureCid
name := ipns.NameFromPeer(node.PeerID())
t.Run("Publish with sequence=0 is not allowed", func(t *testing.T) {
// Sequence=0 is never valid, even on a fresh node
res := node.RunIPFS("name", "publish", "--allow-offline", "--ttl=0", "--sequence=0", publishPath)
require.NotEqual(t, 0, res.ExitCode(), "Expected publish with sequence=0 to fail")
require.Contains(t, res.Stderr.String(), "sequence number must be greater than the current record sequence")
})
t.Run("Publish with sequence=1 on fresh node", func(t *testing.T) {
// Sequence=1 is the minimum valid sequence number for first publish
res := node.IPFS("name", "publish", "--allow-offline", "--ttl=0", "--sequence=1", publishPath)
require.Equal(t, fmt.Sprintf("Published to %s: %s\n", name.String(), publishPath), res.Stdout.String())
})
t.Run("Publish with sequence=42", func(t *testing.T) {
res := node.IPFS("name", "publish", "--allow-offline", "--ttl=0", "--sequence=42", publishPath)
require.Equal(t, fmt.Sprintf("Published to %s: %s\n", name.String(), publishPath), res.Stdout.String())
})
t.Run("Publish with large sequence number", func(t *testing.T) {
res := node.IPFS("name", "publish", "--allow-offline", "--ttl=0", "--sequence=18446744073709551615", publishPath) // Max uint64
require.Equal(t, fmt.Sprintf("Published to %s: %s\n", name.String(), publishPath), res.Stdout.String())
})
})
t.Run("Sequence number monotonic check", func(t *testing.T) {
t.Parallel()
node := makeDaemon(t, nil).StartDaemon()
publishPath1 := "/ipfs/" + fixtureCid
publishPath2 := "/ipfs/" + dagCid // Different content
name := ipns.NameFromPeer(node.PeerID())
// First, publish with a high sequence number (1000)
res := node.IPFS("name", "publish", "--ttl=0", "--sequence=1000", publishPath1)
require.Equal(t, fmt.Sprintf("Published to %s: %s\n", name.String(), publishPath1), res.Stdout.String())
// Verify the record was published successfully
res = node.IPFS("name", "resolve", name.String())
require.Contains(t, res.Stdout.String(), publishPath1)
// Now try to publish different content with a LOWER sequence number (500)
// This should fail due to monotonic sequence check
res = node.RunIPFS("name", "publish", "--ttl=0", "--sequence=500", publishPath2)
require.NotEqual(t, 0, res.ExitCode(), "Expected publish with lower sequence to fail")
require.Contains(t, res.Stderr.String(), "sequence number", "Expected error about sequence number")
// Verify the original content is still published (not overwritten)
res = node.IPFS("name", "resolve", name.String())
require.Contains(t, res.Stdout.String(), publishPath1, "Original content should still be published")
require.NotContains(t, res.Stdout.String(), publishPath2, "New content should not have been published")
// Publishing with a HIGHER sequence number should succeed
res = node.IPFS("name", "publish", "--ttl=0", "--sequence=2000", publishPath2)
require.Equal(t, fmt.Sprintf("Published to %s: %s\n", name.String(), publishPath2), res.Stdout.String())
// Verify the new content is now published
res = node.IPFS("name", "resolve", name.String())
require.Contains(t, res.Stdout.String(), publishPath2, "New content should now be published")
})
}

View File

@@ -129,7 +129,7 @@ require (
github.com/huin/goupnp v1.3.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/ipfs/bbloom v0.0.4 // indirect
github.com/ipfs/boxo v0.33.2-0.20250804224807-e5da058ebb08 // indirect
github.com/ipfs/boxo v0.33.2-0.20250813013451-825361b44b4e // indirect
github.com/ipfs/go-bitfield v1.1.0 // indirect
github.com/ipfs/go-block-format v0.2.2 // indirect
github.com/ipfs/go-cid v0.5.0 // indirect

View File

@@ -321,8 +321,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
github.com/ipfs/boxo v0.33.2-0.20250804224807-e5da058ebb08 h1:PtntQQtYOh7YTCRnrU1idTuOwxEi0ZmYM4u7ZfSAExY=
github.com/ipfs/boxo v0.33.2-0.20250804224807-e5da058ebb08/go.mod h1:KwlJTzv5fb1GLlA9KyMqHQmvP+4mrFuiE3PnjdrPJHs=
github.com/ipfs/boxo v0.33.2-0.20250813013451-825361b44b4e h1:A2zSzpyrerCtdN69iDxt9S9z27cD1R4Uw3l1ctLTxX0=
github.com/ipfs/boxo v0.33.2-0.20250813013451-825361b44b4e/go.mod h1:ehi6uM9NBRkAaB7Q7u2kZgGArXPfbNRe0X/CYTqUwq8=
github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
github.com/ipfs/go-block-format v0.2.2 h1:uecCTgRwDIXyZPgYspaLXoMiMmxQpSx2aq34eNc4YvQ=