From a8477ffde5a334170fa4023618788f4d0d0afffc Mon Sep 17 00:00:00 2001 From: Carson Farmer Date: Fri, 12 Jun 2020 11:34:25 -0700 Subject: [PATCH] Flag locally created logs as direct (#374) feat: own vs managed log concepts and tooling Directly "managed" logs are those logs created or directly added by the local peer. Otherwise, unmanaged logs are those from other peers in a given thread. Related, "owned" logs are those logs for which the peer has the private key. There can only be one "owned" log, versus potentially many "managed" logs. --- .gitignore | 4 + core/logstore/logstore.go | 14 +++- core/thread/id.go | 21 ++++-- db/db.go | 2 +- db/db_test.go | 10 ++- db/options.go | 8 ++ logstore/logstore.go | 75 +++++++++++++++++-- logstore/lstoreds/metadata.go | 16 ++++ logstore/lstoremem/metadata.go | 13 ++++ net/net.go | 103 ++++++++++++++++---------- net/net_test.go | 129 +++++++++++++++++++++++++++++++++ test/logstore_suite.go | 56 ++++++++++++++ 12 files changed, 395 insertions(+), 56 deletions(-) diff --git a/.gitignore b/.gitignore index 418a4484..b2f27c8b 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,10 @@ api/pb/dart/lib # vscode config folder .vscode/ +.idea/ **/node_modules tags + +# Misc +**.DS_Store diff --git a/core/logstore/logstore.go b/core/logstore/logstore.go index 98b5fe9a..3ed1673c 100644 --- a/core/logstore/logstore.go +++ b/core/logstore/logstore.go @@ -22,6 +22,9 @@ var ErrThreadNotFound = fmt.Errorf("thread not found") // ErrLogNotFound indicates a requested log was not found. var ErrLogNotFound = fmt.Errorf("log not found") +// ErrLogExists indicates a requested log already exists. +var ErrLogExists = fmt.Errorf("log already exists") + // Logstore stores log keys, addresses, heads and thread meta data. type Logstore interface { Close() error @@ -49,6 +52,9 @@ type Logstore interface { // GetLog returns info about a log. GetLog(thread.ID, peer.ID) (thread.LogInfo, error) + // GetManagedLogs returns info about locally managed logs. + GetManagedLogs(thread.ID) ([]thread.LogInfo, error) + // DeleteLog deletes a log. DeleteLog(thread.ID, peer.ID) error } @@ -61,12 +67,18 @@ type ThreadMetadata interface { // PutInt64 stores an int value under key. PutInt64(t thread.ID, key string, val int64) error - // GetString retrieves an int value under key. + // GetString retrieves a string value under key. GetString(t thread.ID, key string) (*string, error) // PutString stores a string value under key. PutString(t thread.ID, key string, val string) error + // GetBool retrieves a boolean value under key. + GetBool(t thread.ID, key string) (*bool, error) + + // PutBool stores a boolean value under key. + PutBool(t thread.ID, key string, val bool) error + // GetBytes retrieves a byte value under key. GetBytes(t thread.ID, key string) (*[]byte, error) diff --git a/core/thread/id.go b/core/thread/id.go index 511f8101..f00f9c3b 100644 --- a/core/thread/id.go +++ b/core/thread/id.go @@ -110,7 +110,7 @@ func Decode(v string) (ID, error) { return Cast(data) } -// Extract the encoding from an ID. If Decode on the same string did +// ExtractEncoding from an ID. If Decode on the same string did // not return an error neither will this function. func ExtractEncoding(v string) (mbase.Encoding, error) { if len(v) < 2 { @@ -171,6 +171,7 @@ func uvError(read int) error { } } +// Validate the ID. func (i ID) Validate() error { data := i.Bytes() return validateIDData(data) @@ -269,7 +270,7 @@ func (i ID) String() string { } } -// String returns the string representation of an ID +// StringOfBase returns the string representation of an ID // encoded is selected base. func (i ID) StringOfBase(base mbase.Encoding) (string, error) { if err := i.Validate(); err != nil { @@ -353,6 +354,7 @@ type Info struct { } // GetOwnLog returns the first log found with a private key. +// This is a strict owership check, vs returning all directly 'managed' logs. func (i Info) GetOwnLog() *LogInfo { for _, lg := range i.Logs { if lg.PrivKey != nil { @@ -364,9 +366,16 @@ func (i Info) GetOwnLog() *LogInfo { // LogInfo holds log keys, addresses, and heads. type LogInfo struct { - ID peer.ID - PubKey crypto.PubKey + // ID is the log's identifier. + ID peer.ID + // PubKey is the log's public key. + PubKey crypto.PubKey + // PrivKey is the log's private key. PrivKey crypto.PrivKey - Addrs []ma.Multiaddr - Head cid.Cid + // Addrs are the addresses associated with the given log. + Addrs []ma.Multiaddr + // Head is the log's current head. + Head cid.Cid + // Managed logs are any logs directly added/created by the host, and/or logs for which we have the private key + Managed bool } diff --git a/db/db.go b/db/db.go index ba2488de..e3fdff0e 100644 --- a/db/db.go +++ b/db/db.go @@ -87,7 +87,7 @@ func NewDB(ctx context.Context, network app.Net, id thread.ID, opts ...NewOption opt(args) } - if _, err := network.CreateThread(ctx, id, net.WithNewThreadToken(args.Token)); err != nil { + if _, err := network.CreateThread(ctx, id, net.WithNewThreadToken(args.Token), net.WithThreadKey(args.ThreadKey)); err != nil { if !errors.Is(err, lstore.ErrThreadExists) { return nil, err } diff --git a/db/db_test.go b/db/db_test.go index 41ecf8d8..e5c4aa82 100644 --- a/db/db_test.go +++ b/db/db_test.go @@ -108,6 +108,9 @@ func TestWithNewName(t *testing.T) { t.Fatalf("expected name %s, got %s", name, d.name) } + _, key, err := d.GetDBInfo() + checkErr(t, err) + // Re-do again to re-use key. If something wasn't closed correctly, would fail checkErr(t, n.Close()) checkErr(t, d.Close()) @@ -117,7 +120,7 @@ func TestWithNewName(t *testing.T) { checkErr(t, err) defer n.Close() defer d.Close() - d, err = NewDB(context.Background(), n, id, WithNewRepoPath(tmpDir)) + d, err = NewDB(context.Background(), n, id, WithNewRepoPath(tmpDir), WithNewThreadKey(key)) checkErr(t, err) if d.name != name { t.Fatalf("expected name %s, got %s", name, d.name) @@ -150,6 +153,9 @@ func TestWithNewEventCodec(t *testing.T) { t.Fatalf("custom event codec wasn't called") } + _, key, err := d.GetDBInfo() + checkErr(t, err) + // Re-do again to re-use key. If something wasn't closed correctly, would fail checkErr(t, n.Close()) checkErr(t, d.Close()) @@ -158,7 +164,7 @@ func TestWithNewEventCodec(t *testing.T) { n, err = common.DefaultNetwork(tmpDir, common.WithNetDebug(true), common.WithNetHostAddr(util.FreeLocalAddr())) checkErr(t, err) defer n.Close() - d, err = NewDB(context.Background(), n, id, WithNewRepoPath(tmpDir), WithNewEventCodec(ec)) + d, err = NewDB(context.Background(), n, id, WithNewRepoPath(tmpDir), WithNewEventCodec(ec), WithNewThreadKey(key)) checkErr(t, err) checkErr(t, d.Close()) } diff --git a/db/options.go b/db/options.go index 24494997..37bdb1aa 100644 --- a/db/options.go +++ b/db/options.go @@ -42,6 +42,7 @@ type NewOptions struct { EventCodec core.EventCodec LowMem bool Debug bool + ThreadKey thread.Key } // NewOption specifies a new db option. @@ -68,6 +69,13 @@ func WithNewToken(t thread.Token) NewOption { } } +// WithNewThreadKey provides control over thread keys to use with a db. +func WithNewThreadKey(key thread.Key) NewOption { + return func(o *NewOptions) { + o.ThreadKey = key + } +} + // WithNewCollections is used to specify collections that // will be created. func WithNewCollections(cs ...CollectionConfig) NewOption { diff --git a/logstore/logstore.go b/logstore/logstore.go index 29e255c4..f84bb11b 100644 --- a/logstore/logstore.go +++ b/logstore/logstore.go @@ -1,6 +1,7 @@ package logstore import ( + "bytes" "fmt" "io" "sync" @@ -11,6 +12,8 @@ import ( "github.com/textileio/go-threads/core/thread" ) +var managedSuffix = "/managed" + // logstore is a collection of books for storing thread logs. type logstore struct { sync.RWMutex @@ -89,13 +92,35 @@ func (ls *logstore) AddThread(info thread.Info) error { if info.Key.Service() == nil { return fmt.Errorf("a service-key is required to add a thread") } - if err := ls.AddServiceKey(info.ID, info.Key.Service()); err != nil { + sk, err := ls.ServiceKey(info.ID) + if err != nil { return err } + if sk == nil { + if err := ls.AddServiceKey(info.ID, info.Key.Service()); err != nil { + return err + } + } else { + // Ensure keys are the same + if !bytes.Equal(info.Key.Service().Bytes(), sk.Bytes()) { + return fmt.Errorf("service-key mismatch") + } + } if info.Key.CanRead() { - if err := ls.AddReadKey(info.ID, info.Key.Read()); err != nil { + rk, err := ls.ReadKey(info.ID) + if err != nil { return err } + if rk == nil { + if err := ls.AddReadKey(info.ID, info.Key.Read()); err != nil { + return err + } + } else { + // Ensure keys are the same + if !bytes.Equal(info.Key.Read().Bytes(), rk.Bytes()) { + return fmt.Errorf("read-key mismatch") + } + } } return nil } @@ -186,15 +211,18 @@ func (ls *logstore) AddLog(id thread.ID, lg thread.LogInfo) error { ls.Lock() defer ls.Unlock() - err := ls.AddPubKey(id, lg.ID, lg.PubKey) - if err != nil { - return err - } if lg.PrivKey != nil { - if err = ls.AddPrivKey(id, lg.ID, lg.PrivKey); err != nil { + if pk, _ := ls.PrivKey(id, lg.ID); pk != nil { + return core.ErrLogExists + } + if err := ls.AddPrivKey(id, lg.ID, lg.PrivKey); err != nil { return err } } + err := ls.AddPubKey(id, lg.ID, lg.PubKey) + if err != nil { + return err + } if err = ls.AddAddrs(id, lg.ID, lg.Addrs, pstore.PermanentAddrTTL); err != nil { return err } @@ -203,6 +231,12 @@ func (ls *logstore) AddLog(id thread.ID, lg thread.LogInfo) error { return err } } + // By definition 'owned' logs are also 'managed' logs. + if lg.Managed || lg.PrivKey != nil { + if err = ls.PutBool(id, lg.ID.Pretty()+managedSuffix, true); err != nil { + return err + } + } return nil } @@ -234,6 +268,13 @@ func (ls *logstore) getLog(id thread.ID, lid peer.ID) (info thread.LogInfo, err if err != nil { return } + managed, err := ls.GetBool(id, lid.Pretty()+managedSuffix) + if err != nil { + return + } + if managed != nil { + info.Managed = *managed + } info.ID = lid info.PubKey = pk info.PrivKey = sk @@ -244,6 +285,26 @@ func (ls *logstore) getLog(id thread.ID, lid peer.ID) (info thread.LogInfo, err return } +// GetManagedLogs returns the logs the host is 'managing' under the given thread. +func (ls *logstore) GetManagedLogs(id thread.ID) ([]thread.LogInfo, error) { + logs, err := ls.LogsWithKeys(id) + if err != nil { + return nil, err + } + var managed []thread.LogInfo + for _, lid := range logs { + lg, err := ls.GetLog(id, lid) + if err != nil { + return nil, err + } + if lg.Managed || lg.PrivKey != nil { + managed = append(managed, lg) + continue + } + } + return managed, nil +} + // DeleteLog deletes a log. func (ls *logstore) DeleteLog(id thread.ID, lid peer.ID) (err error) { ls.Lock() diff --git a/logstore/lstoreds/metadata.go b/logstore/lstoreds/metadata.go index e8213c3a..2d83691b 100644 --- a/logstore/lstoreds/metadata.go +++ b/logstore/lstoreds/metadata.go @@ -60,6 +60,22 @@ func (m *dsThreadMetadata) PutString(t thread.ID, key string, val string) error return m.setValue(t, key, val) } +func (m *dsThreadMetadata) GetBool(t thread.ID, key string) (*bool, error) { + var val bool + err := m.getValue(t, key, &val) + if err == ds.ErrNotFound { + return nil, nil + } + if err != nil { + return nil, err + } + return &val, nil +} + +func (m *dsThreadMetadata) PutBool(t thread.ID, key string, val bool) error { + return m.setValue(t, key, val) +} + func (m *dsThreadMetadata) GetBytes(t thread.ID, key string) (*[]byte, error) { var val []byte err := m.getValue(t, key, &val) diff --git a/logstore/lstoremem/metadata.go b/logstore/lstoremem/metadata.go index 72acf548..c6ad853a 100644 --- a/logstore/lstoremem/metadata.go +++ b/logstore/lstoremem/metadata.go @@ -57,6 +57,19 @@ func (m *memoryThreadMetadata) GetString(t thread.ID, key string) (*string, erro return &val, nil } +func (m *memoryThreadMetadata) PutBool(t thread.ID, key string, val bool) error { + m.putValue(t, key, val) + return nil +} + +func (m *memoryThreadMetadata) GetBool(t thread.ID, key string) (*bool, error) { + val, ok := m.getValue(t, key).(bool) + if !ok { + return nil, nil + } + return &val, nil +} + func (m *memoryThreadMetadata) PutBytes(t thread.ID, key string, val []byte) error { b := make([]byte, len(val)) copy(b, val) diff --git a/net/net.go b/net/net.go index 4d4b6430..652b8dfd 100644 --- a/net/net.go +++ b/net/net.go @@ -207,7 +207,7 @@ func (n *net) CreateThread(_ context.Context, id thread.ID, opts ...core.NewThre log.Debugf("creating thread with identity: %s", identity) } - if err = n.ensureUnique(id); err != nil { + if err = n.ensureUnique(id, args.LogKey); err != nil { return } @@ -234,12 +234,40 @@ func (n *net) CreateThread(_ context.Context, id thread.ID, opts ...core.NewThre return n.getThreadWithAddrs(id) } -func (n *net) ensureUnique(id thread.ID) error { - _, err := n.store.GetThread(id) +func (n *net) ensureUnique(id thread.ID, key crypto.Key) error { + // Check if thread exists. + thrd, err := n.store.GetThread(id) + if errors.Is(err, lstore.ErrThreadNotFound) { + return nil + } + if err != nil { + return err + } + // Early out if no log key required. + if key == nil { + return nil + } + // Ensure we don't already have this log in our store. + if _, ok := key.(crypto.PrivKey); ok { + // Ensure we don't already have our 'own' log. + if thrd.GetOwnLog().PrivKey != nil { + return lstore.ErrThreadExists + } + return nil + } + pubKey, ok := key.(crypto.PubKey) + if !ok { + return fmt.Errorf("invalid log-key") + } + logID, err := peer.IDFromPublicKey(pubKey) + if err != nil { + return err + } + _, err = n.store.GetLog(id, logID) if err == nil { - return lstore.ErrThreadExists + return lstore.ErrLogExists } - if !errors.Is(err, lstore.ErrThreadNotFound) { + if !errors.Is(err, lstore.ErrLogNotFound) { return err } return nil @@ -261,7 +289,7 @@ func (n *net) AddThread(ctx context.Context, addr ma.Multiaddr, opts ...core.New if err = id.Validate(); err != nil { return } - if err = n.ensureUnique(id); err != nil { + if err = n.ensureUnique(id, args.LogKey); err != nil { return } @@ -534,12 +562,14 @@ func (n *net) AddReplicator(ctx context.Context, id thread.ID, paddr ma.Multiadd if err != nil { return } - ownlg, err := n.getOwnLog(info.ID) + managedLogs, err := n.store.GetManagedLogs(info.ID) if err != nil { return } - if err = n.store.AddAddr(info.ID, ownlg.ID, addr, pstore.PermanentAddrTTL); err != nil { - return + for _, lg := range managedLogs { + if err = n.store.AddAddr(info.ID, lg.ID, addr, pstore.PermanentAddrTTL); err != nil { + return + } } info, err = n.store.GetThread(info.ID) // Update info if err != nil { @@ -557,14 +587,20 @@ func (n *net) AddReplicator(ctx context.Context, id thread.ID, paddr ma.Multiadd // Send all logs to the new replicator for _, l := range info.Logs { if err = n.server.pushLog(ctx, info.ID, l, pid, info.Key.Service(), nil); err != nil { - if err := n.store.SetAddrs(info.ID, ownlg.ID, ownlg.Addrs, pstore.PermanentAddrTTL); err != nil { - log.Errorf("error rolling back log address change: %s", err) + for _, lg := range managedLogs { + // Rollback this log only and then bail + if lg.ID.Pretty() == l.ID.Pretty() { + if err := n.store.SetAddrs(info.ID, lg.ID, lg.Addrs, pstore.PermanentAddrTTL); err != nil { + log.Errorf("error rolling back log address change: %s", err) + } + break + } } return } } - // Send the updated log to peers + // Send the updated log(s) to peers var addrs []ma.Multiaddr for _, l := range info.Logs { addrs = append(addrs, l.Addrs...) @@ -588,9 +624,10 @@ func (n *net) AddReplicator(ctx context.Context, id thread.ID, paddr ma.Multiadd if pid.String() == n.host.ID().String() { return } - - if err = n.server.pushLog(ctx, info.ID, ownlg, pid, nil, nil); err != nil { - log.Errorf("error pushing log %s to %s", ownlg.ID, p) + for _, lg := range managedLogs { + if err = n.server.pushLog(ctx, info.ID, lg, pid, nil, nil); err != nil { + log.Errorf("error pushing log %s to %s", lg.ID, p) + } } }(addr) } @@ -706,6 +743,7 @@ func (n *net) getRecord(ctx context.Context, id thread.ID, rid cid.Cid) (core.Re return cbor.GetRecord(ctx, n, rid, sk) } +// Record implements core.Record. The most basic component of a Log. type Record struct { core.Record threadID thread.ID @@ -1022,32 +1060,17 @@ func (n *net) startPulling() { } } -// getOwnLoad returns the log owned by the host under the given thread. -func (n *net) getOwnLog(id thread.ID) (info thread.LogInfo, err error) { - logs, err := n.store.LogsWithKeys(id) - if err != nil { - return - } - for _, lid := range logs { - sk, err := n.store.PrivKey(id, lid) - if err != nil { - return info, err - } - if sk != nil { - return n.store.GetLog(id, lid) - } - } - return info, nil -} - -// getOrCreateOwnLoad returns the log owned by the host under the given thread. -// If no log exists, a new one is created under the given thread. +// getOrCreateOwnLoad returns the first log 'owned' by the host under the given thread. +// If no log exists, a new one is created under the given thread and returned. +// This is a strict 'ownership' check vs returning managed logs. func (n *net) getOrCreateOwnLog(id thread.ID) (info thread.LogInfo, err error) { - info, err = n.getOwnLog(id) + thread, err := n.store.GetThread(id) if err != nil { - return info, err + return } - if info.PubKey != nil { + ownLog := thread.GetOwnLog() + if ownLog != nil { + info = *ownLog return } info, err = createLog(n.host.ID(), nil) @@ -1055,7 +1078,7 @@ func (n *net) getOrCreateOwnLog(id thread.ID) (info thread.LogInfo, err error) { return } err = n.store.AddLog(id, info) - return info, err + return } // createExternalLogIfNotExist creates an external log if doesn't exists. The created @@ -1133,5 +1156,7 @@ func createLog(host peer.ID, key crypto.Key) (info thread.LogInfo, err error) { return } info.Addrs = []ma.Multiaddr{addr} + // If we're creating the log, we're 'managing' it + info.Managed = true return info, nil } diff --git a/net/net_test.go b/net/net_test.go index f9d8c66d..93f9212b 100644 --- a/net/net_test.go +++ b/net/net_test.go @@ -174,6 +174,82 @@ func TestNet_AddThread(t *testing.T) { } } +func TestNet_CreateThreadManaged(t *testing.T) { + t.Parallel() + n := makeNetwork(t) + defer n.Close() + + ctx := context.Background() + info, err := n.CreateThread(ctx, thread.NewIDV1(thread.Raw, 32)) + if err != nil { + t.Fatal(err) + } + sk, pk, err := crypto.GenerateEd25519Key(rand.Reader) + if err != nil { + t.Fatal(err) + } + // Should fail if trying to re-create locally 'owned' thread + _, err = n.CreateThread(ctx, info.ID, core.WithLogKey(sk), core.WithThreadKey(info.Key)) + if err == nil { + t.Fatalf("expected creating thread with second private key to fail") + } + // Should fail if trying to re-create thread with wrong read/service keys + _, err = n.CreateThread(ctx, info.ID, core.WithLogKey(pk)) + if err == nil { + t.Fatalf("expected to fail when using wrong thread key(s)") + } + // Should work if only going to 'manage' re-created thread/log + _, err = n.CreateThread(ctx, info.ID, core.WithLogKey(pk), core.WithThreadKey(info.Key)) + if err != nil { + t.Fatal(err) + } +} + +func TestNet_AddThreadManaged(t *testing.T) { + t.Parallel() + n1 := makeNetwork(t) + defer n1.Close() + n2 := makeNetwork(t) + defer n2.Close() + + n1.Host().Peerstore().AddAddrs(n2.Host().ID(), n2.Host().Addrs(), peerstore.PermanentAddrTTL) + n2.Host().Peerstore().AddAddrs(n1.Host().ID(), n1.Host().Addrs(), peerstore.PermanentAddrTTL) + + ctx := context.Background() + info, err := n1.CreateThread(ctx, thread.NewIDV1(thread.Raw, 32)) + if err != nil { + t.Fatal(err) + } + + addr, err := ma.NewMultiaddr("/p2p/" + n1.Host().ID().String() + "/thread/" + info.ID.String()) + if err != nil { + t.Fatal(err) + } + sk, pk, err := crypto.GenerateEd25519Key(rand.Reader) + if err != nil { + t.Fatal(err) + } + _, err = n2.AddThread(ctx, addr, core.WithThreadKey(info.Key)) + if err != nil { + t.Fatal(err) + } + // Should fail if trying to re-create locally 'owned' thread + _, err = n2.AddThread(ctx, addr, core.WithLogKey(sk), core.WithThreadKey(info.Key)) + if err == nil { + t.Fatalf("expected creating thread with second private key to fail") + } + // Should fail if trying to re-create thread with wrong/missing read/service keys + _, err = n2.AddThread(ctx, addr, core.WithLogKey(pk)) + if err == nil { + t.Fatalf("expected to fail when using wrong thread key(s)") + } + // Should work if only going to 'manage' re-created thread/log + _, err = n2.AddThread(ctx, addr, core.WithLogKey(pk), core.WithThreadKey(info.Key)) + if err != nil { + t.Fatal(err) + } +} + func TestNet_AddReplicator(t *testing.T) { t.Parallel() n1 := makeNetwork(t) @@ -228,6 +304,59 @@ func TestNet_AddReplicator(t *testing.T) { } } +func TestNet_AddReplicatorManaged(t *testing.T) { + t.Parallel() + n1 := makeNetwork(t) + defer n1.Close() + n2 := makeNetwork(t) + defer n2.Close() + + n1.Host().Peerstore().AddAddrs(n2.Host().ID(), n2.Host().Addrs(), peerstore.PermanentAddrTTL) + n2.Host().Peerstore().AddAddrs(n1.Host().ID(), n1.Host().Addrs(), peerstore.PermanentAddrTTL) + + // Create managed thread + tid := thread.NewIDV1(thread.Raw, 32) + ctx := context.Background() + _, pk, err := crypto.GenerateEd25519Key(rand.Reader) + if err != nil { + t.Fatal(err) + } + info, err := n1.CreateThread(ctx, tid, core.WithLogKey(pk)) + if err != nil { + t.Fatal(err) + } + + addr, err := ma.NewMultiaddr("/p2p/" + n2.Host().ID().String()) + if err != nil { + t.Fatal(err) + } + if _, err = n1.AddReplicator(ctx, info.ID, addr); err != nil { + t.Fatal(err) + } + + info2, err := n1.GetThread(context.Background(), info.ID) + if err != nil { + t.Fatal(err) + } + if len(info2.Logs) != 1 { + t.Fatalf("expected 1 log got %d", len(info2.Logs)) + } + if len(info2.Logs[0].Addrs) != 2 { + t.Fatalf("expected 2 addresses got %d", len(info2.Logs[0].Addrs)) + } + + info3, err := n2.GetThread(context.Background(), info.ID) + if err != nil { + t.Fatal(err) + } + if len(info3.Logs) != 1 { + t.Fatalf("expected 1 log got %d", len(info2.Logs)) + } + if len(info3.Logs[0].Addrs) != 2 { + t.Fatalf("expected 2 addresses got %d", len(info3.Logs[0].Addrs)) + } +} + func TestNet_DeleteThread(t *testing.T) { t.Parallel() n := makeNetwork(t) diff --git a/test/logstore_suite.go b/test/logstore_suite.go index 7936e6d4..aa86ccad 100644 --- a/test/logstore_suite.go +++ b/test/logstore_suite.go @@ -311,6 +311,62 @@ func testBasicLogstore(ls core.Logstore) func(t *testing.T) { } } +func testLogstoreManaged(ls core.Logstore) func(t *testing.T) { + return func(t *testing.T) { + tid := thread.NewIDV1(thread.Raw, 24) + addrs := getAddrs(t, 1) + err := ls.AddServiceKey(tid, sym.New()) + check(t, err) + err = ls.AddReadKey(tid, sym.New()) + check(t, err) + priv, pub, _ := crypto.GenerateKeyPair(crypto.RSA, crypto.MinRsaKeyBits) + p, _ := peer.IDFromPrivateKey(priv) + err = ls.AddAddr(tid, p, addrs[0], pstore.PermanentAddrTTL) + check(t, err) + err = ls.AddPubKey(tid, p, pub) + check(t, err) + err = ls.AddPrivKey(tid, p, priv) + check(t, err) + + // Check that log is managed + info, err := ls.GetThread(tid) + check(t, err) + + log, err := ls.GetLog(info.ID, info.Logs[0].ID) + check(t, err) + if log.Managed != true { + t.Fatal("log not managed") + } + + // Test adding owned log to existing thread + priv, pub, _ = crypto.GenerateKeyPair(crypto.RSA, crypto.MinRsaKeyBits) + p, _ = peer.IDFromPrivateKey(priv) + err = ls.AddLog(tid, thread.LogInfo{ + ID: p, + PubKey: pub, + PrivKey: priv, // This should cause a failure + Addrs: getAddrs(t, 1), + }) + if err == nil { + t.Fatal("can't add more than one owned log") + } + + // Test adding managed log to existing thread + err = ls.AddLog(tid, thread.LogInfo{ + ID: p, + PubKey: pub, + Addrs: getAddrs(t, 1), + }) + check(t, err) + + log, err = ls.GetLog(tid, p) + check(t, err) + if log.Managed != true { + t.Fatal("log not managed") + } + } +} + func testMetadata(ls core.Logstore) func(t *testing.T) { return func(t *testing.T) { tids := make([]thread.ID, 10)