Skip to content
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

feat: seed-based automatic peering #111

Merged
merged 4 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ The following emojis are used to highlight certain changes:

### Added

- ✨ Now supports automatic peering with peers that have the same seed via `--seed-peering` (`RAINBOW_SEED_PEERING`). To enable this, you must configure `--seed` (`RAINBOW_SEED`) and `--seed-index` (`RAINBOW_SEED_INDEX`).

### Changed

### Removed
Expand Down
63 changes: 53 additions & 10 deletions docs/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
- [`RAINBOW_GC_THRESHOLD`](#rainbow_gc_threshold)
- [`RAINBOW_IPNS_MAX_CACHE_TTL`](#rainbow_ipns_max_cache_ttl)
- [`RAINBOW_PEERING`](#rainbow_peering)
- [`RAINBOW_SEED`](#rainbow_seed)
- [`RAINBOW_SEED_INDEX`](#rainbow_seed_index)
- [`RAINBOW_SEED_PEERING`](#rainbow_seed_peering)
- [`RAINBOW_SEED_PEERING_MAX_INDEX`](#rainbow_seed_peering_max_index)
- [`RAINBOW_PEERING_SHARED_CACHE`](#rainbow_peering_shared_cache)
- [Logging](#logging)
- [`GOLOG_LOG_LEVEL`](#golog_log_level)
Expand Down Expand Up @@ -96,26 +100,65 @@ Default: No upper bound, [TTL from IPNS Record](https://specs.ipfs.tech/ipns/ipn

A comma-separated list of [multiaddresses](https://docs.libp2p.io/concepts/fundamentals/addressing/) of peers to stay connected to.


If `RAINBOW_SEED` is set and `/p2p/rainbow-seed/N` value is found here, Rainbow
will replace it with a valid `/p2p/` for a peer ID generated from same seed
and index `N`.
> [!TIP]
> If `RAINBOW_SEED` is set and `/p2p/rainbow-seed/N` value is found here, Rainbow
> will replace it with a valid `/p2p/` for a peer ID generated from same seed
> and index `N`. This is useful when `RAINBOW_SEED_PEERING` can't be used,
> or when peer routing should be skipped and specific address should be used.

Default: not set (no peering)

### `RAINBOW_SEED`

Base58 seed to derive PeerID from. Can be generated with `rainbow gen-seed`.
If set, requires `RAINBOW_SEED_INDEX` to be set as well.

Default: not set

### `RAINBOW_SEED_INDEX`

Index to derivate the PeerID identity from `RAINBOW_SEED`.

Default: not set

### `RAINBOW_SEED_PEERING`

> [!WARNING]
> Experimental feature.

Automated version of `RAINBOW_PEERING` which does not require providing multiaddrs.

Instead, it will set up peering with peers that share the same seed (requires `RAINBOW_SEED_INDEX` to be set up).

> [!NOTE]
> Runs a separate light DHT for peer routing with the main host if DHT routing is disabled.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do light DHT and main host mean?

Is the light DHT a DHT instance that will only publish the peer records of the rainbow instance?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By default, Rainbow runs with two DHT hosts:

  • The "main host" which gives the identity of the Rainbow peer.
  • The "dht host" that is only used for DHT lookups.

I don't have the full context on why this was decided but maybe @aschmahmann can give more information (he made #5 after all).


If the DHT shared host is enabled, the main host is used for the DHT.


To find peers using the seed-based peering, we need the DHT. This DHT instance must run with the main host, since this is the one that really identifies the Rainbow peer. If DHT shared host is enabled, we can just use that DHT. Otherwise, we need to run a DHT instance in parallel with the DHT host just for peer routing.


Default: `false` (disabled)

### `RAINBOW_SEED_PEERING_MAX_INDEX`

Informs the largest index to derive for `RAINBOW_SEED_PEERING`.
If you have more instances than the default, increase it here.

Default: 100

### `RAINBOW_PEERING_SHARED_CACHE`

Enable sharing of local cache to peers safe-listed with `RAINBOW_PEERING`.
> [!WARNING]
> Experimental feature.

Enable sharing of local cache to peers safe-listed with `RAINBOW_PEERING`
or `RAINBOW_SEED_PEERING`.

Once enabled, Rainbow will respond to [Bitswap](https://docs.ipfs.tech/concepts/bitswap/)
queries from these safelisted peers, serving locally cached blocks if requested.

The main use case for this feature is scaling and load balancing across a
fleet of rainbow, or other bitswap-capable IPFS services. Cache sharing allows
clustered services to check if any of the other instances has a requested CID.
This saves resources as data cached on other instance can be fetched internally
(e.g. LAN) rather than externally (WAN, p2p).
> [!TIP]
> The main use case for this feature is scaling and load balancing across a
> fleet of rainbow, or other bitswap-capable IPFS services. Cache sharing allows
> clustered services to check if any of the other instances has a requested CID.
> This saves resources as data cached on other instance can be fetched internally
> (e.g. LAN) rather than externally (WAN, p2p).

Default: `false` (no cache sharing)

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ require (
github.com/libp2p/go-libp2p-kad-dht v0.25.2
github.com/libp2p/go-libp2p-record v0.2.0
github.com/libp2p/go-libp2p-routing-helpers v0.7.3
github.com/libp2p/go-libp2p-testing v0.12.0
github.com/mitchellh/go-server-timing v1.0.1
github.com/mr-tron/base58 v1.2.0
github.com/multiformats/go-multiaddr v0.12.3
Expand Down
29 changes: 28 additions & 1 deletion keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"io"

libp2p "github.com/libp2p/go-libp2p/core/crypto"
peer "github.com/libp2p/go-libp2p/core/peer"
"github.com/mr-tron/base58"
"golang.org/x/crypto/hkdf"
)
Expand All @@ -25,7 +26,7 @@
return base58.Encode(bs), nil
}

// derive derives libp2p keys from a b58-encoded seed.
// deriveKey derives libp2p keys from a b58-encoded seed.
func deriveKey(b58secret string, info []byte) (libp2p.PrivKey, error) {
secret, err := base58.Decode(b58secret)
if err != nil {
Expand All @@ -45,6 +46,32 @@
return libp2p.UnmarshalEd25519PrivateKey(key)
}

// derivePeerIDs derives the peer IDs of all the peers with the same seed up to
// maxIndex. Our peer ID (with index 'ourIndex') is not generated.
func derivePeerIDs(seed string, ourIndex int, maxIndex int) ([]peer.ID, error) {
peerIDs := []peer.ID{}

Check warning on line 52 in keys.go

View check run for this annotation

Codecov / codecov/patch

keys.go#L51-L52

Added lines #L51 - L52 were not covered by tests

for i := 0; i <= maxIndex; i++ {
if i == ourIndex {
continue

Check warning on line 56 in keys.go

View check run for this annotation

Codecov / codecov/patch

keys.go#L54-L56

Added lines #L54 - L56 were not covered by tests
}

peerPriv, err := deriveKey(seed, deriveKeyInfo(i))
if err != nil {
return nil, err

Check warning on line 61 in keys.go

View check run for this annotation

Codecov / codecov/patch

keys.go#L59-L61

Added lines #L59 - L61 were not covered by tests
}

pid, err := peer.IDFromPrivateKey(peerPriv)
if err != nil {
return nil, err

Check warning on line 66 in keys.go

View check run for this annotation

Codecov / codecov/patch

keys.go#L64-L66

Added lines #L64 - L66 were not covered by tests
}

peerIDs = append(peerIDs, pid)

Check warning on line 69 in keys.go

View check run for this annotation

Codecov / codecov/patch

keys.go#L69

Added line #L69 was not covered by tests
}

return peerIDs, nil

Check warning on line 72 in keys.go

View check run for this annotation

Codecov / codecov/patch

keys.go#L72

Added line #L72 was not covered by tests
}

func deriveKeyInfo(index int) []byte {
return []byte(fmt.Sprintf("rainbow-%d", index))
}
27 changes: 27 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,29 @@
EnvVars: []string{"RAINBOW_SEED_INDEX"},
Usage: "Index to derivate the peerID (needs --seed)",
},
&cli.BoolFlag{
Name: "seed-peering",
Value: false,
EnvVars: []string{"RAINBOW_SEED_PEERING"},
Usage: "Automatic peering with peers with the same seed (requires --seed and --seed-index). Runs a separate light DHT for peer routing with the main host if --dht-routing or --dht-shared-host are disabled",
Action: func(ctx *cli.Context, b bool) error {
if !b {
return nil

Check warning on line 101 in main.go

View check run for this annotation

Codecov / codecov/patch

main.go#L94-L101

Added lines #L94 - L101 were not covered by tests
}

if !ctx.IsSet("seed") || !ctx.IsSet("seed-index") {
return errors.New("--seed and --seed-index must be explicitly defined when --seed-peering is enabled")

Check warning on line 105 in main.go

View check run for this annotation

Codecov / codecov/patch

main.go#L104-L105

Added lines #L104 - L105 were not covered by tests
}

return nil

Check warning on line 108 in main.go

View check run for this annotation

Codecov / codecov/patch

main.go#L108

Added line #L108 was not covered by tests
},
},
&cli.IntFlag{
Name: "seed-peering-max-index",
Value: 100,
EnvVars: []string{"RAINBOW_SEED_PEERING_MAX_INDEX"},
Usage: "Largest index to derive automatic peering peer IDs for",
},
&cli.StringSliceFlag{
Name: "gateway-domains",
Value: cli.NewStringSlice(),
Expand Down Expand Up @@ -338,6 +361,10 @@
DenylistSubs: cctx.StringSlice("denylists"),
Peering: peeringAddrs,
PeeringCache: cctx.Bool("peering-shared-cache"),
Seed: seed,
SeedIndex: index,
SeedPeering: cctx.Bool("seed-peering"),
SeedPeeringMaxIndex: cctx.Int("seed-peering-max-index"),

Check warning on line 367 in main.go

View check run for this annotation

Codecov / codecov/patch

main.go#L364-L367

Added lines #L364 - L367 were not covered by tests
GCInterval: cctx.Duration("gc-interval"),
GCThreshold: cctx.Float64("gc-threshold"),
}
Expand Down
Loading
Loading