Skip to content

feat!: add ipns reproviding/republishing #764

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 48 commits into
base: main
Choose a base branch
from
Open

Conversation

2color
Copy link
Member

@2color 2color commented Mar 13, 2025

What

This PR adds the functionality necessary for a Helia node to reprovide records it created before expiry.

Fixes #367

Related: ipshipyard/roadmaps#1

Why

Currently, the ipns.republish method which runs the republishing loop does not actually handle republishing.

How

  • Add the the ability to iterate on records in the data store.

Open question about storing keys

  • We don't store the private key when publish is called. This means that we can only reprovide valid IPNS records, but with the current defaults (48 hour lifetime and 23 hour republish interval), that means one round of republishing, after which all stored IPNS records become invalid.
    -This begs the question of whether we should store the keys so that we can create new valid records for stored IPNS names.
  • After looking at how we manage the self key for the libp2p peer we create, it seems like we probably want to add the keychain. The question then is whether to add the keychain to the Helia interface or just the IPNS module.
  • Adding a keychain would allow us to iterate on keys rather than IPNS names when republishing. Though I'm not sure if there's any obvious benefit to this.

publish(key: PrivateKey, value: CID | PublicKey | MultihashDigest<0x00 | 0x12> | string, options?: PublishOptions): Promise<IPNSRecord>

TODO

  • Add protobuf for serialising IPNS record metadata in the datastore
  • Configure @helia/http to use a libp2p instance that's only configured with HTTP routers to ensure we always have the js-libp2p keychain here
  • republish multiple records concurrently (see example)

2color and others added 5 commits February 24, 2025 13:45
* origin/main:
  chore: use peer id parsing function from libp2p (#762)
  feat: add republish signed ipns records (#745)
  fix: use bytestream methods to add byte streams (#758)
  chore: bump codecov/codecov-action from 5.3.1 to 5.4.0 (#752)
  feat: allow modifying trustless-gateway fetch (#751)
  fix: align implicit default ttl with specs (#749)
  docs: add spell checker to ci (#743)
  chore: Update FUNDING.json for Optimism RPF (#746)
@2color 2color changed the title feat: add ipns republishing feat: add ipns reproviding Mar 26, 2025
@2color 2color added kind/enhancement A net-new feature or improvement to an existing feature status/blocked Unable to be worked further until needs are met labels Apr 4, 2025
@2color

This comment was marked as outdated.

@2color
Copy link
Member Author

2color commented Apr 30, 2025

Following a discussion offline with @achingbrain, we need to move key management internally to implement reproviding.

Key management is typically (and store encrypted at rest) handled by the js-libp2p keychain, however we don't have libp2p in @helia/http (which can also be used for @helia/ipns with delegated routers). This means that we probably need a dedicated keychain instance in the IPNS class, so that we don't depend on the libp2p instance.

@achingbrain
Copy link
Member

achingbrain commented Apr 30, 2025

This means that we probably need a dedicated keychain instance in the IPNS class

@helia/http could also use a libp2p instance that's only configured with HTTP routers? Then you don't need to handle things differently at this level.

It'd also be good to see just how lightweight we can make libp2p for this sort of use-case.

@2color
Copy link
Member Author

2color commented Apr 30, 2025

@helia/http could also use a libp2p instance that's only configured with HTTP routers? Then you don't need to handle things differently at this level.

Yeah we could try that first and see how it feels. It may increase the bundle size.

I also vaguely remember there being a bug with libp2p whereby instantiating it without the DHT, causes an infinite random walk loop with the defaults, as it's looking for a circuit relay reservation by default.

Would this also allow Helia to make use of the libp2p over http stuff you've be working on?

@achingbrain
Copy link
Member

It may increase the bundle size.

Looking at the breakdown, the usual suspects are already there - @libp2p/crypto, @noble/hashes, etc so it might not increase it by that much.

I also vaguely remember there being a bug with libp2p whereby instantiating it without the DHT, causes an infinite random walk loop with the defaults, as it's looking for a circuit relay reservation by default.

I'd imagine this configuration wouldn't configure a relay listener so it wouldn't try to find a relay?

We need this landing, really - ipfs/specs#476

Would this also allow Helia to make use of the libp2p over http stuff you've be working on?

Yes, though you'd need transports etc in order to use libp2p for the transport layer.

@2color
Copy link
Member Author

2color commented Jun 2, 2025

As discussed in #807, the next steps here:

  • Take named keys (string), persisted in the keychain using a separate namespace) as input to publish. Keys will be generated internally.
  • Republish will iterate over records in datastore
  • Add unpublish(‘keyname’) for deleting records
  • Add the public key to the return type of publish, because we can't derive the name when the key is managed by the keychain.

* origin/main: (27 commits)
  fix: flaky test to publish an IPNS record  (#810)
  docs: fix API docs link (#809)
  fix: trustless gateway returned blocks can be limited (#791)
  deps: bump kubo from 0.34.1 to 0.35.0 in the kubo-deps group (#806)
  chore: fix linting
  chore: release main (#805)
  chore: bump execa from 8.0.1 to 9.5.3 (#797)
  chore: bump @helia/unixfs from 4.0.3 to 5.0.2 in /benchmarks/add-dir (#796)
  chore: bump codecov/codecov-action from 5.4.0 to 5.4.3 (#802)
  chore: bump tinybench from 3.1.1 to 4.0.1 (#798)
  deps: update aegir to 47.x.x (#804)
  chore: dep groups again
  chore: restore groups
  chore: remove groups
  chore: release main (#793)
  deps: update all deps (#792)
  chore: release main (#774)
  feat: pass initial providers to session (#777)
  ci: uci/copy-templates (#782)
  fix: propagate ipns failures (#789)
  ...
@2color 2color changed the title feat: add ipns reproviding feat: add ipns reproviding/republishing Jun 18, 2025
@2color 2color changed the title feat: add ipns reproviding/republishing feat!: add ipns reproviding/republishing Jun 18, 2025
@2color
Copy link
Member Author

2color commented Jun 18, 2025

I have an open question: we currently store the marshalled DHT record for IPNS records in the datastore.

As part of republishing, we need to also persist some additional local metadata associated with the IPNS record, like the keychain’s keyname (the key name string that the user passes) associated with the record and potentially also the lifetime. Both of these are needed for republishing.

The two ways I’ve explored storing this metadata are:

  • Take the current libp2p DHT record we store and make it part of an envelope which also stores some local metadata (lifetime and keychain’s keyname)
  • Store the local metadata using a separate key in the datastore specific for ipns metadata.

The latter approach seems a bit easier and less likely to have downstream effects or unintended consequences (like polluting /dht/record/ namespaced keys in the datastore with other data that isn’t part of a DHT record)

Since the datastore expect binary values, what are the conventions around storing such data in binary format? Should I use Protobuf for this or can I use cbor (which we already depend on)

For the sake of the example, let's assume I have the following struct that I want to persiste:

export interface IPNSRecordMetadata {
  keyName: string
  lifetime: number
}

@achingbrain

@achingbrain
Copy link
Member

Most of the datastore values are protobuf encoded - it's nice because it has predictable decoding behaviour and uses schemas which will protect us from ourselves.

I think storing extra metadata under a separate key prefix is the way to go, the /dht/record/ prefix is populated and read by at least kad-dht and ipns-pubsub so we can't change the data structures stored there, not without a very tedious upgrade process.

* origin/main:
  deps: update @multiformats/multiaddr matcher (#827)
  feat: add libp2p to @helia/http (#826)
  docs: add example of adding browser file/dir to unixfs (#817)
@2color 2color force-pushed the add-republishing branch from 8fa7929 to 3415283 Compare July 10, 2025 08:12
@2color 2color force-pushed the add-republishing branch from e27ddd8 to a17581f Compare July 10, 2025 19:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/enhancement A net-new feature or improvement to an existing feature status/blocked Unable to be worked further until needs are met
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement IPNS republish
2 participants