Skip to content

Commit b914ea1

Browse files
committed
feat(net): remove Boar bootstrap nodes and replace with operator peers
Replace all Boar bootstrap node entries with curated DNS-backed operator peers to complete the bootstrap infrastructure decommission. This is the final phase — Staked nodes were removed in v2.5.1. Peer list changes: - Mainnet: 2 Boar entries replaced with 5 operator peers at keep-nodes.io - Testnet: 1 Boar entry replaced with 2 operator peers at test.keep-nodes.io - All entries use /dns4/ format for IP change resilience Security — AllowList decoupling: - Pass firewall.EmptyAllowList instead of extracting embedded peer keys - All peers (including embedded operators) now pass IsRecognized() staking checks with no firewall bypass - Remove dead ExtractPeersPublicKeys function and its tests Deprecations and renames: - Deprecate --network.bootstrap flag with runtime warning - Rename connected_bootstrap_count metric to connected_wellknown_peers_count Documentation: - Add operator migration guide covering Boar address removal, --network.peers override behavior, and monitoring updates
1 parent 66b187e commit b914ea1

File tree

12 files changed

+544
-171
lines changed

12 files changed

+544
-171
lines changed

cmd/flags.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ func initNetworkFlags(cmd *cobra.Command, cfg *config.Config) {
206206
&cfg.LibP2P.Bootstrap,
207207
"network.bootstrap",
208208
false,
209-
"Run the client in bootstrap mode.",
209+
"[DEPRECATED] Run the client in bootstrap mode. This flag is deprecated and will be removed in a future release.",
210210
)
211211

212212
cmd.Flags().StringSliceVar(

cmd/start.go

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,9 @@ func start(cmd *cobra.Command) error {
188188
}
189189

190190
func isBootstrap() bool {
191+
if clientConfig.LibP2P.Bootstrap {
192+
logger.Warnf("--network.bootstrap is deprecated and will be removed in a future release")
193+
}
191194
return clientConfig.LibP2P.Bootstrap
192195
}
193196

@@ -197,19 +200,9 @@ func initializeNetwork(
197200
operatorPrivateKey *operator.PrivateKey,
198201
blockCounter chain.BlockCounter,
199202
) (net.Provider, error) {
200-
bootstrapPeersPublicKeys, err := libp2p.ExtractPeersPublicKeys(
201-
clientConfig.LibP2P.Peers,
202-
)
203-
if err != nil {
204-
return nil, fmt.Errorf(
205-
"error extracting bootstrap peers public keys: [%v]",
206-
err,
207-
)
208-
}
209-
210203
firewall := firewall.AnyApplicationPolicy(
211204
applications,
212-
firewall.NewAllowList(bootstrapPeersPublicKeys),
205+
firewall.EmptyAllowList,
213206
)
214207

215208
netProvider, err := libp2p.Connect(
@@ -244,7 +237,7 @@ func initializeClientInfo(
244237
config.ClientInfo.NetworkMetricsTick,
245238
)
246239

247-
registry.ObserveConnectedBootstrapCount(
240+
registry.ObserveConnectedWellknownPeersCount(
248241
netProvider,
249242
config.LibP2P.Peers,
250243
config.ClientInfo.NetworkMetricsTick,

cmd/start_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package cmd
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/keep-network/keep-core/config"
8+
"github.com/spf13/cobra"
9+
)
10+
11+
func TestNetworkBootstrapFlagDescription_ContainsDeprecationNotice(t *testing.T) {
12+
cmd := &cobra.Command{Use: "test"}
13+
cfg := &config.Config{}
14+
15+
initNetworkFlags(cmd, cfg)
16+
17+
flag := cmd.Flags().Lookup("network.bootstrap")
18+
if flag == nil {
19+
t.Fatal("expected network.bootstrap flag to be registered")
20+
}
21+
22+
usageLower := strings.ToLower(flag.Usage)
23+
if !strings.Contains(usageLower, "deprecated") {
24+
t.Errorf(
25+
"expected flag description to contain deprecation notice, got: %q",
26+
flag.Usage,
27+
)
28+
}
29+
}
30+
31+
func TestIsBootstrap(t *testing.T) {
32+
tests := map[string]struct {
33+
bootstrapValue bool
34+
expected bool
35+
}{
36+
"returns true when bootstrap flag is set": {
37+
bootstrapValue: true,
38+
expected: true,
39+
},
40+
"returns false when bootstrap flag is not set": {
41+
bootstrapValue: false,
42+
expected: false,
43+
},
44+
}
45+
46+
for name, tc := range tests {
47+
t.Run(name, func(t *testing.T) {
48+
originalBootstrap := clientConfig.LibP2P.Bootstrap
49+
defer func() { clientConfig.LibP2P.Bootstrap = originalBootstrap }()
50+
51+
clientConfig.LibP2P.Bootstrap = tc.bootstrapValue
52+
53+
got := isBootstrap()
54+
if got != tc.expected {
55+
t.Errorf("expected isBootstrap() to return %v, got %v", tc.expected, got)
56+
}
57+
})
58+
}
59+
}

config/_peers/mainnet

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
1-
/dns4/bst-a01.tbtc.boar.network/tcp/5001/ipfs/16Uiu2HAmAmCrLuUmnBgpavU8y8JBUN6jWAQ93JwydZy3ABRyY6wU
2-
/dns4/bst-b01.tbtc.boar.network/tcp/5001/ipfs/16Uiu2HAm4w5HdJQxBnadGRepaiGfWVvtMzhdAGZVcrf9i71mv69V
1+
/dns4/keep-operator-1.keep-nodes.io/tcp/3919/ipfs/16Uiu2HAmVUxCz2YjBpGaGirVLx6RGtHbPg5rygEWMPoUFE4bHTkr
2+
/dns4/keep-operator-2.keep-nodes.io/tcp/3919/ipfs/16Uiu2HAm8bLqTcGMDFaNPGPC6gxStKCnJr2DaVsMbce1ZEyaKo9S
3+
/dns4/keep-operator-3.keep-nodes.io/tcp/3919/ipfs/16Uiu2HAmQLCwPnNmFMDQkc5hLfapGKtXPvFJQKB3rUFYa1wjVnfi
4+
/dns4/keep-operator-4.keep-nodes.io/tcp/3919/ipfs/16Uiu2HAmTv4atEFadTVPz7BWhE3gRFMeJ5Kk4LQfgN2V8ViWYFRx
5+
/dns4/keep-operator-5.keep-nodes.io/tcp/3919/ipfs/16Uiu2HAmPwQuywYq9qFRn8gLCtiKaDZwg2u3JQhWia7RYHRdfk1r

config/_peers/testnet

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
/dns4/bst-a01.test.keep.boar.network/tcp/6001/ipfs/16Uiu2HAmSLDSahiKyTbCNNu8wJmZAsiKF7wuYJ8mogY8ZuAG1jhu
1+
/dns4/keep-operator-1.test.keep-nodes.io/tcp/3920/ipfs/16Uiu2HAmDrk2Bh4VNPUJfKRHTE2CvH9xfKzN4KFnmRJbGLkJFDqL
2+
/dns4/keep-operator-2.test.keep-nodes.io/tcp/3920/ipfs/16Uiu2HAm3ex8rGzwFpWYbRreRUiX9JEYCKxp7KDMzB8RZ6fQWnMa

config/peers_test.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,17 @@ func TestResolvePeers(t *testing.T) {
1818
"mainnet network": {
1919
network: network.Mainnet,
2020
expectedPeers: []string{
21-
"/dns4/bst-a01.tbtc.boar.network/tcp/5001/ipfs/16Uiu2HAmAmCrLuUmnBgpavU8y8JBUN6jWAQ93JwydZy3ABRyY6wU",
22-
"/dns4/bst-b01.tbtc.boar.network/tcp/5001/ipfs/16Uiu2HAm4w5HdJQxBnadGRepaiGfWVvtMzhdAGZVcrf9i71mv69V",
21+
"/dns4/keep-operator-1.keep-nodes.io/tcp/3919/ipfs/16Uiu2HAmVUxCz2YjBpGaGirVLx6RGtHbPg5rygEWMPoUFE4bHTkr",
22+
"/dns4/keep-operator-2.keep-nodes.io/tcp/3919/ipfs/16Uiu2HAm8bLqTcGMDFaNPGPC6gxStKCnJr2DaVsMbce1ZEyaKo9S",
23+
"/dns4/keep-operator-3.keep-nodes.io/tcp/3919/ipfs/16Uiu2HAmQLCwPnNmFMDQkc5hLfapGKtXPvFJQKB3rUFYa1wjVnfi",
24+
"/dns4/keep-operator-4.keep-nodes.io/tcp/3919/ipfs/16Uiu2HAmTv4atEFadTVPz7BWhE3gRFMeJ5Kk4LQfgN2V8ViWYFRx",
25+
"/dns4/keep-operator-5.keep-nodes.io/tcp/3919/ipfs/16Uiu2HAmPwQuywYq9qFRn8gLCtiKaDZwg2u3JQhWia7RYHRdfk1r",
2326
}},
2427
"sepolia network": {
2528
network: network.Testnet,
2629
expectedPeers: []string{
27-
"/dns4/bst-a01.test.keep.boar.network/tcp/6001/ipfs/16Uiu2HAmSLDSahiKyTbCNNu8wJmZAsiKF7wuYJ8mogY8ZuAG1jhu",
30+
"/dns4/keep-operator-1.test.keep-nodes.io/tcp/3920/ipfs/16Uiu2HAmDrk2Bh4VNPUJfKRHTE2CvH9xfKzN4KFnmRJbGLkJFDqL",
31+
"/dns4/keep-operator-2.test.keep-nodes.io/tcp/3920/ipfs/16Uiu2HAm3ex8rGzwFpWYbRreRUiX9JEYCKxp7KDMzB8RZ6fQWnMa",
2832
},
2933
},
3034
"developer network": {
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
# Network Peer Migration Guide
2+
3+
## Quick Action
4+
5+
**If you have NOT manually configured `--network.peers` or `[LibP2P].Peers`**,
6+
no action is needed. Your node uses embedded defaults that are updated
7+
automatically with each client release.
8+
9+
**If you HAVE manually configured peers**, check your configuration for any
10+
address containing `boar.network` or `staked.cloud` and remove it. The
11+
recommended fix is to remove the `--network.peers` / `[LibP2P].Peers` setting
12+
entirely so your node uses the new built-in defaults. See
13+
[Required Action](#required-action-remove-hardcoded-boar-addresses) below.
14+
15+
> **Warning**: If you do nothing and your custom peer list contains only Boar
16+
> addresses, your node will lose network connectivity when Boar infrastructure
17+
> is decommissioned.
18+
19+
---
20+
21+
## Summary of Changes
22+
23+
The keep-client embedded peer list has been updated to replace Boar bootstrap
24+
node addresses with curated operator-run peers.
25+
26+
The following changes are included in this release:
27+
28+
- **Boar bootstrap addresses removed** from embedded peer lists (mainnet and
29+
testnet). The Boar infrastructure (`bst-*.boar.network`) is being
30+
decommissioned.
31+
- **New curated operator peers** are now embedded as defaults, hosted at
32+
`keep-nodes.io` (mainnet) and `test.keep-nodes.io` (testnet).
33+
- **Firewall validation strengthened**: all peers are now validated through
34+
on-chain staking checks. Previously, embedded peer public keys bypassed
35+
staking validation via a firewall allow-list.
36+
- **`--network.bootstrap` flag deprecated**: the flag still functions but will
37+
be removed in a future release.
38+
- **Metric renamed**: `connected_bootstrap_count` has been renamed to
39+
`connected_wellknown_peers_count`.
40+
41+
Most operators do not need to take any action. If you have manually configured
42+
`--network.peers` or `[LibP2P].Peers` in your configuration, read the next
43+
section carefully.
44+
45+
---
46+
47+
## Required Action: Remove Hardcoded Boar Addresses
48+
49+
**If your node is configured with `--network.peers` (CLI flag) or
50+
`[LibP2P].Peers` (configuration file), you must review and update your
51+
configuration before Boar infrastructure is decommissioned.** Failure to act
52+
will result in your node being unable to discover peers on the network.
53+
54+
### How to Check if You Are Affected
55+
56+
1. **Check your startup command** for the `--network.peers` flag:
57+
```
58+
--network.peers=/dns4/bst-a01.tbtc.boar.network/tcp/3919/ipfs/16Uiu2HAm...
59+
```
60+
61+
2. **Check your configuration file** (TOML, YAML, or JSON) for a `Peers`
62+
entry under the `[LibP2P]` section:
63+
```toml
64+
[LibP2P]
65+
Peers = [
66+
"/dns4/bst-a01.tbtc.boar.network/tcp/3919/ipfs/16Uiu2HAm...",
67+
"/dns4/bst-b01.tbtc.boar.network/tcp/3919/ipfs/16Uiu2HAm..."
68+
]
69+
```
70+
71+
3. **If neither is set**, you are using the embedded defaults and no action is
72+
needed. The new operator peers are included automatically.
73+
74+
### Boar Addresses to Remove
75+
76+
Remove any peer address whose hostname contains `boar.network` or
77+
`staked.cloud`. The specific Boar hostnames being decommissioned are:
78+
79+
| Network | Hostname |
80+
|---------|----------|
81+
| Mainnet | `bst-a01.tbtc.boar.network` |
82+
| Mainnet | `bst-b01.tbtc.boar.network` |
83+
| Testnet | `bst-a01.test.keep.boar.network` |
84+
85+
The peer ID suffix (the `/ipfs/16Uiu2HAm...` portion) varies, but the hostname
86+
is the identifying part. Any multiaddress containing one of the hostnames above
87+
must be removed.
88+
89+
### What to Do
90+
91+
**Option A (recommended):** Remove `--network.peers` / `[LibP2P].Peers`
92+
entirely. Your node will then use the new embedded defaults, which are
93+
maintained and updated with each client release.
94+
95+
Before:
96+
```toml
97+
[LibP2P]
98+
Peers = [
99+
"/dns4/bst-a01.tbtc.boar.network/tcp/3919/ipfs/16Uiu2HAm...",
100+
"/dns4/bst-b01.tbtc.boar.network/tcp/3919/ipfs/16Uiu2HAm..."
101+
]
102+
```
103+
104+
After:
105+
```toml
106+
[LibP2P]
107+
# Peers removed -- the client now uses built-in operator peer defaults.
108+
```
109+
110+
Or, if using the CLI flag, simply remove `--network.peers` from your startup
111+
command.
112+
113+
**Option B:** Replace the Boar addresses with currently active operator peer
114+
addresses. Only use this option if you have a specific reason to maintain a
115+
custom peer list.
116+
117+
### Why This Matters
118+
119+
When you set `--network.peers` (or `[LibP2P].Peers` in your configuration
120+
file), the client uses your list exclusively and ignores the built-in defaults.
121+
122+
Internally, the `resolvePeers()` function in `config/peers.go` checks whether
123+
`LibP2P.Peers` is already populated. If it is, the function returns immediately
124+
without loading the embedded peer list. This means manually configured peers
125+
completely override the defaults -- there is no merging.
126+
127+
If your custom peer list contains only Boar addresses, your node will be unable
128+
to discover any peers once Boar infrastructure is decommissioned.
129+
130+
---
131+
132+
## Bootstrap Flag Deprecation
133+
134+
The `--network.bootstrap=true` flag is deprecated and will be removed in a
135+
future release.
136+
137+
Historically, this flag marked a node as a bootstrap/relay node, which enabled
138+
special behaviors such as adjusted dissemination timing and self-dial skipping.
139+
As the network has matured, dedicated bootstrap mode is no longer necessary for
140+
standard peer discovery.
141+
142+
The flag still functions and the node will log a deprecation warning when it is
143+
used. Operators currently running with `--network.bootstrap=true` should plan
144+
to stop using it. If you need to adjust dissemination timing directly, use the
145+
`--network.disseminationTime` flag instead.
146+
147+
---
148+
149+
## Metric Rename
150+
151+
The `connected_bootstrap_count` metric has been renamed to
152+
`connected_wellknown_peers_count`.
153+
154+
The metric semantics are unchanged -- it tracks the number of currently
155+
connected well-known peers that are embedded in the client. Only the name has
156+
changed to more accurately reflect that these peers are curated operator nodes
157+
rather than traditional bootstrap nodes.
158+
159+
If you have Grafana dashboards, Prometheus alerts, or other monitoring that
160+
references the old metric name, update your queries:
161+
162+
| Before | After |
163+
|--------|-------|
164+
| `connected_bootstrap_count` | `connected_wellknown_peers_count` |
165+
166+
The metric is exposed on the Client Info HTTP endpoint (port 9601 by default).
167+
168+
---
169+
170+
## Technical Background
171+
172+
This section provides additional context for operators who want to understand
173+
the underlying mechanisms.
174+
175+
### Embedded Peer Resolution
176+
177+
The keep-client binary embeds a default peer list at compile time using Go's
178+
`embed` package. The peer addresses are stored in the `config/_peers/` directory
179+
with separate files for each network (mainnet, testnet).
180+
181+
When the client starts, `resolvePeers()` in `config/peers.go` runs the
182+
following logic:
183+
184+
1. If `LibP2P.Peers` is already populated (via `--network.peers` flag or
185+
configuration file), return immediately without changes.
186+
2. If the network type is `developer` or `unknown`, log a warning and return
187+
(no embedded defaults for these networks).
188+
3. Otherwise, read the embedded peer list for the current network and set
189+
`LibP2P.Peers` to those values.
190+
191+
This design means that manually configured peers always take precedence over
192+
embedded defaults. There is no merging of the two lists.
193+
194+
### Firewall Allow-List Change
195+
196+
Previously, the public keys of embedded peers were added to a firewall
197+
allow-list, which allowed them to bypass the `IsRecognized()` on-chain staking
198+
validation. This was a convenience for bootstrap infrastructure but weakened
199+
the security model.
200+
201+
With this release, the allow-list is no longer populated with embedded peer
202+
keys. All peers -- including the embedded operator peers -- must pass staking
203+
validation through the on-chain contracts. This ensures that only properly
204+
staked operators can participate in the network.
205+
206+
### Network Discovery Flow
207+
208+
The overall peer discovery architecture follows this path:
209+
210+
1. **Embedded peers** provide initial connectivity (the addresses in
211+
`config/_peers/<network>`).
212+
2. **libp2p connections** are established to these well-known peers.
213+
3. **DHT discovery** uses these initial connections to find additional peers
214+
across the network.
215+
4. **Full mesh connectivity** is established as more peers are discovered.
216+
217+
Removing Boar addresses from the embedded list and replacing them with active
218+
operator peers ensures that step 1 connects to reliable, staked infrastructure.

0 commit comments

Comments
 (0)