[go: nahoru, domu]

Skip to content

Commit

Permalink
feat(core): add periodic version check
Browse files Browse the repository at this point in the history
  • Loading branch information
Matrix89 committed Jun 4, 2024
1 parent e6f9dc6 commit b4a5919
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 100 deletions.
2 changes: 2 additions & 0 deletions cmd/ipfs/kubo/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,8 @@ take effect.
// start MFS pinning thread
startPinMFS(daemonConfigPollInterval, cctx, &ipfsPinMFSNode{node})

core.StartVersionChecker(node)

// The daemon is *finally* ready.
fmt.Printf("Daemon is ready\n")
notifyReady()
Expand Down
112 changes: 12 additions & 100 deletions core/commands/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,11 @@ import (
"fmt"
"io"
"runtime/debug"
"strings"

cmds "github.com/ipfs/go-ipfs-cmds"
version "github.com/ipfs/kubo"
"github.com/ipfs/kubo/core"
"github.com/ipfs/kubo/core/commands/cmdenv"

cmds "github.com/ipfs/go-ipfs-cmds"

versioncmp "github.com/hashicorp/go-version"
"github.com/libp2p/go-libp2p-kad-dht/fullrt"
pstore "github.com/libp2p/go-libp2p/core/peerstore"
)

const (
Expand Down Expand Up @@ -139,12 +134,6 @@ Print out all dependencies and their versions.`,
},
}

type CheckOutput struct {
PeersCounted int
GreatestVersion string
OldVersion bool
}

var checkVersionCommand = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "Checks IPFS version against network (online only).",
Expand All @@ -154,13 +143,10 @@ Checks node versions in our DHT to compare if we're running an older version.`,
Options: []cmds.Option{
cmds.FloatOption(versionCompareNewFractionOptionName, "f", "Fraction of peers with new version to generate update warning.").WithDefault(0.1),
},
Type: CheckOutput{},
Type: core.VersionCheckOutput{},

Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
nd, err := cmdenv.GetNode(env)
if err != nil {
return err
}

if !nd.IsOnline {
return ErrNotOnline
Expand All @@ -170,100 +156,26 @@ Checks node versions in our DHT to compare if we're running an older version.`,
return ErrNotDHT
}

ourVersion, err := versioncmp.NewVersion(strings.Replace(version.CurrentVersionNumber, "-dev", "", -1))
if err != nil {
return fmt.Errorf("could not parse our own version %s: %w",
version.CurrentVersionNumber, err)
return err
}

greatestVersionSeen := ourVersion
totalPeersCounted := 1 // Us (and to avoid division-by-zero edge case).
withGreaterVersion := 0

recordPeerVersion := func(agentVersion string) {
// We process the version as is it assembled in GetUserAgentVersion.
segments := strings.Split(agentVersion, "/")
if len(segments) < 2 {
return
}
if segments[0] != "kubo" {
return
}
versionNumber := segments[1] // As in our CurrentVersionNumber.

// Ignore development releases.
if strings.Contains(versionNumber, "-dev") {
return
}
if strings.Contains(versionNumber, "-rc") {
return
}

peerVersion, err := versioncmp.NewVersion(versionNumber)
if err != nil {
// Do not error on invalid remote versions, just ignore.
return
}

// Valid peer version number.
totalPeersCounted += 1
if ourVersion.LessThan(peerVersion) {
withGreaterVersion += 1
}
if peerVersion.GreaterThan(greatestVersionSeen) {
greatestVersionSeen = peerVersion
}
}
newerFraction, _ := req.Options[versionCompareNewFractionOptionName].(float64)
output, err := core.CheckVersion(nd, newerFraction)

// Logic taken from `ipfs stats dht` command.
if nd.DHTClient != nd.DHT {
client, ok := nd.DHTClient.(*fullrt.FullRT)
if !ok {
return cmds.Errorf(cmds.ErrClient, "could not generate stats for the WAN DHT client type")
}
for _, p := range client.Stat() {
if ver, err := nd.Peerstore.Get(p, "AgentVersion"); err == nil {
recordPeerVersion(ver.(string))
} else if err == pstore.ErrNotFound {
// ignore
} else {
// this is a bug, usually.
log.Errorw(
"failed to get agent version from peerstore",
"error", err,
)
}
}
} else {
for _, pi := range nd.DHT.WAN.RoutingTable().GetPeerInfos() {
if ver, err := nd.Peerstore.Get(pi.Id, "AgentVersion"); err == nil {
recordPeerVersion(ver.(string))
} else if err == pstore.ErrNotFound {
// ignore
} else {
// this is a bug, usually.
log.Errorw(
"failed to get agent version from peerstore",
"error", err,
)
}
}
if err != nil {
return err
}

newerFraction, _ := req.Options[versionCompareNewFractionOptionName].(float64)
if err := cmds.EmitOnce(res, CheckOutput{
PeersCounted: totalPeersCounted,
GreatestVersion: greatestVersionSeen.String(),
OldVersion: (float64(withGreaterVersion) / float64(totalPeersCounted)) > newerFraction,
}); err != nil {
if err := cmds.EmitOnce(res, output); err != nil {
return err
}
return nil
},
Encoders: cmds.EncoderMap{
cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, checkOutput CheckOutput) error {
if checkOutput.OldVersion {
fmt.Fprintf(w, "⚠️WARNING: this Kubo node is running an outdated version compared to other peers, update to %s\n", checkOutput.GreatestVersion)
cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, checkOutput core.VersionCheckOutput) error {
if checkOutput.IsOutdated {
fmt.Fprint(w, checkOutput.Msg)
}
return nil
}),
Expand Down
130 changes: 130 additions & 0 deletions core/ver_checker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package core

import (
"errors"
"fmt"
versioncmp "github.com/hashicorp/go-version"
version "github.com/ipfs/kubo"
"github.com/libp2p/go-libp2p-kad-dht/fullrt"
pstore "github.com/libp2p/go-libp2p/core/peerstore"
"strings"
"time"
)

type VersionCheckOutput struct {
IsOutdated bool
Msg string
}

func StartVersionChecker(nd *IpfsNode) {
ticker := time.NewTicker(time.Hour)
go func() {
for {
output, err := CheckVersion(nd, 0.1)
if err != nil {
log.Errorw("Failed to check version", "error", err)
}
if output.IsOutdated {
fmt.Println(output.Msg)
}

select {
case <-nd.Process.Closing():
return
case <-ticker.C:
continue
}
}
}()
}

func CheckVersion(nd *IpfsNode, newerFraction float64) (VersionCheckOutput, error) {
ourVersion, err := versioncmp.NewVersion(strings.Replace(version.CurrentVersionNumber, "-dev", "", -1))
if err != nil {
return VersionCheckOutput{}, fmt.Errorf("could not parse our own version %s: %w",
version.CurrentVersionNumber, err)
}

greatestVersionSeen := ourVersion
totalPeersCounted := 1 // Us (and to avoid division-by-zero edge case).
withGreaterVersion := 0

recordPeerVersion := func(agentVersion string) {
// We process the version as is it assembled in GetUserAgentVersion.
segments := strings.Split(agentVersion, "/")
if len(segments) < 2 {
return
}
if segments[0] != "kubo" {
return
}
versionNumber := segments[1] // As in our CurrentVersionNumber.

// Ignore development releases.
if strings.Contains(versionNumber, "-dev") {
return
}
if strings.Contains(versionNumber, "-rc") {
return
}

peerVersion, err := versioncmp.NewVersion(versionNumber)
if err != nil {
// Do not error on invalid remote versions, just ignore.
return
}

// Valid peer version number.
totalPeersCounted += 1
if ourVersion.LessThan(peerVersion) {
withGreaterVersion += 1
}
if peerVersion.GreaterThan(greatestVersionSeen) {
greatestVersionSeen = peerVersion
}
}

// Logic taken from `ipfs stats dht` command.
if nd.DHTClient != nd.DHT {
client, ok := nd.DHTClient.(*fullrt.FullRT)
if !ok {
return VersionCheckOutput{}, errors.New("could not generate stats for the WAN DHT client type")
}
for _, p := range client.Stat() {
if ver, err := nd.Peerstore.Get(p, "AgentVersion"); err == nil {
recordPeerVersion(ver.(string))
} else if errors.Is(err, pstore.ErrNotFound) {
// ignore
} else {
// this is a bug, usually.
log.Errorw(
"failed to get agent version from peerstore",
"error", err,
)
}
}
} else {
for _, pi := range nd.DHT.WAN.RoutingTable().GetPeerInfos() {
if ver, err := nd.Peerstore.Get(pi.Id, "AgentVersion"); err == nil {
recordPeerVersion(ver.(string))
} else if errors.Is(err, pstore.ErrNotFound) {
// ignore
} else {
// this is a bug, usually.
log.Errorw(
"failed to get agent version from peerstore",
"error", err,
)
}
}
}

if (float64(withGreaterVersion) / float64(totalPeersCounted)) > newerFraction {
return VersionCheckOutput{
IsOutdated: true,
Msg: fmt.Sprintf("⚠️WARNING: this Kubo node is running an outdated version compared to other peers, update to %s\n", greatestVersionSeen.String()),
}, nil
} else {
return VersionCheckOutput{}, nil
}
}

0 comments on commit b4a5919

Please sign in to comment.