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

core/bootstrap: fix panic without backup bootstrap peer functions #10029

Merged
merged 6 commits into from
Sep 21, 2023
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
50 changes: 43 additions & 7 deletions core/bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@
// as backup bootstrap peers.
MaxBackupBootstrapSize int

SaveBackupBootstrapPeers func(context.Context, []peer.AddrInfo)
LoadBackupBootstrapPeers func(context.Context) []peer.AddrInfo
saveBackupBootstrapPeers func(context.Context, []peer.AddrInfo)
loadBackupBootstrapPeers func(context.Context) []peer.AddrInfo
}

// DefaultBootstrapConfig specifies default sane parameters for bootstrapping.
Expand All @@ -72,14 +72,41 @@
MaxBackupBootstrapSize: 20,
}

func BootstrapConfigWithPeers(pis []peer.AddrInfo) BootstrapConfig {
// BootstrapConfigWithPeers creates a default BootstrapConfig configured with
// the specified peers, and optional functions to load and save backup peers.
func BootstrapConfigWithPeers(pis []peer.AddrInfo, options ...func(*BootstrapConfig)) BootstrapConfig {
cfg := DefaultBootstrapConfig
cfg.BootstrapPeers = func() []peer.AddrInfo {
return pis
}
for _, opt := range options {
opt(&cfg)
}
return cfg
}

// WithBackupPeers configures functions to load and save backup bootstrap peers.
func WithBackupPeers(load func(context.Context) []peer.AddrInfo, save func(context.Context, []peer.AddrInfo)) func(*BootstrapConfig) {
if save == nil && load != nil || save != nil && load == nil {
hacdias marked this conversation as resolved.
Show resolved Hide resolved
panic("both load and save backup bootstrap peers functions must be defined")
}
return func(cfg *BootstrapConfig) {
cfg.loadBackupBootstrapPeers = load
cfg.saveBackupBootstrapPeers = save
}
}

// BackupPeers returns the load and save backup peers functions.
func (cfg *BootstrapConfig) BackupPeers() (func(context.Context) []peer.AddrInfo, func(context.Context, []peer.AddrInfo)) {
return cfg.loadBackupBootstrapPeers, cfg.saveBackupBootstrapPeers
}

// SetBackupPeers sets the load and save backup peers functions.
func (cfg *BootstrapConfig) SetBackupPeers(load func(context.Context) []peer.AddrInfo, save func(context.Context, []peer.AddrInfo)) {
opt := WithBackupPeers(load, save)
opt(cfg)
}
Comment on lines +100 to +108
Copy link
Contributor

Choose a reason for hiding this comment

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

That looks racy.
I meant Kubo should pass WithBackupPeers option.
I'll submit a patch given this is unfair to ask you to go rumble in the go internals, thx for this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Jorropo bump - Is there anything else you want me to do, or wait for your patch?


// Bootstrap kicks off IpfsNode bootstrapping. This function will periodically
// check the number of open connections and -- if there are too few -- initiate
// connections to well-known bootstrap peers. It also kicks off subsystem
Expand Down Expand Up @@ -124,7 +151,11 @@
doneWithRound <- struct{}{}
close(doneWithRound) // it no longer blocks periodic

startSavePeersAsTemporaryBootstrapProc(cfg, host, proc)
// If loadBackupBootstrapPeers is not nil then saveBackupBootstrapPeers
// must also not be nil.
if cfg.loadBackupBootstrapPeers != nil {
startSavePeersAsTemporaryBootstrapProc(cfg, host, proc)
}

Check warning on line 158 in core/bootstrap/bootstrap.go

View check run for this annotation

Codecov / codecov/patch

core/bootstrap/bootstrap.go#L157-L158

Added lines #L157 - L158 were not covered by tests

return proc, nil
}
Expand Down Expand Up @@ -185,7 +216,7 @@

// If we didn't reach the target number use previously stored connected peers.
if len(backupPeers) < cfg.MaxBackupBootstrapSize {
oldSavedPeers := cfg.LoadBackupBootstrapPeers(ctx)
oldSavedPeers := cfg.loadBackupBootstrapPeers(ctx)

Check warning on line 219 in core/bootstrap/bootstrap.go

View check run for this annotation

Codecov / codecov/patch

core/bootstrap/bootstrap.go#L219

Added line #L219 was not covered by tests
log.Debugf("missing %d peers to reach backup bootstrap target of %d, trying from previous list of %d saved peers",
cfg.MaxBackupBootstrapSize-len(backupPeers), cfg.MaxBackupBootstrapSize, len(oldSavedPeers))

Expand All @@ -209,7 +240,7 @@
}
}

cfg.SaveBackupBootstrapPeers(ctx, backupPeers)
cfg.saveBackupBootstrapPeers(ctx, backupPeers)

Check warning on line 243 in core/bootstrap/bootstrap.go

View check run for this annotation

Codecov / codecov/patch

core/bootstrap/bootstrap.go#L243

Added line #L243 was not covered by tests
log.Debugf("saved %d peers (of %d target) as bootstrap backup in the config", len(backupPeers), cfg.MaxBackupBootstrapSize)
return nil
}
Expand Down Expand Up @@ -241,9 +272,14 @@
}
}

if cfg.loadBackupBootstrapPeers == nil {
log.Debugf("not enough bootstrap peers to fill the remaining target of %d connections", numToDial)
return ErrNotEnoughBootstrapPeers
}

log.Debugf("not enough bootstrap peers to fill the remaining target of %d connections, trying backup list", numToDial)

tempBootstrapPeers := cfg.LoadBackupBootstrapPeers(ctx)
tempBootstrapPeers := cfg.loadBackupBootstrapPeers(ctx)

Check warning on line 282 in core/bootstrap/bootstrap.go

View check run for this annotation

Codecov / codecov/patch

core/bootstrap/bootstrap.go#L282

Added line #L282 was not covered by tests
if len(tempBootstrapPeers) > 0 {
numToDial -= int(peersConnect(ctx, host, tempBootstrapPeers, numToDial, false))
if numToDial <= 0 {
Expand Down
114 changes: 114 additions & 0 deletions core/bootstrap/bootstrap_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
package bootstrap

import (
"context"
"crypto/rand"
"reflect"
"testing"
"time"

"github.com/libp2p/go-libp2p"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/test"
)
Expand All @@ -23,3 +29,111 @@ func TestRandomizeAddressList(t *testing.T) {
t.Fail()
}
}

func TestLoadAndSaveOptions(t *testing.T) {
loadFunc := func(_ context.Context) []peer.AddrInfo { return nil }
saveFunc := func(_ context.Context, _ []peer.AddrInfo) {}

bootCfg := BootstrapConfigWithPeers(nil, WithBackupPeers(loadFunc, saveFunc))
load, save := bootCfg.BackupPeers()
if load == nil {
t.Fatal("load function not assigned")
}
if reflect.ValueOf(load).Pointer() != reflect.ValueOf(loadFunc).Pointer() {
t.Fatal("load not assigned correct function")
}
if save == nil {
t.Fatal("save function not assigned")
}
if reflect.ValueOf(save).Pointer() != reflect.ValueOf(saveFunc).Pointer() {
t.Fatal("save not assigned correct function")
}

assertPanics(t, "with only load func", func() {
BootstrapConfigWithPeers(nil, WithBackupPeers(loadFunc, nil))
})

assertPanics(t, "with only save func", func() {
BootstrapConfigWithPeers(nil, WithBackupPeers(nil, saveFunc))
})

bootCfg = BootstrapConfigWithPeers(nil, WithBackupPeers(nil, nil))
load, save = bootCfg.BackupPeers()
if load != nil || save != nil {
t.Fatal("load and save functions should both be nil")
}
}

func TestSetBackupPeers(t *testing.T) {
loadFunc := func(_ context.Context) []peer.AddrInfo { return nil }
saveFunc := func(_ context.Context, _ []peer.AddrInfo) {}

bootCfg := DefaultBootstrapConfig
bootCfg.SetBackupPeers(loadFunc, saveFunc)
load, save := bootCfg.BackupPeers()
if load == nil {
t.Fatal("load function not assigned")
}
if reflect.ValueOf(load).Pointer() != reflect.ValueOf(loadFunc).Pointer() {
t.Fatal("load not assigned correct function")
}
if save == nil {
t.Fatal("save function not assigned")
}
if reflect.ValueOf(save).Pointer() != reflect.ValueOf(saveFunc).Pointer() {
t.Fatal("save not assigned correct function")
}

assertPanics(t, "with only load func", func() {
bootCfg.SetBackupPeers(loadFunc, nil)
})

assertPanics(t, "with only save func", func() {
bootCfg.SetBackupPeers(nil, saveFunc)
})

bootCfg.SetBackupPeers(nil, nil)
load, save = bootCfg.BackupPeers()
if load != nil || save != nil {
t.Fatal("load and save functions should both be nil")
}
}

func TestNoTempPeersLoadAndSave(t *testing.T) {
period := 500 * time.Millisecond
bootCfg := BootstrapConfigWithPeers(nil)
bootCfg.MinPeerThreshold = 2
bootCfg.Period = period

priv, pub, err := crypto.GenerateEd25519Key(rand.Reader)
if err != nil {
t.Fatal(err)
}
peerID, err := peer.IDFromPublicKey(pub)
if err != nil {
t.Fatal(err)
}
p2pHost, err := libp2p.New(libp2p.Identity(priv))
if err != nil {
t.Fatal(err)
}

bootstrapper, err := Bootstrap(peerID, p2pHost, nil, bootCfg)
if err != nil {
t.Fatal(err)
}

time.Sleep(4 * period)
bootstrapper.Close()

}

func assertPanics(t *testing.T, name string, f func()) {
defer func() {
if r := recover(); r == nil {
t.Errorf("%s: did not panic as expected", name)
}
}()

f()
}
9 changes: 4 additions & 5 deletions core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,24 +168,23 @@
return ps
}
}
if cfg.SaveBackupBootstrapPeers == nil {
cfg.SaveBackupBootstrapPeers = func(ctx context.Context, peerList []peer.AddrInfo) {
if load, _ := cfg.BackupPeers(); load == nil {
save := func(ctx context.Context, peerList []peer.AddrInfo) {

Check warning on line 172 in core/core.go

View check run for this annotation

Codecov / codecov/patch

core/core.go#L171-L172

Added lines #L171 - L172 were not covered by tests
err := n.saveTempBootstrapPeers(ctx, peerList)
if err != nil {
log.Warnf("saveTempBootstrapPeers failed: %s", err)
return
}
}
}
if cfg.LoadBackupBootstrapPeers == nil {
cfg.LoadBackupBootstrapPeers = func(ctx context.Context) []peer.AddrInfo {
load = func(ctx context.Context) []peer.AddrInfo {

Check warning on line 179 in core/core.go

View check run for this annotation

Codecov / codecov/patch

core/core.go#L179

Added line #L179 was not covered by tests
peerList, err := n.loadTempBootstrapPeers(ctx)
if err != nil {
log.Warnf("loadTempBootstrapPeers failed: %s", err)
return nil
}
return peerList
}
cfg.SetBackupPeers(load, save)

Check warning on line 187 in core/core.go

View check run for this annotation

Codecov / codecov/patch

core/core.go#L187

Added line #L187 was not covered by tests
}

repoConf, err := n.Repo.Config()
Expand Down
Loading