Unverified 提交 66bde413 作者: Whyrusleeping 提交者: GitHub

Merge pull request #4047 from vyzo/ipns-pubsub

namesys/pubsub: pubsub Publisher and Resolver
...@@ -46,6 +46,7 @@ const ( ...@@ -46,6 +46,7 @@ const (
unrestrictedApiAccessKwd = "unrestricted-api" unrestrictedApiAccessKwd = "unrestricted-api"
writableKwd = "writable" writableKwd = "writable"
enableFloodSubKwd = "enable-pubsub-experiment" enableFloodSubKwd = "enable-pubsub-experiment"
enableIPNSPubSubKwd = "enable-namesys-pubsub"
enableMultiplexKwd = "enable-mplex-experiment" enableMultiplexKwd = "enable-mplex-experiment"
// apiAddrKwd = "address-api" // apiAddrKwd = "address-api"
// swarmAddrKwd = "address-swarm" // swarmAddrKwd = "address-swarm"
...@@ -157,6 +158,7 @@ Headers. ...@@ -157,6 +158,7 @@ Headers.
cmdkit.BoolOption(offlineKwd, "Run offline. Do not connect to the rest of the network but provide local API."), cmdkit.BoolOption(offlineKwd, "Run offline. Do not connect to the rest of the network but provide local API."),
cmdkit.BoolOption(migrateKwd, "If true, assume yes at the migrate prompt. If false, assume no."), cmdkit.BoolOption(migrateKwd, "If true, assume yes at the migrate prompt. If false, assume no."),
cmdkit.BoolOption(enableFloodSubKwd, "Instantiate the ipfs daemon with the experimental pubsub feature enabled."), cmdkit.BoolOption(enableFloodSubKwd, "Instantiate the ipfs daemon with the experimental pubsub feature enabled."),
cmdkit.BoolOption(enableIPNSPubSubKwd, "Enable IPNS record distribution through pubsub; enables pubsub."),
cmdkit.BoolOption(enableMultiplexKwd, "Add the experimental 'go-multiplex' stream muxer to libp2p on construction.").WithDefault(true), cmdkit.BoolOption(enableMultiplexKwd, "Add the experimental 'go-multiplex' stream muxer to libp2p on construction.").WithDefault(true),
// TODO: add way to override addresses. tricky part: updating the config if also --init. // TODO: add way to override addresses. tricky part: updating the config if also --init.
...@@ -283,6 +285,7 @@ func daemonFunc(req cmds.Request, re cmds.ResponseEmitter) { ...@@ -283,6 +285,7 @@ func daemonFunc(req cmds.Request, re cmds.ResponseEmitter) {
offline, _, _ := req.Option(offlineKwd).Bool() offline, _, _ := req.Option(offlineKwd).Bool()
pubsub, _, _ := req.Option(enableFloodSubKwd).Bool() pubsub, _, _ := req.Option(enableFloodSubKwd).Bool()
ipnsps, _, _ := req.Option(enableIPNSPubSubKwd).Bool()
mplex, _, _ := req.Option(enableMultiplexKwd).Bool() mplex, _, _ := req.Option(enableMultiplexKwd).Bool()
// Start assembling node config // Start assembling node config
...@@ -292,6 +295,7 @@ func daemonFunc(req cmds.Request, re cmds.ResponseEmitter) { ...@@ -292,6 +295,7 @@ func daemonFunc(req cmds.Request, re cmds.ResponseEmitter) {
Online: !offline, Online: !offline,
ExtraOpts: map[string]bool{ ExtraOpts: map[string]bool{
"pubsub": pubsub, "pubsub": pubsub,
"ipnsps": ipnsps,
"mplex": mplex, "mplex": mplex,
}, },
//TODO(Kubuxu): refactor Online vs Offline by adding Permanent vs Ephemeral //TODO(Kubuxu): refactor Online vs Offline by adding Permanent vs Ephemeral
......
...@@ -210,7 +210,7 @@ func setupNode(ctx context.Context, n *IpfsNode, cfg *BuildCfg) error { ...@@ -210,7 +210,7 @@ func setupNode(ctx context.Context, n *IpfsNode, cfg *BuildCfg) error {
if cfg.Online { if cfg.Online {
do := setupDiscoveryOption(rcfg.Discovery) do := setupDiscoveryOption(rcfg.Discovery)
if err := n.startOnlineServices(ctx, cfg.Routing, cfg.Host, do, cfg.getOpt("pubsub"), cfg.getOpt("mplex")); err != nil { if err := n.startOnlineServices(ctx, cfg.Routing, cfg.Host, do, cfg.getOpt("pubsub"), cfg.getOpt("ipnsps"), cfg.getOpt("mplex")); err != nil {
return err return err
} }
} else { } else {
......
package commands
import (
"errors"
"fmt"
"io"
"strings"
cmds "github.com/ipfs/go-ipfs/commands"
e "github.com/ipfs/go-ipfs/core/commands/e"
ns "github.com/ipfs/go-ipfs/namesys"
cmdkit "gx/ipfs/QmUyfy4QSr3NXym4etEiRyxBLqqAeKHJuRdi8AACxg63fZ/go-ipfs-cmdkit"
)
type ipnsPubsubState struct {
Enabled bool
}
type ipnsPubsubCancel struct {
Canceled bool
}
// IpnsPubsubCmd is the subcommand that allows us to manage the IPNS pubsub system
var IpnsPubsubCmd = &cmds.Command{
Helptext: cmdkit.HelpText{
Tagline: "IPNS pubsub management",
ShortDescription: `
Manage and inspect the state of the IPNS pubsub resolver.
Note: this command is experimental and subject to change as the system is refined
`,
},
Subcommands: map[string]*cmds.Command{
"state": ipnspsStateCmd,
"subs": ipnspsSubsCmd,
"cancel": ipnspsCancelCmd,
},
}
var ipnspsStateCmd = &cmds.Command{
Helptext: cmdkit.HelpText{
Tagline: "Query the state of IPNS pubsub",
},
Run: func(req cmds.Request, res cmds.Response) {
n, err := req.InvocContext().GetNode()
if err != nil {
res.SetError(err, cmdkit.ErrNormal)
return
}
_, ok := n.Namesys.GetResolver("pubsub")
res.SetOutput(&ipnsPubsubState{ok})
},
Type: ipnsPubsubState{},
Marshalers: cmds.MarshalerMap{
cmds.Text: func(res cmds.Response) (io.Reader, error) {
v, err := unwrapOutput(res.Output())
if err != nil {
return nil, err
}
output, ok := v.(*ipnsPubsubState)
if !ok {
return nil, e.TypeErr(output, v)
}
var state string
if output.Enabled {
state = "enabled"
} else {
state = "disabled"
}
return strings.NewReader(state + "\n"), nil
},
},
}
var ipnspsSubsCmd = &cmds.Command{
Helptext: cmdkit.HelpText{
Tagline: "Show current name subscriptions",
},
Run: func(req cmds.Request, res cmds.Response) {
n, err := req.InvocContext().GetNode()
if err != nil {
res.SetError(err, cmdkit.ErrNormal)
return
}
r, ok := n.Namesys.GetResolver("pubsub")
if !ok {
res.SetError(errors.New("IPNS pubsub subsystem is not enabled"), cmdkit.ErrClient)
return
}
psr, ok := r.(*ns.PubsubResolver)
if !ok {
res.SetError(fmt.Errorf("unexpected resolver type: %v", r), cmdkit.ErrNormal)
return
}
res.SetOutput(&stringList{psr.GetSubscriptions()})
},
Type: stringList{},
Marshalers: cmds.MarshalerMap{
cmds.Text: stringListMarshaler,
},
}
var ipnspsCancelCmd = &cmds.Command{
Helptext: cmdkit.HelpText{
Tagline: "Cancel a name subscription",
},
Run: func(req cmds.Request, res cmds.Response) {
n, err := req.InvocContext().GetNode()
if err != nil {
res.SetError(err, cmdkit.ErrNormal)
return
}
r, ok := n.Namesys.GetResolver("pubsub")
if !ok {
res.SetError(errors.New("IPNS pubsub subsystem is not enabled"), cmdkit.ErrClient)
return
}
psr, ok := r.(*ns.PubsubResolver)
if !ok {
res.SetError(fmt.Errorf("unexpected resolver type: %v", r), cmdkit.ErrNormal)
return
}
ok = psr.Cancel(req.Arguments()[0])
res.SetOutput(&ipnsPubsubCancel{ok})
},
Arguments: []cmdkit.Argument{
cmdkit.StringArg("name", true, false, "Name to cancel the subscription for."),
},
Type: ipnsPubsubCancel{},
Marshalers: cmds.MarshalerMap{
cmds.Text: func(res cmds.Response) (io.Reader, error) {
v, err := unwrapOutput(res.Output())
if err != nil {
return nil, err
}
output, ok := v.(*ipnsPubsubCancel)
if !ok {
return nil, e.TypeErr(output, v)
}
var state string
if output.Canceled {
state = "canceled"
} else {
state = "no subscription"
}
return strings.NewReader(state + "\n"), nil
},
},
}
...@@ -63,5 +63,6 @@ Resolve the value of a dnslink: ...@@ -63,5 +63,6 @@ Resolve the value of a dnslink:
Subcommands: map[string]*cmds.Command{ Subcommands: map[string]*cmds.Command{
"publish": PublishCmd, "publish": PublishCmd,
"resolve": IpnsCmd, "resolve": IpnsCmd,
"pubsub": IpnsPubsubCmd,
}, },
} }
...@@ -152,7 +152,7 @@ type Mounts struct { ...@@ -152,7 +152,7 @@ type Mounts struct {
Ipns mount.Mount Ipns mount.Mount
} }
func (n *IpfsNode) startOnlineServices(ctx context.Context, routingOption RoutingOption, hostOption HostOption, do DiscoveryOption, pubsub, mplex bool) error { func (n *IpfsNode) startOnlineServices(ctx context.Context, routingOption RoutingOption, hostOption HostOption, do DiscoveryOption, pubsub, ipnsps, mplex bool) error {
if n.PeerHost != nil { // already online. if n.PeerHost != nil { // already online.
return errors.New("node already online") return errors.New("node already online")
...@@ -249,10 +249,17 @@ func (n *IpfsNode) startOnlineServices(ctx context.Context, routingOption Routin ...@@ -249,10 +249,17 @@ func (n *IpfsNode) startOnlineServices(ctx context.Context, routingOption Routin
return err return err
} }
if pubsub { if pubsub || ipnsps {
n.Floodsub = floodsub.NewFloodSub(ctx, peerhost) n.Floodsub = floodsub.NewFloodSub(ctx, peerhost)
} }
if ipnsps {
err = namesys.AddPubsubNameSystem(ctx, n.Namesys, n.PeerHost, n.Routing, n.Repo.Datastore(), n.Floodsub)
if err != nil {
return err
}
}
n.P2P = p2p.NewP2P(n.Identity, n.PeerHost, n.Peerstore) n.P2P = p2p.NewP2P(n.Identity, n.PeerHost, n.Peerstore)
// setup local discovery // setup local discovery
......
...@@ -48,6 +48,10 @@ func (m mockNamesys) PublishWithEOL(ctx context.Context, name ci.PrivKey, value ...@@ -48,6 +48,10 @@ func (m mockNamesys) PublishWithEOL(ctx context.Context, name ci.PrivKey, value
return errors.New("not implemented for mockNamesys") return errors.New("not implemented for mockNamesys")
} }
func (m mockNamesys) GetResolver(subs string) (namesys.Resolver, bool) {
return nil, false
}
func newNodeWithMockNamesys(ns mockNamesys) (*core.IpfsNode, error) { func newNodeWithMockNamesys(ns mockNamesys) (*core.IpfsNode, error) {
c := config.Config{ c := config.Config{
Identity: config.Identity{ Identity: config.Identity{
......
...@@ -70,6 +70,7 @@ var ErrPublishFailed = errors.New("Could not publish name.") ...@@ -70,6 +70,7 @@ var ErrPublishFailed = errors.New("Could not publish name.")
type NameSystem interface { type NameSystem interface {
Resolver Resolver
Publisher Publisher
ResolverLookup
} }
// Resolver is an object capable of resolving names. // Resolver is an object capable of resolving names.
...@@ -112,3 +113,10 @@ type Publisher interface { ...@@ -112,3 +113,10 @@ type Publisher interface {
// call once the records spec is implemented // call once the records spec is implemented
PublishWithEOL(ctx context.Context, name ci.PrivKey, value path.Path, eol time.Time) error PublishWithEOL(ctx context.Context, name ci.PrivKey, value path.Path, eol time.Time) error
} }
// ResolverLookup is an object capable of finding resolvers for a subsystem
type ResolverLookup interface {
// GetResolver retrieves a resolver associated with a subsystem
GetResolver(subs string) (Resolver, bool)
}
...@@ -2,14 +2,20 @@ package namesys ...@@ -2,14 +2,20 @@ package namesys
import ( import (
"context" "context"
"errors"
"strings" "strings"
"sync"
"time" "time"
path "github.com/ipfs/go-ipfs/path" path "github.com/ipfs/go-ipfs/path"
routing "gx/ipfs/QmPR2JzfKd9poHx9XBhzoFeBBC31ZM3W5iUPKJZWyaoZZm/go-libp2p-routing" routing "gx/ipfs/QmPR2JzfKd9poHx9XBhzoFeBBC31ZM3W5iUPKJZWyaoZZm/go-libp2p-routing"
p2phost "gx/ipfs/QmRS46AyqtpJBsf1zmQdeizSDEzo1qkWR7rdEuPFAv8237/go-libp2p-host"
mh "gx/ipfs/QmU9a9NV9RdPNwZQDYd5uKsm6N6LJLSvLbywDDYFbaaC6P/go-multihash"
floodsub "gx/ipfs/QmVNv1WV6XxzQV4MBuiLX5729wMazaf8TNzm2Sq6ejyHh7/go-libp2p-floodsub"
ds "gx/ipfs/QmVSase1JP7cq9QkPT46oNwdp9pT6kBkG3oqS14y3QcZjG/go-datastore" ds "gx/ipfs/QmVSase1JP7cq9QkPT46oNwdp9pT6kBkG3oqS14y3QcZjG/go-datastore"
peer "gx/ipfs/QmXYjuNuxVzXKJCfWasQk1RqkhVLDM9jtUKhqc2WPQmFSB/go-libp2p-peer" peer "gx/ipfs/QmXYjuNuxVzXKJCfWasQk1RqkhVLDM9jtUKhqc2WPQmFSB/go-libp2p-peer"
isd "gx/ipfs/QmZmmuAXgX73UQmX1jRKjTGmjzq24Jinqkq8vzkBtno4uX/go-is-domain"
ci "gx/ipfs/QmaPbCnUMBohSGo3KnxEa2bHqyJVVeEEcwtqJAYxerieBo/go-libp2p-crypto" ci "gx/ipfs/QmaPbCnUMBohSGo3KnxEa2bHqyJVVeEEcwtqJAYxerieBo/go-libp2p-crypto"
) )
...@@ -36,11 +42,28 @@ func NewNameSystem(r routing.ValueStore, ds ds.Datastore, cachesize int) NameSys ...@@ -36,11 +42,28 @@ func NewNameSystem(r routing.ValueStore, ds ds.Datastore, cachesize int) NameSys
"dht": NewRoutingResolver(r, cachesize), "dht": NewRoutingResolver(r, cachesize),
}, },
publishers: map[string]Publisher{ publishers: map[string]Publisher{
"/ipns/": NewRoutingPublisher(r, ds), "dht": NewRoutingPublisher(r, ds),
}, },
} }
} }
// AddPubsubNameSystem adds the pubsub publisher and resolver to the namesystem
func AddPubsubNameSystem(ctx context.Context, ns NameSystem, host p2phost.Host, r routing.IpfsRouting, ds ds.Datastore, ps *floodsub.PubSub) error {
mpns, ok := ns.(*mpns)
if !ok {
return errors.New("unexpected NameSystem; not an mpns instance")
}
pkf, ok := r.(routing.PubKeyFetcher)
if !ok {
return errors.New("unexpected IpfsRouting; not a PubKeyFetcher instance")
}
mpns.resolvers["pubsub"] = NewPubsubResolver(ctx, host, r, pkf, ps)
mpns.publishers["pubsub"] = NewPubsubPublisher(ctx, host, ds, r, ps)
return nil
}
const DefaultResolverCacheTTL = time.Minute const DefaultResolverCacheTTL = time.Minute
// Resolve implements Resolver. // Resolve implements Resolver.
...@@ -72,38 +95,100 @@ func (ns *mpns) resolveOnce(ctx context.Context, name string) (path.Path, error) ...@@ -72,38 +95,100 @@ func (ns *mpns) resolveOnce(ctx context.Context, name string) (path.Path, error)
return "", ErrResolveFailed return "", ErrResolveFailed
} }
for protocol, resolver := range ns.resolvers { makePath := func(p path.Path) (path.Path, error) {
log.Debugf("Attempting to resolve %s with %s", segments[2], protocol) if len(segments) > 3 {
p, err := resolver.resolveOnce(ctx, segments[2]) return path.FromSegments("", strings.TrimRight(p.String(), "/"), segments[3])
if err == nil { } else {
if len(segments) > 3 { return p, nil
return path.FromSegments("", strings.TrimRight(p.String(), "/"), segments[3]) }
} else { }
return p, err
// Resolver selection:
// 1. if it is a multihash resolve through "pubsub" (if available),
// with fallback to "dht"
// 2. if it is a domain name, resolve through "dns"
// 3. otherwise resolve through the "proquint" resolver
key := segments[2]
_, err := mh.FromB58String(key)
if err == nil {
res, ok := ns.resolvers["pubsub"]
if ok {
p, err := res.resolveOnce(ctx, key)
if err == nil {
return makePath(p)
}
}
res, ok = ns.resolvers["dht"]
if ok {
p, err := res.resolveOnce(ctx, key)
if err == nil {
return makePath(p)
}
}
return "", ErrResolveFailed
}
if isd.IsDomain(key) {
res, ok := ns.resolvers["dns"]
if ok {
p, err := res.resolveOnce(ctx, key)
if err == nil {
return makePath(p)
} }
} }
return "", ErrResolveFailed
} }
res, ok := ns.resolvers["proquint"]
if ok {
p, err := res.resolveOnce(ctx, key)
if err == nil {
return makePath(p)
}
return "", ErrResolveFailed
}
log.Warningf("No resolver found for %s", name) log.Warningf("No resolver found for %s", name)
return "", ErrResolveFailed return "", ErrResolveFailed
} }
// Publish implements Publisher // Publish implements Publisher
func (ns *mpns) Publish(ctx context.Context, name ci.PrivKey, value path.Path) error { func (ns *mpns) Publish(ctx context.Context, name ci.PrivKey, value path.Path) error {
err := ns.publishers["/ipns/"].Publish(ctx, name, value) return ns.PublishWithEOL(ctx, name, value, time.Now().Add(DefaultRecordTTL))
if err != nil {
return err
}
ns.addToDHTCache(name, value, time.Now().Add(DefaultRecordTTL))
return nil
} }
func (ns *mpns) PublishWithEOL(ctx context.Context, name ci.PrivKey, value path.Path, eol time.Time) error { func (ns *mpns) PublishWithEOL(ctx context.Context, name ci.PrivKey, value path.Path, eol time.Time) error {
err := ns.publishers["/ipns/"].PublishWithEOL(ctx, name, value, eol) var dhtErr error
if err != nil {
return err wg := &sync.WaitGroup{}
wg.Add(1)
go func() {
dhtErr = ns.publishers["dht"].PublishWithEOL(ctx, name, value, eol)
if dhtErr == nil {
ns.addToDHTCache(name, value, eol)
}
wg.Done()
}()
pub, ok := ns.publishers["pubsub"]
if ok {
wg.Add(1)
go func() {
err := pub.PublishWithEOL(ctx, name, value, eol)
if err != nil {
log.Warningf("error publishing %s with pubsub: %s", name, err.Error())
}
wg.Done()
}()
} }
ns.addToDHTCache(name, value, eol)
return nil wg.Wait()
return dhtErr
} }
func (ns *mpns) addToDHTCache(key ci.PrivKey, value path.Path, eol time.Time) { func (ns *mpns) addToDHTCache(key ci.PrivKey, value path.Path, eol time.Time) {
...@@ -138,3 +223,16 @@ func (ns *mpns) addToDHTCache(key ci.PrivKey, value path.Path, eol time.Time) { ...@@ -138,3 +223,16 @@ func (ns *mpns) addToDHTCache(key ci.PrivKey, value path.Path, eol time.Time) {
eol: eol, eol: eol,
}) })
} }
// GetResolver implements ResolverLookup
func (ns *mpns) GetResolver(subs string) (Resolver, bool) {
res, ok := ns.resolvers[subs]
if ok {
ires, ok := res.(Resolver)
if ok {
return ires, true
}
}
return nil, false
}
...@@ -58,8 +58,8 @@ func mockResolverTwo() *mockResolver { ...@@ -58,8 +58,8 @@ func mockResolverTwo() *mockResolver {
func TestNamesysResolution(t *testing.T) { func TestNamesysResolution(t *testing.T) {
r := &mpns{ r := &mpns{
resolvers: map[string]resolver{ resolvers: map[string]resolver{
"one": mockResolverOne(), "dht": mockResolverOne(),
"two": mockResolverTwo(), "dns": mockResolverTwo(),
}, },
} }
......
package namesys
import (
"context"
"sync"
"testing"
"time"
path "github.com/ipfs/go-ipfs/path"
mockrouting "github.com/ipfs/go-ipfs/routing/mock"
routing "gx/ipfs/QmPR2JzfKd9poHx9XBhzoFeBBC31ZM3W5iUPKJZWyaoZZm/go-libp2p-routing"
pstore "gx/ipfs/QmPgDWmTmuzvP7QE5zwo1TmjbJme9pmZHNujB2453jkCTr/go-libp2p-peerstore"
testutil "gx/ipfs/QmQgLZP9haZheimMHqqAjJh2LhRmNfEoZDfbtkpeMhi9xK/go-testutil"
p2phost "gx/ipfs/QmRS46AyqtpJBsf1zmQdeizSDEzo1qkWR7rdEuPFAv8237/go-libp2p-host"
netutil "gx/ipfs/QmUUNDRYXgfqdjxTg79ogkciczU5y4WY1tKMU2vEX9CRN7/go-libp2p-netutil"
floodsub "gx/ipfs/QmVNv1WV6XxzQV4MBuiLX5729wMazaf8TNzm2Sq6ejyHh7/go-libp2p-floodsub"
ds "gx/ipfs/QmVSase1JP7cq9QkPT46oNwdp9pT6kBkG3oqS14y3QcZjG/go-datastore"
peer "gx/ipfs/QmXYjuNuxVzXKJCfWasQk1RqkhVLDM9jtUKhqc2WPQmFSB/go-libp2p-peer"
ci "gx/ipfs/QmaPbCnUMBohSGo3KnxEa2bHqyJVVeEEcwtqJAYxerieBo/go-libp2p-crypto"
bhost "gx/ipfs/Qmb37wDRoh9VZMZXmmZktN35szvj9GeBYDtA9giDmXwwd7/go-libp2p-blankhost"
)
func newNetHost(ctx context.Context, t *testing.T) p2phost.Host {
netw := netutil.GenSwarmNetwork(t, ctx)
return bhost.NewBlankHost(netw)
}
func newNetHosts(ctx context.Context, t *testing.T, n int) []p2phost.Host {
var out []p2phost.Host
for i := 0; i < n; i++ {
h := newNetHost(ctx, t)
out = append(out, h)
}
return out
}
// PubKeyFetcher implementation with a global key store
type mockKeyStore struct {
keys map[peer.ID]ci.PubKey
mx sync.Mutex
}
func (m *mockKeyStore) addPubKey(id peer.ID, pkey ci.PubKey) {
m.mx.Lock()
defer m.mx.Unlock()
m.keys[id] = pkey
}
func (m *mockKeyStore) getPubKey(id peer.ID) (ci.PubKey, error) {
m.mx.Lock()
defer m.mx.Unlock()
pkey, ok := m.keys[id]
if ok {
return pkey, nil
}
return nil, routing.ErrNotFound
}
func (m *mockKeyStore) GetPublicKey(ctx context.Context, id peer.ID) (ci.PubKey, error) {
return m.getPubKey(id)
}
func newMockKeyStore() *mockKeyStore {
return &mockKeyStore{
keys: make(map[peer.ID]ci.PubKey),
}
}
// ConentRouting mock
func newMockRouting(ms mockrouting.Server, ks *mockKeyStore, host p2phost.Host) routing.ContentRouting {
id := host.ID()
privk := host.Peerstore().PrivKey(id)
pubk := host.Peerstore().PubKey(id)
pi := host.Peerstore().PeerInfo(id)
ks.addPubKey(id, pubk)
return ms.Client(testutil.NewIdentity(id, pi.Addrs[0], privk, pubk))
}
func newMockRoutingForHosts(ms mockrouting.Server, ks *mockKeyStore, hosts []p2phost.Host) []routing.ContentRouting {
rs := make([]routing.ContentRouting, len(hosts))
for i := 0; i < len(hosts); i++ {
rs[i] = newMockRouting(ms, ks, hosts[i])
}
return rs
}
// tests
func TestPubsubPublishSubscribe(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ms := mockrouting.NewServer()
ks := newMockKeyStore()
pubhost := newNetHost(ctx, t)
pubmr := newMockRouting(ms, ks, pubhost)
pub := NewPubsubPublisher(ctx, pubhost, ds.NewMapDatastore(), pubmr, floodsub.NewFloodSub(ctx, pubhost))
privk := pubhost.Peerstore().PrivKey(pubhost.ID())
pubpinfo := pstore.PeerInfo{ID: pubhost.ID(), Addrs: pubhost.Addrs()}
name := "/ipns/" + pubhost.ID().Pretty()
reshosts := newNetHosts(ctx, t, 5)
resmrs := newMockRoutingForHosts(ms, ks, reshosts)
res := make([]*PubsubResolver, len(reshosts))
for i := 0; i < len(res); i++ {
res[i] = NewPubsubResolver(ctx, reshosts[i], resmrs[i], ks, floodsub.NewFloodSub(ctx, reshosts[i]))
if err := reshosts[i].Connect(ctx, pubpinfo); err != nil {
t.Fatal(err)
}
}
time.Sleep(time.Millisecond * 100)
for i := 0; i < len(res); i++ {
checkResolveNotFound(ctx, t, i, res[i], name)
// delay to avoid connection storms
time.Sleep(time.Millisecond * 100)
}
// let the bootstrap finish
time.Sleep(time.Second * 1)
val := path.Path("/ipfs/QmP1DfoUjiWH2ZBo1PBH6FupdBucbDepx3HpWmEY6JMUpY")
err := pub.Publish(ctx, privk, val)
if err != nil {
t.Fatal(err)
}
// let the flood propagate
time.Sleep(time.Second * 1)
for i := 0; i < len(res); i++ {
checkResolve(ctx, t, i, res[i], name, val)
}
val = path.Path("/ipfs/QmP1wMAqk6aZYRZirbaAwmrNeqFRgQrwBt3orUtvSa1UYD")
err = pub.Publish(ctx, privk, val)
if err != nil {
t.Fatal(err)
}
// let the flood propagate
time.Sleep(time.Second * 1)
for i := 0; i < len(res); i++ {
checkResolve(ctx, t, i, res[i], name, val)
}
// cancel subscriptions
for i := 0; i < len(res); i++ {
res[i].Cancel(name)
}
time.Sleep(time.Millisecond * 100)
nval := path.Path("/ipfs/QmPgDWmTmuzvP7QE5zwo1TmjbJme9pmZHNujB2453jkCTr")
err = pub.Publish(ctx, privk, nval)
if err != nil {
t.Fatal(err)
}
// check we still have the old value in the resolver
time.Sleep(time.Second * 1)
for i := 0; i < len(res); i++ {
checkResolve(ctx, t, i, res[i], name, val)
}
}
func checkResolveNotFound(ctx context.Context, t *testing.T, i int, resolver Resolver, name string) {
_, err := resolver.Resolve(ctx, name)
if err != ErrResolveFailed {
t.Fatalf("[resolver %d] unexpected error: %s", i, err.Error())
}
}
func checkResolve(ctx context.Context, t *testing.T, i int, resolver Resolver, name string, val path.Path) {
xval, err := resolver.Resolve(ctx, name)
if err != nil {
t.Fatalf("[resolver %d] resolve failed: %s", i, err.Error())
}
if xval != val {
t.Fatalf("[resolver %d] unexpected value: %s %s", i, val, xval)
}
}
#!/bin/sh
test_description="Test IPNS pubsub"
. lib/test-lib.sh
# start iptb + wait for peering
NUM_NODES=5
test_expect_success 'init iptb' '
iptb init -n $NUM_NODES --bootstrap=none --port=0
'
startup_cluster $NUM_NODES --enable-namesys-pubsub
test_expect_success 'peer ids' '
PEERID_0=$(iptb get id 0)
'
test_expect_success 'check namesys pubsub state' '
echo enabled > expected &&
ipfsi 0 name pubsub state > state0 &&
ipfsi 1 name pubsub state > state1 &&
ipfsi 2 name pubsub state > state2 &&
test_cmp expected state0 &&
test_cmp expected state1 &&
test_cmp expected state2
'
test_expect_success 'subscribe nodes to the publisher topic' '
ipfsi 1 name resolve /ipns/$PEERID_0 &&
ipfsi 2 name resolve /ipns/$PEERID_0
'
test_expect_success 'check subscriptions' '
echo /ipns/$PEERID_0 > expected &&
ipfsi 1 name pubsub subs > subs1 &&
ipfsi 2 name pubsub subs > subs2 &&
test_cmp expected subs1 &&
test_cmp expected subs2
'
test_expect_success 'add an obect on publisher node' '
echo "ipns is super fun" > file &&
HASH_FILE=$(ipfsi 0 add -q file)
'
test_expect_success 'publish that object as an ipns entry' '
ipfsi 0 name publish $HASH_FILE
'
test_expect_success 'wait for the flood' '
sleep 1
'
test_expect_success 'resolve name in subscriber nodes' '
echo "/ipfs/$HASH_FILE" > expected &&
ipfsi 1 name resolve /ipns/$PEERID_0 > name1 &&
ipfsi 2 name resolve /ipns/$PEERID_0 > name2 &&
test_cmp expected name1 &&
test_cmp expected name2
'
test_expect_success 'cancel subscriptions to the publisher topic' '
ipfsi 1 name pubsub cancel /ipns/$PEERID_0 &&
ipfsi 2 name pubsub cancel /ipns/$PEERID_0
'
test_expect_success 'check subscriptions' '
rm -f expected && touch expected &&
ipfsi 1 name pubsub subs > subs1 &&
ipfsi 2 name pubsub subs > subs2 &&
test_cmp expected subs1 &&
test_cmp expected subs2
'
test_expect_success "shut down iptb" '
iptb stop
'
test_done
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论