Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b914ea1
feat(net): remove Boar bootstrap nodes and replace with operator peers
lrsaturnino Mar 25, 2026
f097b5e
chore: remove migration guide from release branch
lrsaturnino Mar 25, 2026
25c1abc
feat(config): set beta staker as mainnet embedded peer
lrsaturnino Mar 25, 2026
c5faea7
fix(ci): run Go tests across all packages
piotr-roslaniec Mar 25, 2026
c33c218
fix(config): correct testnet peer hostnames in test expectations
piotr-roslaniec Mar 25, 2026
68e8c8d
refactor(clientinfo): remove over-specified metric constant tests
piotr-roslaniec Mar 25, 2026
16ea00f
refactor(firewall): make EmptyAllowList a function instead of exporte…
piotr-roslaniec Mar 25, 2026
3b1aee7
docs(clientinfo): document metric rename from connected_bootstrap_count
piotr-roslaniec Mar 25, 2026
8e7712e
chore(cmd): add v3.0 removal timeline for deprecated bootstrap flag
piotr-roslaniec Mar 25, 2026
834b63a
docs(config): document mainnet single-peer SPOF risk
piotr-roslaniec Mar 25, 2026
9508fb5
fix(tbtcpg): use format string in fmt.Errorf to fix go vet error
piotr-roslaniec Mar 25, 2026
57f5358
test(tbtc): stabilize coordination layer assertion
piotr-roslaniec Mar 26, 2026
16ace1b
fix(ci): run Go tests across all packages (#3910)
piotr-roslaniec Mar 27, 2026
14792f6
fix(ci): run Go tests across all packages
piotr-roslaniec Mar 25, 2026
139c7e1
fix(config): correct testnet peer hostnames in test expectations
piotr-roslaniec Mar 25, 2026
6b43715
refactor(clientinfo): remove over-specified metric constant tests
piotr-roslaniec Mar 25, 2026
df8a47d
refactor(firewall): make EmptyAllowList a function instead of exporte…
piotr-roslaniec Mar 25, 2026
b3ad46d
docs(clientinfo): document metric rename from connected_bootstrap_count
piotr-roslaniec Mar 25, 2026
ff48882
chore(cmd): add v3.0 removal timeline for deprecated bootstrap flag
piotr-roslaniec Mar 25, 2026
3f5307e
docs(config): document mainnet single-peer SPOF risk
piotr-roslaniec Mar 25, 2026
3c58b76
fix(tbtcpg): use format string in fmt.Errorf to fix go vet error
piotr-roslaniec Mar 25, 2026
a8bdaeb
test(tbtc): stabilize coordination layer assertion
piotr-roslaniec Mar 26, 2026
5a6bb93
fix(ci): run Go tests across all packages (#3910)
piotr-roslaniec Mar 27, 2026
cc30229
Merge branch 'feature/decouple-firewall-allowlist' of https://github.…
lrsaturnino Apr 3, 2026
2545897
fix(chain/local_v1): prevent double-close panic in block counter watcher
lrsaturnino Apr 3, 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
2 changes: 1 addition & 1 deletion .github/workflows/client.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ jobs:
docker run \
--workdir /go/src/github.com/keep-network/keep-core \
go-build-env \
gotestsum -- -timeout 15m
gotestsum -- -timeout 15m ./...

- name: Build Docker Runtime Image
if: github.event_name != 'workflow_dispatch'
Expand Down
4 changes: 3 additions & 1 deletion cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,11 +202,13 @@ func initBitcoinElectrumFlags(cmd *cobra.Command, cfg *config.Config) {

// Initialize flags for Network configuration.
func initNetworkFlags(cmd *cobra.Command, cfg *config.Config) {
// TODO: Remove in v3.0.0 along with isBootstrap() in start.go and
// the LibP2P.Bootstrap config field.
cmd.Flags().BoolVar(
&cfg.LibP2P.Bootstrap,
"network.bootstrap",
false,
"Run the client in bootstrap mode.",
"[DEPRECATED: remove in v3.0] Run the client in bootstrap mode. This flag is deprecated and will be removed in v3.0.",
)

cmd.Flags().StringSliceVar(
Expand Down
17 changes: 5 additions & 12 deletions cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ func start(cmd *cobra.Command) error {
}

func isBootstrap() bool {
if clientConfig.LibP2P.Bootstrap {
logger.Warnf("--network.bootstrap is deprecated and will be removed in a future release")
}
return clientConfig.LibP2P.Bootstrap
}

Expand All @@ -197,19 +200,9 @@ func initializeNetwork(
operatorPrivateKey *operator.PrivateKey,
blockCounter chain.BlockCounter,
) (net.Provider, error) {
bootstrapPeersPublicKeys, err := libp2p.ExtractPeersPublicKeys(
clientConfig.LibP2P.Peers,
)
if err != nil {
return nil, fmt.Errorf(
"error extracting bootstrap peers public keys: [%v]",
err,
)
}

firewall := firewall.AnyApplicationPolicy(
applications,
firewall.NewAllowList(bootstrapPeersPublicKeys),
firewall.EmptyAllowList(),
)

netProvider, err := libp2p.Connect(
Expand Down Expand Up @@ -244,7 +237,7 @@ func initializeClientInfo(
config.ClientInfo.NetworkMetricsTick,
)

registry.ObserveConnectedBootstrapCount(
registry.ObserveConnectedWellknownPeersCount(
netProvider,
config.LibP2P.Peers,
config.ClientInfo.NetworkMetricsTick,
Expand Down
59 changes: 59 additions & 0 deletions cmd/start_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package cmd

import (
"strings"
"testing"

"github.com/keep-network/keep-core/config"
"github.com/spf13/cobra"
)

func TestNetworkBootstrapFlagDescription_ContainsDeprecationNotice(t *testing.T) {
cmd := &cobra.Command{Use: "test"}
cfg := &config.Config{}

initNetworkFlags(cmd, cfg)

flag := cmd.Flags().Lookup("network.bootstrap")
if flag == nil {
t.Fatal("expected network.bootstrap flag to be registered")
}

usageLower := strings.ToLower(flag.Usage)
if !strings.Contains(usageLower, "deprecated") {
t.Errorf(
"expected flag description to contain deprecation notice, got: %q",
flag.Usage,
)
}
}

func TestIsBootstrap(t *testing.T) {
tests := map[string]struct {
bootstrapValue bool
expected bool
}{
"returns true when bootstrap flag is set": {
bootstrapValue: true,
expected: true,
},
"returns false when bootstrap flag is not set": {
bootstrapValue: false,
expected: false,
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
originalBootstrap := clientConfig.LibP2P.Bootstrap
defer func() { clientConfig.LibP2P.Bootstrap = originalBootstrap }()

clientConfig.LibP2P.Bootstrap = tc.bootstrapValue

got := isBootstrap()
if got != tc.expected {
t.Errorf("expected isBootstrap() to return %v, got %v", tc.expected, got)
}
})
}
}
6 changes: 4 additions & 2 deletions config/_peers/mainnet
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
/dns4/bst-a01.tbtc.boar.network/tcp/5001/ipfs/16Uiu2HAmAmCrLuUmnBgpavU8y8JBUN6jWAQ93JwydZy3ABRyY6wU
/dns4/bst-b01.tbtc.boar.network/tcp/5001/ipfs/16Uiu2HAm4w5HdJQxBnadGRepaiGfWVvtMzhdAGZVcrf9i71mv69V
# TODO: Add at least one additional mainnet peer across a different
# operator/ASN before production rollout. A single peer is a SPOF for
# initial peer discovery of fresh nodes.
/ip4/143.198.18.229/tcp/3919/ipfs/16Uiu2HAmDP4Z6LCogRMictJ6deGs4DRo99A5JTz5u3CLMg7URxC6
3 changes: 2 additions & 1 deletion config/_peers/testnet
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/dns4/bst-a01.test.keep.boar.network/tcp/6001/ipfs/16Uiu2HAmSLDSahiKyTbCNNu8wJmZAsiKF7wuYJ8mogY8ZuAG1jhu
/dns4/keep-operator-1.test.keep-nodes.io/tcp/3920/ipfs/16Uiu2HAmDrk2Bh4VNPUJfKRHTE2CvH9xfKzN4KFnmRJbGLkJFDqL
/dns4/keep-operator-2.test.keep-nodes.io/tcp/3920/ipfs/16Uiu2HAm3ex8rGzwFpWYbRreRUiX9JEYCKxp7KDMzB8RZ6fQWnMa
6 changes: 3 additions & 3 deletions config/peers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ func TestResolvePeers(t *testing.T) {
"mainnet network": {
network: network.Mainnet,
expectedPeers: []string{
"/dns4/bst-a01.tbtc.boar.network/tcp/5001/ipfs/16Uiu2HAmAmCrLuUmnBgpavU8y8JBUN6jWAQ93JwydZy3ABRyY6wU",
"/dns4/bst-b01.tbtc.boar.network/tcp/5001/ipfs/16Uiu2HAm4w5HdJQxBnadGRepaiGfWVvtMzhdAGZVcrf9i71mv69V",
"/ip4/143.198.18.229/tcp/3919/ipfs/16Uiu2HAmDP4Z6LCogRMictJ6deGs4DRo99A5JTz5u3CLMg7URxC6",
}},
"sepolia network": {
network: network.Testnet,
expectedPeers: []string{
"/dns4/bst-a01.test.keep.boar.network/tcp/6001/ipfs/16Uiu2HAmSLDSahiKyTbCNNu8wJmZAsiKF7wuYJ8mogY8ZuAG1jhu",
"/dns4/keep-operator-1.test.keep-nodes.io/tcp/3920/ipfs/16Uiu2HAmDrk2Bh4VNPUJfKRHTE2CvH9xfKzN4KFnmRJbGLkJFDqL",
"/dns4/keep-operator-2.test.keep-nodes.io/tcp/3920/ipfs/16Uiu2HAm3ex8rGzwFpWYbRreRUiX9JEYCKxp7KDMzB8RZ6fQWnMa",
},
},
"developer network": {
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.24

toolchain go1.24.1


replace (
github.com/bnb-chain/tss-lib => github.com/threshold-network/tss-lib v0.0.0-20230901144531-2e712689cfbe
// btcd in version v.0.23 extracted `btcd/btcec` to a separate package `btcd/btcec/v2`.
Expand Down
7 changes: 4 additions & 3 deletions pkg/chain/local_v1/blockcounter.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ type localBlockCounter struct {
}

type watcher struct {
ctx context.Context
channel chan uint64
ctx context.Context
channel chan uint64
closeOnce sync.Once
}

var defaultBlockTime = 500 * time.Millisecond
Expand Down Expand Up @@ -120,7 +121,7 @@ func (lbc *localBlockCounter) count(blockTime ...time.Duration) {

for _, watcher := range watchers {
if watcher.ctx.Err() != nil {
close(watcher.channel)
watcher.closeOnce.Do(func() { close(watcher.channel) })
continue
}

Expand Down
26 changes: 15 additions & 11 deletions pkg/clientinfo/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@ import (
type Source func() float64

// Names under which metrics are exposed.
//
// NOTE: ConnectedWellknownPeersCountMetricName was renamed from
// "connected_bootstrap_count" in v2.6.0. Update any Prometheus queries or
// Grafana dashboards that reference the old name.
const (
ConnectedPeersCountMetricName = "connected_peers_count"
ConnectedBootstrapCountMetricName = "connected_bootstrap_count"
EthConnectivityMetricName = "eth_connectivity"
BtcConnectivityMetricName = "btc_connectivity"
ClientInfoMetricName = "client_info"
ConnectedPeersCountMetricName = "connected_peers_count"
ConnectedWellknownPeersCountMetricName = "connected_wellknown_peers_count"
EthConnectivityMetricName = "eth_connectivity"
BtcConnectivityMetricName = "btc_connectivity"
ClientInfoMetricName = "client_info"
)

const (
Expand Down Expand Up @@ -55,17 +59,17 @@ func (r *Registry) ObserveConnectedPeersCount(
)
}

// ObserveConnectedBootstrapCount triggers an observation process of the
// connected_bootstrap_count metric.
func (r *Registry) ObserveConnectedBootstrapCount(
// ObserveConnectedWellknownPeersCount triggers an observation process of the
// connected_wellknown_peers_count metric.
func (r *Registry) ObserveConnectedWellknownPeersCount(
netProvider net.Provider,
bootstraps []string,
wellknownPeers []string,
tick time.Duration,
) {
input := func() float64 {
currentCount := 0

for _, address := range bootstraps {
for _, address := range wellknownPeers {
if netProvider.ConnectionManager().IsConnected(address) {
currentCount++
}
Expand All @@ -75,7 +79,7 @@ func (r *Registry) ObserveConnectedBootstrapCount(
}

r.observe(
ConnectedBootstrapCountMetricName,
ConnectedWellknownPeersCountMetricName,
input,
validateTick(tick, DefaultNetworkMetricsTick),
)
Expand Down
91 changes: 91 additions & 0 deletions pkg/clientinfo/metrics_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package clientinfo

import (
"context"
"testing"
"time"

keepclientinfo "github.com/keep-network/keep-common/pkg/clientinfo"
"github.com/keep-network/keep-core/pkg/net"
"github.com/keep-network/keep-core/pkg/operator"
)

// mockTransportIdentifier implements net.TransportIdentifier for testing.
type mockTransportIdentifier struct{}

func (m *mockTransportIdentifier) String() string { return "mock-id" }

// mockConnectionManager implements net.ConnectionManager for testing.
type mockConnectionManager struct {
connectedAddresses map[string]bool
}

func (m *mockConnectionManager) ConnectedPeers() []string { return nil }
func (m *mockConnectionManager) ConnectedPeersAddrInfo() map[string][]string {
return nil
}
func (m *mockConnectionManager) GetPeerPublicKey(string) (*operator.PublicKey, error) {
return nil, nil
}
func (m *mockConnectionManager) DisconnectPeer(string) {}
func (m *mockConnectionManager) AddrStrings() []string { return nil }
func (m *mockConnectionManager) IsConnected(address string) bool {
if m.connectedAddresses == nil {
return false
}
return m.connectedAddresses[address]
}

// mockProvider implements net.Provider for testing.
type mockProvider struct {
connectionManager net.ConnectionManager
}

func (m *mockProvider) ID() net.TransportIdentifier { return &mockTransportIdentifier{} }
func (m *mockProvider) Type() string { return "mock" }
func (m *mockProvider) BroadcastChannelFor(string) (net.BroadcastChannel, error) {
return nil, nil
}
func (m *mockProvider) ConnectionManager() net.ConnectionManager {
return m.connectionManager
}
func (m *mockProvider) CreateTransportIdentifier(
*operator.PublicKey,
) (net.TransportIdentifier, error) {
return nil, nil
}
func (m *mockProvider) BroadcastChannelForwarderFor(string) {}

// TestObserveConnectedWellknownPeersCount_Callable verifies that the renamed
// function exists on the Registry type and can be called without panicking.
func TestObserveConnectedWellknownPeersCount_Callable(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

registry := &Registry{keepclientinfo.NewRegistry(), ctx}

provider := &mockProvider{
connectionManager: &mockConnectionManager{
connectedAddresses: map[string]bool{
"/ip4/127.0.0.1/tcp/3919": true,
},
},
}

// The function should execute without panic. We use a recovered call
// to detect if the method does not exist or panics.
defer func() {
if r := recover(); r != nil {
t.Fatalf(
"ObserveConnectedWellknownPeersCount panicked: %v",
r,
)
}
}()

registry.ObserveConnectedWellknownPeersCount(
provider,
[]string{"/ip4/127.0.0.1/tcp/3919"},
1*time.Minute,
)
}
12 changes: 10 additions & 2 deletions pkg/firewall/firewall.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,16 @@ func (al *AllowList) Contains(operatorPublicKey *operator.PublicKey) bool {
return al.allowedPublicKeys[operatorPublicKey.String()]
}

// EmptyAllowList represents an empty firewall allowlist.
var EmptyAllowList = NewAllowList([]*operator.PublicKey{})
// emptyAllowList is the singleton empty allowlist used in production.
// All peers must pass IsRecognized checks; no bypass is available.
var emptyAllowList = NewAllowList([]*operator.PublicKey{})

// EmptyAllowList returns the empty firewall allowlist. In production, this
// ensures all peers are subject to on-chain staking verification with no
// AllowList bypass.
func EmptyAllowList() *AllowList {
return emptyAllowList
}

const (
// PositiveIsRecognizedCachePeriod is the time period the cache maintains
Expand Down
Loading
Loading