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

Merge pull request #4047 from vyzo/ipns-pubsub

namesys/pubsub: pubsub Publisher and Resolver
......@@ -46,6 +46,7 @@ const (
unrestrictedApiAccessKwd = "unrestricted-api"
writableKwd = "writable"
enableFloodSubKwd = "enable-pubsub-experiment"
enableIPNSPubSubKwd = "enable-namesys-pubsub"
enableMultiplexKwd = "enable-mplex-experiment"
// apiAddrKwd = "address-api"
// swarmAddrKwd = "address-swarm"
......@@ -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(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(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),
// 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) {
offline, _, _ := req.Option(offlineKwd).Bool()
pubsub, _, _ := req.Option(enableFloodSubKwd).Bool()
ipnsps, _, _ := req.Option(enableIPNSPubSubKwd).Bool()
mplex, _, _ := req.Option(enableMultiplexKwd).Bool()
// Start assembling node config
......@@ -292,6 +295,7 @@ func daemonFunc(req cmds.Request, re cmds.ResponseEmitter) {
Online: !offline,
ExtraOpts: map[string]bool{
"pubsub": pubsub,
"ipnsps": ipnsps,
"mplex": mplex,
},
//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 {
if cfg.Online {
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
}
} 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:
Subcommands: map[string]*cmds.Command{
"publish": PublishCmd,
"resolve": IpnsCmd,
"pubsub": IpnsPubsubCmd,
},
}
......@@ -152,7 +152,7 @@ type Mounts struct {
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.
return errors.New("node already online")
......@@ -249,10 +249,17 @@ func (n *IpfsNode) startOnlineServices(ctx context.Context, routingOption Routin
return err
}
if pubsub {
if pubsub || ipnsps {
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)
// setup local discovery
......
......@@ -48,6 +48,10 @@ func (m mockNamesys) PublishWithEOL(ctx context.Context, name ci.PrivKey, value
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) {
c := config.Config{
Identity: config.Identity{
......
......@@ -70,6 +70,7 @@ var ErrPublishFailed = errors.New("Could not publish name.")
type NameSystem interface {
Resolver
Publisher
ResolverLookup
}
// Resolver is an object capable of resolving names.
......@@ -112,3 +113,10 @@ type Publisher interface {
// call once the records spec is implemented
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
import (
"context"
"errors"
"strings"
"sync"
"time"
path "github.com/ipfs/go-ipfs/path"
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"
peer "gx/ipfs/QmXYjuNuxVzXKJCfWasQk1RqkhVLDM9jtUKhqc2WPQmFSB/go-libp2p-peer"
isd "gx/ipfs/QmZmmuAXgX73UQmX1jRKjTGmjzq24Jinqkq8vzkBtno4uX/go-is-domain"
ci "gx/ipfs/QmaPbCnUMBohSGo3KnxEa2bHqyJVVeEEcwtqJAYxerieBo/go-libp2p-crypto"
)
......@@ -36,11 +42,28 @@ func NewNameSystem(r routing.ValueStore, ds ds.Datastore, cachesize int) NameSys
"dht": NewRoutingResolver(r, cachesize),
},
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
// Resolve implements Resolver.
......@@ -72,38 +95,100 @@ func (ns *mpns) resolveOnce(ctx context.Context, name string) (path.Path, error)
return "", ErrResolveFailed
}
for protocol, resolver := range ns.resolvers {
log.Debugf("Attempting to resolve %s with %s", segments[2], protocol)
p, err := resolver.resolveOnce(ctx, segments[2])
if err == nil {
if len(segments) > 3 {
return path.FromSegments("", strings.TrimRight(p.String(), "/"), segments[3])
} else {
return p, err
makePath := func(p path.Path) (path.Path, error) {
if len(segments) > 3 {
return path.FromSegments("", strings.TrimRight(p.String(), "/"), segments[3])
} else {
return p, nil
}
}
// 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)
return "", ErrResolveFailed
}
// Publish implements Publisher
func (ns *mpns) Publish(ctx context.Context, name ci.PrivKey, value path.Path) error {
err := ns.publishers["/ipns/"].Publish(ctx, name, value)
if err != nil {
return err
}
ns.addToDHTCache(name, value, time.Now().Add(DefaultRecordTTL))
return nil
return ns.PublishWithEOL(ctx, name, value, time.Now().Add(DefaultRecordTTL))
}
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)
if err != nil {
return err
var dhtErr error
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) {
......@@ -138,3 +223,16 @@ func (ns *mpns) addToDHTCache(key ci.PrivKey, value path.Path, eol time.Time) {
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 {
func TestNamesysResolution(t *testing.T) {
r := &mpns{
resolvers: map[string]resolver{
"one": mockResolverOne(),
"two": mockResolverTwo(),
"dht": mockResolverOne(),
"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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论