Compare commits
81
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e08b3ba659 | ||
|
|
22ec754ca7 | ||
|
|
febfb72cec | ||
|
|
098120da84 | ||
|
|
bf7e256065 | ||
|
|
ebf4034ec7 | ||
|
|
b4cc8d0a61 | ||
|
|
94aea5bafb | ||
|
|
1a23081336 | ||
|
|
3c5e3bed96 | ||
|
|
ef128dd33f | ||
|
|
648aa15b55 | ||
|
|
cd3c6c736a | ||
|
|
79cc89bec2 | ||
|
|
fe31afc337 | ||
|
|
ac8c8e4a31 | ||
|
|
ab34ff4bc9 | ||
|
|
bef93d46ad | ||
|
|
0c1d752acf | ||
|
|
8aa41da1ff | ||
|
|
b12a056dd9 | ||
|
|
0d16c5d7e4 | ||
|
|
145332b0f7 | ||
|
|
51ac955f1c | ||
|
|
b10d72a092 | ||
|
|
822e7f374d | ||
|
|
e094743fbc | ||
|
|
92cbfb1cb3 | ||
|
|
59cf8b46fd | ||
|
|
50f94e7518 | ||
|
|
cbdbe0c34b | ||
|
|
02dd4783bc | ||
|
|
5ffc175f4f | ||
|
|
68cda47ede | ||
|
|
f4cbbfa374 | ||
|
|
53e9c4ab02 | ||
|
|
8dc0ead8f3 | ||
|
|
02b326cc90 | ||
|
|
e5b4f62e48 | ||
|
|
3f08fcb555 | ||
|
|
39f41c52d1 | ||
|
|
ffd613a5fc | ||
|
|
ea6ca799e7 | ||
|
|
6057fc2c7c | ||
|
|
8770da4abf | ||
|
|
a092f7b4a1 | ||
|
|
0ffbb6fa5a | ||
|
|
061522eec2 | ||
|
|
b768dedd56 | ||
|
|
5a02d1c338 | ||
|
|
62f23b7229 | ||
|
|
81aadfde7e | ||
|
|
f943991bc0 | ||
|
|
f0ffc9b7bf | ||
|
|
291b8daf5f | ||
|
|
e929d5431d | ||
|
|
49c3178f65 | ||
|
|
894f4db1ff | ||
|
|
7577c14d5f | ||
|
|
f9bab4d51d | ||
|
|
1fada6f336 | ||
|
|
0095a34167 | ||
|
|
2c573138e6 | ||
|
|
78a3bc6aeb | ||
|
|
89d32044e3 | ||
|
|
d1be3c8a43 | ||
|
|
718a5b04dc | ||
|
|
9d2691ddfd | ||
|
|
4eb2245669 | ||
|
|
fa178d7c26 | ||
|
|
1b47a54f87 | ||
|
|
8e7df9eae5 | ||
|
|
d3636604d7 | ||
|
|
1d4447f126 | ||
|
|
e9668c73a8 | ||
|
|
fe7b97bee9 | ||
|
|
a9889a6245 | ||
|
|
16be33fc96 | ||
|
|
2ea446184d | ||
|
|
18716eebb4 | ||
|
|
53cbfca3ca |
No files matched your search
@@ -20,6 +20,7 @@ proto:
|
||||
protoc --gogofaster_out=$(PKGMAP):. --go-drpc_out=protolib=github.com/gogo/protobuf:. net/streampool/testservice/protos/*.proto
|
||||
protoc --gogofaster_out=:. net/secureservice/handshake/handshakeproto/protos/*.proto
|
||||
protoc --gogofaster_out=$(PKGMAP):. --go-drpc_out=protolib=github.com/gogo/protobuf:. coordinator/coordinatorproto/protos/*.proto
|
||||
protoc --gogofaster_out=:. --go-drpc_out=protolib=github.com/gogo/protobuf:. consensus/consensusproto/protos/*.proto
|
||||
|
||||
deps:
|
||||
go mod download
|
||||
|
||||
@@ -3,7 +3,7 @@ package mock_accountservice
|
||||
import (
|
||||
"github.com/anyproto/any-sync/accountservice"
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/golang/mock/gomock"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func NewAccountServiceWithAccount(ctrl *gomock.Controller, acc *accountdata.AccountKeys) *MockService {
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
app "github.com/anyproto/any-sync/app"
|
||||
accountdata "github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockService is a mock of Service interface.
|
||||
|
||||
@@ -262,6 +262,15 @@ func (app *App) Start(ctx context.Context) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// IterateComponents iterates over all registered components. It's safe for concurrent use.
|
||||
func (app *App) IterateComponents(fn func(Component)) {
|
||||
app.mu.RLock()
|
||||
defer app.mu.RUnlock()
|
||||
for _, s := range app.components {
|
||||
fn(s)
|
||||
}
|
||||
}
|
||||
|
||||
func stackAllGoroutines() []byte {
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
|
||||
@@ -55,6 +55,21 @@ func TestAppServiceRegistry(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestApp_IterateComponents(t *testing.T) {
|
||||
app := new(App)
|
||||
|
||||
app.Register(newTestService(testTypeRunnable, "c1", nil, nil))
|
||||
app.Register(newTestService(testTypeRunnable, "r1", nil, nil))
|
||||
app.Register(newTestService(testTypeComponent, "s1", nil, nil))
|
||||
|
||||
var got []string
|
||||
app.IterateComponents(func(s Component) {
|
||||
got = append(got, s.Name())
|
||||
})
|
||||
|
||||
assert.ElementsMatch(t, []string{"c1", "r1", "s1"}, got)
|
||||
}
|
||||
|
||||
func TestAppStart(t *testing.T) {
|
||||
t.Run("SuccessStartStop", func(t *testing.T) {
|
||||
app := new(App)
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
reflect "reflect"
|
||||
|
||||
ldiff "github.com/anyproto/any-sync/app/ldiff"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockDiff is a mock of Diff interface.
|
||||
|
||||
+17
-1
@@ -2,9 +2,10 @@ package ocache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go.uber.org/zap"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type entryState int
|
||||
@@ -25,6 +26,7 @@ type entry struct {
|
||||
value Object
|
||||
close chan struct{}
|
||||
mx sync.Mutex
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func newEntry(id string, value Object, state entryState) *entry {
|
||||
@@ -49,6 +51,20 @@ func (e *entry) isClosing() bool {
|
||||
return e.state == entryStateClosed || e.state == entryStateClosing
|
||||
}
|
||||
|
||||
func (e *entry) setCancel(cancel context.CancelFunc) {
|
||||
e.mx.Lock()
|
||||
defer e.mx.Unlock()
|
||||
e.cancel = cancel
|
||||
}
|
||||
|
||||
func (e *entry) cancelLoad() {
|
||||
e.mx.Lock()
|
||||
defer e.mx.Unlock()
|
||||
if e.cancel != nil {
|
||||
e.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
func (e *entry) waitLoad(ctx context.Context, id string) (value Object, err error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
|
||||
@@ -3,10 +3,11 @@ package ocache
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"go.uber.org/zap"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -157,7 +158,10 @@ func (c *oCache) Pick(ctx context.Context, id string) (value Object, err error)
|
||||
|
||||
func (c *oCache) load(ctx context.Context, id string, e *entry) {
|
||||
defer close(e.load)
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
e.setCancel(cancel)
|
||||
value, err := c.loadFunc(ctx, id)
|
||||
cancel()
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
@@ -315,6 +319,7 @@ func (c *oCache) Close() (err error) {
|
||||
close(c.closeCh)
|
||||
var toClose []*entry
|
||||
for _, e := range c.data {
|
||||
e.cancelLoad()
|
||||
toClose = append(toClose, e)
|
||||
}
|
||||
c.mu.Unlock()
|
||||
|
||||
@@ -386,6 +386,25 @@ func Test_OCache_Remove(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestOCacheCancelWhenRemove(t *testing.T) {
|
||||
c := New(func(ctx context.Context, id string) (value Object, err error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
}, WithTTL(time.Millisecond*10))
|
||||
stopLoad := make(chan struct{})
|
||||
var err error
|
||||
go func() {
|
||||
_, err = c.Get(context.TODO(), "id")
|
||||
stopLoad <- struct{}{}
|
||||
}()
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
c.Close()
|
||||
<-stopLoad
|
||||
require.Equal(t, context.Canceled, err)
|
||||
}
|
||||
|
||||
func TestOCacheFuzzy(t *testing.T) {
|
||||
t.Run("test many objects gc, get and remove simultaneously, close after", func(t *testing.T) {
|
||||
tryCloseIds := make(map[string]bool)
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
app "github.com/anyproto/any-sync/app"
|
||||
spacesyncproto "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockCredentialProvider is a mock of CredentialProvider interface.
|
||||
|
||||
@@ -3,8 +3,8 @@ package deletionstate
|
||||
import (
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage/mock_spacestorage"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
app "github.com/anyproto/any-sync/app"
|
||||
deletionstate "github.com/anyproto/any-sync/commonspace/deletionstate"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockObjectDeletionState is a mock of ObjectDeletionState interface.
|
||||
|
||||
@@ -3,10 +3,13 @@ package headsync
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/app/ldiff"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/credentialprovider"
|
||||
"github.com/anyproto/any-sync/commonspace/deletionstate"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl"
|
||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
@@ -14,8 +17,8 @@ import (
|
||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||
"github.com/anyproto/any-sync/net/peer"
|
||||
"github.com/anyproto/any-sync/net/rpc/rpcerr"
|
||||
"github.com/anyproto/any-sync/util/slice"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DiffSyncer interface {
|
||||
@@ -38,6 +41,7 @@ func newDiffSyncer(hs *headSync) DiffSyncer {
|
||||
log: log,
|
||||
syncStatus: hs.syncStatus,
|
||||
deletionState: hs.deletionState,
|
||||
syncAcl: hs.syncAcl,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +57,7 @@ type diffSyncer struct {
|
||||
credentialProvider credentialprovider.CredentialProvider
|
||||
syncStatus syncstatus.StatusUpdater
|
||||
treeSyncer treemanager.TreeSyncer
|
||||
syncAcl syncacl.SyncAcl
|
||||
}
|
||||
|
||||
func (d *diffSyncer) Init() {
|
||||
@@ -116,6 +121,7 @@ func (d *diffSyncer) syncWithPeer(ctx context.Context, p peer.Peer) (err error)
|
||||
cl = d.clientFactory.Client(conn)
|
||||
rdiff = NewRemoteDiff(d.spaceId, cl)
|
||||
stateCounter = d.syncStatus.StateCounter()
|
||||
syncAclId = d.syncAcl.Id()
|
||||
)
|
||||
|
||||
newIds, changedIds, removedIds, err := d.diff.Diff(ctx, rdiff)
|
||||
@@ -138,17 +144,29 @@ func (d *diffSyncer) syncWithPeer(ctx context.Context, p peer.Peer) (err error)
|
||||
// not syncing ids which were removed through settings document
|
||||
missingIds := d.deletionState.Filter(newIds)
|
||||
existingIds := append(d.deletionState.Filter(removedIds), d.deletionState.Filter(changedIds)...)
|
||||
|
||||
d.syncStatus.RemoveAllExcept(p.Id(), existingIds, stateCounter)
|
||||
|
||||
prevExistingLen := len(existingIds)
|
||||
existingIds = slice.DiscardFromSlice(existingIds, func(s string) bool {
|
||||
return s == syncAclId
|
||||
})
|
||||
// if we removed acl head from the list
|
||||
if len(existingIds) < prevExistingLen {
|
||||
if syncErr := d.syncAcl.SyncWithPeer(ctx, p.Id()); syncErr != nil {
|
||||
log.Warn("failed to send acl sync message to peer", zap.String("aclId", syncAclId))
|
||||
}
|
||||
}
|
||||
|
||||
// treeSyncer should not get acl id, that's why we filter existing ids before
|
||||
err = d.treeSyncer.SyncAll(ctx, p.Id(), existingIds, missingIds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.log.Info("sync done:", zap.Int("newIds", len(newIds)),
|
||||
d.log.Info("sync done:",
|
||||
zap.Int("newIds", len(newIds)),
|
||||
zap.Int("changedIds", len(changedIds)),
|
||||
zap.Int("removedIds", len(removedIds)),
|
||||
zap.Int("already deleted ids", totalLen-len(existingIds)-len(missingIds)),
|
||||
zap.Int("already deleted ids", totalLen-prevExistingLen-len(missingIds)),
|
||||
zap.String("peerId", p.Id()),
|
||||
)
|
||||
return
|
||||
|
||||
@@ -4,18 +4,19 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/app/ldiff"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage/mock_liststorage"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage/mock_treestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/anyproto/any-sync/net/peer"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
"storj.io/drpc"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type pushSpaceRequestMatcher struct {
|
||||
@@ -108,6 +109,7 @@ func TestDiffSyncer(t *testing.T) {
|
||||
fx.initDiffSyncer(t)
|
||||
defer fx.stop()
|
||||
mPeer := mockPeer{}
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
||||
fx.peerManagerMock.EXPECT().
|
||||
GetResponsiblePeers(gomock.Any()).
|
||||
Return([]peer.Peer{mPeer}, nil)
|
||||
@@ -121,6 +123,26 @@ func TestDiffSyncer(t *testing.T) {
|
||||
require.NoError(t, fx.diffSyncer.Sync(ctx))
|
||||
})
|
||||
|
||||
t.Run("diff syncer sync, acl changed", func(t *testing.T) {
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.initDiffSyncer(t)
|
||||
defer fx.stop()
|
||||
mPeer := mockPeer{}
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
||||
fx.peerManagerMock.EXPECT().
|
||||
GetResponsiblePeers(gomock.Any()).
|
||||
Return([]peer.Peer{mPeer}, nil)
|
||||
fx.diffMock.EXPECT().
|
||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(fx.spaceState.SpaceId, fx.clientMock))).
|
||||
Return([]string{"new"}, []string{"changed"}, nil, nil)
|
||||
fx.deletionStateMock.EXPECT().Filter([]string{"new"}).Return([]string{"new"}).Times(1)
|
||||
fx.deletionStateMock.EXPECT().Filter([]string{"changed"}).Return([]string{"changed", "aclId"}).Times(1)
|
||||
fx.deletionStateMock.EXPECT().Filter(nil).Return(nil).Times(1)
|
||||
fx.treeSyncerMock.EXPECT().SyncAll(gomock.Any(), mPeer.Id(), []string{"changed"}, []string{"new"}).Return(nil)
|
||||
fx.aclMock.EXPECT().SyncWithPeer(gomock.Any(), mPeer.Id()).Return(nil)
|
||||
require.NoError(t, fx.diffSyncer.Sync(ctx))
|
||||
})
|
||||
|
||||
t.Run("diff syncer sync conf error", func(t *testing.T) {
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.initDiffSyncer(t)
|
||||
@@ -138,6 +160,7 @@ func TestDiffSyncer(t *testing.T) {
|
||||
fx.initDiffSyncer(t)
|
||||
defer fx.stop()
|
||||
deletedId := "id"
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
||||
fx.deletionStateMock.EXPECT().Exists(deletedId).Return(true)
|
||||
|
||||
// this should not result in any mock being called
|
||||
@@ -151,6 +174,7 @@ func TestDiffSyncer(t *testing.T) {
|
||||
newId := "newId"
|
||||
newHeads := []string{"h1", "h2"}
|
||||
hash := "hash"
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
||||
fx.diffMock.EXPECT().Set(ldiff.Element{
|
||||
Id: newId,
|
||||
Head: concatStrings(newHeads),
|
||||
@@ -165,11 +189,12 @@ func TestDiffSyncer(t *testing.T) {
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.initDiffSyncer(t)
|
||||
defer fx.stop()
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
||||
aclStorageMock := mock_liststorage.NewMockListStorage(fx.ctrl)
|
||||
settingsStorage := mock_treestorage.NewMockTreeStorage(fx.ctrl)
|
||||
settingsId := "settingsId"
|
||||
aclRootId := "aclRootId"
|
||||
aclRoot := &aclrecordproto.RawAclRecordWithId{
|
||||
aclRoot := &consensusproto.RawRecordWithId{
|
||||
Id: aclRootId,
|
||||
}
|
||||
settingsRoot := &treechangeproto.RawTreeChangeWithId{
|
||||
@@ -210,6 +235,7 @@ func TestDiffSyncer(t *testing.T) {
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.initDiffSyncer(t)
|
||||
defer fx.stop()
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
||||
fx.peerManagerMock.EXPECT().
|
||||
GetResponsiblePeers(gomock.Any()).
|
||||
Return([]peer.Peer{mockPeer{}}, nil)
|
||||
@@ -225,6 +251,7 @@ func TestDiffSyncer(t *testing.T) {
|
||||
fx.initDiffSyncer(t)
|
||||
defer fx.stop()
|
||||
mPeer := mockPeer{}
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
||||
fx.peerManagerMock.EXPECT().
|
||||
GetResponsiblePeers(gomock.Any()).
|
||||
Return([]peer.Peer{mPeer}, nil)
|
||||
|
||||
@@ -3,12 +3,16 @@ package headsync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/app/ldiff"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
config2 "github.com/anyproto/any-sync/commonspace/config"
|
||||
"github.com/anyproto/any-sync/commonspace/credentialprovider"
|
||||
"github.com/anyproto/any-sync/commonspace/deletionstate"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl"
|
||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestate"
|
||||
@@ -21,8 +25,6 @@ import (
|
||||
"github.com/anyproto/any-sync/util/slice"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/exp/slices"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
var log = logger.NewNamed(CName)
|
||||
@@ -60,6 +62,7 @@ type headSync struct {
|
||||
credentialProvider credentialprovider.CredentialProvider
|
||||
syncStatus syncstatus.StatusService
|
||||
deletionState deletionstate.ObjectDeletionState
|
||||
syncAcl syncacl.SyncAcl
|
||||
}
|
||||
|
||||
func New() HeadSync {
|
||||
@@ -71,6 +74,7 @@ var createDiffSyncer = newDiffSyncer
|
||||
func (h *headSync) Init(a *app.App) (err error) {
|
||||
shared := a.MustComponent(spacestate.CName).(*spacestate.SpaceState)
|
||||
cfg := a.MustComponent("config").(config2.ConfigGetter)
|
||||
h.syncAcl = a.MustComponent(syncacl.CName).(syncacl.SyncAcl)
|
||||
h.spaceId = shared.SpaceId
|
||||
h.spaceIsDeleted = shared.SpaceIsDeleted
|
||||
h.syncPeriod = cfg.GetSpace().SyncPeriod
|
||||
@@ -92,6 +96,7 @@ func (h *headSync) Init(a *app.App) (err error) {
|
||||
return h.syncer.Sync(ctx)
|
||||
}
|
||||
h.periodicSync = periodicsync.NewPeriodicSync(h.syncPeriod, time.Minute, sync, h.log)
|
||||
h.syncAcl.SetHeadUpdater(h)
|
||||
// TODO: move to run?
|
||||
h.syncer.Init()
|
||||
return nil
|
||||
@@ -177,6 +182,10 @@ func (h *headSync) fillDiff(objectIds []string) {
|
||||
Head: concatStrings(heads),
|
||||
})
|
||||
}
|
||||
els = append(els, ldiff.Element{
|
||||
Id: h.syncAcl.Id(),
|
||||
Head: h.syncAcl.Head().Id,
|
||||
})
|
||||
h.diff.Set(els...)
|
||||
if err := h.storage.WriteSpaceHash(h.diff.Hash()); err != nil {
|
||||
h.log.Error("can't write space hash", zap.Error(err))
|
||||
|
||||
@@ -11,6 +11,9 @@ import (
|
||||
"github.com/anyproto/any-sync/commonspace/deletionstate"
|
||||
"github.com/anyproto/any-sync/commonspace/deletionstate/mock_deletionstate"
|
||||
"github.com/anyproto/any-sync/commonspace/headsync/mock_headsync"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl/mock_syncacl"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage/mock_treestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||
"github.com/anyproto/any-sync/commonspace/object/treemanager/mock_treemanager"
|
||||
@@ -23,8 +26,8 @@ import (
|
||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||
"github.com/anyproto/any-sync/nodeconf"
|
||||
"github.com/anyproto/any-sync/nodeconf/mock_nodeconf"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
)
|
||||
@@ -60,6 +63,7 @@ type headSyncFixture struct {
|
||||
treeSyncerMock *mock_treemanager.MockTreeSyncer
|
||||
diffMock *mock_ldiff.MockDiff
|
||||
clientMock *mock_spacesyncproto.MockDRPCSpaceSyncClient
|
||||
aclMock *mock_syncacl.MockSyncAcl
|
||||
headSync *headSync
|
||||
diffSyncer *diffSyncer
|
||||
}
|
||||
@@ -87,9 +91,13 @@ func newHeadSyncFixture(t *testing.T) *headSyncFixture {
|
||||
treeSyncerMock := mock_treemanager.NewMockTreeSyncer(ctrl)
|
||||
diffMock := mock_ldiff.NewMockDiff(ctrl)
|
||||
clientMock := mock_spacesyncproto.NewMockDRPCSpaceSyncClient(ctrl)
|
||||
aclMock := mock_syncacl.NewMockSyncAcl(ctrl)
|
||||
aclMock.EXPECT().Name().AnyTimes().Return(syncacl.CName)
|
||||
aclMock.EXPECT().SetHeadUpdater(gomock.Any()).AnyTimes()
|
||||
hs := &headSync{}
|
||||
a := &app.App{}
|
||||
a.Register(spaceState).
|
||||
Register(aclMock).
|
||||
Register(mockConfig{}).
|
||||
Register(configurationMock).
|
||||
Register(storageMock).
|
||||
@@ -115,6 +123,7 @@ func newHeadSyncFixture(t *testing.T) *headSyncFixture {
|
||||
treeSyncerMock: treeSyncerMock,
|
||||
diffMock: diffMock,
|
||||
clientMock: clientMock,
|
||||
aclMock: aclMock,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,6 +153,8 @@ func TestHeadSync(t *testing.T) {
|
||||
treeMock := mock_treestorage.NewMockTreeStorage(fx.ctrl)
|
||||
fx.storageMock.EXPECT().StoredIds().Return(ids, nil)
|
||||
fx.storageMock.EXPECT().TreeStorage(ids[0]).Return(treeMock, nil)
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
||||
fx.aclMock.EXPECT().Head().AnyTimes().Return(&list.AclRecord{Id: "headId"})
|
||||
treeMock.EXPECT().Heads().Return([]string{"h1", "h2"}, nil)
|
||||
fx.diffMock.EXPECT().Set(ldiff.Element{
|
||||
Id: "id1",
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockDiffSyncer is a mock of DiffSyncer interface.
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -2,26 +2,7 @@ syntax = "proto3";
|
||||
package aclrecord;
|
||||
option go_package = "commonspace/object/acl/aclrecordproto";
|
||||
|
||||
message RawAclRecord {
|
||||
bytes payload = 1;
|
||||
bytes signature = 2;
|
||||
bytes acceptorIdentity = 3;
|
||||
bytes acceptorSignature = 4;
|
||||
}
|
||||
|
||||
message RawAclRecordWithId {
|
||||
bytes payload = 1;
|
||||
string id = 2;
|
||||
}
|
||||
|
||||
message AclRecord {
|
||||
string prevId = 1;
|
||||
bytes identity = 2;
|
||||
bytes data = 3;
|
||||
string readKeyId = 4;
|
||||
int64 timestamp = 5;
|
||||
}
|
||||
|
||||
// AclRoot is a root of access control list
|
||||
message AclRoot {
|
||||
bytes identity = 1;
|
||||
bytes masterKey = 2;
|
||||
@@ -31,82 +12,95 @@ message AclRoot {
|
||||
bytes identitySignature = 6;
|
||||
}
|
||||
|
||||
message AclContentValue {
|
||||
oneof value {
|
||||
AclUserAdd userAdd = 1;
|
||||
AclUserRemove userRemove = 2;
|
||||
AclUserPermissionChange userPermissionChange = 3;
|
||||
AclUserInvite userInvite = 4;
|
||||
AclUserJoin userJoin = 5;
|
||||
}
|
||||
// AclAccountInvite contains the public invite key, the private part of which is sent to the user directly
|
||||
message AclAccountInvite {
|
||||
bytes inviteKey = 1;
|
||||
}
|
||||
|
||||
message AclData {
|
||||
repeated AclContentValue aclContent = 1;
|
||||
// AclAccountRequestJoin contains the reference to the invite record and the data of the person who wants to join, confirmed by the private invite key
|
||||
message AclAccountRequestJoin {
|
||||
bytes inviteIdentity = 1;
|
||||
string inviteRecordId = 2;
|
||||
bytes inviteIdentitySignature = 3;
|
||||
bytes metadata = 4;
|
||||
}
|
||||
|
||||
message AclState {
|
||||
repeated string readKeyIds = 1;
|
||||
repeated AclUserState userStates = 2;
|
||||
map<string, AclUserInvite> invites = 3;
|
||||
}
|
||||
|
||||
message AclUserState {
|
||||
// AclAccountRequestAccept contains the reference to join record and all read keys, encrypted with the identity of the requestor
|
||||
message AclAccountRequestAccept {
|
||||
bytes identity = 1;
|
||||
AclUserPermissions permissions = 2;
|
||||
string requestRecordId = 2;
|
||||
repeated AclReadKeyWithRecord encryptedReadKeys = 3;
|
||||
AclUserPermissions permissions = 4;
|
||||
}
|
||||
|
||||
message AclUserAdd {
|
||||
bytes identity = 1;
|
||||
repeated bytes encryptedReadKeys = 2;
|
||||
AclUserPermissions permissions = 3;
|
||||
// AclAccountRequestDecline contains the reference to join record
|
||||
message AclAccountRequestDecline {
|
||||
string requestRecordId = 1;
|
||||
}
|
||||
|
||||
message AclUserInvite {
|
||||
bytes acceptPublicKey = 1;
|
||||
repeated bytes encryptedReadKeys = 2;
|
||||
AclUserPermissions permissions = 3;
|
||||
// AclAccountInviteRevoke revokes the invite record
|
||||
message AclAccountInviteRevoke {
|
||||
string inviteRecordId = 1;
|
||||
}
|
||||
|
||||
message AclUserJoin {
|
||||
bytes identity = 1;
|
||||
bytes acceptSignature = 2;
|
||||
bytes acceptPubKey = 3;
|
||||
repeated bytes encryptedReadKeys = 4;
|
||||
// AclReadKeys are a read key with record id
|
||||
message AclReadKeyWithRecord {
|
||||
string recordId = 1;
|
||||
bytes encryptedReadKey = 2;
|
||||
}
|
||||
|
||||
message AclUserRemove {
|
||||
bytes identity = 1;
|
||||
repeated AclReadKeyReplace readKeyReplaces = 2;
|
||||
}
|
||||
|
||||
message AclReadKeyReplace {
|
||||
// AclEncryptedReadKeys are new key for specific identity
|
||||
message AclEncryptedReadKey {
|
||||
bytes identity = 1;
|
||||
bytes encryptedReadKey = 2;
|
||||
}
|
||||
|
||||
message AclUserPermissionChange {
|
||||
// AclAccountPermissionChange changes permissions of specific account
|
||||
message AclAccountPermissionChange {
|
||||
bytes identity = 1;
|
||||
AclUserPermissions permissions = 2;
|
||||
}
|
||||
|
||||
enum AclUserPermissions {
|
||||
Admin = 0;
|
||||
Writer = 1;
|
||||
Reader = 2;
|
||||
// AclReadKeyChange changes the key for a space
|
||||
message AclReadKeyChange {
|
||||
repeated AclEncryptedReadKey accountKeys = 1;
|
||||
}
|
||||
|
||||
message AclSyncMessage {
|
||||
AclSyncContentValue content = 1;
|
||||
// AclAccountRemove removes an account and changes read key for space
|
||||
message AclAccountRemove {
|
||||
repeated bytes identities = 1;
|
||||
repeated AclEncryptedReadKey accountKeys = 2;
|
||||
}
|
||||
|
||||
// AclSyncContentValue provides different types for acl sync
|
||||
message AclSyncContentValue {
|
||||
// AclAccountRequestRemove adds a request to remove an account
|
||||
message AclAccountRequestRemove {
|
||||
}
|
||||
|
||||
// AclContentValue contains possible values for Acl
|
||||
message AclContentValue {
|
||||
oneof value {
|
||||
AclAddRecords addRecords = 1;
|
||||
AclAccountInvite invite = 1;
|
||||
AclAccountInviteRevoke inviteRevoke = 2;
|
||||
AclAccountRequestJoin requestJoin = 3;
|
||||
AclAccountRequestAccept requestAccept = 4;
|
||||
AclAccountPermissionChange permissionChange = 5;
|
||||
AclAccountRemove accountRemove = 6;
|
||||
AclReadKeyChange readKeyChange = 7;
|
||||
AclAccountRequestDecline requestDecline = 8;
|
||||
AclAccountRequestRemove accountRequestRemove = 9;
|
||||
}
|
||||
}
|
||||
|
||||
message AclAddRecords {
|
||||
repeated RawAclRecordWithId records = 1;
|
||||
}
|
||||
// AclData contains different acl content
|
||||
message AclData {
|
||||
repeated AclContentValue aclContent = 1;
|
||||
}
|
||||
|
||||
// AclUserPermissions contains different possible user roles
|
||||
enum AclUserPermissions {
|
||||
None = 0;
|
||||
Owner = 1;
|
||||
Admin = 2;
|
||||
Writer = 3;
|
||||
Reader = 4;
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/anyproto/any-sync/util/cidutil"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RootContent struct {
|
||||
@@ -15,26 +18,387 @@ type RootContent struct {
|
||||
EncryptedReadKey []byte
|
||||
}
|
||||
|
||||
type RequestJoinPayload struct {
|
||||
InviteRecordId string
|
||||
InviteKey crypto.PrivKey
|
||||
Metadata []byte
|
||||
}
|
||||
|
||||
type RequestAcceptPayload struct {
|
||||
RequestRecordId string
|
||||
Permissions AclPermissions
|
||||
}
|
||||
|
||||
type PermissionChangePayload struct {
|
||||
Identity crypto.PubKey
|
||||
Permissions AclPermissions
|
||||
}
|
||||
|
||||
type AccountRemovePayload struct {
|
||||
Identities []crypto.PubKey
|
||||
ReadKey crypto.SymKey
|
||||
}
|
||||
|
||||
type InviteResult struct {
|
||||
InviteRec *consensusproto.RawRecord
|
||||
InviteKey crypto.PrivKey
|
||||
}
|
||||
|
||||
type AclRecordBuilder interface {
|
||||
Unmarshall(rawIdRecord *aclrecordproto.RawAclRecordWithId) (rec *AclRecord, err error)
|
||||
BuildRoot(content RootContent) (rec *aclrecordproto.RawAclRecordWithId, err error)
|
||||
UnmarshallWithId(rawIdRecord *consensusproto.RawRecordWithId) (rec *AclRecord, err error)
|
||||
Unmarshall(rawRecord *consensusproto.RawRecord) (rec *AclRecord, err error)
|
||||
|
||||
BuildRoot(content RootContent) (rec *consensusproto.RawRecordWithId, err error)
|
||||
BuildInvite() (res InviteResult, err error)
|
||||
BuildInviteRevoke(inviteRecordId string) (rawRecord *consensusproto.RawRecord, err error)
|
||||
BuildRequestJoin(payload RequestJoinPayload) (rawRecord *consensusproto.RawRecord, err error)
|
||||
BuildRequestAccept(payload RequestAcceptPayload) (rawRecord *consensusproto.RawRecord, err error)
|
||||
BuildRequestDecline(requestRecordId string) (rawRecord *consensusproto.RawRecord, err error)
|
||||
BuildRequestRemove() (rawRecord *consensusproto.RawRecord, err error)
|
||||
BuildPermissionChange(payload PermissionChangePayload) (rawRecord *consensusproto.RawRecord, err error)
|
||||
BuildReadKeyChange(newKey crypto.SymKey) (rawRecord *consensusproto.RawRecord, err error)
|
||||
BuildAccountRemove(payload AccountRemovePayload) (rawRecord *consensusproto.RawRecord, err error)
|
||||
}
|
||||
|
||||
type aclRecordBuilder struct {
|
||||
id string
|
||||
keyStorage crypto.KeyStorage
|
||||
id string
|
||||
keyStorage crypto.KeyStorage
|
||||
accountKeys *accountdata.AccountKeys
|
||||
verifier AcceptorVerifier
|
||||
state *AclState
|
||||
}
|
||||
|
||||
func NewAclRecordBuilder(id string, keyStorage crypto.KeyStorage) AclRecordBuilder {
|
||||
func NewAclRecordBuilder(id string, keyStorage crypto.KeyStorage, keys *accountdata.AccountKeys, verifier AcceptorVerifier) AclRecordBuilder {
|
||||
return &aclRecordBuilder{
|
||||
id: id,
|
||||
keyStorage: keyStorage,
|
||||
id: id,
|
||||
keyStorage: keyStorage,
|
||||
accountKeys: keys,
|
||||
verifier: verifier,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) Unmarshall(rawIdRecord *aclrecordproto.RawAclRecordWithId) (rec *AclRecord, err error) {
|
||||
func (a *aclRecordBuilder) buildRecord(aclContent *aclrecordproto.AclContentValue) (rawRec *consensusproto.RawRecord, err error) {
|
||||
aclData := &aclrecordproto.AclData{AclContent: []*aclrecordproto.AclContentValue{
|
||||
aclContent,
|
||||
}}
|
||||
marshalledData, err := aclData.Marshal()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
protoKey, err := a.accountKeys.SignKey.GetPublic().Marshall()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rec := &consensusproto.Record{
|
||||
PrevId: a.state.lastRecordId,
|
||||
Identity: protoKey,
|
||||
Data: marshalledData,
|
||||
Timestamp: time.Now().Unix(),
|
||||
}
|
||||
marshalledRec, err := rec.Marshal()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
signature, err := a.accountKeys.SignKey.Sign(marshalledRec)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rawRec = &consensusproto.RawRecord{
|
||||
Payload: marshalledRec,
|
||||
Signature: signature,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildInvite() (res InviteResult, err error) {
|
||||
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
||||
err = ErrInsufficientPermissions
|
||||
return
|
||||
}
|
||||
privKey, pubKey, err := crypto.GenerateRandomEd25519KeyPair()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
invitePubKey, err := pubKey.Marshall()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
inviteRec := &aclrecordproto.AclAccountInvite{InviteKey: invitePubKey}
|
||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_Invite{Invite: inviteRec}}
|
||||
rawRec, err := a.buildRecord(content)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
res.InviteKey = privKey
|
||||
res.InviteRec = rawRec
|
||||
return
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildInviteRevoke(inviteRecordId string) (rawRecord *consensusproto.RawRecord, err error) {
|
||||
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
||||
err = ErrInsufficientPermissions
|
||||
return
|
||||
}
|
||||
_, exists := a.state.inviteKeys[inviteRecordId]
|
||||
if !exists {
|
||||
err = ErrNoSuchInvite
|
||||
return
|
||||
}
|
||||
revokeRec := &aclrecordproto.AclAccountInviteRevoke{InviteRecordId: inviteRecordId}
|
||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_InviteRevoke{InviteRevoke: revokeRec}}
|
||||
return a.buildRecord(content)
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildRequestJoin(payload RequestJoinPayload) (rawRecord *consensusproto.RawRecord, err error) {
|
||||
key, exists := a.state.inviteKeys[payload.InviteRecordId]
|
||||
if !exists {
|
||||
err = ErrNoSuchInvite
|
||||
return
|
||||
}
|
||||
if !payload.InviteKey.GetPublic().Equals(key) {
|
||||
err = ErrIncorrectInviteKey
|
||||
}
|
||||
rawIdentity, err := a.accountKeys.SignKey.GetPublic().Raw()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
signature, err := payload.InviteKey.Sign(rawIdentity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
protoIdentity, err := a.accountKeys.SignKey.GetPublic().Marshall()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
joinRec := &aclrecordproto.AclAccountRequestJoin{
|
||||
InviteIdentity: protoIdentity,
|
||||
InviteRecordId: payload.InviteRecordId,
|
||||
InviteIdentitySignature: signature,
|
||||
Metadata: payload.Metadata,
|
||||
}
|
||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_RequestJoin{RequestJoin: joinRec}}
|
||||
return a.buildRecord(content)
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildRequestAccept(payload RequestAcceptPayload) (rawRecord *consensusproto.RawRecord, err error) {
|
||||
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
||||
err = ErrInsufficientPermissions
|
||||
return
|
||||
}
|
||||
request, exists := a.state.requestRecords[payload.RequestRecordId]
|
||||
if !exists {
|
||||
err = ErrNoSuchRequest
|
||||
return
|
||||
}
|
||||
var encryptedReadKeys []*aclrecordproto.AclReadKeyWithRecord
|
||||
for keyId, key := range a.state.userReadKeys {
|
||||
rawKey, err := key.Raw()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
enc, err := request.RequestIdentity.Encrypt(rawKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encryptedReadKeys = append(encryptedReadKeys, &aclrecordproto.AclReadKeyWithRecord{
|
||||
RecordId: keyId,
|
||||
EncryptedReadKey: enc,
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
requestIdentityProto, err := request.RequestIdentity.Marshall()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
acceptRec := &aclrecordproto.AclAccountRequestAccept{
|
||||
Identity: requestIdentityProto,
|
||||
RequestRecordId: payload.RequestRecordId,
|
||||
EncryptedReadKeys: encryptedReadKeys,
|
||||
Permissions: aclrecordproto.AclUserPermissions(payload.Permissions),
|
||||
}
|
||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_RequestAccept{RequestAccept: acceptRec}}
|
||||
return a.buildRecord(content)
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildRequestDecline(requestRecordId string) (rawRecord *consensusproto.RawRecord, err error) {
|
||||
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
||||
err = ErrInsufficientPermissions
|
||||
return
|
||||
}
|
||||
_, exists := a.state.requestRecords[requestRecordId]
|
||||
if !exists {
|
||||
err = ErrNoSuchRequest
|
||||
return
|
||||
}
|
||||
declineRec := &aclrecordproto.AclAccountRequestDecline{RequestRecordId: requestRecordId}
|
||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_RequestDecline{RequestDecline: declineRec}}
|
||||
return a.buildRecord(content)
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildPermissionChange(payload PermissionChangePayload) (rawRecord *consensusproto.RawRecord, err error) {
|
||||
permissions := a.state.Permissions(a.state.pubKey)
|
||||
if !permissions.CanManageAccounts() || payload.Identity.Equals(a.state.pubKey) {
|
||||
err = ErrInsufficientPermissions
|
||||
return
|
||||
}
|
||||
if payload.Permissions.IsOwner() {
|
||||
err = ErrIsOwner
|
||||
return
|
||||
}
|
||||
protoIdentity, err := payload.Identity.Marshall()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
permissionRec := &aclrecordproto.AclAccountPermissionChange{
|
||||
Identity: protoIdentity,
|
||||
Permissions: aclrecordproto.AclUserPermissions(payload.Permissions),
|
||||
}
|
||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_PermissionChange{PermissionChange: permissionRec}}
|
||||
return a.buildRecord(content)
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildReadKeyChange(newKey crypto.SymKey) (rawRecord *consensusproto.RawRecord, err error) {
|
||||
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
||||
err = ErrInsufficientPermissions
|
||||
return
|
||||
}
|
||||
rawKey, err := newKey.Raw()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(rawKey) != crypto.KeyBytes {
|
||||
err = ErrIncorrectReadKey
|
||||
return
|
||||
}
|
||||
var aclReadKeys []*aclrecordproto.AclEncryptedReadKey
|
||||
for _, st := range a.state.userStates {
|
||||
protoIdentity, err := st.PubKey.Marshall()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
enc, err := st.PubKey.Encrypt(rawKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aclReadKeys = append(aclReadKeys, &aclrecordproto.AclEncryptedReadKey{
|
||||
Identity: protoIdentity,
|
||||
EncryptedReadKey: enc,
|
||||
})
|
||||
}
|
||||
readRec := &aclrecordproto.AclReadKeyChange{AccountKeys: aclReadKeys}
|
||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_ReadKeyChange{ReadKeyChange: readRec}}
|
||||
return a.buildRecord(content)
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildAccountRemove(payload AccountRemovePayload) (rawRecord *consensusproto.RawRecord, err error) {
|
||||
deletedMap := map[string]struct{}{}
|
||||
for _, key := range payload.Identities {
|
||||
permissions := a.state.Permissions(key)
|
||||
if permissions.IsOwner() {
|
||||
return nil, ErrInsufficientPermissions
|
||||
}
|
||||
if permissions.NoPermissions() {
|
||||
return nil, ErrNoSuchAccount
|
||||
}
|
||||
deletedMap[mapKeyFromPubKey(key)] = struct{}{}
|
||||
}
|
||||
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
||||
err = ErrInsufficientPermissions
|
||||
return
|
||||
}
|
||||
rawKey, err := payload.ReadKey.Raw()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(rawKey) != crypto.KeyBytes {
|
||||
err = ErrIncorrectReadKey
|
||||
return
|
||||
}
|
||||
var aclReadKeys []*aclrecordproto.AclEncryptedReadKey
|
||||
for _, st := range a.state.userStates {
|
||||
if _, exists := deletedMap[mapKeyFromPubKey(st.PubKey)]; exists {
|
||||
continue
|
||||
}
|
||||
protoIdentity, err := st.PubKey.Marshall()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
enc, err := st.PubKey.Encrypt(rawKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aclReadKeys = append(aclReadKeys, &aclrecordproto.AclEncryptedReadKey{
|
||||
Identity: protoIdentity,
|
||||
EncryptedReadKey: enc,
|
||||
})
|
||||
}
|
||||
var marshalledIdentities [][]byte
|
||||
for _, key := range payload.Identities {
|
||||
protoIdentity, err := key.Marshall()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
marshalledIdentities = append(marshalledIdentities, protoIdentity)
|
||||
}
|
||||
removeRec := &aclrecordproto.AclAccountRemove{AccountKeys: aclReadKeys, Identities: marshalledIdentities}
|
||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_AccountRemove{AccountRemove: removeRec}}
|
||||
return a.buildRecord(content)
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildRequestRemove() (rawRecord *consensusproto.RawRecord, err error) {
|
||||
permissions := a.state.Permissions(a.state.pubKey)
|
||||
if permissions.NoPermissions() {
|
||||
err = ErrNoSuchAccount
|
||||
return
|
||||
}
|
||||
if permissions.IsOwner() {
|
||||
err = ErrIsOwner
|
||||
return
|
||||
}
|
||||
removeRec := &aclrecordproto.AclAccountRequestRemove{}
|
||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_AccountRequestRemove{AccountRequestRemove: removeRec}}
|
||||
return a.buildRecord(content)
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) Unmarshall(rawRecord *consensusproto.RawRecord) (rec *AclRecord, err error) {
|
||||
aclRecord := &consensusproto.Record{}
|
||||
err = proto.Unmarshal(rawRecord.Payload, aclRecord)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pubKey, err := a.keyStorage.PubKeyFromProto(aclRecord.Identity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
aclData := &aclrecordproto.AclData{}
|
||||
err = proto.Unmarshal(aclRecord.Data, aclData)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rec = &AclRecord{
|
||||
PrevId: aclRecord.PrevId,
|
||||
Timestamp: aclRecord.Timestamp,
|
||||
Data: aclRecord.Data,
|
||||
Signature: rawRecord.Signature,
|
||||
Identity: pubKey,
|
||||
Model: aclData,
|
||||
}
|
||||
res, err := pubKey.Verify(rawRecord.Payload, rawRecord.Signature)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !res {
|
||||
err = ErrInvalidSignature
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) UnmarshallWithId(rawIdRecord *consensusproto.RawRecordWithId) (rec *AclRecord, err error) {
|
||||
var (
|
||||
rawRec = &aclrecordproto.RawAclRecord{}
|
||||
rawRec = &consensusproto.RawRecord{}
|
||||
pubKey crypto.PubKey
|
||||
)
|
||||
err = proto.Unmarshal(rawIdRecord.Payload, rawRec)
|
||||
@@ -53,14 +417,17 @@ func (a *aclRecordBuilder) Unmarshall(rawIdRecord *aclrecordproto.RawAclRecordWi
|
||||
}
|
||||
rec = &AclRecord{
|
||||
Id: rawIdRecord.Id,
|
||||
ReadKeyId: rawIdRecord.Id,
|
||||
Timestamp: aclRoot.Timestamp,
|
||||
Signature: rawRec.Signature,
|
||||
Identity: pubKey,
|
||||
Model: aclRoot,
|
||||
}
|
||||
} else {
|
||||
aclRecord := &aclrecordproto.AclRecord{}
|
||||
err = a.verifier.VerifyAcceptor(rawRec)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
aclRecord := &consensusproto.Record{}
|
||||
err = proto.Unmarshal(rawRec.Payload, aclRecord)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -69,14 +436,19 @@ func (a *aclRecordBuilder) Unmarshall(rawIdRecord *aclrecordproto.RawAclRecordWi
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
aclData := &aclrecordproto.AclData{}
|
||||
err = proto.Unmarshal(aclRecord.Data, aclData)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rec = &AclRecord{
|
||||
Id: rawIdRecord.Id,
|
||||
PrevId: aclRecord.PrevId,
|
||||
ReadKeyId: aclRecord.ReadKeyId,
|
||||
Timestamp: aclRecord.Timestamp,
|
||||
Data: aclRecord.Data,
|
||||
Signature: rawRec.Signature,
|
||||
Identity: pubKey,
|
||||
Model: aclData,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +456,7 @@ func (a *aclRecordBuilder) Unmarshall(rawIdRecord *aclrecordproto.RawAclRecordWi
|
||||
return
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildRoot(content RootContent) (rec *aclrecordproto.RawAclRecordWithId, err error) {
|
||||
func (a *aclRecordBuilder) BuildRoot(content RootContent) (rec *consensusproto.RawRecordWithId, err error) {
|
||||
rawIdentity, err := content.PrivKey.GetPublic().Raw()
|
||||
if err != nil {
|
||||
return
|
||||
@@ -118,8 +490,8 @@ func (a *aclRecordBuilder) BuildRoot(content RootContent) (rec *aclrecordproto.R
|
||||
|
||||
func verifyRaw(
|
||||
pubKey crypto.PubKey,
|
||||
rawRec *aclrecordproto.RawAclRecord,
|
||||
recWithId *aclrecordproto.RawAclRecordWithId) (err error) {
|
||||
rawRec *consensusproto.RawRecord,
|
||||
recWithId *consensusproto.RawRecordWithId) (err error) {
|
||||
// verifying signature
|
||||
res, err := pubKey.Verify(rawRec.Payload, rawRec.Signature)
|
||||
if err != nil {
|
||||
@@ -137,7 +509,7 @@ func verifyRaw(
|
||||
return
|
||||
}
|
||||
|
||||
func marshalAclRoot(aclRoot *aclrecordproto.AclRoot, key crypto.PrivKey) (rawWithId *aclrecordproto.RawAclRecordWithId, err error) {
|
||||
func marshalAclRoot(aclRoot *aclrecordproto.AclRoot, key crypto.PrivKey) (rawWithId *consensusproto.RawRecordWithId, err error) {
|
||||
marshalledRoot, err := aclRoot.Marshal()
|
||||
if err != nil {
|
||||
return
|
||||
@@ -146,7 +518,7 @@ func marshalAclRoot(aclRoot *aclrecordproto.AclRoot, key crypto.PrivKey) (rawWit
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
raw := &aclrecordproto.RawAclRecord{
|
||||
raw := &consensusproto.RawRecord{
|
||||
Payload: marshalledRoot,
|
||||
Signature: signature,
|
||||
}
|
||||
@@ -158,7 +530,7 @@ func marshalAclRoot(aclRoot *aclrecordproto.AclRoot, key crypto.PrivKey) (rawWit
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rawWithId = &aclrecordproto.RawAclRecordWithId{
|
||||
rawWithId = &consensusproto.RawRecordWithId{
|
||||
Payload: marshalledRaw,
|
||||
Id: aclHeadId,
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAclRecordBuilder_BuildUserJoin(t *testing.T) {
|
||||
return
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package list
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
@@ -13,19 +13,24 @@ import (
|
||||
var log = logger.NewNamedSugared("common.commonspace.acllist")
|
||||
|
||||
var (
|
||||
ErrNoSuchUser = errors.New("no such user")
|
||||
ErrFailedToDecrypt = errors.New("failed to decrypt key")
|
||||
ErrUserRemoved = errors.New("user was removed from the document")
|
||||
ErrDocumentForbidden = errors.New("your user was forbidden access to the document")
|
||||
ErrUserAlreadyExists = errors.New("user already exists")
|
||||
ErrNoSuchRecord = errors.New("no such record")
|
||||
ErrNoSuchInvite = errors.New("no such invite")
|
||||
ErrOldInvite = errors.New("invite is too old")
|
||||
ErrInsufficientPermissions = errors.New("insufficient permissions")
|
||||
ErrNoReadKey = errors.New("acl state doesn't have a read key")
|
||||
ErrInvalidSignature = errors.New("signature is invalid")
|
||||
ErrIncorrectRoot = errors.New("incorrect root")
|
||||
ErrIncorrectRecordSequence = errors.New("incorrect prev id of a record")
|
||||
ErrNoSuchAccount = errors.New("no such account")
|
||||
ErrPendingRequest = errors.New("already exists pending request")
|
||||
ErrUnexpectedContentType = errors.New("unexpected content type")
|
||||
ErrIncorrectIdentity = errors.New("incorrect identity")
|
||||
ErrIncorrectInviteKey = errors.New("incorrect invite key")
|
||||
ErrFailedToDecrypt = errors.New("failed to decrypt key")
|
||||
ErrNoSuchRecord = errors.New("no such record")
|
||||
ErrNoSuchRequest = errors.New("no such request")
|
||||
ErrNoSuchInvite = errors.New("no such invite")
|
||||
ErrInsufficientPermissions = errors.New("insufficient permissions")
|
||||
ErrIsOwner = errors.New("can't be made by owner")
|
||||
ErrIncorrectNumberOfAccounts = errors.New("incorrect number of accounts")
|
||||
ErrDuplicateAccounts = errors.New("duplicate accounts")
|
||||
ErrNoReadKey = errors.New("acl state doesn't have a read key")
|
||||
ErrIncorrectReadKey = errors.New("incorrect read key")
|
||||
ErrInvalidSignature = errors.New("signature is invalid")
|
||||
ErrIncorrectRoot = errors.New("incorrect root")
|
||||
ErrIncorrectRecordSequence = errors.New("incorrect prev id of a record")
|
||||
)
|
||||
|
||||
type UserPermissionPair struct {
|
||||
@@ -36,37 +41,71 @@ type UserPermissionPair struct {
|
||||
type AclState struct {
|
||||
id string
|
||||
currentReadKeyId string
|
||||
userReadKeys map[string]crypto.SymKey
|
||||
userStates map[string]AclUserState
|
||||
statesAtRecord map[string][]AclUserState
|
||||
key crypto.PrivKey
|
||||
pubKey crypto.PubKey
|
||||
keyStore crypto.KeyStorage
|
||||
totalReadKeys int
|
||||
// userReadKeys is a map recordId -> read key which tells us about every read key
|
||||
userReadKeys map[string]crypto.SymKey
|
||||
// userStates is a map pubKey -> state which defines current user state
|
||||
userStates map[string]AclUserState
|
||||
// statesAtRecord is a map recordId -> state which define user state at particular record
|
||||
// probably this can grow rather large at some point, so we can maybe optimise later to have:
|
||||
// - map pubKey -> []recordIds (where recordIds is an array where such identity permissions were changed)
|
||||
statesAtRecord map[string][]AclUserState
|
||||
// inviteKeys is a map recordId -> invite
|
||||
inviteKeys map[string]crypto.PubKey
|
||||
// requestRecords is a map recordId -> RequestRecord
|
||||
requestRecords map[string]RequestRecord
|
||||
// pendingRequests is a map pubKey -> recordId
|
||||
pendingRequests map[string]string
|
||||
key crypto.PrivKey
|
||||
pubKey crypto.PubKey
|
||||
keyStore crypto.KeyStorage
|
||||
totalReadKeys int
|
||||
|
||||
lastRecordId string
|
||||
lastRecordId string
|
||||
contentValidator ContentValidator
|
||||
}
|
||||
|
||||
func newAclStateWithKeys(
|
||||
id string,
|
||||
key crypto.PrivKey) (*AclState, error) {
|
||||
return &AclState{
|
||||
id: id,
|
||||
key: key,
|
||||
pubKey: key.GetPublic(),
|
||||
userReadKeys: make(map[string]crypto.SymKey),
|
||||
userStates: make(map[string]AclUserState),
|
||||
statesAtRecord: make(map[string][]AclUserState),
|
||||
}, nil
|
||||
st := &AclState{
|
||||
id: id,
|
||||
key: key,
|
||||
pubKey: key.GetPublic(),
|
||||
userReadKeys: make(map[string]crypto.SymKey),
|
||||
userStates: make(map[string]AclUserState),
|
||||
statesAtRecord: make(map[string][]AclUserState),
|
||||
inviteKeys: make(map[string]crypto.PubKey),
|
||||
requestRecords: make(map[string]RequestRecord),
|
||||
pendingRequests: make(map[string]string),
|
||||
keyStore: crypto.NewKeyStorage(),
|
||||
}
|
||||
st.contentValidator = &contentValidator{
|
||||
keyStore: st.keyStore,
|
||||
aclState: st,
|
||||
}
|
||||
return st, nil
|
||||
}
|
||||
|
||||
func newAclState(id string) *AclState {
|
||||
return &AclState{
|
||||
id: id,
|
||||
userReadKeys: make(map[string]crypto.SymKey),
|
||||
userStates: make(map[string]AclUserState),
|
||||
statesAtRecord: make(map[string][]AclUserState),
|
||||
st := &AclState{
|
||||
id: id,
|
||||
userReadKeys: make(map[string]crypto.SymKey),
|
||||
userStates: make(map[string]AclUserState),
|
||||
statesAtRecord: make(map[string][]AclUserState),
|
||||
inviteKeys: make(map[string]crypto.PubKey),
|
||||
requestRecords: make(map[string]RequestRecord),
|
||||
pendingRequests: make(map[string]string),
|
||||
keyStore: crypto.NewKeyStorage(),
|
||||
}
|
||||
st.contentValidator = &contentValidator{
|
||||
keyStore: st.keyStore,
|
||||
aclState: st,
|
||||
}
|
||||
return st
|
||||
}
|
||||
|
||||
func (st *AclState) Validator() ContentValidator {
|
||||
return st.contentValidator
|
||||
}
|
||||
|
||||
func (st *AclState) CurrentReadKeyId() string {
|
||||
@@ -74,7 +113,7 @@ func (st *AclState) CurrentReadKeyId() string {
|
||||
}
|
||||
|
||||
func (st *AclState) CurrentReadKey() (crypto.SymKey, error) {
|
||||
key, exists := st.userReadKeys[st.currentReadKeyId]
|
||||
key, exists := st.userReadKeys[st.CurrentReadKeyId()]
|
||||
if !exists {
|
||||
return nil, ErrNoReadKey
|
||||
}
|
||||
@@ -97,7 +136,7 @@ func (st *AclState) StateAtRecord(id string, pubKey crypto.PubKey) (AclUserState
|
||||
return perm, nil
|
||||
}
|
||||
}
|
||||
return AclUserState{}, ErrNoSuchUser
|
||||
return AclUserState{}, ErrNoSuchAccount
|
||||
}
|
||||
|
||||
func (st *AclState) applyRecord(record *AclRecord) (err error) {
|
||||
@@ -110,17 +149,18 @@ func (st *AclState) applyRecord(record *AclRecord) (err error) {
|
||||
err = ErrIncorrectRecordSequence
|
||||
return
|
||||
}
|
||||
// if the record is root record
|
||||
if record.Id == st.id {
|
||||
err = st.applyRoot(record)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
st.statesAtRecord[record.Id] = []AclUserState{
|
||||
{PubKey: record.Identity, Permissions: aclrecordproto.AclUserPermissions_Admin},
|
||||
st.userStates[mapKeyFromPubKey(record.Identity)],
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// if the model is not cached
|
||||
if record.Model == nil {
|
||||
aclData := &aclrecordproto.AclData{}
|
||||
err = proto.Unmarshal(record.Data, aclData)
|
||||
@@ -129,18 +169,16 @@ func (st *AclState) applyRecord(record *AclRecord) (err error) {
|
||||
}
|
||||
record.Model = aclData
|
||||
}
|
||||
|
||||
// applying records contents
|
||||
err = st.applyChangeData(record)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// getting all states for users at record
|
||||
// getting all states for users at record and saving them
|
||||
var states []AclUserState
|
||||
for _, state := range st.userStates {
|
||||
states = append(states, state)
|
||||
}
|
||||
|
||||
st.statesAtRecord[record.Id] = states
|
||||
return
|
||||
}
|
||||
@@ -156,9 +194,9 @@ func (st *AclState) applyRoot(record *AclRecord) (err error) {
|
||||
// adding user to the list
|
||||
userState := AclUserState{
|
||||
PubKey: record.Identity,
|
||||
Permissions: aclrecordproto.AclUserPermissions_Admin,
|
||||
Permissions: AclPermissions(aclrecordproto.AclUserPermissions_Owner),
|
||||
}
|
||||
st.currentReadKeyId = record.ReadKeyId
|
||||
st.currentReadKeyId = record.Id
|
||||
st.userStates[mapKeyFromPubKey(record.Identity)] = userState
|
||||
st.totalReadKeys++
|
||||
return
|
||||
@@ -181,92 +219,191 @@ func (st *AclState) saveReadKeyFromRoot(record *AclRecord) (err error) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
st.userReadKeys[record.Id] = readKey
|
||||
return
|
||||
}
|
||||
|
||||
func (st *AclState) applyChangeData(record *AclRecord) (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if record.ReadKeyId != st.currentReadKeyId {
|
||||
st.totalReadKeys++
|
||||
st.currentReadKeyId = record.ReadKeyId
|
||||
}
|
||||
}()
|
||||
model := record.Model.(*aclrecordproto.AclData)
|
||||
if !st.isUserJoin(model) {
|
||||
// we check signature when we add this to the List, so no need to do it here
|
||||
if _, exists := st.userStates[mapKeyFromPubKey(record.Identity)]; !exists {
|
||||
err = ErrNoSuchUser
|
||||
return
|
||||
}
|
||||
|
||||
// only Admins can do non-user join changes
|
||||
if !st.HasPermission(record.Identity, aclrecordproto.AclUserPermissions_Admin) {
|
||||
// TODO: add string encoding
|
||||
err = fmt.Errorf("user %s must have admin permissions", record.Identity.Account())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for _, ch := range model.GetAclContent() {
|
||||
if err = st.applyChangeContent(ch, record.Id); err != nil {
|
||||
if err = st.applyChangeContent(ch, record.Id, record.Identity); err != nil {
|
||||
log.Info("error while applying changes: %v; ignore", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *AclState) applyChangeContent(ch *aclrecordproto.AclContentValue, recordId string) error {
|
||||
func (st *AclState) applyChangeContent(ch *aclrecordproto.AclContentValue, recordId string, authorIdentity crypto.PubKey) error {
|
||||
switch {
|
||||
case ch.GetUserPermissionChange() != nil:
|
||||
return st.applyUserPermissionChange(ch.GetUserPermissionChange(), recordId)
|
||||
case ch.GetUserAdd() != nil:
|
||||
return st.applyUserAdd(ch.GetUserAdd(), recordId)
|
||||
case ch.GetUserRemove() != nil:
|
||||
return st.applyUserRemove(ch.GetUserRemove(), recordId)
|
||||
case ch.GetUserInvite() != nil:
|
||||
return st.applyUserInvite(ch.GetUserInvite(), recordId)
|
||||
case ch.GetUserJoin() != nil:
|
||||
return st.applyUserJoin(ch.GetUserJoin(), recordId)
|
||||
case ch.GetPermissionChange() != nil:
|
||||
return st.applyPermissionChange(ch.GetPermissionChange(), recordId, authorIdentity)
|
||||
case ch.GetInvite() != nil:
|
||||
return st.applyInvite(ch.GetInvite(), recordId, authorIdentity)
|
||||
case ch.GetInviteRevoke() != nil:
|
||||
return st.applyInviteRevoke(ch.GetInviteRevoke(), recordId, authorIdentity)
|
||||
case ch.GetRequestJoin() != nil:
|
||||
return st.applyRequestJoin(ch.GetRequestJoin(), recordId, authorIdentity)
|
||||
case ch.GetRequestAccept() != nil:
|
||||
return st.applyRequestAccept(ch.GetRequestAccept(), recordId, authorIdentity)
|
||||
case ch.GetRequestDecline() != nil:
|
||||
return st.applyRequestDecline(ch.GetRequestDecline(), recordId, authorIdentity)
|
||||
case ch.GetAccountRemove() != nil:
|
||||
return st.applyAccountRemove(ch.GetAccountRemove(), recordId, authorIdentity)
|
||||
case ch.GetReadKeyChange() != nil:
|
||||
return st.applyReadKeyChange(ch.GetReadKeyChange(), recordId, authorIdentity)
|
||||
case ch.GetAccountRequestRemove() != nil:
|
||||
return st.applyRequestRemove(ch.GetAccountRequestRemove(), recordId, authorIdentity)
|
||||
default:
|
||||
return fmt.Errorf("unexpected change type: %v", ch)
|
||||
return ErrUnexpectedContentType
|
||||
}
|
||||
}
|
||||
|
||||
func (st *AclState) applyUserPermissionChange(ch *aclrecordproto.AclUserPermissionChange, recordId string) error {
|
||||
func (st *AclState) applyPermissionChange(ch *aclrecordproto.AclAccountPermissionChange, recordId string, authorIdentity crypto.PubKey) error {
|
||||
chIdentity, err := st.keyStore.PubKeyFromProto(ch.Identity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
state, exists := st.userStates[mapKeyFromPubKey(chIdentity)]
|
||||
if !exists {
|
||||
return ErrNoSuchUser
|
||||
err = st.contentValidator.ValidatePermissionChange(ch, authorIdentity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
state.Permissions = ch.Permissions
|
||||
stringKey := mapKeyFromPubKey(chIdentity)
|
||||
state, _ := st.userStates[stringKey]
|
||||
state.Permissions = AclPermissions(ch.Permissions)
|
||||
st.userStates[stringKey] = state
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *AclState) applyUserInvite(ch *aclrecordproto.AclUserInvite, recordId string) error {
|
||||
// TODO: check old code and bring it back :-)
|
||||
func (st *AclState) applyInvite(ch *aclrecordproto.AclAccountInvite, recordId string, authorIdentity crypto.PubKey) error {
|
||||
inviteKey, err := st.keyStore.PubKeyFromProto(ch.InviteKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = st.contentValidator.ValidateInvite(ch, authorIdentity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
st.inviteKeys[recordId] = inviteKey
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *AclState) applyUserJoin(ch *aclrecordproto.AclUserJoin, recordId string) error {
|
||||
func (st *AclState) applyInviteRevoke(ch *aclrecordproto.AclAccountInviteRevoke, recordId string, authorIdentity crypto.PubKey) error {
|
||||
err := st.contentValidator.ValidateInviteRevoke(ch, authorIdentity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
delete(st.inviteKeys, ch.InviteRecordId)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *AclState) applyUserAdd(ch *aclrecordproto.AclUserAdd, recordId string) error {
|
||||
func (st *AclState) applyRequestJoin(ch *aclrecordproto.AclAccountRequestJoin, recordId string, authorIdentity crypto.PubKey) error {
|
||||
err := st.contentValidator.ValidateRequestJoin(ch, authorIdentity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
st.pendingRequests[mapKeyFromPubKey(authorIdentity)] = recordId
|
||||
st.requestRecords[recordId] = RequestRecord{
|
||||
RequestIdentity: authorIdentity,
|
||||
RequestMetadata: ch.Metadata,
|
||||
Type: RequestTypeJoin,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *AclState) applyUserRemove(ch *aclrecordproto.AclUserRemove, recordId string) error {
|
||||
func (st *AclState) applyRequestAccept(ch *aclrecordproto.AclAccountRequestAccept, recordId string, authorIdentity crypto.PubKey) error {
|
||||
err := st.contentValidator.ValidateRequestAccept(ch, authorIdentity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
acceptIdentity, err := st.keyStore.PubKeyFromProto(ch.Identity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
record, _ := st.requestRecords[ch.RequestRecordId]
|
||||
st.userStates[mapKeyFromPubKey(acceptIdentity)] = AclUserState{
|
||||
PubKey: acceptIdentity,
|
||||
Permissions: AclPermissions(ch.Permissions),
|
||||
RequestMetadata: record.RequestMetadata,
|
||||
}
|
||||
delete(st.pendingRequests, mapKeyFromPubKey(st.requestRecords[ch.RequestRecordId].RequestIdentity))
|
||||
if !st.pubKey.Equals(acceptIdentity) {
|
||||
return nil
|
||||
}
|
||||
for _, key := range ch.EncryptedReadKeys {
|
||||
decrypted, err := st.key.Decrypt(key.EncryptedReadKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sym, err := crypto.UnmarshallAESKey(decrypted)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
st.userReadKeys[key.RecordId] = sym
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *AclState) applyRequestDecline(ch *aclrecordproto.AclAccountRequestDecline, recordId string, authorIdentity crypto.PubKey) error {
|
||||
err := st.contentValidator.ValidateRequestDecline(ch, authorIdentity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
delete(st.pendingRequests, mapKeyFromPubKey(st.requestRecords[ch.RequestRecordId].RequestIdentity))
|
||||
delete(st.requestRecords, ch.RequestRecordId)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *AclState) applyRequestRemove(ch *aclrecordproto.AclAccountRequestRemove, recordId string, authorIdentity crypto.PubKey) error {
|
||||
err := st.contentValidator.ValidateRequestRemove(ch, authorIdentity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
st.requestRecords[recordId] = RequestRecord{
|
||||
RequestIdentity: authorIdentity,
|
||||
Type: RequestTypeRemove,
|
||||
}
|
||||
st.pendingRequests[mapKeyFromPubKey(authorIdentity)] = recordId
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *AclState) applyAccountRemove(ch *aclrecordproto.AclAccountRemove, recordId string, authorIdentity crypto.PubKey) error {
|
||||
err := st.contentValidator.ValidateAccountRemove(ch, authorIdentity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, rawIdentity := range ch.Identities {
|
||||
identity, err := st.keyStore.PubKeyFromProto(rawIdentity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
idKey := mapKeyFromPubKey(identity)
|
||||
delete(st.userStates, idKey)
|
||||
delete(st.pendingRequests, idKey)
|
||||
}
|
||||
return st.updateReadKey(ch.AccountKeys, recordId)
|
||||
}
|
||||
|
||||
func (st *AclState) applyReadKeyChange(ch *aclrecordproto.AclReadKeyChange, recordId string, authorIdentity crypto.PubKey) error {
|
||||
err := st.contentValidator.ValidateReadKeyChange(ch, authorIdentity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return st.updateReadKey(ch.AccountKeys, recordId)
|
||||
}
|
||||
|
||||
func (st *AclState) updateReadKey(keys []*aclrecordproto.AclEncryptedReadKey, recordId string) error {
|
||||
for _, accKey := range keys {
|
||||
identity, _ := st.keyStore.PubKeyFromProto(accKey.Identity)
|
||||
if st.pubKey.Equals(identity) {
|
||||
res, err := st.decryptReadKey(accKey.EncryptedReadKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
st.userReadKeys[recordId] = res
|
||||
}
|
||||
}
|
||||
st.currentReadKeyId = recordId
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -275,7 +412,6 @@ func (st *AclState) decryptReadKey(msg []byte) (crypto.SymKey, error) {
|
||||
if err != nil {
|
||||
return nil, ErrFailedToDecrypt
|
||||
}
|
||||
|
||||
key, err := crypto.UnmarshallAESKey(decrypted)
|
||||
if err != nil {
|
||||
return nil, ErrFailedToDecrypt
|
||||
@@ -283,29 +419,31 @@ func (st *AclState) decryptReadKey(msg []byte) (crypto.SymKey, error) {
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (st *AclState) HasPermission(identity crypto.PubKey, permission aclrecordproto.AclUserPermissions) bool {
|
||||
func (st *AclState) Permissions(identity crypto.PubKey) AclPermissions {
|
||||
state, exists := st.userStates[mapKeyFromPubKey(identity)]
|
||||
if !exists {
|
||||
return false
|
||||
return AclPermissions(aclrecordproto.AclUserPermissions_None)
|
||||
}
|
||||
|
||||
return state.Permissions == permission
|
||||
return state.Permissions
|
||||
}
|
||||
|
||||
func (st *AclState) isUserJoin(data *aclrecordproto.AclData) bool {
|
||||
// if we have a UserJoin, then it should always be the first one applied
|
||||
return data.GetAclContent() != nil && data.GetAclContent()[0].GetUserJoin() != nil
|
||||
func (st *AclState) JoinRecords() (records []RequestRecord) {
|
||||
for _, recId := range st.pendingRequests {
|
||||
rec := st.requestRecords[recId]
|
||||
if rec.Type == RequestTypeJoin {
|
||||
records = append(records, rec)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (st *AclState) isUserAdd(data *aclrecordproto.AclData, identity []byte) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (st *AclState) UserStates() map[string]AclUserState {
|
||||
return st.userStates
|
||||
}
|
||||
|
||||
func (st *AclState) Invite(acceptPubKey []byte) (invite *aclrecordproto.AclUserInvite, err error) {
|
||||
func (st *AclState) RemoveRecords() (records []RequestRecord) {
|
||||
for _, recId := range st.pendingRequests {
|
||||
rec := st.requestRecords[recId]
|
||||
if rec.Type == RequestTypeRemove {
|
||||
records = append(records, rec)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -5,16 +5,21 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"sync"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/anyproto/any-sync/util/cidutil"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
)
|
||||
|
||||
type IterFunc = func(record *AclRecord) (IsContinue bool)
|
||||
|
||||
var ErrIncorrectCID = errors.New("incorrect CID")
|
||||
var (
|
||||
ErrIncorrectCID = errors.New("incorrect CID")
|
||||
ErrRecordAlreadyExists = errors.New("record already exists")
|
||||
)
|
||||
|
||||
type RWLocker interface {
|
||||
sync.Locker
|
||||
@@ -22,26 +27,45 @@ type RWLocker interface {
|
||||
RUnlock()
|
||||
}
|
||||
|
||||
type AcceptorVerifier interface {
|
||||
VerifyAcceptor(rec *consensusproto.RawRecord) (err error)
|
||||
}
|
||||
|
||||
type NoOpAcceptorVerifier struct {
|
||||
}
|
||||
|
||||
func (n NoOpAcceptorVerifier) VerifyAcceptor(rec *consensusproto.RawRecord) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
type AclList interface {
|
||||
RWLocker
|
||||
Id() string
|
||||
Root() *aclrecordproto.RawAclRecordWithId
|
||||
Root() *consensusproto.RawRecordWithId
|
||||
Records() []*AclRecord
|
||||
AclState() *AclState
|
||||
IsAfter(first string, second string) (bool, error)
|
||||
HasHead(head string) bool
|
||||
Head() *AclRecord
|
||||
|
||||
RecordsAfter(ctx context.Context, id string) (records []*consensusproto.RawRecordWithId, err error)
|
||||
Get(id string) (*AclRecord, error)
|
||||
GetIndex(idx int) (*AclRecord, error)
|
||||
Iterate(iterFunc IterFunc)
|
||||
IterateFrom(startId string, iterFunc IterFunc)
|
||||
|
||||
KeyStorage() crypto.KeyStorage
|
||||
RecordBuilder() AclRecordBuilder
|
||||
|
||||
AddRawRecord(rawRec *aclrecordproto.RawAclRecordWithId) (added bool, err error)
|
||||
ValidateRawRecord(record *consensusproto.RawRecord) (err error)
|
||||
AddRawRecord(rawRec *consensusproto.RawRecordWithId) (err error)
|
||||
AddRawRecords(rawRecords []*consensusproto.RawRecordWithId) (err error)
|
||||
|
||||
Close() (err error)
|
||||
Close(ctx context.Context) (err error)
|
||||
}
|
||||
|
||||
type aclList struct {
|
||||
root *aclrecordproto.RawAclRecordWithId
|
||||
root *consensusproto.RawRecordWithId
|
||||
records []*AclRecord
|
||||
indexes map[string]int
|
||||
id string
|
||||
@@ -55,18 +79,45 @@ type aclList struct {
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func BuildAclListWithIdentity(acc *accountdata.AccountKeys, storage liststorage.ListStorage) (AclList, error) {
|
||||
builder := newAclStateBuilderWithIdentity(acc)
|
||||
keyStorage := crypto.NewKeyStorage()
|
||||
return build(storage.Id(), keyStorage, builder, NewAclRecordBuilder(storage.Id(), keyStorage), storage)
|
||||
type internalDeps struct {
|
||||
storage liststorage.ListStorage
|
||||
keyStorage crypto.KeyStorage
|
||||
stateBuilder *aclStateBuilder
|
||||
recordBuilder AclRecordBuilder
|
||||
acceptorVerifier AcceptorVerifier
|
||||
}
|
||||
|
||||
func BuildAclList(storage liststorage.ListStorage) (AclList, error) {
|
||||
func BuildAclListWithIdentity(acc *accountdata.AccountKeys, storage liststorage.ListStorage, verifier AcceptorVerifier) (AclList, error) {
|
||||
keyStorage := crypto.NewKeyStorage()
|
||||
return build(storage.Id(), keyStorage, newAclStateBuilder(), NewAclRecordBuilder(storage.Id(), crypto.NewKeyStorage()), storage)
|
||||
deps := internalDeps{
|
||||
storage: storage,
|
||||
keyStorage: keyStorage,
|
||||
stateBuilder: newAclStateBuilderWithIdentity(acc),
|
||||
recordBuilder: NewAclRecordBuilder(storage.Id(), keyStorage, acc, verifier),
|
||||
acceptorVerifier: verifier,
|
||||
}
|
||||
return build(deps)
|
||||
}
|
||||
|
||||
func build(id string, keyStorage crypto.KeyStorage, stateBuilder *aclStateBuilder, recBuilder AclRecordBuilder, storage liststorage.ListStorage) (list AclList, err error) {
|
||||
func BuildAclList(storage liststorage.ListStorage, verifier AcceptorVerifier) (AclList, error) {
|
||||
keyStorage := crypto.NewKeyStorage()
|
||||
deps := internalDeps{
|
||||
storage: storage,
|
||||
keyStorage: keyStorage,
|
||||
stateBuilder: newAclStateBuilder(),
|
||||
recordBuilder: NewAclRecordBuilder(storage.Id(), keyStorage, nil, verifier),
|
||||
acceptorVerifier: verifier,
|
||||
}
|
||||
return build(deps)
|
||||
}
|
||||
|
||||
func build(deps internalDeps) (list AclList, err error) {
|
||||
var (
|
||||
storage = deps.storage
|
||||
id = deps.storage.Id()
|
||||
recBuilder = deps.recordBuilder
|
||||
stateBuilder = deps.stateBuilder
|
||||
)
|
||||
head, err := storage.Head()
|
||||
if err != nil {
|
||||
return
|
||||
@@ -77,7 +128,7 @@ func build(id string, keyStorage crypto.KeyStorage, stateBuilder *aclStateBuilde
|
||||
return
|
||||
}
|
||||
|
||||
record, err := recBuilder.Unmarshall(rawRecordWithId)
|
||||
record, err := recBuilder.UnmarshallWithId(rawRecordWithId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -89,7 +140,7 @@ func build(id string, keyStorage crypto.KeyStorage, stateBuilder *aclStateBuilde
|
||||
return
|
||||
}
|
||||
|
||||
record, err = recBuilder.Unmarshall(rawRecordWithId)
|
||||
record, err = recBuilder.UnmarshallWithId(rawRecordWithId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -119,6 +170,7 @@ func build(id string, keyStorage crypto.KeyStorage, stateBuilder *aclStateBuilde
|
||||
return
|
||||
}
|
||||
|
||||
recBuilder.(*aclRecordBuilder).state = state
|
||||
list = &aclList{
|
||||
root: rootWithId,
|
||||
records: records,
|
||||
@@ -132,15 +184,37 @@ func build(id string, keyStorage crypto.KeyStorage, stateBuilder *aclStateBuilde
|
||||
return
|
||||
}
|
||||
|
||||
func (a *aclList) RecordBuilder() AclRecordBuilder {
|
||||
return a.recordBuilder
|
||||
}
|
||||
|
||||
func (a *aclList) Records() []*AclRecord {
|
||||
return a.records
|
||||
}
|
||||
|
||||
func (a *aclList) AddRawRecord(rawRec *aclrecordproto.RawAclRecordWithId) (added bool, err error) {
|
||||
if _, ok := a.indexes[rawRec.Id]; ok {
|
||||
func (a *aclList) ValidateRawRecord(rawRec *consensusproto.RawRecord) (err error) {
|
||||
record, err := a.recordBuilder.Unmarshall(rawRec)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
record, err := a.recordBuilder.Unmarshall(rawRec)
|
||||
return a.aclState.Validator().ValidateAclRecordContents(record)
|
||||
}
|
||||
|
||||
func (a *aclList) AddRawRecords(rawRecords []*consensusproto.RawRecordWithId) (err error) {
|
||||
for _, rec := range rawRecords {
|
||||
err = a.AddRawRecord(rec)
|
||||
if err != nil && err != ErrRecordAlreadyExists {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (a *aclList) AddRawRecord(rawRec *consensusproto.RawRecordWithId) (err error) {
|
||||
if _, ok := a.indexes[rawRec.Id]; ok {
|
||||
return ErrRecordAlreadyExists
|
||||
}
|
||||
record, err := a.recordBuilder.UnmarshallWithId(rawRec)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -155,15 +229,6 @@ func (a *aclList) AddRawRecord(rawRec *aclrecordproto.RawAclRecordWithId) (added
|
||||
if err = a.storage.SetHead(rawRec.Id); err != nil {
|
||||
return
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (a *aclList) IsValidNext(rawRec *aclrecordproto.RawAclRecordWithId) (err error) {
|
||||
_, err = a.recordBuilder.Unmarshall(rawRec)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// TODO: change state and add "check" method for records
|
||||
return
|
||||
}
|
||||
|
||||
@@ -171,7 +236,7 @@ func (a *aclList) Id() string {
|
||||
return a.id
|
||||
}
|
||||
|
||||
func (a *aclList) Root() *aclrecordproto.RawAclRecordWithId {
|
||||
func (a *aclList) Root() *consensusproto.RawRecordWithId {
|
||||
return a.root
|
||||
}
|
||||
|
||||
@@ -196,14 +261,27 @@ func (a *aclList) Head() *AclRecord {
|
||||
return a.records[len(a.records)-1]
|
||||
}
|
||||
|
||||
func (a *aclList) HasHead(head string) bool {
|
||||
_, exists := a.indexes[head]
|
||||
return exists
|
||||
}
|
||||
|
||||
func (a *aclList) Get(id string) (*AclRecord, error) {
|
||||
recIdx, ok := a.indexes[id]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no such record")
|
||||
return nil, ErrNoSuchRecord
|
||||
}
|
||||
return a.records[recIdx], nil
|
||||
}
|
||||
|
||||
func (a *aclList) GetIndex(idx int) (*AclRecord, error) {
|
||||
// TODO: when we add snapshots we will have to monitor record num in snapshots
|
||||
if idx < 0 || idx >= len(a.records) {
|
||||
return nil, ErrNoSuchRecord
|
||||
}
|
||||
return a.records[idx], nil
|
||||
}
|
||||
|
||||
func (a *aclList) Iterate(iterFunc IterFunc) {
|
||||
for _, rec := range a.records {
|
||||
if !iterFunc(rec) {
|
||||
@@ -212,6 +290,21 @@ func (a *aclList) Iterate(iterFunc IterFunc) {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *aclList) RecordsAfter(ctx context.Context, id string) (records []*consensusproto.RawRecordWithId, err error) {
|
||||
recIdx, ok := a.indexes[id]
|
||||
if !ok {
|
||||
return nil, ErrNoSuchRecord
|
||||
}
|
||||
for i := recIdx + 1; i < len(a.records); i++ {
|
||||
rawRec, err := a.storage.GetRawRecord(ctx, a.records[i].Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
records = append(records, rawRec)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (a *aclList) IterateFrom(startId string, iterFunc IterFunc) {
|
||||
recIdx, ok := a.indexes[startId]
|
||||
if !ok {
|
||||
@@ -224,6 +317,21 @@ func (a *aclList) IterateFrom(startId string, iterFunc IterFunc) {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *aclList) Close() (err error) {
|
||||
func (a *aclList) Close(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func WrapAclRecord(rawRec *consensusproto.RawRecord) *consensusproto.RawRecordWithId {
|
||||
payload, err := rawRec.Marshal()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
id, err := cidutil.NewCidFromBytes(payload)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &consensusproto.RawRecordWithId{
|
||||
Payload: payload,
|
||||
Id: id,
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,98 @@ package list
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type aclFixture struct {
|
||||
ownerKeys *accountdata.AccountKeys
|
||||
accountKeys *accountdata.AccountKeys
|
||||
ownerAcl *aclList
|
||||
accountAcl *aclList
|
||||
spaceId string
|
||||
}
|
||||
|
||||
func newFixture(t *testing.T) *aclFixture {
|
||||
ownerKeys, err := accountdata.NewRandom()
|
||||
require.NoError(t, err)
|
||||
accountKeys, err := accountdata.NewRandom()
|
||||
require.NoError(t, err)
|
||||
spaceId := "spaceId"
|
||||
ownerAcl, err := NewTestDerivedAcl(spaceId, ownerKeys)
|
||||
require.NoError(t, err)
|
||||
accountAcl, err := NewTestAclWithRoot(accountKeys, ownerAcl.Root())
|
||||
require.NoError(t, err)
|
||||
return &aclFixture{
|
||||
ownerKeys: ownerKeys,
|
||||
accountKeys: accountKeys,
|
||||
ownerAcl: ownerAcl.(*aclList),
|
||||
accountAcl: accountAcl.(*aclList),
|
||||
spaceId: spaceId,
|
||||
}
|
||||
}
|
||||
|
||||
func (fx *aclFixture) addRec(t *testing.T, rec *consensusproto.RawRecordWithId) {
|
||||
err := fx.ownerAcl.AddRawRecord(rec)
|
||||
require.NoError(t, err)
|
||||
err = fx.accountAcl.AddRawRecord(rec)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func (fx *aclFixture) inviteAccount(t *testing.T, perms AclPermissions) {
|
||||
var (
|
||||
ownerAcl = fx.ownerAcl
|
||||
ownerState = fx.ownerAcl.aclState
|
||||
accountAcl = fx.accountAcl
|
||||
accountState = fx.accountAcl.aclState
|
||||
)
|
||||
// building invite
|
||||
inv, err := ownerAcl.RecordBuilder().BuildInvite()
|
||||
require.NoError(t, err)
|
||||
inviteRec := WrapAclRecord(inv.InviteRec)
|
||||
fx.addRec(t, inviteRec)
|
||||
|
||||
// building request join
|
||||
requestJoin, err := accountAcl.RecordBuilder().BuildRequestJoin(RequestJoinPayload{
|
||||
InviteRecordId: inviteRec.Id,
|
||||
InviteKey: inv.InviteKey,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
requestJoinRec := WrapAclRecord(requestJoin)
|
||||
fx.addRec(t, requestJoinRec)
|
||||
|
||||
// building request accept
|
||||
requestAccept, err := ownerAcl.RecordBuilder().BuildRequestAccept(RequestAcceptPayload{
|
||||
RequestRecordId: requestJoinRec.Id,
|
||||
Permissions: perms,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// validate
|
||||
err = ownerAcl.ValidateRawRecord(requestAccept)
|
||||
require.NoError(t, err)
|
||||
requestAcceptRec := WrapAclRecord(requestAccept)
|
||||
fx.addRec(t, requestAcceptRec)
|
||||
|
||||
// checking acl state
|
||||
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
||||
require.True(t, ownerState.Permissions(accountState.pubKey).CanWrite())
|
||||
require.Equal(t, 0, len(ownerState.pendingRequests))
|
||||
require.Equal(t, 0, len(accountState.pendingRequests))
|
||||
require.True(t, accountState.Permissions(ownerState.pubKey).IsOwner())
|
||||
require.True(t, accountState.Permissions(accountState.pubKey).CanWrite())
|
||||
|
||||
_, err = ownerState.StateAtRecord(requestJoinRec.Id, accountState.pubKey)
|
||||
require.Equal(t, ErrNoSuchAccount, err)
|
||||
stateAtRec, err := ownerState.StateAtRecord(requestAcceptRec.Id, accountState.pubKey)
|
||||
require.NoError(t, err)
|
||||
require.True(t, stateAtRec.Permissions == perms)
|
||||
}
|
||||
|
||||
func TestAclList_BuildRoot(t *testing.T) {
|
||||
randomKeys, err := accountdata.NewRandom()
|
||||
require.NoError(t, err)
|
||||
@@ -14,3 +101,193 @@ func TestAclList_BuildRoot(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
fmt.Println(randomAcl.Id())
|
||||
}
|
||||
|
||||
func TestAclList_InvitePipeline(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Writer))
|
||||
}
|
||||
|
||||
func TestAclList_InviteRevoke(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
var (
|
||||
ownerState = fx.ownerAcl.aclState
|
||||
accountState = fx.accountAcl.aclState
|
||||
)
|
||||
// building invite
|
||||
inv, err := fx.ownerAcl.RecordBuilder().BuildInvite()
|
||||
require.NoError(t, err)
|
||||
inviteRec := WrapAclRecord(inv.InviteRec)
|
||||
fx.addRec(t, inviteRec)
|
||||
|
||||
// building invite revoke
|
||||
inviteRevoke, err := fx.ownerAcl.RecordBuilder().BuildInviteRevoke(ownerState.lastRecordId)
|
||||
require.NoError(t, err)
|
||||
inviteRevokeRec := WrapAclRecord(inviteRevoke)
|
||||
fx.addRec(t, inviteRevokeRec)
|
||||
|
||||
// checking acl state
|
||||
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
||||
require.True(t, ownerState.Permissions(accountState.pubKey).NoPermissions())
|
||||
require.Empty(t, ownerState.inviteKeys)
|
||||
require.Empty(t, accountState.inviteKeys)
|
||||
}
|
||||
|
||||
func TestAclList_RequestDecline(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
var (
|
||||
ownerAcl = fx.ownerAcl
|
||||
ownerState = fx.ownerAcl.aclState
|
||||
accountAcl = fx.accountAcl
|
||||
accountState = fx.accountAcl.aclState
|
||||
)
|
||||
// building invite
|
||||
inv, err := ownerAcl.RecordBuilder().BuildInvite()
|
||||
require.NoError(t, err)
|
||||
inviteRec := WrapAclRecord(inv.InviteRec)
|
||||
fx.addRec(t, inviteRec)
|
||||
|
||||
// building request join
|
||||
requestJoin, err := accountAcl.RecordBuilder().BuildRequestJoin(RequestJoinPayload{
|
||||
InviteRecordId: inviteRec.Id,
|
||||
InviteKey: inv.InviteKey,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
requestJoinRec := WrapAclRecord(requestJoin)
|
||||
fx.addRec(t, requestJoinRec)
|
||||
|
||||
// building request decline
|
||||
requestDecline, err := ownerAcl.RecordBuilder().BuildRequestDecline(ownerState.lastRecordId)
|
||||
require.NoError(t, err)
|
||||
requestDeclineRec := WrapAclRecord(requestDecline)
|
||||
fx.addRec(t, requestDeclineRec)
|
||||
|
||||
// checking acl state
|
||||
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
||||
require.True(t, ownerState.Permissions(accountState.pubKey).NoPermissions())
|
||||
require.Empty(t, ownerState.pendingRequests)
|
||||
require.Empty(t, accountState.pendingRequests)
|
||||
}
|
||||
|
||||
func TestAclList_Remove(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
var (
|
||||
ownerState = fx.ownerAcl.aclState
|
||||
accountState = fx.accountAcl.aclState
|
||||
)
|
||||
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Writer))
|
||||
|
||||
newReadKey := crypto.NewAES()
|
||||
remove, err := fx.ownerAcl.RecordBuilder().BuildAccountRemove(AccountRemovePayload{
|
||||
Identities: []crypto.PubKey{fx.accountKeys.SignKey.GetPublic()},
|
||||
ReadKey: newReadKey,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
removeRec := WrapAclRecord(remove)
|
||||
fx.addRec(t, removeRec)
|
||||
|
||||
// checking acl state
|
||||
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
||||
require.True(t, ownerState.Permissions(accountState.pubKey).NoPermissions())
|
||||
require.True(t, ownerState.userReadKeys[removeRec.Id].Equals(newReadKey))
|
||||
require.NotNil(t, ownerState.userReadKeys[fx.ownerAcl.Id()])
|
||||
require.Equal(t, 0, len(ownerState.pendingRequests))
|
||||
require.Equal(t, 0, len(accountState.pendingRequests))
|
||||
require.True(t, accountState.Permissions(ownerState.pubKey).IsOwner())
|
||||
require.True(t, accountState.Permissions(accountState.pubKey).NoPermissions())
|
||||
require.Nil(t, accountState.userReadKeys[removeRec.Id])
|
||||
require.NotNil(t, accountState.userReadKeys[fx.ownerAcl.Id()])
|
||||
}
|
||||
|
||||
func TestAclList_ReadKeyChange(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
var (
|
||||
ownerState = fx.ownerAcl.aclState
|
||||
accountState = fx.accountAcl.aclState
|
||||
)
|
||||
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Admin))
|
||||
|
||||
newReadKey := crypto.NewAES()
|
||||
readKeyChange, err := fx.ownerAcl.RecordBuilder().BuildReadKeyChange(newReadKey)
|
||||
require.NoError(t, err)
|
||||
readKeyRec := WrapAclRecord(readKeyChange)
|
||||
fx.addRec(t, readKeyRec)
|
||||
|
||||
// checking acl state
|
||||
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
||||
require.True(t, ownerState.Permissions(accountState.pubKey).CanManageAccounts())
|
||||
require.True(t, ownerState.userReadKeys[readKeyRec.Id].Equals(newReadKey))
|
||||
require.True(t, accountState.userReadKeys[readKeyRec.Id].Equals(newReadKey))
|
||||
require.NotNil(t, ownerState.userReadKeys[fx.ownerAcl.Id()])
|
||||
require.NotNil(t, accountState.userReadKeys[fx.ownerAcl.Id()])
|
||||
readKey, err := ownerState.CurrentReadKey()
|
||||
require.NoError(t, err)
|
||||
require.True(t, newReadKey.Equals(readKey))
|
||||
require.Equal(t, 0, len(ownerState.pendingRequests))
|
||||
require.Equal(t, 0, len(accountState.pendingRequests))
|
||||
}
|
||||
|
||||
func TestAclList_PermissionChange(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
var (
|
||||
ownerState = fx.ownerAcl.aclState
|
||||
accountState = fx.accountAcl.aclState
|
||||
)
|
||||
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Admin))
|
||||
|
||||
permissionChange, err := fx.ownerAcl.RecordBuilder().BuildPermissionChange(PermissionChangePayload{
|
||||
Identity: fx.accountKeys.SignKey.GetPublic(),
|
||||
Permissions: AclPermissions(aclrecordproto.AclUserPermissions_Writer),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
permissionChangeRec := WrapAclRecord(permissionChange)
|
||||
fx.addRec(t, permissionChangeRec)
|
||||
|
||||
// checking acl state
|
||||
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
||||
require.True(t, ownerState.Permissions(accountState.pubKey) == AclPermissions(aclrecordproto.AclUserPermissions_Writer))
|
||||
require.True(t, accountState.Permissions(ownerState.pubKey).IsOwner())
|
||||
require.True(t, accountState.Permissions(accountState.pubKey) == AclPermissions(aclrecordproto.AclUserPermissions_Writer))
|
||||
require.NotNil(t, ownerState.userReadKeys[fx.ownerAcl.Id()])
|
||||
require.NotNil(t, accountState.userReadKeys[fx.ownerAcl.Id()])
|
||||
require.Equal(t, 0, len(ownerState.pendingRequests))
|
||||
require.Equal(t, 0, len(accountState.pendingRequests))
|
||||
}
|
||||
|
||||
func TestAclList_RequestRemove(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
var (
|
||||
ownerState = fx.ownerAcl.aclState
|
||||
accountState = fx.accountAcl.aclState
|
||||
)
|
||||
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Writer))
|
||||
|
||||
removeRequest, err := fx.accountAcl.RecordBuilder().BuildRequestRemove()
|
||||
require.NoError(t, err)
|
||||
removeRequestRec := WrapAclRecord(removeRequest)
|
||||
fx.addRec(t, removeRequestRec)
|
||||
|
||||
recs := fx.accountAcl.AclState().RemoveRecords()
|
||||
require.Len(t, recs, 1)
|
||||
require.True(t, accountState.pubKey.Equals(recs[0].RequestIdentity))
|
||||
|
||||
newReadKey := crypto.NewAES()
|
||||
remove, err := fx.ownerAcl.RecordBuilder().BuildAccountRemove(AccountRemovePayload{
|
||||
Identities: []crypto.PubKey{recs[0].RequestIdentity},
|
||||
ReadKey: newReadKey,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
removeRec := WrapAclRecord(remove)
|
||||
fx.addRec(t, removeRec)
|
||||
|
||||
// checking acl state
|
||||
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
||||
require.True(t, ownerState.Permissions(accountState.pubKey).NoPermissions())
|
||||
require.True(t, ownerState.userReadKeys[removeRec.Id].Equals(newReadKey))
|
||||
require.NotNil(t, ownerState.userReadKeys[fx.ownerAcl.Id()])
|
||||
require.Equal(t, 0, len(ownerState.pendingRequests))
|
||||
require.Equal(t, 0, len(accountState.pendingRequests))
|
||||
require.True(t, accountState.Permissions(ownerState.pubKey).IsOwner())
|
||||
require.True(t, accountState.Permissions(accountState.pubKey).NoPermissions())
|
||||
require.Nil(t, accountState.userReadKeys[removeRec.Id])
|
||||
require.NotNil(t, accountState.userReadKeys[fx.ownerAcl.Id()])
|
||||
}
|
||||
@@ -2,13 +2,13 @@ package list
|
||||
|
||||
import (
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
)
|
||||
|
||||
func NewTestDerivedAcl(spaceId string, keys *accountdata.AccountKeys) (AclList, error) {
|
||||
builder := NewAclRecordBuilder("", crypto.NewKeyStorage())
|
||||
builder := NewAclRecordBuilder("", crypto.NewKeyStorage(), keys, NoOpAcceptorVerifier{})
|
||||
masterKey, _, err := crypto.GenerateRandomEd25519KeyPair()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -21,11 +21,21 @@ func NewTestDerivedAcl(spaceId string, keys *accountdata.AccountKeys) (AclList,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
st, err := liststorage.NewInMemoryAclListStorage(root.Id, []*aclrecordproto.RawAclRecordWithId{
|
||||
st, err := liststorage.NewInMemoryAclListStorage(root.Id, []*consensusproto.RawRecordWithId{
|
||||
root,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return BuildAclListWithIdentity(keys, st)
|
||||
return BuildAclListWithIdentity(keys, st, NoOpAcceptorVerifier{})
|
||||
}
|
||||
|
||||
func NewTestAclWithRoot(keys *accountdata.AccountKeys, root *consensusproto.RawRecordWithId) (AclList, error) {
|
||||
st, err := liststorage.NewInMemoryAclListStorage(root.Id, []*consensusproto.RawRecordWithId{
|
||||
root,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return BuildAclListWithIdentity(keys, st, NoOpAcceptorVerifier{})
|
||||
}
|
||||
@@ -5,12 +5,13 @@
|
||||
package mock_list
|
||||
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
aclrecordproto "github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
list "github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
consensusproto "github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
crypto "github.com/anyproto/any-sync/util/crypto"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockAclList is a mock of AclList interface.
|
||||
@@ -51,12 +52,11 @@ func (mr *MockAclListMockRecorder) AclState() *gomock.Call {
|
||||
}
|
||||
|
||||
// AddRawRecord mocks base method.
|
||||
func (m *MockAclList) AddRawRecord(arg0 *aclrecordproto.RawAclRecordWithId) (bool, error) {
|
||||
func (m *MockAclList) AddRawRecord(arg0 *consensusproto.RawRecordWithId) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AddRawRecord", arg0)
|
||||
ret0, _ := ret[0].(bool)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddRawRecord indicates an expected call of AddRawRecord.
|
||||
@@ -65,18 +65,32 @@ func (mr *MockAclListMockRecorder) AddRawRecord(arg0 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawRecord", reflect.TypeOf((*MockAclList)(nil).AddRawRecord), arg0)
|
||||
}
|
||||
|
||||
// Close mocks base method.
|
||||
func (m *MockAclList) Close() error {
|
||||
// AddRawRecords mocks base method.
|
||||
func (m *MockAclList) AddRawRecords(arg0 []*consensusproto.RawRecordWithId) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Close")
|
||||
ret := m.ctrl.Call(m, "AddRawRecords", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddRawRecords indicates an expected call of AddRawRecords.
|
||||
func (mr *MockAclListMockRecorder) AddRawRecords(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawRecords", reflect.TypeOf((*MockAclList)(nil).AddRawRecords), arg0)
|
||||
}
|
||||
|
||||
// Close mocks base method.
|
||||
func (m *MockAclList) Close(arg0 context.Context) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Close", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Close indicates an expected call of Close.
|
||||
func (mr *MockAclListMockRecorder) Close() *gomock.Call {
|
||||
func (mr *MockAclListMockRecorder) Close(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockAclList)(nil).Close))
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockAclList)(nil).Close), arg0)
|
||||
}
|
||||
|
||||
// Get mocks base method.
|
||||
@@ -94,6 +108,35 @@ func (mr *MockAclListMockRecorder) Get(arg0 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockAclList)(nil).Get), arg0)
|
||||
}
|
||||
|
||||
// GetIndex mocks base method.
|
||||
func (m *MockAclList) GetIndex(arg0 int) (*list.AclRecord, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetIndex", arg0)
|
||||
ret0, _ := ret[0].(*list.AclRecord)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetIndex indicates an expected call of GetIndex.
|
||||
func (mr *MockAclListMockRecorder) GetIndex(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIndex", reflect.TypeOf((*MockAclList)(nil).GetIndex), arg0)
|
||||
}
|
||||
|
||||
// HasHead mocks base method.
|
||||
func (m *MockAclList) HasHead(arg0 string) bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "HasHead", arg0)
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// HasHead indicates an expected call of HasHead.
|
||||
func (mr *MockAclListMockRecorder) HasHead(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasHead", reflect.TypeOf((*MockAclList)(nil).HasHead), arg0)
|
||||
}
|
||||
|
||||
// Head mocks base method.
|
||||
func (m *MockAclList) Head() *list.AclRecord {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -211,6 +254,20 @@ func (mr *MockAclListMockRecorder) RUnlock() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RUnlock", reflect.TypeOf((*MockAclList)(nil).RUnlock))
|
||||
}
|
||||
|
||||
// RecordBuilder mocks base method.
|
||||
func (m *MockAclList) RecordBuilder() list.AclRecordBuilder {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "RecordBuilder")
|
||||
ret0, _ := ret[0].(list.AclRecordBuilder)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// RecordBuilder indicates an expected call of RecordBuilder.
|
||||
func (mr *MockAclListMockRecorder) RecordBuilder() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordBuilder", reflect.TypeOf((*MockAclList)(nil).RecordBuilder))
|
||||
}
|
||||
|
||||
// Records mocks base method.
|
||||
func (m *MockAclList) Records() []*list.AclRecord {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -225,11 +282,26 @@ func (mr *MockAclListMockRecorder) Records() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Records", reflect.TypeOf((*MockAclList)(nil).Records))
|
||||
}
|
||||
|
||||
// RecordsAfter mocks base method.
|
||||
func (m *MockAclList) RecordsAfter(arg0 context.Context, arg1 string) ([]*consensusproto.RawRecordWithId, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "RecordsAfter", arg0, arg1)
|
||||
ret0, _ := ret[0].([]*consensusproto.RawRecordWithId)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// RecordsAfter indicates an expected call of RecordsAfter.
|
||||
func (mr *MockAclListMockRecorder) RecordsAfter(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordsAfter", reflect.TypeOf((*MockAclList)(nil).RecordsAfter), arg0, arg1)
|
||||
}
|
||||
|
||||
// Root mocks base method.
|
||||
func (m *MockAclList) Root() *aclrecordproto.RawAclRecordWithId {
|
||||
func (m *MockAclList) Root() *consensusproto.RawRecordWithId {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Root")
|
||||
ret0, _ := ret[0].(*aclrecordproto.RawAclRecordWithId)
|
||||
ret0, _ := ret[0].(*consensusproto.RawRecordWithId)
|
||||
return ret0
|
||||
}
|
||||
|
||||
@@ -250,3 +322,17 @@ func (mr *MockAclListMockRecorder) Unlock() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockAclList)(nil).Unlock))
|
||||
}
|
||||
|
||||
// ValidateRawRecord mocks base method.
|
||||
func (m *MockAclList) ValidateRawRecord(arg0 *consensusproto.RawRecord) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ValidateRawRecord", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ValidateRawRecord indicates an expected call of ValidateRawRecord.
|
||||
func (mr *MockAclListMockRecorder) ValidateRawRecord(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateRawRecord", reflect.TypeOf((*MockAclList)(nil).ValidateRawRecord), arg0)
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
type AclRecord struct {
|
||||
Id string
|
||||
PrevId string
|
||||
ReadKeyId string
|
||||
Timestamp int64
|
||||
Data []byte
|
||||
Identity crypto.PubKey
|
||||
@@ -16,7 +15,55 @@ type AclRecord struct {
|
||||
Signature []byte
|
||||
}
|
||||
|
||||
type AclUserState struct {
|
||||
PubKey crypto.PubKey
|
||||
Permissions aclrecordproto.AclUserPermissions
|
||||
type RequestRecord struct {
|
||||
RequestIdentity crypto.PubKey
|
||||
RequestMetadata []byte
|
||||
Type RequestType
|
||||
}
|
||||
|
||||
type AclUserState struct {
|
||||
PubKey crypto.PubKey
|
||||
Permissions AclPermissions
|
||||
RequestMetadata []byte
|
||||
}
|
||||
|
||||
type RequestType int
|
||||
|
||||
const (
|
||||
RequestTypeRemove RequestType = iota
|
||||
RequestTypeJoin
|
||||
)
|
||||
|
||||
type AclPermissions aclrecordproto.AclUserPermissions
|
||||
|
||||
func (p AclPermissions) NoPermissions() bool {
|
||||
return aclrecordproto.AclUserPermissions(p) == aclrecordproto.AclUserPermissions_None
|
||||
}
|
||||
|
||||
func (p AclPermissions) IsOwner() bool {
|
||||
return aclrecordproto.AclUserPermissions(p) == aclrecordproto.AclUserPermissions_Owner
|
||||
}
|
||||
|
||||
func (p AclPermissions) CanWrite() bool {
|
||||
switch aclrecordproto.AclUserPermissions(p) {
|
||||
case aclrecordproto.AclUserPermissions_Admin:
|
||||
return true
|
||||
case aclrecordproto.AclUserPermissions_Writer:
|
||||
return true
|
||||
case aclrecordproto.AclUserPermissions_Owner:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (p AclPermissions) CanManageAccounts() bool {
|
||||
switch aclrecordproto.AclUserPermissions(p) {
|
||||
case aclrecordproto.AclUserPermissions_Admin:
|
||||
return true
|
||||
case aclrecordproto.AclUserPermissions_Owner:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
)
|
||||
|
||||
type ContentValidator interface {
|
||||
ValidateAclRecordContents(ch *AclRecord) (err error)
|
||||
ValidatePermissionChange(ch *aclrecordproto.AclAccountPermissionChange, authorIdentity crypto.PubKey) (err error)
|
||||
ValidateInvite(ch *aclrecordproto.AclAccountInvite, authorIdentity crypto.PubKey) (err error)
|
||||
ValidateInviteRevoke(ch *aclrecordproto.AclAccountInviteRevoke, authorIdentity crypto.PubKey) (err error)
|
||||
ValidateRequestJoin(ch *aclrecordproto.AclAccountRequestJoin, authorIdentity crypto.PubKey) (err error)
|
||||
ValidateRequestAccept(ch *aclrecordproto.AclAccountRequestAccept, authorIdentity crypto.PubKey) (err error)
|
||||
ValidateRequestDecline(ch *aclrecordproto.AclAccountRequestDecline, authorIdentity crypto.PubKey) (err error)
|
||||
ValidateAccountRemove(ch *aclrecordproto.AclAccountRemove, authorIdentity crypto.PubKey) (err error)
|
||||
ValidateRequestRemove(ch *aclrecordproto.AclAccountRequestRemove, authorIdentity crypto.PubKey) (err error)
|
||||
ValidateReadKeyChange(ch *aclrecordproto.AclReadKeyChange, authorIdentity crypto.PubKey) (err error)
|
||||
}
|
||||
|
||||
type contentValidator struct {
|
||||
keyStore crypto.KeyStorage
|
||||
aclState *AclState
|
||||
}
|
||||
|
||||
func (c *contentValidator) ValidateAclRecordContents(ch *AclRecord) (err error) {
|
||||
if ch.PrevId != c.aclState.lastRecordId {
|
||||
return ErrIncorrectRecordSequence
|
||||
}
|
||||
aclData := ch.Model.(*aclrecordproto.AclData)
|
||||
for _, content := range aclData.AclContent {
|
||||
err = c.validateAclRecordContent(content, ch.Identity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *contentValidator) validateAclRecordContent(ch *aclrecordproto.AclContentValue, authorIdentity crypto.PubKey) (err error) {
|
||||
switch {
|
||||
case ch.GetPermissionChange() != nil:
|
||||
return c.ValidatePermissionChange(ch.GetPermissionChange(), authorIdentity)
|
||||
case ch.GetInvite() != nil:
|
||||
return c.ValidateInvite(ch.GetInvite(), authorIdentity)
|
||||
case ch.GetInviteRevoke() != nil:
|
||||
return c.ValidateInviteRevoke(ch.GetInviteRevoke(), authorIdentity)
|
||||
case ch.GetRequestJoin() != nil:
|
||||
return c.ValidateRequestJoin(ch.GetRequestJoin(), authorIdentity)
|
||||
case ch.GetRequestAccept() != nil:
|
||||
return c.ValidateRequestAccept(ch.GetRequestAccept(), authorIdentity)
|
||||
case ch.GetRequestDecline() != nil:
|
||||
return c.ValidateRequestDecline(ch.GetRequestDecline(), authorIdentity)
|
||||
case ch.GetAccountRemove() != nil:
|
||||
return c.ValidateAccountRemove(ch.GetAccountRemove(), authorIdentity)
|
||||
case ch.GetAccountRequestRemove() != nil:
|
||||
return c.ValidateRequestRemove(ch.GetAccountRequestRemove(), authorIdentity)
|
||||
case ch.GetReadKeyChange() != nil:
|
||||
return c.ValidateReadKeyChange(ch.GetReadKeyChange(), authorIdentity)
|
||||
default:
|
||||
return ErrUnexpectedContentType
|
||||
}
|
||||
}
|
||||
|
||||
func (c *contentValidator) ValidatePermissionChange(ch *aclrecordproto.AclAccountPermissionChange, authorIdentity crypto.PubKey) (err error) {
|
||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
chIdentity, err := c.keyStore.PubKeyFromProto(ch.Identity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, exists := c.aclState.userStates[mapKeyFromPubKey(chIdentity)]
|
||||
if !exists {
|
||||
return ErrNoSuchAccount
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *contentValidator) ValidateInvite(ch *aclrecordproto.AclAccountInvite, authorIdentity crypto.PubKey) (err error) {
|
||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
_, err = c.keyStore.PubKeyFromProto(ch.InviteKey)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *contentValidator) ValidateInviteRevoke(ch *aclrecordproto.AclAccountInviteRevoke, authorIdentity crypto.PubKey) (err error) {
|
||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
_, exists := c.aclState.inviteKeys[ch.InviteRecordId]
|
||||
if !exists {
|
||||
return ErrNoSuchInvite
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *contentValidator) ValidateRequestJoin(ch *aclrecordproto.AclAccountRequestJoin, authorIdentity crypto.PubKey) (err error) {
|
||||
inviteKey, exists := c.aclState.inviteKeys[ch.InviteRecordId]
|
||||
if !exists {
|
||||
return ErrNoSuchInvite
|
||||
}
|
||||
inviteIdentity, err := c.keyStore.PubKeyFromProto(ch.InviteIdentity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if _, exists := c.aclState.pendingRequests[mapKeyFromPubKey(inviteIdentity)]; exists {
|
||||
return ErrPendingRequest
|
||||
}
|
||||
if !authorIdentity.Equals(inviteIdentity) {
|
||||
return ErrIncorrectIdentity
|
||||
}
|
||||
rawInviteIdentity, err := inviteIdentity.Raw()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ok, err := inviteKey.Verify(rawInviteIdentity, ch.InviteIdentitySignature)
|
||||
if err != nil {
|
||||
return ErrInvalidSignature
|
||||
}
|
||||
if !ok {
|
||||
return ErrInvalidSignature
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *contentValidator) ValidateRequestAccept(ch *aclrecordproto.AclAccountRequestAccept, authorIdentity crypto.PubKey) (err error) {
|
||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
record, exists := c.aclState.requestRecords[ch.RequestRecordId]
|
||||
if !exists {
|
||||
return ErrNoSuchRequest
|
||||
}
|
||||
acceptIdentity, err := c.keyStore.PubKeyFromProto(ch.Identity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !acceptIdentity.Equals(record.RequestIdentity) {
|
||||
return ErrIncorrectIdentity
|
||||
}
|
||||
if ch.Permissions == aclrecordproto.AclUserPermissions_Owner {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *contentValidator) ValidateRequestDecline(ch *aclrecordproto.AclAccountRequestDecline, authorIdentity crypto.PubKey) (err error) {
|
||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
_, exists := c.aclState.requestRecords[ch.RequestRecordId]
|
||||
if !exists {
|
||||
return ErrNoSuchRequest
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *contentValidator) ValidateAccountRemove(ch *aclrecordproto.AclAccountRemove, authorIdentity crypto.PubKey) (err error) {
|
||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
seenIdentities := map[string]struct{}{}
|
||||
for _, rawIdentity := range ch.Identities {
|
||||
identity, err := c.keyStore.PubKeyFromProto(rawIdentity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if identity.Equals(authorIdentity) {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
permissions := c.aclState.Permissions(identity)
|
||||
if permissions.NoPermissions() {
|
||||
return ErrNoSuchAccount
|
||||
}
|
||||
if permissions.IsOwner() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
idKey := mapKeyFromPubKey(identity)
|
||||
if _, exists := seenIdentities[idKey]; exists {
|
||||
return ErrDuplicateAccounts
|
||||
}
|
||||
seenIdentities[mapKeyFromPubKey(identity)] = struct{}{}
|
||||
}
|
||||
return c.validateAccountReadKeys(ch.AccountKeys, len(c.aclState.userStates)-len(ch.Identities))
|
||||
}
|
||||
|
||||
func (c *contentValidator) ValidateRequestRemove(ch *aclrecordproto.AclAccountRequestRemove, authorIdentity crypto.PubKey) (err error) {
|
||||
if c.aclState.Permissions(authorIdentity).NoPermissions() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
if _, exists := c.aclState.pendingRequests[mapKeyFromPubKey(authorIdentity)]; exists {
|
||||
return ErrPendingRequest
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *contentValidator) ValidateReadKeyChange(ch *aclrecordproto.AclReadKeyChange, authorIdentity crypto.PubKey) (err error) {
|
||||
return c.validateAccountReadKeys(ch.AccountKeys, len(c.aclState.userStates))
|
||||
}
|
||||
|
||||
func (c *contentValidator) validateAccountReadKeys(accountKeys []*aclrecordproto.AclEncryptedReadKey, usersNum int) (err error) {
|
||||
if len(accountKeys) != usersNum {
|
||||
return ErrIncorrectNumberOfAccounts
|
||||
}
|
||||
for _, encKeys := range accountKeys {
|
||||
identity, err := c.keyStore.PubKeyFromProto(encKeys.Identity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, exists := c.aclState.userStates[mapKeyFromPubKey(identity)]
|
||||
if !exists {
|
||||
return ErrNoSuchAccount
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -3,24 +3,26 @@ package liststorage
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
|
||||
"sync"
|
||||
)
|
||||
|
||||
type inMemoryAclListStorage struct {
|
||||
id string
|
||||
root *aclrecordproto.RawAclRecordWithId
|
||||
root *consensusproto.RawRecordWithId
|
||||
head string
|
||||
records map[string]*aclrecordproto.RawAclRecordWithId
|
||||
records map[string]*consensusproto.RawRecordWithId
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func NewInMemoryAclListStorage(
|
||||
id string,
|
||||
records []*aclrecordproto.RawAclRecordWithId) (ListStorage, error) {
|
||||
records []*consensusproto.RawRecordWithId) (ListStorage, error) {
|
||||
|
||||
allRecords := make(map[string]*aclrecordproto.RawAclRecordWithId)
|
||||
allRecords := make(map[string]*consensusproto.RawRecordWithId)
|
||||
for _, ch := range records {
|
||||
allRecords[ch.Id] = ch
|
||||
}
|
||||
@@ -41,7 +43,7 @@ func (t *inMemoryAclListStorage) Id() string {
|
||||
return t.id
|
||||
}
|
||||
|
||||
func (t *inMemoryAclListStorage) Root() (*aclrecordproto.RawAclRecordWithId, error) {
|
||||
func (t *inMemoryAclListStorage) Root() (*consensusproto.RawRecordWithId, error) {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
return t.root, nil
|
||||
@@ -60,7 +62,7 @@ func (t *inMemoryAclListStorage) SetHead(head string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *inMemoryAclListStorage) AddRawRecord(ctx context.Context, record *aclrecordproto.RawAclRecordWithId) error {
|
||||
func (t *inMemoryAclListStorage) AddRawRecord(ctx context.Context, record *consensusproto.RawRecordWithId) error {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
// TODO: better to do deep copy
|
||||
@@ -68,7 +70,7 @@ func (t *inMemoryAclListStorage) AddRawRecord(ctx context.Context, record *aclre
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *inMemoryAclListStorage) GetRawRecord(ctx context.Context, recordId string) (*aclrecordproto.RawAclRecordWithId, error) {
|
||||
func (t *inMemoryAclListStorage) GetRawRecord(ctx context.Context, recordId string) (*consensusproto.RawRecordWithId, error) {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
if res, exists := t.records[recordId]; exists {
|
||||
|
||||
@@ -4,7 +4,8 @@ package liststorage
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -14,15 +15,15 @@ var (
|
||||
)
|
||||
|
||||
type Exporter interface {
|
||||
ListStorage(root *aclrecordproto.RawAclRecordWithId) (ListStorage, error)
|
||||
ListStorage(root *consensusproto.RawRecordWithId) (ListStorage, error)
|
||||
}
|
||||
|
||||
type ListStorage interface {
|
||||
Id() string
|
||||
Root() (*aclrecordproto.RawAclRecordWithId, error)
|
||||
Root() (*consensusproto.RawRecordWithId, error)
|
||||
Head() (string, error)
|
||||
SetHead(headId string) error
|
||||
|
||||
GetRawRecord(ctx context.Context, id string) (*aclrecordproto.RawAclRecordWithId, error)
|
||||
AddRawRecord(ctx context.Context, rec *aclrecordproto.RawAclRecordWithId) error
|
||||
GetRawRecord(ctx context.Context, id string) (*consensusproto.RawRecordWithId, error)
|
||||
AddRawRecord(ctx context.Context, rec *consensusproto.RawRecordWithId) error
|
||||
}
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
aclrecordproto "github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
consensusproto "github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockListStorage is a mock of ListStorage interface.
|
||||
@@ -36,7 +36,7 @@ func (m *MockListStorage) EXPECT() *MockListStorageMockRecorder {
|
||||
}
|
||||
|
||||
// AddRawRecord mocks base method.
|
||||
func (m *MockListStorage) AddRawRecord(arg0 context.Context, arg1 *aclrecordproto.RawAclRecordWithId) error {
|
||||
func (m *MockListStorage) AddRawRecord(arg0 context.Context, arg1 *consensusproto.RawRecordWithId) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AddRawRecord", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
@@ -50,10 +50,10 @@ func (mr *MockListStorageMockRecorder) AddRawRecord(arg0, arg1 interface{}) *gom
|
||||
}
|
||||
|
||||
// GetRawRecord mocks base method.
|
||||
func (m *MockListStorage) GetRawRecord(arg0 context.Context, arg1 string) (*aclrecordproto.RawAclRecordWithId, error) {
|
||||
func (m *MockListStorage) GetRawRecord(arg0 context.Context, arg1 string) (*consensusproto.RawRecordWithId, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetRawRecord", arg0, arg1)
|
||||
ret0, _ := ret[0].(*aclrecordproto.RawAclRecordWithId)
|
||||
ret0, _ := ret[0].(*consensusproto.RawRecordWithId)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
@@ -94,10 +94,10 @@ func (mr *MockListStorageMockRecorder) Id() *gomock.Call {
|
||||
}
|
||||
|
||||
// Root mocks base method.
|
||||
func (m *MockListStorage) Root() (*aclrecordproto.RawAclRecordWithId, error) {
|
||||
func (m *MockListStorage) Root() (*consensusproto.RawRecordWithId, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Root")
|
||||
ret0, _ := ret[0].(*aclrecordproto.RawAclRecordWithId)
|
||||
ret0, _ := ret[0].(*consensusproto.RawRecordWithId)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
//go:generate mockgen -destination mock_syncacl/mock_syncacl.go github.com/anyproto/any-sync/commonspace/object/acl/syncacl SyncAcl,SyncClient,RequestFactory,AclSyncProtocol
|
||||
package syncacl
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type AclSyncProtocol interface {
|
||||
HeadUpdate(ctx context.Context, senderId string, update *consensusproto.LogHeadUpdate) (request *consensusproto.LogSyncMessage, err error)
|
||||
FullSyncRequest(ctx context.Context, senderId string, request *consensusproto.LogFullSyncRequest) (response *consensusproto.LogSyncMessage, err error)
|
||||
FullSyncResponse(ctx context.Context, senderId string, response *consensusproto.LogFullSyncResponse) (err error)
|
||||
}
|
||||
|
||||
type aclSyncProtocol struct {
|
||||
log logger.CtxLogger
|
||||
spaceId string
|
||||
aclList list.AclList
|
||||
reqFactory RequestFactory
|
||||
}
|
||||
|
||||
func (a *aclSyncProtocol) HeadUpdate(ctx context.Context, senderId string, update *consensusproto.LogHeadUpdate) (request *consensusproto.LogSyncMessage, err error) {
|
||||
isEmptyUpdate := len(update.Records) == 0
|
||||
log := a.log.With(
|
||||
zap.String("senderId", senderId),
|
||||
zap.String("update head", update.Head),
|
||||
zap.Int("len(update records)", len(update.Records)))
|
||||
log.DebugCtx(ctx, "received acl head update message")
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.ErrorCtx(ctx, "acl head update finished with error", zap.Error(err))
|
||||
} else if request != nil {
|
||||
cnt := request.Content.GetFullSyncRequest()
|
||||
log.DebugCtx(ctx, "returning acl full sync request", zap.String("request head", cnt.Head))
|
||||
} else {
|
||||
if !isEmptyUpdate {
|
||||
log.DebugCtx(ctx, "acl head update finished correctly")
|
||||
}
|
||||
}
|
||||
}()
|
||||
if isEmptyUpdate {
|
||||
headEquals := a.aclList.Head().Id == update.Head
|
||||
log.DebugCtx(ctx, "is empty acl head update", zap.Bool("headEquals", headEquals))
|
||||
if headEquals {
|
||||
return
|
||||
}
|
||||
return a.reqFactory.CreateFullSyncRequest(a.aclList, update.Head)
|
||||
}
|
||||
if a.aclList.HasHead(update.Head) {
|
||||
return
|
||||
}
|
||||
err = a.aclList.AddRawRecords(update.Records)
|
||||
if err == list.ErrIncorrectRecordSequence {
|
||||
return a.reqFactory.CreateFullSyncRequest(a.aclList, update.Head)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (a *aclSyncProtocol) FullSyncRequest(ctx context.Context, senderId string, request *consensusproto.LogFullSyncRequest) (response *consensusproto.LogSyncMessage, err error) {
|
||||
log := a.log.With(
|
||||
zap.String("senderId", senderId),
|
||||
zap.String("request head", request.Head),
|
||||
zap.Int("len(request records)", len(request.Records)))
|
||||
log.DebugCtx(ctx, "received acl full sync request message")
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.ErrorCtx(ctx, "acl full sync request finished with error", zap.Error(err))
|
||||
} else if response != nil {
|
||||
cnt := response.Content.GetFullSyncResponse()
|
||||
log.DebugCtx(ctx, "acl full sync response sent", zap.String("response head", cnt.Head), zap.Int("len(response records)", len(cnt.Records)))
|
||||
}
|
||||
}()
|
||||
if !a.aclList.HasHead(request.Head) {
|
||||
if len(request.Records) > 0 {
|
||||
// in this case we can try to add some records
|
||||
err = a.aclList.AddRawRecords(request.Records)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// here it is impossible for us to do anything, we can't return records after head as defined in request, because we don't have it
|
||||
return nil, list.ErrIncorrectRecordSequence
|
||||
}
|
||||
}
|
||||
return a.reqFactory.CreateFullSyncResponse(a.aclList, request.Head)
|
||||
}
|
||||
|
||||
func (a *aclSyncProtocol) FullSyncResponse(ctx context.Context, senderId string, response *consensusproto.LogFullSyncResponse) (err error) {
|
||||
log := a.log.With(
|
||||
zap.String("senderId", senderId),
|
||||
zap.String("response head", response.Head),
|
||||
zap.Int("len(response records)", len(response.Records)))
|
||||
log.DebugCtx(ctx, "received acl full sync response message")
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.ErrorCtx(ctx, "acl full sync response failed", zap.Error(err))
|
||||
} else {
|
||||
log.DebugCtx(ctx, "acl full sync response succeeded")
|
||||
}
|
||||
}()
|
||||
if a.aclList.HasHead(response.Head) {
|
||||
return
|
||||
}
|
||||
return a.aclList.AddRawRecords(response.Records)
|
||||
}
|
||||
|
||||
func newAclSyncProtocol(spaceId string, aclList list.AclList, reqFactory RequestFactory) *aclSyncProtocol {
|
||||
return &aclSyncProtocol{
|
||||
log: log.With(zap.String("spaceId", spaceId), zap.String("aclId", aclList.Id())),
|
||||
spaceId: spaceId,
|
||||
aclList: aclList,
|
||||
reqFactory: reqFactory,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
package syncacl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list/mock_list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl/mock_syncacl"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type aclSyncProtocolFixture struct {
|
||||
log logger.CtxLogger
|
||||
spaceId string
|
||||
senderId string
|
||||
aclId string
|
||||
aclMock *mock_list.MockAclList
|
||||
reqFactory *mock_syncacl.MockRequestFactory
|
||||
ctrl *gomock.Controller
|
||||
syncProtocol AclSyncProtocol
|
||||
}
|
||||
|
||||
func newSyncProtocolFixture(t *testing.T) *aclSyncProtocolFixture {
|
||||
ctrl := gomock.NewController(t)
|
||||
aclList := mock_list.NewMockAclList(ctrl)
|
||||
spaceId := "spaceId"
|
||||
reqFactory := mock_syncacl.NewMockRequestFactory(ctrl)
|
||||
aclList.EXPECT().Id().Return("aclId")
|
||||
syncProtocol := newAclSyncProtocol(spaceId, aclList, reqFactory)
|
||||
return &aclSyncProtocolFixture{
|
||||
log: log,
|
||||
spaceId: spaceId,
|
||||
senderId: "senderId",
|
||||
aclId: "aclId",
|
||||
aclMock: aclList,
|
||||
reqFactory: reqFactory,
|
||||
ctrl: ctrl,
|
||||
syncProtocol: syncProtocol,
|
||||
}
|
||||
}
|
||||
|
||||
func (fx *aclSyncProtocolFixture) stop() {
|
||||
fx.ctrl.Finish()
|
||||
}
|
||||
|
||||
func TestHeadUpdate(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
fullRequest := &consensusproto.LogSyncMessage{
|
||||
Content: &consensusproto.LogSyncContentValue{
|
||||
Value: &consensusproto.LogSyncContentValue_FullSyncRequest{
|
||||
FullSyncRequest: &consensusproto.LogFullSyncRequest{},
|
||||
},
|
||||
},
|
||||
}
|
||||
t.Run("head update non empty all heads added", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &consensusproto.RawRecordWithId{}
|
||||
headUpdate := &consensusproto.LogHeadUpdate{
|
||||
Head: "h1",
|
||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
||||
}
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
fx.aclMock.EXPECT().HasHead("h1").Return(false)
|
||||
fx.aclMock.EXPECT().AddRawRecords(headUpdate.Records).Return(nil)
|
||||
req, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
||||
require.Nil(t, req)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("head update results in full request", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &consensusproto.RawRecordWithId{}
|
||||
headUpdate := &consensusproto.LogHeadUpdate{
|
||||
Head: "h1",
|
||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
||||
}
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
fx.aclMock.EXPECT().HasHead("h1").Return(false)
|
||||
fx.aclMock.EXPECT().AddRawRecords(headUpdate.Records).Return(list.ErrIncorrectRecordSequence)
|
||||
fx.reqFactory.EXPECT().CreateFullSyncRequest(fx.aclMock, headUpdate.Head).Return(fullRequest, nil)
|
||||
req, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
||||
require.Equal(t, fullRequest, req)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("head update old heads", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &consensusproto.RawRecordWithId{}
|
||||
headUpdate := &consensusproto.LogHeadUpdate{
|
||||
Head: "h1",
|
||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
||||
}
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
fx.aclMock.EXPECT().HasHead("h1").Return(true)
|
||||
req, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
||||
require.Nil(t, req)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("head update empty equals", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
headUpdate := &consensusproto.LogHeadUpdate{
|
||||
Head: "h1",
|
||||
}
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
fx.aclMock.EXPECT().Head().Return(&list.AclRecord{Id: "h1"})
|
||||
req, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
||||
require.Nil(t, req)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("head update empty results in full request", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
headUpdate := &consensusproto.LogHeadUpdate{
|
||||
Head: "h1",
|
||||
}
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
fx.aclMock.EXPECT().Head().Return(&list.AclRecord{Id: "h2"})
|
||||
fx.reqFactory.EXPECT().CreateFullSyncRequest(fx.aclMock, headUpdate.Head).Return(fullRequest, nil)
|
||||
req, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
||||
require.Equal(t, fullRequest, req)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFullSyncRequest(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
fullResponse := &consensusproto.LogSyncMessage{
|
||||
Content: &consensusproto.LogSyncContentValue{
|
||||
Value: &consensusproto.LogSyncContentValue_FullSyncResponse{
|
||||
FullSyncResponse: &consensusproto.LogFullSyncResponse{},
|
||||
},
|
||||
},
|
||||
}
|
||||
t.Run("full sync request non empty all heads added", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &consensusproto.RawRecordWithId{}
|
||||
fullRequest := &consensusproto.LogFullSyncRequest{
|
||||
Head: "h1",
|
||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
||||
}
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
fx.aclMock.EXPECT().HasHead("h1").Return(false)
|
||||
fx.aclMock.EXPECT().AddRawRecords(fullRequest.Records).Return(nil)
|
||||
fx.reqFactory.EXPECT().CreateFullSyncResponse(fx.aclMock, fullRequest.Head).Return(fullResponse, nil)
|
||||
resp, err := fx.syncProtocol.FullSyncRequest(ctx, fx.senderId, fullRequest)
|
||||
require.Equal(t, fullResponse, resp)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("full sync request non empty head exists", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &consensusproto.RawRecordWithId{}
|
||||
fullRequest := &consensusproto.LogFullSyncRequest{
|
||||
Head: "h1",
|
||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
||||
}
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
fx.aclMock.EXPECT().HasHead("h1").Return(true)
|
||||
fx.reqFactory.EXPECT().CreateFullSyncResponse(fx.aclMock, fullRequest.Head).Return(fullResponse, nil)
|
||||
resp, err := fx.syncProtocol.FullSyncRequest(ctx, fx.senderId, fullRequest)
|
||||
require.Equal(t, fullResponse, resp)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("full sync request empty head not exists", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
fullRequest := &consensusproto.LogFullSyncRequest{
|
||||
Head: "h1",
|
||||
}
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
fx.aclMock.EXPECT().HasHead("h1").Return(false)
|
||||
resp, err := fx.syncProtocol.FullSyncRequest(ctx, fx.senderId, fullRequest)
|
||||
require.Nil(t, resp)
|
||||
require.Error(t, list.ErrIncorrectRecordSequence, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFullSyncResponse(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
t.Run("full sync response no heads", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &consensusproto.RawRecordWithId{}
|
||||
fullResponse := &consensusproto.LogFullSyncResponse{
|
||||
Head: "h1",
|
||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
||||
}
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
fx.aclMock.EXPECT().HasHead("h1").Return(false)
|
||||
fx.aclMock.EXPECT().AddRawRecords(fullResponse.Records).Return(nil)
|
||||
err := fx.syncProtocol.FullSyncResponse(ctx, fx.senderId, fullResponse)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("full sync response has heads", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &consensusproto.RawRecordWithId{}
|
||||
fullResponse := &consensusproto.LogFullSyncResponse{
|
||||
Head: "h1",
|
||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
||||
}
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
fx.aclMock.EXPECT().HasHead("h1").Return(true)
|
||||
err := fx.syncProtocol.FullSyncResponse(ctx, fx.senderId, fullResponse)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package headupdater
|
||||
|
||||
type HeadUpdater interface {
|
||||
UpdateHeads(id string, heads []string)
|
||||
}
|
||||
@@ -0,0 +1,694 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anyproto/any-sync/commonspace/object/acl/syncacl (interfaces: SyncAcl,SyncClient,RequestFactory,AclSyncProtocol)
|
||||
|
||||
// Package mock_syncacl is a generated GoMock package.
|
||||
package mock_syncacl
|
||||
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
app "github.com/anyproto/any-sync/app"
|
||||
list "github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
headupdater "github.com/anyproto/any-sync/commonspace/object/acl/syncacl/headupdater"
|
||||
spacesyncproto "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
consensusproto "github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
crypto "github.com/anyproto/any-sync/util/crypto"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockSyncAcl is a mock of SyncAcl interface.
|
||||
type MockSyncAcl struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockSyncAclMockRecorder
|
||||
}
|
||||
|
||||
// MockSyncAclMockRecorder is the mock recorder for MockSyncAcl.
|
||||
type MockSyncAclMockRecorder struct {
|
||||
mock *MockSyncAcl
|
||||
}
|
||||
|
||||
// NewMockSyncAcl creates a new mock instance.
|
||||
func NewMockSyncAcl(ctrl *gomock.Controller) *MockSyncAcl {
|
||||
mock := &MockSyncAcl{ctrl: ctrl}
|
||||
mock.recorder = &MockSyncAclMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockSyncAcl) EXPECT() *MockSyncAclMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// AclState mocks base method.
|
||||
func (m *MockSyncAcl) AclState() *list.AclState {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AclState")
|
||||
ret0, _ := ret[0].(*list.AclState)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AclState indicates an expected call of AclState.
|
||||
func (mr *MockSyncAclMockRecorder) AclState() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AclState", reflect.TypeOf((*MockSyncAcl)(nil).AclState))
|
||||
}
|
||||
|
||||
// AddRawRecord mocks base method.
|
||||
func (m *MockSyncAcl) AddRawRecord(arg0 *consensusproto.RawRecordWithId) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AddRawRecord", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddRawRecord indicates an expected call of AddRawRecord.
|
||||
func (mr *MockSyncAclMockRecorder) AddRawRecord(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawRecord", reflect.TypeOf((*MockSyncAcl)(nil).AddRawRecord), arg0)
|
||||
}
|
||||
|
||||
// AddRawRecords mocks base method.
|
||||
func (m *MockSyncAcl) AddRawRecords(arg0 []*consensusproto.RawRecordWithId) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AddRawRecords", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddRawRecords indicates an expected call of AddRawRecords.
|
||||
func (mr *MockSyncAclMockRecorder) AddRawRecords(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawRecords", reflect.TypeOf((*MockSyncAcl)(nil).AddRawRecords), arg0)
|
||||
}
|
||||
|
||||
// Close mocks base method.
|
||||
func (m *MockSyncAcl) Close(arg0 context.Context) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Close", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Close indicates an expected call of Close.
|
||||
func (mr *MockSyncAclMockRecorder) Close(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockSyncAcl)(nil).Close), arg0)
|
||||
}
|
||||
|
||||
// Get mocks base method.
|
||||
func (m *MockSyncAcl) Get(arg0 string) (*list.AclRecord, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Get", arg0)
|
||||
ret0, _ := ret[0].(*list.AclRecord)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Get indicates an expected call of Get.
|
||||
func (mr *MockSyncAclMockRecorder) Get(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockSyncAcl)(nil).Get), arg0)
|
||||
}
|
||||
|
||||
// GetIndex mocks base method.
|
||||
func (m *MockSyncAcl) GetIndex(arg0 int) (*list.AclRecord, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetIndex", arg0)
|
||||
ret0, _ := ret[0].(*list.AclRecord)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetIndex indicates an expected call of GetIndex.
|
||||
func (mr *MockSyncAclMockRecorder) GetIndex(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIndex", reflect.TypeOf((*MockSyncAcl)(nil).GetIndex), arg0)
|
||||
}
|
||||
|
||||
// HandleMessage mocks base method.
|
||||
func (m *MockSyncAcl) HandleMessage(arg0 context.Context, arg1 string, arg2 *spacesyncproto.ObjectSyncMessage) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "HandleMessage", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// HandleMessage indicates an expected call of HandleMessage.
|
||||
func (mr *MockSyncAclMockRecorder) HandleMessage(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleMessage", reflect.TypeOf((*MockSyncAcl)(nil).HandleMessage), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// HandleRequest mocks base method.
|
||||
func (m *MockSyncAcl) HandleRequest(arg0 context.Context, arg1 string, arg2 *spacesyncproto.ObjectSyncMessage) (*spacesyncproto.ObjectSyncMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "HandleRequest", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(*spacesyncproto.ObjectSyncMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// HandleRequest indicates an expected call of HandleRequest.
|
||||
func (mr *MockSyncAclMockRecorder) HandleRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleRequest", reflect.TypeOf((*MockSyncAcl)(nil).HandleRequest), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// HasHead mocks base method.
|
||||
func (m *MockSyncAcl) HasHead(arg0 string) bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "HasHead", arg0)
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// HasHead indicates an expected call of HasHead.
|
||||
func (mr *MockSyncAclMockRecorder) HasHead(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasHead", reflect.TypeOf((*MockSyncAcl)(nil).HasHead), arg0)
|
||||
}
|
||||
|
||||
// Head mocks base method.
|
||||
func (m *MockSyncAcl) Head() *list.AclRecord {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Head")
|
||||
ret0, _ := ret[0].(*list.AclRecord)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Head indicates an expected call of Head.
|
||||
func (mr *MockSyncAclMockRecorder) Head() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Head", reflect.TypeOf((*MockSyncAcl)(nil).Head))
|
||||
}
|
||||
|
||||
// Id mocks base method.
|
||||
func (m *MockSyncAcl) Id() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Id")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Id indicates an expected call of Id.
|
||||
func (mr *MockSyncAclMockRecorder) Id() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Id", reflect.TypeOf((*MockSyncAcl)(nil).Id))
|
||||
}
|
||||
|
||||
// Init mocks base method.
|
||||
func (m *MockSyncAcl) Init(arg0 *app.App) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Init", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Init indicates an expected call of Init.
|
||||
func (mr *MockSyncAclMockRecorder) Init(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockSyncAcl)(nil).Init), arg0)
|
||||
}
|
||||
|
||||
// IsAfter mocks base method.
|
||||
func (m *MockSyncAcl) IsAfter(arg0, arg1 string) (bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "IsAfter", arg0, arg1)
|
||||
ret0, _ := ret[0].(bool)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// IsAfter indicates an expected call of IsAfter.
|
||||
func (mr *MockSyncAclMockRecorder) IsAfter(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAfter", reflect.TypeOf((*MockSyncAcl)(nil).IsAfter), arg0, arg1)
|
||||
}
|
||||
|
||||
// Iterate mocks base method.
|
||||
func (m *MockSyncAcl) Iterate(arg0 func(*list.AclRecord) bool) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Iterate", arg0)
|
||||
}
|
||||
|
||||
// Iterate indicates an expected call of Iterate.
|
||||
func (mr *MockSyncAclMockRecorder) Iterate(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Iterate", reflect.TypeOf((*MockSyncAcl)(nil).Iterate), arg0)
|
||||
}
|
||||
|
||||
// IterateFrom mocks base method.
|
||||
func (m *MockSyncAcl) IterateFrom(arg0 string, arg1 func(*list.AclRecord) bool) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "IterateFrom", arg0, arg1)
|
||||
}
|
||||
|
||||
// IterateFrom indicates an expected call of IterateFrom.
|
||||
func (mr *MockSyncAclMockRecorder) IterateFrom(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IterateFrom", reflect.TypeOf((*MockSyncAcl)(nil).IterateFrom), arg0, arg1)
|
||||
}
|
||||
|
||||
// KeyStorage mocks base method.
|
||||
func (m *MockSyncAcl) KeyStorage() crypto.KeyStorage {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "KeyStorage")
|
||||
ret0, _ := ret[0].(crypto.KeyStorage)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// KeyStorage indicates an expected call of KeyStorage.
|
||||
func (mr *MockSyncAclMockRecorder) KeyStorage() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeyStorage", reflect.TypeOf((*MockSyncAcl)(nil).KeyStorage))
|
||||
}
|
||||
|
||||
// Lock mocks base method.
|
||||
func (m *MockSyncAcl) Lock() {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Lock")
|
||||
}
|
||||
|
||||
// Lock indicates an expected call of Lock.
|
||||
func (mr *MockSyncAclMockRecorder) Lock() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Lock", reflect.TypeOf((*MockSyncAcl)(nil).Lock))
|
||||
}
|
||||
|
||||
// Name mocks base method.
|
||||
func (m *MockSyncAcl) Name() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Name")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Name indicates an expected call of Name.
|
||||
func (mr *MockSyncAclMockRecorder) Name() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockSyncAcl)(nil).Name))
|
||||
}
|
||||
|
||||
// RLock mocks base method.
|
||||
func (m *MockSyncAcl) RLock() {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "RLock")
|
||||
}
|
||||
|
||||
// RLock indicates an expected call of RLock.
|
||||
func (mr *MockSyncAclMockRecorder) RLock() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RLock", reflect.TypeOf((*MockSyncAcl)(nil).RLock))
|
||||
}
|
||||
|
||||
// RUnlock mocks base method.
|
||||
func (m *MockSyncAcl) RUnlock() {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "RUnlock")
|
||||
}
|
||||
|
||||
// RUnlock indicates an expected call of RUnlock.
|
||||
func (mr *MockSyncAclMockRecorder) RUnlock() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RUnlock", reflect.TypeOf((*MockSyncAcl)(nil).RUnlock))
|
||||
}
|
||||
|
||||
// RecordBuilder mocks base method.
|
||||
func (m *MockSyncAcl) RecordBuilder() list.AclRecordBuilder {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "RecordBuilder")
|
||||
ret0, _ := ret[0].(list.AclRecordBuilder)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// RecordBuilder indicates an expected call of RecordBuilder.
|
||||
func (mr *MockSyncAclMockRecorder) RecordBuilder() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordBuilder", reflect.TypeOf((*MockSyncAcl)(nil).RecordBuilder))
|
||||
}
|
||||
|
||||
// Records mocks base method.
|
||||
func (m *MockSyncAcl) Records() []*list.AclRecord {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Records")
|
||||
ret0, _ := ret[0].([]*list.AclRecord)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Records indicates an expected call of Records.
|
||||
func (mr *MockSyncAclMockRecorder) Records() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Records", reflect.TypeOf((*MockSyncAcl)(nil).Records))
|
||||
}
|
||||
|
||||
// RecordsAfter mocks base method.
|
||||
func (m *MockSyncAcl) RecordsAfter(arg0 context.Context, arg1 string) ([]*consensusproto.RawRecordWithId, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "RecordsAfter", arg0, arg1)
|
||||
ret0, _ := ret[0].([]*consensusproto.RawRecordWithId)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// RecordsAfter indicates an expected call of RecordsAfter.
|
||||
func (mr *MockSyncAclMockRecorder) RecordsAfter(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordsAfter", reflect.TypeOf((*MockSyncAcl)(nil).RecordsAfter), arg0, arg1)
|
||||
}
|
||||
|
||||
// Root mocks base method.
|
||||
func (m *MockSyncAcl) Root() *consensusproto.RawRecordWithId {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Root")
|
||||
ret0, _ := ret[0].(*consensusproto.RawRecordWithId)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Root indicates an expected call of Root.
|
||||
func (mr *MockSyncAclMockRecorder) Root() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Root", reflect.TypeOf((*MockSyncAcl)(nil).Root))
|
||||
}
|
||||
|
||||
// Run mocks base method.
|
||||
func (m *MockSyncAcl) Run(arg0 context.Context) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Run", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Run indicates an expected call of Run.
|
||||
func (mr *MockSyncAclMockRecorder) Run(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockSyncAcl)(nil).Run), arg0)
|
||||
}
|
||||
|
||||
// SetHeadUpdater mocks base method.
|
||||
func (m *MockSyncAcl) SetHeadUpdater(arg0 headupdater.HeadUpdater) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "SetHeadUpdater", arg0)
|
||||
}
|
||||
|
||||
// SetHeadUpdater indicates an expected call of SetHeadUpdater.
|
||||
func (mr *MockSyncAclMockRecorder) SetHeadUpdater(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetHeadUpdater", reflect.TypeOf((*MockSyncAcl)(nil).SetHeadUpdater), arg0)
|
||||
}
|
||||
|
||||
// SyncWithPeer mocks base method.
|
||||
func (m *MockSyncAcl) SyncWithPeer(arg0 context.Context, arg1 string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SyncWithPeer", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SyncWithPeer indicates an expected call of SyncWithPeer.
|
||||
func (mr *MockSyncAclMockRecorder) SyncWithPeer(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncWithPeer", reflect.TypeOf((*MockSyncAcl)(nil).SyncWithPeer), arg0, arg1)
|
||||
}
|
||||
|
||||
// Unlock mocks base method.
|
||||
func (m *MockSyncAcl) Unlock() {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Unlock")
|
||||
}
|
||||
|
||||
// Unlock indicates an expected call of Unlock.
|
||||
func (mr *MockSyncAclMockRecorder) Unlock() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockSyncAcl)(nil).Unlock))
|
||||
}
|
||||
|
||||
// ValidateRawRecord mocks base method.
|
||||
func (m *MockSyncAcl) ValidateRawRecord(arg0 *consensusproto.RawRecord) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ValidateRawRecord", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ValidateRawRecord indicates an expected call of ValidateRawRecord.
|
||||
func (mr *MockSyncAclMockRecorder) ValidateRawRecord(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateRawRecord", reflect.TypeOf((*MockSyncAcl)(nil).ValidateRawRecord), arg0)
|
||||
}
|
||||
|
||||
// MockSyncClient is a mock of SyncClient interface.
|
||||
type MockSyncClient struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockSyncClientMockRecorder
|
||||
}
|
||||
|
||||
// MockSyncClientMockRecorder is the mock recorder for MockSyncClient.
|
||||
type MockSyncClientMockRecorder struct {
|
||||
mock *MockSyncClient
|
||||
}
|
||||
|
||||
// NewMockSyncClient creates a new mock instance.
|
||||
func NewMockSyncClient(ctrl *gomock.Controller) *MockSyncClient {
|
||||
mock := &MockSyncClient{ctrl: ctrl}
|
||||
mock.recorder = &MockSyncClientMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockSyncClient) EXPECT() *MockSyncClientMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Broadcast mocks base method.
|
||||
func (m *MockSyncClient) Broadcast(arg0 *consensusproto.LogSyncMessage) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Broadcast", arg0)
|
||||
}
|
||||
|
||||
// Broadcast indicates an expected call of Broadcast.
|
||||
func (mr *MockSyncClientMockRecorder) Broadcast(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Broadcast", reflect.TypeOf((*MockSyncClient)(nil).Broadcast), arg0)
|
||||
}
|
||||
|
||||
// CreateFullSyncRequest mocks base method.
|
||||
func (m *MockSyncClient) CreateFullSyncRequest(arg0 list.AclList, arg1 string) (*consensusproto.LogSyncMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateFullSyncRequest", arg0, arg1)
|
||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CreateFullSyncRequest indicates an expected call of CreateFullSyncRequest.
|
||||
func (mr *MockSyncClientMockRecorder) CreateFullSyncRequest(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncRequest", reflect.TypeOf((*MockSyncClient)(nil).CreateFullSyncRequest), arg0, arg1)
|
||||
}
|
||||
|
||||
// CreateFullSyncResponse mocks base method.
|
||||
func (m *MockSyncClient) CreateFullSyncResponse(arg0 list.AclList, arg1 string) (*consensusproto.LogSyncMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateFullSyncResponse", arg0, arg1)
|
||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CreateFullSyncResponse indicates an expected call of CreateFullSyncResponse.
|
||||
func (mr *MockSyncClientMockRecorder) CreateFullSyncResponse(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncResponse", reflect.TypeOf((*MockSyncClient)(nil).CreateFullSyncResponse), arg0, arg1)
|
||||
}
|
||||
|
||||
// CreateHeadUpdate mocks base method.
|
||||
func (m *MockSyncClient) CreateHeadUpdate(arg0 list.AclList, arg1 []*consensusproto.RawRecordWithId) *consensusproto.LogSyncMessage {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateHeadUpdate", arg0, arg1)
|
||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// CreateHeadUpdate indicates an expected call of CreateHeadUpdate.
|
||||
func (mr *MockSyncClientMockRecorder) CreateHeadUpdate(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateHeadUpdate", reflect.TypeOf((*MockSyncClient)(nil).CreateHeadUpdate), arg0, arg1)
|
||||
}
|
||||
|
||||
// QueueRequest mocks base method.
|
||||
func (m *MockSyncClient) QueueRequest(arg0 string, arg1 *consensusproto.LogSyncMessage) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "QueueRequest", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// QueueRequest indicates an expected call of QueueRequest.
|
||||
func (mr *MockSyncClientMockRecorder) QueueRequest(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueRequest", reflect.TypeOf((*MockSyncClient)(nil).QueueRequest), arg0, arg1)
|
||||
}
|
||||
|
||||
// SendRequest mocks base method.
|
||||
func (m *MockSyncClient) SendRequest(arg0 context.Context, arg1 string, arg2 *consensusproto.LogSyncMessage) (*spacesyncproto.ObjectSyncMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SendRequest", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(*spacesyncproto.ObjectSyncMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// SendRequest indicates an expected call of SendRequest.
|
||||
func (mr *MockSyncClientMockRecorder) SendRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendRequest", reflect.TypeOf((*MockSyncClient)(nil).SendRequest), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// SendUpdate mocks base method.
|
||||
func (m *MockSyncClient) SendUpdate(arg0 string, arg1 *consensusproto.LogSyncMessage) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SendUpdate", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SendUpdate indicates an expected call of SendUpdate.
|
||||
func (mr *MockSyncClientMockRecorder) SendUpdate(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendUpdate", reflect.TypeOf((*MockSyncClient)(nil).SendUpdate), arg0, arg1)
|
||||
}
|
||||
|
||||
// MockRequestFactory is a mock of RequestFactory interface.
|
||||
type MockRequestFactory struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockRequestFactoryMockRecorder
|
||||
}
|
||||
|
||||
// MockRequestFactoryMockRecorder is the mock recorder for MockRequestFactory.
|
||||
type MockRequestFactoryMockRecorder struct {
|
||||
mock *MockRequestFactory
|
||||
}
|
||||
|
||||
// NewMockRequestFactory creates a new mock instance.
|
||||
func NewMockRequestFactory(ctrl *gomock.Controller) *MockRequestFactory {
|
||||
mock := &MockRequestFactory{ctrl: ctrl}
|
||||
mock.recorder = &MockRequestFactoryMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockRequestFactory) EXPECT() *MockRequestFactoryMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// CreateFullSyncRequest mocks base method.
|
||||
func (m *MockRequestFactory) CreateFullSyncRequest(arg0 list.AclList, arg1 string) (*consensusproto.LogSyncMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateFullSyncRequest", arg0, arg1)
|
||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CreateFullSyncRequest indicates an expected call of CreateFullSyncRequest.
|
||||
func (mr *MockRequestFactoryMockRecorder) CreateFullSyncRequest(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncRequest", reflect.TypeOf((*MockRequestFactory)(nil).CreateFullSyncRequest), arg0, arg1)
|
||||
}
|
||||
|
||||
// CreateFullSyncResponse mocks base method.
|
||||
func (m *MockRequestFactory) CreateFullSyncResponse(arg0 list.AclList, arg1 string) (*consensusproto.LogSyncMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateFullSyncResponse", arg0, arg1)
|
||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CreateFullSyncResponse indicates an expected call of CreateFullSyncResponse.
|
||||
func (mr *MockRequestFactoryMockRecorder) CreateFullSyncResponse(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncResponse", reflect.TypeOf((*MockRequestFactory)(nil).CreateFullSyncResponse), arg0, arg1)
|
||||
}
|
||||
|
||||
// CreateHeadUpdate mocks base method.
|
||||
func (m *MockRequestFactory) CreateHeadUpdate(arg0 list.AclList, arg1 []*consensusproto.RawRecordWithId) *consensusproto.LogSyncMessage {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateHeadUpdate", arg0, arg1)
|
||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// CreateHeadUpdate indicates an expected call of CreateHeadUpdate.
|
||||
func (mr *MockRequestFactoryMockRecorder) CreateHeadUpdate(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateHeadUpdate", reflect.TypeOf((*MockRequestFactory)(nil).CreateHeadUpdate), arg0, arg1)
|
||||
}
|
||||
|
||||
// MockAclSyncProtocol is a mock of AclSyncProtocol interface.
|
||||
type MockAclSyncProtocol struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockAclSyncProtocolMockRecorder
|
||||
}
|
||||
|
||||
// MockAclSyncProtocolMockRecorder is the mock recorder for MockAclSyncProtocol.
|
||||
type MockAclSyncProtocolMockRecorder struct {
|
||||
mock *MockAclSyncProtocol
|
||||
}
|
||||
|
||||
// NewMockAclSyncProtocol creates a new mock instance.
|
||||
func NewMockAclSyncProtocol(ctrl *gomock.Controller) *MockAclSyncProtocol {
|
||||
mock := &MockAclSyncProtocol{ctrl: ctrl}
|
||||
mock.recorder = &MockAclSyncProtocolMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockAclSyncProtocol) EXPECT() *MockAclSyncProtocolMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// FullSyncRequest mocks base method.
|
||||
func (m *MockAclSyncProtocol) FullSyncRequest(arg0 context.Context, arg1 string, arg2 *consensusproto.LogFullSyncRequest) (*consensusproto.LogSyncMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "FullSyncRequest", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// FullSyncRequest indicates an expected call of FullSyncRequest.
|
||||
func (mr *MockAclSyncProtocolMockRecorder) FullSyncRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FullSyncRequest", reflect.TypeOf((*MockAclSyncProtocol)(nil).FullSyncRequest), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// FullSyncResponse mocks base method.
|
||||
func (m *MockAclSyncProtocol) FullSyncResponse(arg0 context.Context, arg1 string, arg2 *consensusproto.LogFullSyncResponse) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "FullSyncResponse", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// FullSyncResponse indicates an expected call of FullSyncResponse.
|
||||
func (mr *MockAclSyncProtocolMockRecorder) FullSyncResponse(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FullSyncResponse", reflect.TypeOf((*MockAclSyncProtocol)(nil).FullSyncResponse), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// HeadUpdate mocks base method.
|
||||
func (m *MockAclSyncProtocol) HeadUpdate(arg0 context.Context, arg1 string, arg2 *consensusproto.LogHeadUpdate) (*consensusproto.LogSyncMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "HeadUpdate", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// HeadUpdate indicates an expected call of HeadUpdate.
|
||||
func (mr *MockAclSyncProtocolMockRecorder) HeadUpdate(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeadUpdate", reflect.TypeOf((*MockAclSyncProtocol)(nil).HeadUpdate), arg0, arg1, arg2)
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package syncacl
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
)
|
||||
|
||||
type RequestFactory interface {
|
||||
CreateHeadUpdate(l list.AclList, added []*consensusproto.RawRecordWithId) (msg *consensusproto.LogSyncMessage)
|
||||
CreateFullSyncRequest(l list.AclList, theirHead string) (req *consensusproto.LogSyncMessage, err error)
|
||||
CreateFullSyncResponse(l list.AclList, theirHead string) (*consensusproto.LogSyncMessage, error)
|
||||
}
|
||||
|
||||
type requestFactory struct{}
|
||||
|
||||
func NewRequestFactory() RequestFactory {
|
||||
return &requestFactory{}
|
||||
}
|
||||
|
||||
func (r *requestFactory) CreateHeadUpdate(l list.AclList, added []*consensusproto.RawRecordWithId) (msg *consensusproto.LogSyncMessage) {
|
||||
return consensusproto.WrapHeadUpdate(&consensusproto.LogHeadUpdate{
|
||||
Head: l.Head().Id,
|
||||
Records: added,
|
||||
}, l.Root())
|
||||
}
|
||||
|
||||
func (r *requestFactory) CreateFullSyncRequest(l list.AclList, theirHead string) (req *consensusproto.LogSyncMessage, err error) {
|
||||
if !l.HasHead(theirHead) {
|
||||
return consensusproto.WrapFullRequest(&consensusproto.LogFullSyncRequest{
|
||||
Head: l.Head().Id,
|
||||
}, l.Root()), nil
|
||||
}
|
||||
records, err := l.RecordsAfter(context.Background(), theirHead)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return consensusproto.WrapFullRequest(&consensusproto.LogFullSyncRequest{
|
||||
Head: l.Head().Id,
|
||||
Records: records,
|
||||
}, l.Root()), nil
|
||||
}
|
||||
|
||||
func (r *requestFactory) CreateFullSyncResponse(l list.AclList, theirHead string) (resp *consensusproto.LogSyncMessage, err error) {
|
||||
records, err := l.RecordsAfter(context.Background(), theirHead)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return consensusproto.WrapFullResponse(&consensusproto.LogFullSyncResponse{
|
||||
Head: l.Head().Id,
|
||||
Records: records,
|
||||
}, l.Root()), nil
|
||||
}
|
||||
@@ -2,42 +2,129 @@ package syncacl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl/headupdater"
|
||||
"github.com/anyproto/any-sync/commonspace/object/syncobjectgetter"
|
||||
|
||||
"github.com/anyproto/any-sync/accountservice"
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
||||
"github.com/anyproto/any-sync/commonspace/requestmanager"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
)
|
||||
|
||||
const CName = "common.acl.syncacl"
|
||||
|
||||
func New() *SyncAcl {
|
||||
return &SyncAcl{}
|
||||
}
|
||||
var (
|
||||
log = logger.NewNamed(CName)
|
||||
|
||||
type SyncAcl struct {
|
||||
ErrSyncAclClosed = errors.New("sync acl is closed")
|
||||
)
|
||||
|
||||
type SyncAcl interface {
|
||||
app.ComponentRunnable
|
||||
list.AclList
|
||||
syncobjectgetter.SyncObject
|
||||
SetHeadUpdater(updater headupdater.HeadUpdater)
|
||||
SyncWithPeer(ctx context.Context, peerId string) (err error)
|
||||
}
|
||||
|
||||
func (s *SyncAcl) HandleRequest(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (response *spacesyncproto.ObjectSyncMessage, err error) {
|
||||
return nil, nil
|
||||
func New() SyncAcl {
|
||||
return &syncAcl{}
|
||||
}
|
||||
|
||||
func (s *SyncAcl) HandleMessage(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
return nil
|
||||
type syncAcl struct {
|
||||
list.AclList
|
||||
syncClient SyncClient
|
||||
syncHandler synchandler.SyncHandler
|
||||
headUpdater headupdater.HeadUpdater
|
||||
isClosed bool
|
||||
}
|
||||
|
||||
func (s *SyncAcl) Init(a *app.App) (err error) {
|
||||
func (s *syncAcl) Run(ctx context.Context) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (s *syncAcl) HandleRequest(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (response *spacesyncproto.ObjectSyncMessage, err error) {
|
||||
return s.syncHandler.HandleRequest(ctx, senderId, request)
|
||||
}
|
||||
|
||||
func (s *syncAcl) SetHeadUpdater(updater headupdater.HeadUpdater) {
|
||||
s.headUpdater = updater
|
||||
}
|
||||
|
||||
func (s *syncAcl) HandleMessage(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
return s.syncHandler.HandleMessage(ctx, senderId, request)
|
||||
}
|
||||
|
||||
func (s *syncAcl) Init(a *app.App) (err error) {
|
||||
storage := a.MustComponent(spacestorage.CName).(spacestorage.SpaceStorage)
|
||||
aclStorage, err := storage.AclStorage()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
acc := a.MustComponent(accountservice.CName).(accountservice.Service)
|
||||
s.AclList, err = list.BuildAclListWithIdentity(acc.Account(), aclStorage)
|
||||
s.AclList, err = list.BuildAclListWithIdentity(acc.Account(), aclStorage, list.NoOpAcceptorVerifier{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
spaceId := storage.Id()
|
||||
requestManager := a.MustComponent(requestmanager.CName).(requestmanager.RequestManager)
|
||||
peerManager := a.MustComponent(peermanager.CName).(peermanager.PeerManager)
|
||||
syncStatus := a.MustComponent(syncstatus.CName).(syncstatus.StatusService)
|
||||
s.syncClient = NewSyncClient(spaceId, requestManager, peerManager)
|
||||
s.syncHandler = newSyncAclHandler(storage.Id(), s, s.syncClient, syncStatus)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *SyncAcl) Name() (name string) {
|
||||
func (s *syncAcl) AddRawRecord(rawRec *consensusproto.RawRecordWithId) (err error) {
|
||||
if s.isClosed {
|
||||
return ErrSyncAclClosed
|
||||
}
|
||||
err = s.AclList.AddRawRecord(rawRec)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
headUpdate := s.syncClient.CreateHeadUpdate(s, []*consensusproto.RawRecordWithId{rawRec})
|
||||
s.headUpdater.UpdateHeads(s.Id(), []string{rawRec.Id})
|
||||
s.syncClient.Broadcast(headUpdate)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *syncAcl) AddRawRecords(rawRecords []*consensusproto.RawRecordWithId) (err error) {
|
||||
if s.isClosed {
|
||||
return ErrSyncAclClosed
|
||||
}
|
||||
err = s.AclList.AddRawRecords(rawRecords)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
headUpdate := s.syncClient.CreateHeadUpdate(s, rawRecords)
|
||||
s.headUpdater.UpdateHeads(s.Id(), []string{rawRecords[len(rawRecords)-1].Id})
|
||||
s.syncClient.Broadcast(headUpdate)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *syncAcl) SyncWithPeer(ctx context.Context, peerId string) (err error) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
headUpdate := s.syncClient.CreateHeadUpdate(s, nil)
|
||||
return s.syncClient.SendUpdate(peerId, headUpdate)
|
||||
}
|
||||
|
||||
func (s *syncAcl) Close(ctx context.Context) (err error) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
s.isClosed = true
|
||||
return
|
||||
}
|
||||
|
||||
func (s *syncAcl) Name() (name string) {
|
||||
return CName
|
||||
}
|
||||
@@ -2,30 +2,81 @@ package syncacl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"errors"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMessageIsRequest = errors.New("message is request")
|
||||
ErrMessageIsNotRequest = errors.New("message is not request")
|
||||
)
|
||||
|
||||
type syncAclHandler struct {
|
||||
acl list.AclList
|
||||
aclList list.AclList
|
||||
syncClient SyncClient
|
||||
syncProtocol AclSyncProtocol
|
||||
syncStatus syncstatus.StatusUpdater
|
||||
spaceId string
|
||||
}
|
||||
|
||||
func (s *syncAclHandler) HandleMessage(ctx context.Context, senderId string, req *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
aclMsg := &aclrecordproto.AclSyncMessage{}
|
||||
if err = aclMsg.Unmarshal(req.Payload); err != nil {
|
||||
func newSyncAclHandler(spaceId string, aclList list.AclList, syncClient SyncClient, syncStatus syncstatus.StatusUpdater) synchandler.SyncHandler {
|
||||
return &syncAclHandler{
|
||||
aclList: aclList,
|
||||
syncClient: syncClient,
|
||||
syncProtocol: newAclSyncProtocol(spaceId, aclList, syncClient),
|
||||
syncStatus: syncStatus,
|
||||
spaceId: spaceId,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *syncAclHandler) HandleMessage(ctx context.Context, senderId string, message *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
unmarshalled := &consensusproto.LogSyncMessage{}
|
||||
err = proto.Unmarshal(message.Payload, unmarshalled)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
content := aclMsg.GetContent()
|
||||
content := unmarshalled.GetContent()
|
||||
head := consensusproto.GetHead(unmarshalled)
|
||||
s.syncStatus.HeadsReceive(senderId, s.aclList.Id(), []string{head})
|
||||
s.aclList.Lock()
|
||||
defer s.aclList.Unlock()
|
||||
switch {
|
||||
case content.GetAddRecords() != nil:
|
||||
return s.handleAddRecords(ctx, senderId, content.GetAddRecords())
|
||||
default:
|
||||
return fmt.Errorf("unexpected aclSync message: %T", content.Value)
|
||||
case content.GetHeadUpdate() != nil:
|
||||
var syncReq *consensusproto.LogSyncMessage
|
||||
syncReq, err = s.syncProtocol.HeadUpdate(ctx, senderId, content.GetHeadUpdate())
|
||||
if err != nil || syncReq == nil {
|
||||
return
|
||||
}
|
||||
return s.syncClient.QueueRequest(senderId, syncReq)
|
||||
case content.GetFullSyncRequest() != nil:
|
||||
return ErrMessageIsRequest
|
||||
case content.GetFullSyncResponse() != nil:
|
||||
return s.syncProtocol.FullSyncResponse(ctx, senderId, content.GetFullSyncResponse())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *syncAclHandler) handleAddRecords(ctx context.Context, senderId string, addRecord *aclrecordproto.AclAddRecords) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (s *syncAclHandler) HandleRequest(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (response *spacesyncproto.ObjectSyncMessage, err error) {
|
||||
unmarshalled := &consensusproto.LogSyncMessage{}
|
||||
err = proto.Unmarshal(request.Payload, unmarshalled)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fullSyncRequest := unmarshalled.GetContent().GetFullSyncRequest()
|
||||
if fullSyncRequest == nil {
|
||||
return nil, ErrMessageIsNotRequest
|
||||
}
|
||||
s.aclList.Lock()
|
||||
defer s.aclList.Unlock()
|
||||
aclResp, err := s.syncProtocol.FullSyncRequest(ctx, senderId, fullSyncRequest)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return spacesyncproto.MarshallSyncMessage(aclResp, s.spaceId, s.aclList.Id())
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
package syncacl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list/mock_list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl/mock_syncacl"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testAclMock struct {
|
||||
*mock_list.MockAclList
|
||||
m sync.RWMutex
|
||||
}
|
||||
|
||||
func newTestAclMock(mockAcl *mock_list.MockAclList) *testAclMock {
|
||||
return &testAclMock{
|
||||
MockAclList: mockAcl,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *testAclMock) Lock() {
|
||||
t.m.Lock()
|
||||
}
|
||||
|
||||
func (t *testAclMock) RLock() {
|
||||
t.m.RLock()
|
||||
}
|
||||
|
||||
func (t *testAclMock) Unlock() {
|
||||
t.m.Unlock()
|
||||
}
|
||||
|
||||
func (t *testAclMock) RUnlock() {
|
||||
t.m.RUnlock()
|
||||
}
|
||||
|
||||
func (t *testAclMock) TryLock() bool {
|
||||
return t.m.TryLock()
|
||||
}
|
||||
|
||||
func (t *testAclMock) TryRLock() bool {
|
||||
return t.m.TryRLock()
|
||||
}
|
||||
|
||||
type syncHandlerFixture struct {
|
||||
ctrl *gomock.Controller
|
||||
syncClientMock *mock_syncacl.MockSyncClient
|
||||
aclMock *testAclMock
|
||||
syncProtocolMock *mock_syncacl.MockAclSyncProtocol
|
||||
spaceId string
|
||||
senderId string
|
||||
aclId string
|
||||
|
||||
syncHandler *syncAclHandler
|
||||
}
|
||||
|
||||
func newSyncHandlerFixture(t *testing.T) *syncHandlerFixture {
|
||||
ctrl := gomock.NewController(t)
|
||||
aclMock := newTestAclMock(mock_list.NewMockAclList(ctrl))
|
||||
syncClientMock := mock_syncacl.NewMockSyncClient(ctrl)
|
||||
syncProtocolMock := mock_syncacl.NewMockAclSyncProtocol(ctrl)
|
||||
spaceId := "spaceId"
|
||||
|
||||
syncHandler := &syncAclHandler{
|
||||
aclList: aclMock,
|
||||
syncClient: syncClientMock,
|
||||
syncProtocol: syncProtocolMock,
|
||||
syncStatus: syncstatus.NewNoOpSyncStatus(),
|
||||
spaceId: spaceId,
|
||||
}
|
||||
return &syncHandlerFixture{
|
||||
ctrl: ctrl,
|
||||
syncClientMock: syncClientMock,
|
||||
aclMock: aclMock,
|
||||
syncProtocolMock: syncProtocolMock,
|
||||
spaceId: spaceId,
|
||||
senderId: "senderId",
|
||||
aclId: "aclId",
|
||||
syncHandler: syncHandler,
|
||||
}
|
||||
}
|
||||
|
||||
func (fx *syncHandlerFixture) stop() {
|
||||
fx.ctrl.Finish()
|
||||
}
|
||||
|
||||
func TestSyncAclHandler_HandleMessage(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
t.Run("handle head update, request returned", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &consensusproto.RawRecordWithId{}
|
||||
headUpdate := &consensusproto.LogHeadUpdate{
|
||||
Head: "h1",
|
||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
||||
}
|
||||
logMessage := consensusproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
|
||||
|
||||
syncReq := &consensusproto.LogSyncMessage{}
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
fx.syncProtocolMock.EXPECT().HeadUpdate(ctx, fx.senderId, gomock.Any()).Return(syncReq, nil)
|
||||
fx.syncClientMock.EXPECT().QueueRequest(fx.senderId, syncReq).Return(nil)
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("handle head update, no request", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &consensusproto.RawRecordWithId{}
|
||||
headUpdate := &consensusproto.LogHeadUpdate{
|
||||
Head: "h1",
|
||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
||||
}
|
||||
logMessage := consensusproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
|
||||
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
fx.syncProtocolMock.EXPECT().HeadUpdate(ctx, fx.senderId, gomock.Any()).Return(nil, nil)
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("handle head update, returned error", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &consensusproto.RawRecordWithId{}
|
||||
headUpdate := &consensusproto.LogHeadUpdate{
|
||||
Head: "h1",
|
||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
||||
}
|
||||
logMessage := consensusproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
|
||||
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
expectedErr := fmt.Errorf("some error")
|
||||
fx.syncProtocolMock.EXPECT().HeadUpdate(ctx, fx.senderId, gomock.Any()).Return(nil, expectedErr)
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
||||
require.Error(t, expectedErr, err)
|
||||
})
|
||||
t.Run("handle full sync request is forbidden", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &consensusproto.RawRecordWithId{}
|
||||
fullRequest := &consensusproto.LogFullSyncRequest{
|
||||
Head: "h1",
|
||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
||||
}
|
||||
logMessage := consensusproto.WrapFullRequest(fullRequest, chWithId)
|
||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
|
||||
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
||||
require.Error(t, ErrMessageIsRequest, err)
|
||||
})
|
||||
t.Run("handle full sync response, no error", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &consensusproto.RawRecordWithId{}
|
||||
fullResponse := &consensusproto.LogFullSyncResponse{
|
||||
Head: "h1",
|
||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
||||
}
|
||||
logMessage := consensusproto.WrapFullResponse(fullResponse, chWithId)
|
||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
|
||||
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
fx.syncProtocolMock.EXPECT().FullSyncResponse(ctx, fx.senderId, gomock.Any()).Return(nil)
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSyncAclHandler_HandleRequest(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
t.Run("handle full sync request, no error", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &consensusproto.RawRecordWithId{}
|
||||
fullRequest := &consensusproto.LogFullSyncRequest{
|
||||
Head: "h1",
|
||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
||||
}
|
||||
logMessage := consensusproto.WrapFullRequest(fullRequest, chWithId)
|
||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
|
||||
fullResp := &consensusproto.LogSyncMessage{
|
||||
Content: &consensusproto.LogSyncContentValue{
|
||||
Value: &consensusproto.LogSyncContentValue_FullSyncResponse{
|
||||
FullSyncResponse: &consensusproto.LogFullSyncResponse{
|
||||
Head: "returnedHead",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
fx.syncProtocolMock.EXPECT().FullSyncRequest(ctx, fx.senderId, gomock.Any()).Return(fullResp, nil)
|
||||
res, err := fx.syncHandler.HandleRequest(ctx, fx.senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
unmarshalled := &consensusproto.LogSyncMessage{}
|
||||
err = proto.Unmarshal(res.Payload, unmarshalled)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
require.Equal(t, "returnedHead", consensusproto.GetHead(unmarshalled))
|
||||
})
|
||||
t.Run("handle other message returns error", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &consensusproto.RawRecordWithId{}
|
||||
headUpdate := &consensusproto.LogHeadUpdate{
|
||||
Head: "h1",
|
||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
||||
}
|
||||
logMessage := consensusproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
|
||||
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
_, err := fx.syncHandler.HandleRequest(ctx, fx.senderId, objectMsg)
|
||||
require.Error(t, ErrMessageIsNotRequest, err)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package syncacl
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
||||
"github.com/anyproto/any-sync/commonspace/requestmanager"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type SyncClient interface {
|
||||
RequestFactory
|
||||
Broadcast(msg *consensusproto.LogSyncMessage)
|
||||
SendUpdate(peerId string, msg *consensusproto.LogSyncMessage) (err error)
|
||||
QueueRequest(peerId string, msg *consensusproto.LogSyncMessage) (err error)
|
||||
SendRequest(ctx context.Context, peerId string, msg *consensusproto.LogSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error)
|
||||
}
|
||||
|
||||
type syncClient struct {
|
||||
RequestFactory
|
||||
spaceId string
|
||||
requestManager requestmanager.RequestManager
|
||||
peerManager peermanager.PeerManager
|
||||
}
|
||||
|
||||
func NewSyncClient(spaceId string, requestManager requestmanager.RequestManager, peerManager peermanager.PeerManager) SyncClient {
|
||||
return &syncClient{
|
||||
RequestFactory: &requestFactory{},
|
||||
spaceId: spaceId,
|
||||
requestManager: requestManager,
|
||||
peerManager: peerManager,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *syncClient) Broadcast(msg *consensusproto.LogSyncMessage) {
|
||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, msg.Id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = s.peerManager.Broadcast(context.Background(), objMsg)
|
||||
if err != nil {
|
||||
log.Debug("broadcast error", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *syncClient) SendUpdate(peerId string, msg *consensusproto.LogSyncMessage) (err error) {
|
||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, msg.Id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return s.peerManager.SendPeer(context.Background(), peerId, objMsg)
|
||||
}
|
||||
|
||||
func (s *syncClient) SendRequest(ctx context.Context, peerId string, msg *consensusproto.LogSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error) {
|
||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, msg.Id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return s.requestManager.SendRequest(ctx, peerId, objMsg)
|
||||
}
|
||||
|
||||
func (s *syncClient) QueueRequest(peerId string, msg *consensusproto.LogSyncMessage) (err error) {
|
||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, msg.Id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return s.requestManager.QueueRequest(peerId, objMsg)
|
||||
}
|
||||
@@ -15,7 +15,7 @@ type TreeImportParams struct {
|
||||
}
|
||||
|
||||
func ImportHistoryTree(params TreeImportParams) (tree objecttree.ReadableObjectTree, err error) {
|
||||
aclList, err := list.BuildAclList(params.ListStorage)
|
||||
aclList, err := list.BuildAclList(params.ListStorage, list.NoOpAcceptorVerifier{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
objecttree "github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
treechangeproto "github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
treestorage "github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockObjectTree is a mock of ObjectTree interface.
|
||||
|
||||
@@ -4,11 +4,11 @@ package objecttree
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
@@ -248,9 +248,7 @@ func (ot *objectTree) prepareBuilderContent(content SignableChangeContent) (cnt
|
||||
pubKey = content.Key.GetPublic()
|
||||
readKeyId string
|
||||
)
|
||||
canWrite := state.HasPermission(pubKey, aclrecordproto.AclUserPermissions_Writer) ||
|
||||
state.HasPermission(pubKey, aclrecordproto.AclUserPermissions_Admin)
|
||||
if !canWrite {
|
||||
if !state.Permissions(pubKey).CanWrite() {
|
||||
err = list.ErrInsufficientPermissions
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ package objecttree
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
@@ -10,8 +13,6 @@ import (
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type testTreeContext struct {
|
||||
@@ -123,6 +124,7 @@ func TestObjectTree(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.GreaterOrEqual(t, start.Unix(), ch.Timestamp)
|
||||
require.LessOrEqual(t, end.Unix(), ch.Timestamp)
|
||||
require.Equal(t, res.Added[0].Id, oTree.(*objectTree).tree.lastIteratedHeadId)
|
||||
})
|
||||
t.Run("timestamp is set correctly", func(t *testing.T) {
|
||||
someTs := time.Now().Add(time.Hour).Unix()
|
||||
@@ -139,6 +141,7 @@ func TestObjectTree(t *testing.T) {
|
||||
ch, err := oTree.(*objectTree).changeBuilder.Unmarshall(res.Added[0], true)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ch.Timestamp, someTs)
|
||||
require.Equal(t, res.Added[0].Id, oTree.(*objectTree).tree.lastIteratedHeadId)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package objecttree
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/util/slice"
|
||||
@@ -52,20 +52,18 @@ func (v *objectTreeValidator) ValidateNewChanges(tree *Tree, aclList list.AclLis
|
||||
|
||||
func (v *objectTreeValidator) validateChange(tree *Tree, aclList list.AclList, c *Change) (err error) {
|
||||
var (
|
||||
perm list.AclUserState
|
||||
state = aclList.AclState()
|
||||
userState list.AclUserState
|
||||
state = aclList.AclState()
|
||||
)
|
||||
// checking if the user could write
|
||||
perm, err = state.StateAtRecord(c.AclHeadId, c.Identity)
|
||||
userState, err = state.StateAtRecord(c.AclHeadId, c.Identity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if perm.Permissions != aclrecordproto.AclUserPermissions_Writer && perm.Permissions != aclrecordproto.AclUserPermissions_Admin {
|
||||
if !userState.Permissions.CanWrite() {
|
||||
err = list.ErrInsufficientPermissions
|
||||
return
|
||||
}
|
||||
|
||||
if c.Id == tree.RootId() {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,10 +2,11 @@ package objecttree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/util/slice"
|
||||
"time"
|
||||
)
|
||||
|
||||
type rawChangeLoader struct {
|
||||
@@ -22,6 +23,7 @@ type rawCacheEntry struct {
|
||||
change *Change
|
||||
rawChange *treechangeproto.RawTreeChangeWithId
|
||||
position int
|
||||
removed bool
|
||||
}
|
||||
|
||||
func newStorageLoader(treeStorage treestorage.TreeStorage, changeBuilder ChangeBuilder) *rawChangeLoader {
|
||||
@@ -126,7 +128,6 @@ func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoi
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
entry.position = -1
|
||||
r.cache[b] = entry
|
||||
existingBreakpoints = append(existingBreakpoints, b)
|
||||
}
|
||||
@@ -135,8 +136,7 @@ func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoi
|
||||
dfs := func(
|
||||
commonSnapshot string,
|
||||
heads []string,
|
||||
startCounter int,
|
||||
shouldVisit func(counter int, mapExists bool) bool,
|
||||
shouldVisit func(entry rawCacheEntry, mapExists bool) bool,
|
||||
visit func(entry rawCacheEntry) rawCacheEntry) bool {
|
||||
|
||||
// resetting stack
|
||||
@@ -150,7 +150,7 @@ func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoi
|
||||
r.idStack = r.idStack[:len(r.idStack)-1]
|
||||
|
||||
entry, exists := r.cache[id]
|
||||
if !shouldVisit(entry.position, exists) {
|
||||
if !shouldVisit(entry, exists) {
|
||||
continue
|
||||
}
|
||||
if id == commonSnapshot {
|
||||
@@ -159,7 +159,6 @@ func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoi
|
||||
}
|
||||
if !exists {
|
||||
entry, err = r.loadEntry(id)
|
||||
entry.position = -1
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -174,7 +173,7 @@ func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoi
|
||||
break
|
||||
}
|
||||
prevEntry, exists := r.cache[prev]
|
||||
if !shouldVisit(prevEntry.position, exists) {
|
||||
if !shouldVisit(prevEntry, exists) {
|
||||
continue
|
||||
}
|
||||
r.idStack = append(r.idStack, prev)
|
||||
@@ -187,8 +186,8 @@ func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoi
|
||||
r.idStack = append(r.idStack, heads...)
|
||||
var buffer []*treechangeproto.RawTreeChangeWithId
|
||||
|
||||
rootVisited := dfs(commonSnapshot, heads, 0,
|
||||
func(counter int, mapExists bool) bool {
|
||||
rootVisited := dfs(commonSnapshot, heads,
|
||||
func(_ rawCacheEntry, mapExists bool) bool {
|
||||
return !mapExists
|
||||
},
|
||||
func(entry rawCacheEntry) rawCacheEntry {
|
||||
@@ -213,11 +212,13 @@ func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoi
|
||||
}
|
||||
|
||||
// marking all visited as nil
|
||||
dfs(commonSnapshot, existingBreakpoints, len(buffer),
|
||||
func(counter int, mapExists bool) bool {
|
||||
return !mapExists || counter < len(buffer)
|
||||
dfs(commonSnapshot, existingBreakpoints,
|
||||
func(entry rawCacheEntry, mapExists bool) bool {
|
||||
// only going through already loaded changes
|
||||
return mapExists && !entry.removed
|
||||
},
|
||||
func(entry rawCacheEntry) rawCacheEntry {
|
||||
entry.removed = true
|
||||
if entry.position != -1 {
|
||||
buffer[entry.position] = nil
|
||||
}
|
||||
@@ -248,6 +249,7 @@ func (r *rawChangeLoader) loadEntry(id string) (entry rawCacheEntry, err error)
|
||||
entry = rawCacheEntry{
|
||||
change: change,
|
||||
rawChange: rawChange,
|
||||
position: -1,
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -82,6 +82,7 @@ func (t *Tree) AddMergedHead(c *Change) error {
|
||||
}
|
||||
}
|
||||
t.headIds = []string{c.Id}
|
||||
t.lastIteratedHeadId = c.Id
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,12 @@ package objecttree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func newChange(id string, snapshotId string, prevIds ...string) *Change {
|
||||
@@ -26,6 +28,17 @@ func newSnapshot(id, snapshotId string, prevIds ...string) *Change {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTree_AddMergedHead(t *testing.T) {
|
||||
tr := new(Tree)
|
||||
_, _ = tr.Add(
|
||||
newSnapshot("root", ""),
|
||||
newChange("one", "root", "root"),
|
||||
)
|
||||
require.Equal(t, tr.lastIteratedHeadId, "one")
|
||||
tr.AddMergedHead(newChange("two", "root", "one"))
|
||||
require.Equal(t, tr.lastIteratedHeadId, "two")
|
||||
}
|
||||
|
||||
func TestTree_Add(t *testing.T) {
|
||||
t.Run("add first el", func(t *testing.T) {
|
||||
tr := new(Tree)
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
treechangeproto "github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
treestorage "github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
spacesyncproto "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockSyncTree is a mock of SyncTree interface.
|
||||
|
||||
@@ -2,6 +2,10 @@ package synctree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
@@ -10,9 +14,6 @@ import (
|
||||
"github.com/anyproto/any-sync/util/slice"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/stretchr/testify/require"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestEmptyClientGetsFullHistory(t *testing.T) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package synctree
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
||||
"github.com/anyproto/any-sync/commonspace/requestmanager"
|
||||
@@ -32,8 +33,9 @@ func NewSyncClient(spaceId string, requestManager requestmanager.RequestManager,
|
||||
peerManager: peerManager,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *syncClient) Broadcast(msg *treechangeproto.TreeSyncMessage) {
|
||||
objMsg, err := MarshallTreeMessage(msg, s.spaceId, msg.RootChange.Id, "")
|
||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, msg.RootChange.Id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -44,7 +46,7 @@ func (s *syncClient) Broadcast(msg *treechangeproto.TreeSyncMessage) {
|
||||
}
|
||||
|
||||
func (s *syncClient) SendUpdate(peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (err error) {
|
||||
objMsg, err := MarshallTreeMessage(msg, s.spaceId, objectId, "")
|
||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, objectId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -52,7 +54,7 @@ func (s *syncClient) SendUpdate(peerId, objectId string, msg *treechangeproto.Tr
|
||||
}
|
||||
|
||||
func (s *syncClient) SendRequest(ctx context.Context, peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error) {
|
||||
objMsg, err := MarshallTreeMessage(msg, s.spaceId, objectId, "")
|
||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, objectId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -60,23 +62,9 @@ func (s *syncClient) SendRequest(ctx context.Context, peerId, objectId string, m
|
||||
}
|
||||
|
||||
func (s *syncClient) QueueRequest(peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (err error) {
|
||||
objMsg, err := MarshallTreeMessage(msg, s.spaceId, objectId, "")
|
||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, objectId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return s.requestManager.QueueRequest(peerId, objMsg)
|
||||
}
|
||||
|
||||
func MarshallTreeMessage(message *treechangeproto.TreeSyncMessage, spaceId, objectId, replyId string) (objMsg *spacesyncproto.ObjectSyncMessage, err error) {
|
||||
payload, err := message.Marshal()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
objMsg = &spacesyncproto.ObjectSyncMessage{
|
||||
ReplyId: replyId,
|
||||
Payload: payload,
|
||||
ObjectId: objectId,
|
||||
SpaceId: spaceId,
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -205,18 +205,27 @@ func (s *syncTree) Delete() (err error) {
|
||||
}
|
||||
|
||||
func (s *syncTree) TryClose(objectTTL time.Duration) (bool, error) {
|
||||
return true, s.Close()
|
||||
if !s.TryLock() {
|
||||
return false, nil
|
||||
}
|
||||
log.Debug("closing sync tree", zap.String("id", s.Id()))
|
||||
return true, s.close()
|
||||
}
|
||||
|
||||
func (s *syncTree) Close() (err error) {
|
||||
log.Debug("closing sync tree", zap.String("id", s.Id()))
|
||||
s.Lock()
|
||||
return s.close()
|
||||
}
|
||||
|
||||
func (s *syncTree) close() (err error) {
|
||||
defer s.Unlock()
|
||||
defer func() {
|
||||
log.Debug("closed sync tree", zap.Error(err), zap.String("id", s.Id()))
|
||||
}()
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
if s.isClosed {
|
||||
return ErrSyncTreeClosed
|
||||
err = ErrSyncTreeClosed
|
||||
return
|
||||
}
|
||||
s.onClose(s.Id())
|
||||
s.isClosed = true
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"github.com/anyproto/any-sync/commonspace/objectsync"
|
||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||
"github.com/anyproto/any-sync/nodeconf"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
@@ -3,18 +3,21 @@ package synctree
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||
"github.com/anyproto/any-sync/util/slice"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMessageIsRequest = errors.New("message is request")
|
||||
ErrMessageIsNotRequest = errors.New("message is not request")
|
||||
ErrMoreThanOneRequest = errors.New("more than one request for same peer")
|
||||
)
|
||||
|
||||
type syncTreeHandler struct {
|
||||
@@ -22,21 +25,23 @@ type syncTreeHandler struct {
|
||||
syncClient SyncClient
|
||||
syncProtocol TreeSyncProtocol
|
||||
syncStatus syncstatus.StatusUpdater
|
||||
handlerLock sync.Mutex
|
||||
spaceId string
|
||||
queue ReceiveQueue
|
||||
|
||||
handlerLock sync.Mutex
|
||||
pendingRequests map[string]struct{}
|
||||
heads []string
|
||||
}
|
||||
|
||||
const maxQueueSize = 5
|
||||
|
||||
func newSyncTreeHandler(spaceId string, objTree objecttree.ObjectTree, syncClient SyncClient, syncStatus syncstatus.StatusUpdater) synchandler.SyncHandler {
|
||||
return &syncTreeHandler{
|
||||
objTree: objTree,
|
||||
syncProtocol: newTreeSyncProtocol(spaceId, objTree, syncClient),
|
||||
syncClient: syncClient,
|
||||
syncStatus: syncStatus,
|
||||
spaceId: spaceId,
|
||||
queue: newReceiveQueue(maxQueueSize),
|
||||
objTree: objTree,
|
||||
syncProtocol: newTreeSyncProtocol(spaceId, objTree, syncClient),
|
||||
syncClient: syncClient,
|
||||
syncStatus: syncStatus,
|
||||
spaceId: spaceId,
|
||||
pendingRequests: make(map[string]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,17 +53,35 @@ func (s *syncTreeHandler) HandleRequest(ctx context.Context, senderId string, re
|
||||
}
|
||||
fullSyncRequest := unmarshalled.GetContent().GetFullSyncRequest()
|
||||
if fullSyncRequest == nil {
|
||||
err = ErrMessageIsNotRequest
|
||||
return
|
||||
return nil, ErrMessageIsNotRequest
|
||||
}
|
||||
s.syncStatus.HeadsReceive(senderId, request.ObjectId, treechangeproto.GetHeads(unmarshalled))
|
||||
// setting pending requests
|
||||
s.handlerLock.Lock()
|
||||
_, exists := s.pendingRequests[senderId]
|
||||
if exists {
|
||||
s.handlerLock.Unlock()
|
||||
return nil, ErrMoreThanOneRequest
|
||||
}
|
||||
s.pendingRequests[senderId] = struct{}{}
|
||||
s.handlerLock.Unlock()
|
||||
|
||||
response, err = s.handleRequest(ctx, senderId, fullSyncRequest)
|
||||
|
||||
// removing pending requests
|
||||
s.handlerLock.Lock()
|
||||
delete(s.pendingRequests, senderId)
|
||||
s.handlerLock.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
func (s *syncTreeHandler) handleRequest(ctx context.Context, senderId string, fullSyncRequest *treechangeproto.TreeFullSyncRequest) (response *spacesyncproto.ObjectSyncMessage, err error) {
|
||||
s.objTree.Lock()
|
||||
defer s.objTree.Unlock()
|
||||
treeResp, err := s.syncProtocol.FullSyncRequest(ctx, senderId, fullSyncRequest)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
response, err = MarshallTreeMessage(treeResp, s.spaceId, request.ObjectId, "")
|
||||
response, err = spacesyncproto.MarshallSyncMessage(treeResp, s.spaceId, s.objTree.Id())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -68,28 +91,41 @@ func (s *syncTreeHandler) HandleMessage(ctx context.Context, senderId string, ms
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.syncStatus.HeadsReceive(senderId, msg.ObjectId, treechangeproto.GetHeads(unmarshalled))
|
||||
|
||||
queueFull := s.queue.AddMessage(senderId, unmarshalled, msg.RequestId)
|
||||
if queueFull {
|
||||
heads := treechangeproto.GetHeads(unmarshalled)
|
||||
s.syncStatus.HeadsReceive(senderId, msg.ObjectId, heads)
|
||||
s.handlerLock.Lock()
|
||||
// if the update has same heads then returning not to hang on a lock
|
||||
if unmarshalled.GetContent().GetHeadUpdate() != nil && slice.UnsortedEquals(heads, s.heads) {
|
||||
s.handlerLock.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
return s.handleMessage(ctx, senderId)
|
||||
s.handlerLock.Unlock()
|
||||
return s.handleMessage(ctx, unmarshalled, senderId)
|
||||
}
|
||||
|
||||
func (s *syncTreeHandler) handleMessage(ctx context.Context, senderId string) (err error) {
|
||||
func (s *syncTreeHandler) handleMessage(ctx context.Context, msg *treechangeproto.TreeSyncMessage, senderId string) (err error) {
|
||||
s.objTree.Lock()
|
||||
defer s.objTree.Unlock()
|
||||
msg, _, err := s.queue.GetMessage(senderId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var (
|
||||
copyHeads = make([]string, 0, len(s.objTree.Heads()))
|
||||
treeId = s.objTree.Id()
|
||||
content = msg.GetContent()
|
||||
)
|
||||
|
||||
defer s.queue.ClearQueue(senderId)
|
||||
// getting old heads
|
||||
copyHeads = append(copyHeads, s.objTree.Heads()...)
|
||||
defer func() {
|
||||
// checking if something changed
|
||||
if !slice.UnsortedEquals(copyHeads, s.objTree.Heads()) {
|
||||
s.handlerLock.Lock()
|
||||
defer s.handlerLock.Unlock()
|
||||
s.heads = s.heads[:0]
|
||||
for _, h := range s.objTree.Heads() {
|
||||
s.heads = append(s.heads, h)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
treeId := s.objTree.Id()
|
||||
content := msg.GetContent()
|
||||
switch {
|
||||
case content.GetHeadUpdate() != nil:
|
||||
var syncReq *treechangeproto.TreeSyncMessage
|
||||
|
||||
@@ -2,15 +2,16 @@ package synctree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/mock_synctree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/stretchr/testify/require"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/mock_synctree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
type testObjTreeMock struct {
|
||||
@@ -52,7 +53,6 @@ type syncHandlerFixture struct {
|
||||
ctrl *gomock.Controller
|
||||
syncClientMock *mock_synctree.MockSyncClient
|
||||
objectTreeMock *testObjTreeMock
|
||||
receiveQueueMock ReceiveQueue
|
||||
syncProtocolMock *mock_synctree.MockTreeSyncProtocol
|
||||
spaceId string
|
||||
senderId string
|
||||
@@ -67,20 +67,18 @@ func newSyncHandlerFixture(t *testing.T) *syncHandlerFixture {
|
||||
syncClientMock := mock_synctree.NewMockSyncClient(ctrl)
|
||||
syncProtocolMock := mock_synctree.NewMockTreeSyncProtocol(ctrl)
|
||||
spaceId := "spaceId"
|
||||
receiveQueue := newReceiveQueue(5)
|
||||
|
||||
syncHandler := &syncTreeHandler{
|
||||
objTree: objectTreeMock,
|
||||
syncClient: syncClientMock,
|
||||
syncProtocol: syncProtocolMock,
|
||||
spaceId: spaceId,
|
||||
queue: receiveQueue,
|
||||
syncStatus: syncstatus.NewNoOpSyncStatus(),
|
||||
objTree: objectTreeMock,
|
||||
syncClient: syncClientMock,
|
||||
syncProtocol: syncProtocolMock,
|
||||
spaceId: spaceId,
|
||||
syncStatus: syncstatus.NewNoOpSyncStatus(),
|
||||
pendingRequests: map[string]struct{}{},
|
||||
}
|
||||
return &syncHandlerFixture{
|
||||
ctrl: ctrl,
|
||||
objectTreeMock: objectTreeMock,
|
||||
receiveQueueMock: receiveQueue,
|
||||
syncProtocolMock: syncProtocolMock,
|
||||
syncClientMock: syncClientMock,
|
||||
syncHandler: syncHandler,
|
||||
@@ -97,38 +95,68 @@ func (fx *syncHandlerFixture) stop() {
|
||||
func TestSyncTreeHandler_HandleMessage(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("handle head update message", func(t *testing.T) {
|
||||
t.Run("handle head update message, heads not equal, request returned", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{}
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||
Heads: []string{"h3"},
|
||||
}
|
||||
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||
objectMsg, _ := MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(treeMsg, "spaceId", treeId)
|
||||
|
||||
syncReq := &treechangeproto.TreeSyncMessage{}
|
||||
fx.syncHandler.heads = []string{"h2"}
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.objectTreeMock.EXPECT().Heads().Times(2).Return([]string{"h2"})
|
||||
fx.objectTreeMock.EXPECT().Heads().Times(2).Return([]string{"h3"})
|
||||
fx.syncProtocolMock.EXPECT().HeadUpdate(ctx, fx.senderId, gomock.Any()).Return(syncReq, nil)
|
||||
fx.syncClientMock.EXPECT().QueueRequest(fx.senderId, fx.treeId, syncReq).Return(nil)
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []string{"h3"}, fx.syncHandler.heads)
|
||||
})
|
||||
|
||||
t.Run("handle head update message, empty sync request", func(t *testing.T) {
|
||||
t.Run("handle head update message, heads equal", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{}
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||
Heads: []string{"h1"},
|
||||
}
|
||||
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||
objectMsg, _ := MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(treeMsg, "spaceId", treeId)
|
||||
|
||||
fx.syncHandler.heads = []string{"h1"}
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("handle head update message, no sync request returned", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||
Heads: []string{"h3"},
|
||||
}
|
||||
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(treeMsg, "spaceId", treeId)
|
||||
|
||||
fx.syncHandler.heads = []string{"h2"}
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.objectTreeMock.EXPECT().Heads().Times(2).Return([]string{"h2"})
|
||||
fx.objectTreeMock.EXPECT().Heads().Times(2).Return([]string{"h3"})
|
||||
fx.syncProtocolMock.EXPECT().HeadUpdate(ctx, fx.senderId, gomock.Any()).Return(nil, nil)
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []string{"h3"}, fx.syncHandler.heads)
|
||||
})
|
||||
|
||||
t.Run("handle full sync request returns error", func(t *testing.T) {
|
||||
@@ -136,11 +164,15 @@ func TestSyncTreeHandler_HandleMessage(t *testing.T) {
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullRequest := &treechangeproto.TreeFullSyncRequest{}
|
||||
fullRequest := &treechangeproto.TreeFullSyncRequest{
|
||||
Heads: []string{"h3"},
|
||||
}
|
||||
treeMsg := treechangeproto.WrapFullRequest(fullRequest, chWithId)
|
||||
objectMsg, _ := MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(treeMsg, "spaceId", treeId)
|
||||
|
||||
fx.syncHandler.heads = []string{"h2"}
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.objectTreeMock.EXPECT().Heads().Times(3).Return([]string{"h2"})
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
||||
require.Equal(t, err, ErrMessageIsRequest)
|
||||
@@ -151,11 +183,16 @@ func TestSyncTreeHandler_HandleMessage(t *testing.T) {
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullSyncResponse := &treechangeproto.TreeFullSyncResponse{}
|
||||
fullSyncResponse := &treechangeproto.TreeFullSyncResponse{
|
||||
Heads: []string{"h3"},
|
||||
}
|
||||
treeMsg := treechangeproto.WrapFullResponse(fullSyncResponse, chWithId)
|
||||
objectMsg, _ := MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(treeMsg, "spaceId", treeId)
|
||||
|
||||
fx.syncHandler.heads = []string{"h2"}
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.objectTreeMock.EXPECT().Heads().Times(2).Return([]string{"h2"})
|
||||
fx.objectTreeMock.EXPECT().Heads().Times(2).Return([]string{"h3"})
|
||||
fx.syncProtocolMock.EXPECT().FullSyncResponse(ctx, fx.senderId, gomock.Any()).Return(nil)
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
||||
@@ -173,25 +210,7 @@ func TestSyncTreeHandler_HandleRequest(t *testing.T) {
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullRequest := &treechangeproto.TreeFullSyncRequest{}
|
||||
treeMsg := treechangeproto.WrapFullRequest(fullRequest, chWithId)
|
||||
objectMsg, _ := MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
|
||||
syncResp := &treechangeproto.TreeSyncMessage{}
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.syncProtocolMock.EXPECT().FullSyncRequest(ctx, fx.senderId, gomock.Any()).Return(syncResp, nil)
|
||||
|
||||
res, err := fx.syncHandler.HandleRequest(ctx, fx.senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, res)
|
||||
})
|
||||
|
||||
t.Run("handle request", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullRequest := &treechangeproto.TreeFullSyncRequest{}
|
||||
treeMsg := treechangeproto.WrapFullRequest(fullRequest, chWithId)
|
||||
objectMsg, _ := MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(treeMsg, "spaceId", treeId)
|
||||
|
||||
syncResp := &treechangeproto.TreeSyncMessage{}
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
@@ -212,7 +231,7 @@ func TestSyncTreeHandler_HandleRequest(t *testing.T) {
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{}
|
||||
headUpdateMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||
for _, msg := range []*treechangeproto.TreeSyncMessage{responseMsg, headUpdateMsg} {
|
||||
objectMsg, _ := MarshallTreeMessage(msg, "spaceId", treeId, "")
|
||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(msg, "spaceId", treeId)
|
||||
|
||||
_, err := fx.syncHandler.HandleRequest(ctx, fx.senderId, objectMsg)
|
||||
require.Equal(t, err, ErrMessageIsNotRequest)
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
"github.com/anyproto/any-sync/net/peer"
|
||||
"github.com/anyproto/any-sync/net/peer/mock_peer"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
type treeRemoteGetterFixture struct {
|
||||
|
||||
@@ -2,6 +2,7 @@ package synctree
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
@@ -59,7 +60,7 @@ func (t *treeSyncProtocol) HeadUpdate(ctx context.Context, senderId string, upda
|
||||
// isEmptyUpdate is sent when the tree is brought up from cache
|
||||
if isEmptyUpdate {
|
||||
headEquals := slice.UnsortedEquals(objTree.Heads(), update.Heads)
|
||||
log.DebugCtx(ctx, "is empty update", zap.String("treeId", objTree.Id()), zap.Bool("headEquals", headEquals))
|
||||
log.DebugCtx(ctx, "is empty update", zap.Bool("headEquals", headEquals))
|
||||
if headEquals {
|
||||
return
|
||||
}
|
||||
@@ -69,7 +70,7 @@ func (t *treeSyncProtocol) HeadUpdate(ctx context.Context, senderId string, upda
|
||||
return
|
||||
}
|
||||
|
||||
if t.alreadyHasHeads(objTree, update.Heads) {
|
||||
if t.hasHeads(objTree, update.Heads) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -81,7 +82,7 @@ func (t *treeSyncProtocol) HeadUpdate(ctx context.Context, senderId string, upda
|
||||
return
|
||||
}
|
||||
|
||||
if t.alreadyHasHeads(objTree, update.Heads) {
|
||||
if t.hasHeads(objTree, update.Heads) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -108,7 +109,7 @@ func (t *treeSyncProtocol) FullSyncRequest(ctx context.Context, senderId string,
|
||||
}
|
||||
}()
|
||||
|
||||
if len(request.Changes) != 0 && !t.alreadyHasHeads(objTree, request.Heads) {
|
||||
if len(request.Changes) != 0 && !t.hasHeads(objTree, request.Heads) {
|
||||
_, err = objTree.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
||||
NewHeads: request.Heads,
|
||||
RawChanges: request.Changes,
|
||||
@@ -136,7 +137,7 @@ func (t *treeSyncProtocol) FullSyncResponse(ctx context.Context, senderId string
|
||||
log.DebugCtx(ctx, "full sync response succeeded")
|
||||
}
|
||||
}()
|
||||
if t.alreadyHasHeads(objTree, response.Heads) {
|
||||
if t.hasHeads(objTree, response.Heads) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -147,6 +148,6 @@ func (t *treeSyncProtocol) FullSyncResponse(ctx context.Context, senderId string
|
||||
return
|
||||
}
|
||||
|
||||
func (t *treeSyncProtocol) alreadyHasHeads(ot objecttree.ObjectTree, heads []string) bool {
|
||||
func (t *treeSyncProtocol) hasHeads(ot objecttree.ObjectTree, heads []string) bool {
|
||||
return slice.UnsortedEquals(ot.Heads(), heads) || ot.HasChanges(heads...)
|
||||
}
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/mock_synctree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ import (
|
||||
reflect "reflect"
|
||||
|
||||
objecttree "github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockUpdateListener is a mock of UpdateListener interface.
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
reflect "reflect"
|
||||
|
||||
treechangeproto "github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockTreeStorage is a mock of TreeStorage interface.
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
app "github.com/anyproto/any-sync/app"
|
||||
objecttree "github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
treemanager "github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockTreeManager is a mock of TreeManager interface.
|
||||
|
||||
@@ -41,7 +41,7 @@ func (o *objectManager) Init(a *app.App) (err error) {
|
||||
o.spaceId = state.SpaceId
|
||||
o.spaceIsClosed = state.SpaceIsClosed
|
||||
settingsObject := a.MustComponent(settings.CName).(settings.Settings).SettingsObject()
|
||||
acl := a.MustComponent(syncacl.CName).(*syncacl.SyncAcl)
|
||||
acl := a.MustComponent(syncacl.CName).(syncacl.SyncAcl)
|
||||
o.AddObject(settingsObject)
|
||||
o.AddObject(acl)
|
||||
return nil
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
app "github.com/anyproto/any-sync/app"
|
||||
objectsync "github.com/anyproto/any-sync/commonspace/objectsync"
|
||||
spacesyncproto "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockObjectSync is a mock of ObjectSync interface.
|
||||
|
||||
@@ -4,6 +4,9 @@ package objectsync
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||
@@ -13,8 +16,6 @@ import (
|
||||
"github.com/anyproto/any-sync/util/multiqueue"
|
||||
"github.com/cheggaaa/mb/v3"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/object/syncobjectgetter"
|
||||
@@ -79,7 +80,7 @@ func (s *objectSync) Init(a *app.App) (err error) {
|
||||
}
|
||||
s.spaceIsDeleted = sharedData.SpaceIsDeleted
|
||||
s.spaceId = sharedData.SpaceId
|
||||
s.handleQueue = multiqueue.New[HandleMessage](s.processHandleMessage, 100)
|
||||
s.handleQueue = multiqueue.New[HandleMessage](s.processHandleMessage, 30)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -148,9 +149,6 @@ func (s *objectSync) processHandleMessage(msg HandleMessage) {
|
||||
err = context.DeadlineExceeded
|
||||
return
|
||||
}
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithDeadline(ctx, msg.Deadline)
|
||||
defer cancel()
|
||||
}
|
||||
if err = s.handleMessage(ctx, msg.SenderId, msg.Message); err != nil {
|
||||
if msg.Message.ObjectId != "" {
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
updatelistener "github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
|
||||
treestorage "github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
objecttreebuilder "github.com/anyproto/any-sync/commonspace/objecttreebuilder"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockTreeBuilder is a mock of TreeBuilder interface.
|
||||
|
||||
@@ -82,7 +82,7 @@ func (t *treeBuilder) Init(a *app.App) (err error) {
|
||||
t.isClosed = state.SpaceIsClosed
|
||||
t.treesUsed = state.TreesUsed
|
||||
t.builder = state.TreeBuilderFunc
|
||||
t.aclList = a.MustComponent(syncacl.CName).(*syncacl.SyncAcl)
|
||||
t.aclList = a.MustComponent(syncacl.CName).(syncacl.SyncAcl)
|
||||
t.spaceStorage = a.MustComponent(spacestorage.CName).(spacestorage.SpaceStorage)
|
||||
t.configuration = a.MustComponent(nodeconf.CName).(nodeconf.NodeConf)
|
||||
t.headsNotifiable = a.MustComponent(headsync.CName).(headsync.HeadSync)
|
||||
|
||||
+11
-9
@@ -2,20 +2,22 @@ package commonspace
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"hash/fnv"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/anyproto/any-sync/util/cidutil"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"hash/fnv"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -71,7 +73,7 @@ func storagePayloadForSpaceCreate(payload SpaceCreatePayload) (storagePayload sp
|
||||
|
||||
// building acl root
|
||||
keyStorage := crypto.NewKeyStorage()
|
||||
aclBuilder := list.NewAclRecordBuilder("", keyStorage)
|
||||
aclBuilder := list.NewAclRecordBuilder("", keyStorage, nil, list.NoOpAcceptorVerifier{})
|
||||
aclRoot, err := aclBuilder.BuildRoot(list.RootContent{
|
||||
PrivKey: payload.SigningKey,
|
||||
MasterKey: payload.MasterKey,
|
||||
@@ -158,7 +160,7 @@ func storagePayloadForSpaceDerive(payload SpaceDerivePayload) (storagePayload sp
|
||||
|
||||
// building acl root
|
||||
keyStorage := crypto.NewKeyStorage()
|
||||
aclBuilder := list.NewAclRecordBuilder("", keyStorage)
|
||||
aclBuilder := list.NewAclRecordBuilder("", keyStorage, nil, list.NoOpAcceptorVerifier{})
|
||||
aclRoot, err := aclBuilder.BuildRoot(list.RootContent{
|
||||
PrivKey: payload.SigningKey,
|
||||
MasterKey: payload.MasterKey,
|
||||
@@ -254,12 +256,12 @@ func ValidateSpaceHeader(rawHeaderWithId *spacesyncproto.RawSpaceHeaderWithId, i
|
||||
return
|
||||
}
|
||||
|
||||
func validateCreateSpaceAclPayload(rawWithId *aclrecordproto.RawAclRecordWithId) (spaceId string, err error) {
|
||||
func validateCreateSpaceAclPayload(rawWithId *consensusproto.RawRecordWithId) (spaceId string, err error) {
|
||||
if !cidutil.VerifyCid(rawWithId.Payload, rawWithId.Id) {
|
||||
err = objecttree.ErrIncorrectCid
|
||||
return
|
||||
}
|
||||
var rawAcl aclrecordproto.RawAclRecord
|
||||
var rawAcl consensusproto.RawRecord
|
||||
err = proto.Unmarshal(rawWithId.Payload, &rawAcl)
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
@@ -2,20 +2,22 @@ package commonspace
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/anyproto/any-sync/util/cidutil"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSuccessHeaderPayloadForSpaceCreate(t *testing.T) {
|
||||
@@ -188,14 +190,14 @@ func TestFailAclPayloadSpace_IncorrectCid(t *testing.T) {
|
||||
marshalled, err := aclRoot.Marshal()
|
||||
require.NoError(t, err)
|
||||
signature, err := accountKeys.SignKey.Sign(marshalled)
|
||||
rawAclRecord := &aclrecordproto.RawAclRecord{
|
||||
rawAclRecord := &consensusproto.RawRecord{
|
||||
Payload: marshalled,
|
||||
Signature: signature,
|
||||
}
|
||||
marshalledRaw, err := rawAclRecord.Marshal()
|
||||
require.NoError(t, err)
|
||||
aclHeadId := "rand"
|
||||
rawWithId := &aclrecordproto.RawAclRecordWithId{
|
||||
rawWithId := &consensusproto.RawRecordWithId{
|
||||
Payload: marshalledRaw,
|
||||
Id: aclHeadId,
|
||||
}
|
||||
@@ -230,7 +232,7 @@ func TestFailedAclPayloadSpace_IncorrectSignature(t *testing.T) {
|
||||
}
|
||||
marshalled, err := aclRoot.Marshal()
|
||||
require.NoError(t, err)
|
||||
rawAclRecord := &aclrecordproto.RawAclRecord{
|
||||
rawAclRecord := &consensusproto.RawRecord{
|
||||
Payload: marshalled,
|
||||
Signature: marshalled,
|
||||
}
|
||||
@@ -238,7 +240,7 @@ func TestFailedAclPayloadSpace_IncorrectSignature(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
aclHeadId, err := cidutil.NewCidFromBytes(marshalledRaw)
|
||||
require.NoError(t, err)
|
||||
rawWithId := &aclrecordproto.RawAclRecordWithId{
|
||||
rawWithId := &consensusproto.RawRecordWithId{
|
||||
Payload: marshalledRaw,
|
||||
Id: aclHeadId,
|
||||
}
|
||||
@@ -286,7 +288,7 @@ func TestFailedAclPayloadSpace_IncorrectIdentitySignature(t *testing.T) {
|
||||
return
|
||||
}
|
||||
signature, err := accountKeys.SignKey.Sign(marshalled)
|
||||
rawAclRecord := &aclrecordproto.RawAclRecord{
|
||||
rawAclRecord := &consensusproto.RawRecord{
|
||||
Payload: marshalled,
|
||||
Signature: signature,
|
||||
}
|
||||
@@ -298,7 +300,7 @@ func TestFailedAclPayloadSpace_IncorrectIdentitySignature(t *testing.T) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rawWithId := &aclrecordproto.RawAclRecordWithId{
|
||||
rawWithId := &consensusproto.RawRecordWithId{
|
||||
Payload: marshalledRaw,
|
||||
Id: aclHeadId,
|
||||
}
|
||||
@@ -540,7 +542,7 @@ func rawSettingsPayload(accountKeys *accountdata.AccountKeys, spaceId, aclHeadId
|
||||
return
|
||||
}
|
||||
|
||||
func rawAclWithId(accountKeys *accountdata.AccountKeys, spaceId string) (aclHeadId string, rawWithId *aclrecordproto.RawAclRecordWithId, err error) {
|
||||
func rawAclWithId(accountKeys *accountdata.AccountKeys, spaceId string) (aclHeadId string, rawWithId *consensusproto.RawRecordWithId, err error) {
|
||||
// TODO: use same storage creation methods as we use in spaces
|
||||
readKeyBytes := make([]byte, 32)
|
||||
_, err = rand.Read(readKeyBytes)
|
||||
@@ -582,7 +584,7 @@ func rawAclWithId(accountKeys *accountdata.AccountKeys, spaceId string) (aclHead
|
||||
return
|
||||
}
|
||||
signature, err := accountKeys.SignKey.Sign(marshalled)
|
||||
rawAclRecord := &aclrecordproto.RawAclRecord{
|
||||
rawAclRecord := &consensusproto.RawRecord{
|
||||
Payload: marshalled,
|
||||
Signature: signature,
|
||||
}
|
||||
@@ -594,7 +596,7 @@ func rawAclWithId(accountKeys *accountdata.AccountKeys, spaceId string) (aclHead
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rawWithId = &aclrecordproto.RawAclRecordWithId{
|
||||
rawWithId = &consensusproto.RawRecordWithId{
|
||||
Payload: marshalledRaw,
|
||||
Id: aclHeadId,
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
app "github.com/anyproto/any-sync/app"
|
||||
spacesyncproto "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
peer "github.com/anyproto/any-sync/net/peer"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockPeerManager is a mock of PeerManager interface.
|
||||
|
||||
@@ -2,6 +2,9 @@ package requestmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/objectsync"
|
||||
"github.com/anyproto/any-sync/commonspace/objectsync/mock_objectsync"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
@@ -9,13 +12,10 @@ import (
|
||||
"github.com/anyproto/any-sync/net/peer"
|
||||
"github.com/anyproto/any-sync/net/peer/mock_peer"
|
||||
"github.com/anyproto/any-sync/net/pool/mock_pool"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
"storj.io/drpc"
|
||||
"storj.io/drpc/drpcconn"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type fixture struct {
|
||||
@@ -146,6 +146,7 @@ func TestRequestManager_QueueRequest(t *testing.T) {
|
||||
_, ok = msgs.Load("otherId1")
|
||||
require.True(t, ok)
|
||||
close(msgRelease)
|
||||
fx.requestManager.Close(context.Background())
|
||||
})
|
||||
|
||||
t.Run("no requests after close", func(t *testing.T) {
|
||||
@@ -179,11 +180,7 @@ func TestRequestManager_QueueRequest(t *testing.T) {
|
||||
|
||||
fx.requestManager.Close(context.Background())
|
||||
close(msgRelease)
|
||||
// waiting to know if the second one is not taken
|
||||
// because the manager is now closed
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
_, ok = msgs.Load("id2")
|
||||
require.False(t, ok)
|
||||
|
||||
})
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/object/treemanager/mock_treemanager"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage/mock_spacestorage"
|
||||
"github.com/golang/mock/gomock"
|
||||
"go.uber.org/mock/gomock"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"github.com/anyproto/any-sync/commonspace/object/treemanager/mock_treemanager"
|
||||
"github.com/anyproto/any-sync/commonspace/settings/mock_settings"
|
||||
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
reflect "reflect"
|
||||
|
||||
settingsstate "github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockDeletionManager is a mock of DeletionManager interface.
|
||||
|
||||
@@ -16,8 +16,8 @@ import (
|
||||
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
||||
"github.com/anyproto/any-sync/commonspace/settings/settingsstate/mock_settingsstate"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage/mock_spacestorage"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
objecttree "github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
settingsstate "github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockStateBuilder is a mock of StateBuilder interface.
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ func NewSpaceId(id string, repKey uint64) string {
|
||||
type Space interface {
|
||||
Id() string
|
||||
Init(ctx context.Context) error
|
||||
Acl() list.AclList
|
||||
|
||||
StoredIds() []string
|
||||
DebugAllHeads() []headsync.TreeHeads
|
||||
@@ -153,6 +154,10 @@ func (s *space) TreeBuilder() objecttreebuilder.TreeBuilder {
|
||||
return s.treeBuilder
|
||||
}
|
||||
|
||||
func (s *space) Acl() list.AclList {
|
||||
return s.aclList
|
||||
}
|
||||
|
||||
func (s *space) Id() string {
|
||||
return s.state.SpaceId
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package commonspace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/anyproto/any-sync/accountservice"
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
@@ -9,7 +11,6 @@ import (
|
||||
"github.com/anyproto/any-sync/commonspace/credentialprovider"
|
||||
"github.com/anyproto/any-sync/commonspace/deletionstate"
|
||||
"github.com/anyproto/any-sync/commonspace/headsync"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
@@ -24,13 +25,13 @@ import (
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/anyproto/any-sync/metric"
|
||||
"github.com/anyproto/any-sync/net/peer"
|
||||
"github.com/anyproto/any-sync/net/pool"
|
||||
"github.com/anyproto/any-sync/net/rpc/rpcerr"
|
||||
"github.com/anyproto/any-sync/nodeconf"
|
||||
"storj.io/drpc"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
const CName = "common.commonspace"
|
||||
@@ -193,7 +194,7 @@ func (s *spaceService) NewSpace(ctx context.Context, id string) (Space, error) {
|
||||
|
||||
func (s *spaceService) addSpaceStorage(ctx context.Context, spaceDescription SpaceDescription) (st spacestorage.SpaceStorage, err error) {
|
||||
payload := spacestorage.SpaceStorageCreatePayload{
|
||||
AclWithId: &aclrecordproto.RawAclRecordWithId{
|
||||
AclWithId: &consensusproto.RawRecordWithId{
|
||||
Payload: spaceDescription.AclPayload,
|
||||
Id: spaceDescription.AclId,
|
||||
},
|
||||
@@ -240,7 +241,7 @@ func (s *spaceService) getSpaceStorageFromRemote(ctx context.Context, id string)
|
||||
}
|
||||
|
||||
st, err = s.createSpaceStorage(spacestorage.SpaceStorageCreatePayload{
|
||||
AclWithId: &aclrecordproto.RawAclRecordWithId{
|
||||
AclWithId: &consensusproto.RawRecordWithId{
|
||||
Payload: res.Payload.AclPayload,
|
||||
Id: res.Payload.AclPayloadId,
|
||||
},
|
||||
|
||||
@@ -2,12 +2,14 @@ package spacestorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -40,7 +42,7 @@ func (i *InMemorySpaceStorage) Name() (name string) {
|
||||
}
|
||||
|
||||
func NewInMemorySpaceStorage(payload SpaceStorageCreatePayload) (SpaceStorage, error) {
|
||||
aclStorage, err := liststorage.NewInMemoryAclListStorage(payload.AclWithId.Id, []*aclrecordproto.RawAclRecordWithId{payload.AclWithId})
|
||||
aclStorage, err := liststorage.NewInMemoryAclListStorage(payload.AclWithId.Id, []*consensusproto.RawRecordWithId{payload.AclWithId})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
treechangeproto "github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
treestorage "github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
spacesyncproto "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockSpaceStorage is a mock of SpaceStorage interface.
|
||||
|
||||
@@ -4,12 +4,13 @@ package spacestorage
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
)
|
||||
|
||||
const CName = "common.commonspace.spacestorage"
|
||||
@@ -47,7 +48,7 @@ type SpaceStorage interface {
|
||||
}
|
||||
|
||||
type SpaceStorageCreatePayload struct {
|
||||
AclWithId *aclrecordproto.RawAclRecordWithId
|
||||
AclWithId *consensusproto.RawRecordWithId
|
||||
SpaceHeaderWithId *spacesyncproto.RawSpaceHeaderWithId
|
||||
SpaceSettingsWithId *treechangeproto.RawTreeChangeWithId
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
reflect "reflect"
|
||||
|
||||
spacesyncproto "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
drpc "storj.io/drpc"
|
||||
)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
package spacesyncproto
|
||||
|
||||
import (
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"storj.io/drpc"
|
||||
)
|
||||
|
||||
@@ -16,3 +17,16 @@ func (c ClientFactoryFunc) Client(cc drpc.Conn) DRPCSpaceSyncClient {
|
||||
type ClientFactory interface {
|
||||
Client(cc drpc.Conn) DRPCSpaceSyncClient
|
||||
}
|
||||
|
||||
func MarshallSyncMessage(message proto.Marshaler, spaceId, objectId string) (objMsg *ObjectSyncMessage, err error) {
|
||||
payload, err := message.Marshal()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
objMsg = &ObjectSyncMessage{
|
||||
Payload: payload,
|
||||
ObjectId: objectId,
|
||||
SpaceId: spaceId,
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -3,11 +3,12 @@ package syncstatus
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestate"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestate"
|
||||
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
@@ -178,8 +179,9 @@ func (s *syncStatusService) update(ctx context.Context) (err error) {
|
||||
}
|
||||
s.treeStatusBuf = append(s.treeStatusBuf, treeStatus{treeId, treeHeads.syncStatus, treeHeads.heads})
|
||||
}
|
||||
nodesOnline := s.nodesOnline
|
||||
s.Unlock()
|
||||
s.updateReceiver.UpdateNodeConnection(s.nodesOnline)
|
||||
s.updateReceiver.UpdateNodeConnection(nodesOnline)
|
||||
for _, entry := range s.treeStatusBuf {
|
||||
err = s.updateReceiver.UpdateTree(ctx, entry.treeId, entry.status)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
//go:generate mockgen -destination mock_consensusclient/mock_consensusclient.go github.com/anyproto/any-sync/consensus/consensusclient Service
|
||||
package consensusclient
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/anyproto/any-sync/net/pool"
|
||||
"github.com/anyproto/any-sync/net/rpc/rpcerr"
|
||||
"github.com/anyproto/any-sync/nodeconf"
|
||||
"go.uber.org/zap"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const CName = "consensus.consensusclient"
|
||||
|
||||
var log = logger.NewNamed(CName)
|
||||
|
||||
var (
|
||||
ErrWatcherExists = errors.New("watcher exists")
|
||||
ErrWatcherNotExists = errors.New("watcher not exists")
|
||||
)
|
||||
|
||||
func New() Service {
|
||||
return new(service)
|
||||
}
|
||||
|
||||
// Watcher watches new events by specified logId
|
||||
type Watcher interface {
|
||||
AddConsensusRecords(recs []*consensusproto.RawRecordWithId)
|
||||
AddConsensusError(err error)
|
||||
}
|
||||
|
||||
type Service interface {
|
||||
// AddLog adds new log to consensus servers
|
||||
AddLog(ctx context.Context, rec *consensusproto.RawRecordWithId) (err error)
|
||||
// AddRecord adds new record to consensus servers
|
||||
AddRecord(ctx context.Context, logId string, rec *consensusproto.RawRecord) (record *consensusproto.RawRecordWithId, err error)
|
||||
// Watch starts watching to given logId and calls watcher when any relative event received
|
||||
Watch(logId string, w Watcher) (err error)
|
||||
// UnWatch stops watching given logId and removes watcher
|
||||
UnWatch(logId string) (err error)
|
||||
app.ComponentRunnable
|
||||
}
|
||||
|
||||
type service struct {
|
||||
pool pool.Pool
|
||||
nodeconf nodeconf.Service
|
||||
|
||||
watchers map[string]Watcher
|
||||
stream *stream
|
||||
close chan struct{}
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (s *service) Init(a *app.App) (err error) {
|
||||
s.pool = a.MustComponent(pool.CName).(pool.Pool)
|
||||
s.nodeconf = a.MustComponent(nodeconf.CName).(nodeconf.Service)
|
||||
s.watchers = make(map[string]Watcher)
|
||||
s.close = make(chan struct{})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Name() (name string) {
|
||||
return CName
|
||||
}
|
||||
|
||||
func (s *service) Run(_ context.Context) error {
|
||||
go s.streamWatcher()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) doClient(ctx context.Context, fn func(cl consensusproto.DRPCConsensusClient) error) error {
|
||||
peer, err := s.pool.GetOneOf(ctx, s.nodeconf.ConsensusPeers())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dc, err := peer.AcquireDrpcConn(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer peer.ReleaseDrpcConn(dc)
|
||||
return fn(consensusproto.NewDRPCConsensusClient(dc))
|
||||
}
|
||||
|
||||
func (s *service) AddLog(ctx context.Context, rec *consensusproto.RawRecordWithId) (err error) {
|
||||
return s.doClient(ctx, func(cl consensusproto.DRPCConsensusClient) error {
|
||||
if _, err = cl.LogAdd(ctx, &consensusproto.LogAddRequest{
|
||||
Record: rec,
|
||||
}); err != nil {
|
||||
return rpcerr.Unwrap(err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *service) AddRecord(ctx context.Context, logId string, rec *consensusproto.RawRecord) (record *consensusproto.RawRecordWithId, err error) {
|
||||
err = s.doClient(ctx, func(cl consensusproto.DRPCConsensusClient) error {
|
||||
if record, err = cl.RecordAdd(ctx, &consensusproto.RecordAddRequest{
|
||||
LogId: logId,
|
||||
Record: rec,
|
||||
}); err != nil {
|
||||
return rpcerr.Unwrap(err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (s *service) Watch(logId string, w Watcher) (err error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if _, ok := s.watchers[logId]; ok {
|
||||
return ErrWatcherExists
|
||||
}
|
||||
s.watchers[logId] = w
|
||||
if s.stream != nil {
|
||||
if wErr := s.stream.WatchIds([]string{logId}); wErr != nil {
|
||||
log.Warn("WatchIds error", zap.Error(wErr))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *service) UnWatch(logId string) (err error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if _, ok := s.watchers[logId]; !ok {
|
||||
return ErrWatcherNotExists
|
||||
}
|
||||
delete(s.watchers, logId)
|
||||
if s.stream != nil {
|
||||
if wErr := s.stream.UnwatchIds([]string{logId}); wErr != nil {
|
||||
log.Warn("UnWatchIds error", zap.Error(wErr))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *service) openStream(ctx context.Context) (st *stream, err error) {
|
||||
pr, err := s.pool.GetOneOf(ctx, s.nodeconf.ConsensusPeers())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dc, err := pr.AcquireDrpcConn(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rpcStream, err := consensusproto.NewDRPCConsensusClient(dc).LogWatch(ctx)
|
||||
if err != nil {
|
||||
return nil, rpcerr.Unwrap(err)
|
||||
}
|
||||
return runStream(rpcStream), nil
|
||||
}
|
||||
|
||||
func (s *service) streamWatcher() {
|
||||
var (
|
||||
err error
|
||||
st *stream
|
||||
i int
|
||||
)
|
||||
for {
|
||||
// open stream
|
||||
if st, err = s.openStream(context.Background()); err != nil {
|
||||
// can't open stream, we will retry until success connection or close
|
||||
if i < 60 {
|
||||
i++
|
||||
}
|
||||
sleepTime := time.Second * time.Duration(i)
|
||||
log.Error("watch log error", zap.Error(err), zap.Duration("waitTime", sleepTime))
|
||||
select {
|
||||
case <-time.After(sleepTime):
|
||||
continue
|
||||
case <-s.close:
|
||||
return
|
||||
}
|
||||
}
|
||||
i = 0
|
||||
|
||||
// collect ids and setup stream
|
||||
s.mu.Lock()
|
||||
var logIds = make([]string, 0, len(s.watchers))
|
||||
for id := range s.watchers {
|
||||
logIds = append(logIds, id)
|
||||
}
|
||||
s.stream = st
|
||||
s.mu.Unlock()
|
||||
|
||||
// restore subscriptions
|
||||
if len(logIds) > 0 {
|
||||
if err = s.stream.WatchIds(logIds); err != nil {
|
||||
log.Error("watch ids error", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// read stream
|
||||
if err = s.streamReader(); err != nil {
|
||||
log.Error("stream read error", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) streamReader() error {
|
||||
for {
|
||||
events := s.stream.WaitLogs()
|
||||
if len(events) == 0 {
|
||||
return s.stream.Err()
|
||||
}
|
||||
s.mu.Lock()
|
||||
for _, e := range events {
|
||||
if w, ok := s.watchers[e.LogId]; ok {
|
||||
if e.Error == nil {
|
||||
w.AddConsensusRecords(e.Records)
|
||||
} else {
|
||||
w.AddConsensusError(rpcerr.Err(uint64(e.Error.Error)))
|
||||
}
|
||||
} else {
|
||||
log.Warn("received unexpected log id", zap.String("logId", e.LogId))
|
||||
}
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) Close(_ context.Context) error {
|
||||
s.mu.Lock()
|
||||
if s.stream != nil {
|
||||
_ = s.stream.Close()
|
||||
}
|
||||
s.mu.Unlock()
|
||||
select {
|
||||
case <-s.close:
|
||||
default:
|
||||
close(s.close)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
package consensusclient
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto/consensuserr"
|
||||
"github.com/anyproto/any-sync/net/pool"
|
||||
"github.com/anyproto/any-sync/net/rpc/rpctest"
|
||||
"github.com/anyproto/any-sync/nodeconf"
|
||||
"github.com/anyproto/any-sync/nodeconf/mock_nodeconf"
|
||||
"github.com/anyproto/any-sync/testutil/accounttest"
|
||||
"github.com/anyproto/any-sync/util/cidutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestService_Watch(t *testing.T) {
|
||||
t.Run("not found error", func(t *testing.T) {
|
||||
fx := newFixture(t).run(t)
|
||||
defer fx.Finish()
|
||||
var logId = "1"
|
||||
w := &testWatcher{ready: make(chan struct{})}
|
||||
require.NoError(t, fx.Watch(logId, w))
|
||||
st := fx.testServer.waitStream(t)
|
||||
req, err := st.Recv()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{logId}, req.WatchIds)
|
||||
require.NoError(t, st.Send(&consensusproto.LogWatchEvent{
|
||||
LogId: logId,
|
||||
Error: &consensusproto.Err{
|
||||
Error: consensusproto.ErrCodes_ErrorOffset + consensusproto.ErrCodes_LogNotFound,
|
||||
},
|
||||
}))
|
||||
<-w.ready
|
||||
assert.Equal(t, consensuserr.ErrLogNotFound, w.err)
|
||||
fx.testServer.releaseStream <- nil
|
||||
})
|
||||
t.Run("watcherExists error", func(t *testing.T) {
|
||||
fx := newFixture(t).run(t)
|
||||
defer fx.Finish()
|
||||
var logId = "1"
|
||||
w := &testWatcher{}
|
||||
require.NoError(t, fx.Watch(logId, w))
|
||||
require.Error(t, fx.Watch(logId, w))
|
||||
st := fx.testServer.waitStream(t)
|
||||
st.Recv()
|
||||
fx.testServer.releaseStream <- nil
|
||||
})
|
||||
t.Run("watch", func(t *testing.T) {
|
||||
fx := newFixture(t).run(t)
|
||||
defer fx.Finish()
|
||||
var logId1 = "1"
|
||||
w := &testWatcher{}
|
||||
require.NoError(t, fx.Watch(logId1, w))
|
||||
st := fx.testServer.waitStream(t)
|
||||
req, err := st.Recv()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{logId1}, req.WatchIds)
|
||||
|
||||
var logId2 = "2"
|
||||
w = &testWatcher{}
|
||||
require.NoError(t, fx.Watch(logId2, w))
|
||||
req, err = st.Recv()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{logId2}, req.WatchIds)
|
||||
|
||||
fx.testServer.releaseStream <- nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestService_UnWatch(t *testing.T) {
|
||||
t.Run("no watcher", func(t *testing.T) {
|
||||
fx := newFixture(t).run(t)
|
||||
defer fx.Finish()
|
||||
require.Error(t, fx.UnWatch("1"))
|
||||
})
|
||||
t.Run("success", func(t *testing.T) {
|
||||
fx := newFixture(t).run(t)
|
||||
defer fx.Finish()
|
||||
w := &testWatcher{}
|
||||
require.NoError(t, fx.Watch("1", w))
|
||||
assert.NoError(t, fx.UnWatch("1"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestService_Init(t *testing.T) {
|
||||
t.Run("reconnect on watch err", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
fx.testServer.watchErrOnce = true
|
||||
fx.run(t)
|
||||
defer fx.Finish()
|
||||
fx.testServer.waitStream(t)
|
||||
fx.testServer.releaseStream <- nil
|
||||
})
|
||||
t.Run("reconnect on start", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
fx.a.MustComponent(pool.CName).(*rpctest.TestPool).WithServer(nil)
|
||||
fx.run(t)
|
||||
defer fx.Finish()
|
||||
time.Sleep(time.Millisecond * 50)
|
||||
fx.a.MustComponent(pool.CName).(*rpctest.TestPool).WithServer(fx.drpcTS)
|
||||
fx.testServer.waitStream(t)
|
||||
fx.testServer.releaseStream <- nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestService_AddLog(t *testing.T) {
|
||||
fx := newFixture(t).run(t)
|
||||
defer fx.Finish()
|
||||
assert.NoError(t, fx.AddLog(ctx, &consensusproto.RawRecordWithId{}))
|
||||
}
|
||||
|
||||
func TestService_AddRecord(t *testing.T) {
|
||||
fx := newFixture(t).run(t)
|
||||
defer fx.Finish()
|
||||
rec, err := fx.AddRecord(ctx, "1", &consensusproto.RawRecord{})
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, rec)
|
||||
}
|
||||
|
||||
var ctx = context.Background()
|
||||
|
||||
func newFixture(t *testing.T) *fixture {
|
||||
fx := &fixture{
|
||||
Service: New(),
|
||||
a: &app.App{},
|
||||
ctrl: gomock.NewController(t),
|
||||
testServer: &testServer{
|
||||
stream: make(chan consensusproto.DRPCConsensus_LogWatchStream),
|
||||
releaseStream: make(chan error),
|
||||
},
|
||||
}
|
||||
fx.nodeconf = mock_nodeconf.NewMockService(fx.ctrl)
|
||||
fx.nodeconf.EXPECT().Name().Return(nodeconf.CName).AnyTimes()
|
||||
fx.nodeconf.EXPECT().Init(gomock.Any()).AnyTimes()
|
||||
fx.nodeconf.EXPECT().Run(gomock.Any()).AnyTimes()
|
||||
fx.nodeconf.EXPECT().Close(gomock.Any()).AnyTimes()
|
||||
fx.nodeconf.EXPECT().ConsensusPeers().Return([]string{"c1", "c2", "c3"}).AnyTimes()
|
||||
|
||||
fx.drpcTS = rpctest.NewTestServer()
|
||||
require.NoError(t, consensusproto.DRPCRegisterConsensus(fx.drpcTS.Mux, fx.testServer))
|
||||
fx.a.Register(fx.Service).
|
||||
Register(&accounttest.AccountTestService{}).
|
||||
Register(fx.nodeconf).
|
||||
Register(rpctest.NewTestPool().WithServer(fx.drpcTS))
|
||||
|
||||
return fx
|
||||
}
|
||||
|
||||
type fixture struct {
|
||||
Service
|
||||
a *app.App
|
||||
ctrl *gomock.Controller
|
||||
testServer *testServer
|
||||
drpcTS *rpctest.TestServer
|
||||
nodeconf *mock_nodeconf.MockService
|
||||
}
|
||||
|
||||
func (fx *fixture) run(t *testing.T) *fixture {
|
||||
require.NoError(t, fx.a.Start(ctx))
|
||||
return fx
|
||||
}
|
||||
|
||||
func (fx *fixture) Finish() {
|
||||
assert.NoError(fx.ctrl.T, fx.a.Close(ctx))
|
||||
fx.ctrl.Finish()
|
||||
}
|
||||
|
||||
type testServer struct {
|
||||
stream chan consensusproto.DRPCConsensus_LogWatchStream
|
||||
addLog func(ctx context.Context, req *consensusproto.LogAddRequest) error
|
||||
addRecord func(ctx context.Context, req *consensusproto.RecordAddRequest) error
|
||||
releaseStream chan error
|
||||
watchErrOnce bool
|
||||
}
|
||||
|
||||
func (t *testServer) LogAdd(ctx context.Context, req *consensusproto.LogAddRequest) (*consensusproto.Ok, error) {
|
||||
if t.addLog != nil {
|
||||
if err := t.addLog(ctx, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &consensusproto.Ok{}, nil
|
||||
}
|
||||
|
||||
func (t *testServer) RecordAdd(ctx context.Context, req *consensusproto.RecordAddRequest) (*consensusproto.RawRecordWithId, error) {
|
||||
if t.addRecord != nil {
|
||||
if err := t.addRecord(ctx, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
data, _ := req.Record.Marshal()
|
||||
id, _ := cidutil.NewCidFromBytes(data)
|
||||
return &consensusproto.RawRecordWithId{Id: id, Payload: data}, nil
|
||||
}
|
||||
|
||||
func (t *testServer) LogWatch(stream consensusproto.DRPCConsensus_LogWatchStream) error {
|
||||
if t.watchErrOnce {
|
||||
t.watchErrOnce = false
|
||||
return fmt.Errorf("error")
|
||||
}
|
||||
t.stream <- stream
|
||||
return <-t.releaseStream
|
||||
}
|
||||
|
||||
func (t *testServer) waitStream(test *testing.T) consensusproto.DRPCConsensus_LogWatchStream {
|
||||
select {
|
||||
case <-time.After(time.Second * 5):
|
||||
test.Fatalf("waiteStream timeout")
|
||||
case st := <-t.stream:
|
||||
return st
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type testWatcher struct {
|
||||
recs [][]*consensusproto.RawRecordWithId
|
||||
err error
|
||||
ready chan struct{}
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func (t *testWatcher) AddConsensusRecords(recs []*consensusproto.RawRecordWithId) {
|
||||
t.recs = append(t.recs, recs)
|
||||
t.once.Do(func() {
|
||||
close(t.ready)
|
||||
})
|
||||
}
|
||||
|
||||
func (t *testWatcher) AddConsensusError(err error) {
|
||||
t.err = err
|
||||
t.once.Do(func() {
|
||||
close(t.ready)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anyproto/any-sync/consensus/consensusclient (interfaces: Service)
|
||||
|
||||
// Package mock_consensusclient is a generated GoMock package.
|
||||
package mock_consensusclient
|
||||
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
app "github.com/anyproto/any-sync/app"
|
||||
consensusclient "github.com/anyproto/any-sync/consensus/consensusclient"
|
||||
consensusproto "github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockService is a mock of Service interface.
|
||||
type MockService struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockServiceMockRecorder
|
||||
}
|
||||
|
||||
// MockServiceMockRecorder is the mock recorder for MockService.
|
||||
type MockServiceMockRecorder struct {
|
||||
mock *MockService
|
||||
}
|
||||
|
||||
// NewMockService creates a new mock instance.
|
||||
func NewMockService(ctrl *gomock.Controller) *MockService {
|
||||
mock := &MockService{ctrl: ctrl}
|
||||
mock.recorder = &MockServiceMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockService) EXPECT() *MockServiceMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// AddLog mocks base method.
|
||||
func (m *MockService) AddLog(arg0 context.Context, arg1 *consensusproto.RawRecordWithId) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AddLog", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddLog indicates an expected call of AddLog.
|
||||
func (mr *MockServiceMockRecorder) AddLog(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddLog", reflect.TypeOf((*MockService)(nil).AddLog), arg0, arg1)
|
||||
}
|
||||
|
||||
// AddRecord mocks base method.
|
||||
func (m *MockService) AddRecord(arg0 context.Context, arg1 string, arg2 *consensusproto.RawRecord) (*consensusproto.RawRecordWithId, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AddRecord", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(*consensusproto.RawRecordWithId)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// AddRecord indicates an expected call of AddRecord.
|
||||
func (mr *MockServiceMockRecorder) AddRecord(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRecord", reflect.TypeOf((*MockService)(nil).AddRecord), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// Close mocks base method.
|
||||
func (m *MockService) Close(arg0 context.Context) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Close", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Close indicates an expected call of Close.
|
||||
func (mr *MockServiceMockRecorder) Close(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockService)(nil).Close), arg0)
|
||||
}
|
||||
|
||||
// Init mocks base method.
|
||||
func (m *MockService) Init(arg0 *app.App) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Init", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Init indicates an expected call of Init.
|
||||
func (mr *MockServiceMockRecorder) Init(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockService)(nil).Init), arg0)
|
||||
}
|
||||
|
||||
// Name mocks base method.
|
||||
func (m *MockService) Name() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Name")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Name indicates an expected call of Name.
|
||||
func (mr *MockServiceMockRecorder) Name() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockService)(nil).Name))
|
||||
}
|
||||
|
||||
// Run mocks base method.
|
||||
func (m *MockService) Run(arg0 context.Context) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Run", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Run indicates an expected call of Run.
|
||||
func (mr *MockServiceMockRecorder) Run(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockService)(nil).Run), arg0)
|
||||
}
|
||||
|
||||
// UnWatch mocks base method.
|
||||
func (m *MockService) UnWatch(arg0 string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UnWatch", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UnWatch indicates an expected call of UnWatch.
|
||||
func (mr *MockServiceMockRecorder) UnWatch(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnWatch", reflect.TypeOf((*MockService)(nil).UnWatch), arg0)
|
||||
}
|
||||
|
||||
// Watch mocks base method.
|
||||
func (m *MockService) Watch(arg0 string, arg1 consensusclient.Watcher) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Watch", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Watch indicates an expected call of Watch.
|
||||
func (mr *MockServiceMockRecorder) Watch(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Watch", reflect.TypeOf((*MockService)(nil).Watch), arg0, arg1)
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package consensusclient
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/cheggaaa/mb/v3"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func runStream(rpcStream consensusproto.DRPCConsensus_LogWatchClient) *stream {
|
||||
st := &stream{
|
||||
rpcStream: rpcStream,
|
||||
mb: mb.New[*consensusproto.LogWatchEvent](100),
|
||||
}
|
||||
go st.readStream()
|
||||
return st
|
||||
}
|
||||
|
||||
type stream struct {
|
||||
rpcStream consensusproto.DRPCConsensus_LogWatchClient
|
||||
mb *mb.MB[*consensusproto.LogWatchEvent]
|
||||
mu sync.Mutex
|
||||
err error
|
||||
}
|
||||
|
||||
func (s *stream) WatchIds(logIds []string) (err error) {
|
||||
return s.rpcStream.Send(&consensusproto.LogWatchRequest{
|
||||
WatchIds: logIds,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *stream) UnwatchIds(logIds []string) (err error) {
|
||||
return s.rpcStream.Send(&consensusproto.LogWatchRequest{
|
||||
UnwatchIds: logIds,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *stream) WaitLogs() []*consensusproto.LogWatchEvent {
|
||||
events, _ := s.mb.Wait(context.TODO())
|
||||
return events
|
||||
}
|
||||
|
||||
func (s *stream) Err() error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.err
|
||||
}
|
||||
|
||||
func (s *stream) readStream() {
|
||||
defer s.Close()
|
||||
for {
|
||||
event, err := s.rpcStream.Recv()
|
||||
if err != nil {
|
||||
s.mu.Lock()
|
||||
s.err = err
|
||||
s.mu.Unlock()
|
||||
return
|
||||
}
|
||||
if err = s.mb.Add(s.rpcStream.Context(), event); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stream) Close() error {
|
||||
if err := s.mb.Close(); err == nil {
|
||||
return s.rpcStream.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package consensusproto
|
||||
|
||||
func WrapHeadUpdate(update *LogHeadUpdate, rootRecord *RawRecordWithId) *LogSyncMessage {
|
||||
return &LogSyncMessage{
|
||||
Content: &LogSyncContentValue{
|
||||
Value: &LogSyncContentValue_HeadUpdate{HeadUpdate: update},
|
||||
},
|
||||
Id: rootRecord.Id,
|
||||
Payload: rootRecord.Payload,
|
||||
}
|
||||
}
|
||||
|
||||
func WrapFullRequest(request *LogFullSyncRequest, rootRecord *RawRecordWithId) *LogSyncMessage {
|
||||
return &LogSyncMessage{
|
||||
Content: &LogSyncContentValue{
|
||||
Value: &LogSyncContentValue_FullSyncRequest{FullSyncRequest: request},
|
||||
},
|
||||
Id: rootRecord.Id,
|
||||
Payload: rootRecord.Payload,
|
||||
}
|
||||
}
|
||||
|
||||
func WrapFullResponse(response *LogFullSyncResponse, rootRecord *RawRecordWithId) *LogSyncMessage {
|
||||
return &LogSyncMessage{
|
||||
Content: &LogSyncContentValue{
|
||||
Value: &LogSyncContentValue_FullSyncResponse{FullSyncResponse: response},
|
||||
},
|
||||
Id: rootRecord.Id,
|
||||
Payload: rootRecord.Payload,
|
||||
}
|
||||
}
|
||||
|
||||
func GetHead(msg *LogSyncMessage) (head string) {
|
||||
content := msg.GetContent()
|
||||
switch {
|
||||
case content.GetHeadUpdate() != nil:
|
||||
return content.GetHeadUpdate().Head
|
||||
case content.GetFullSyncRequest() != nil:
|
||||
return content.GetFullSyncRequest().Head
|
||||
case content.GetFullSyncResponse() != nil:
|
||||
return content.GetFullSyncResponse().Head
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,232 @@
|
||||
// Code generated by protoc-gen-go-drpc. DO NOT EDIT.
|
||||
// protoc-gen-go-drpc version: v0.0.33
|
||||
// source: consensus/consensusproto/protos/consensus.proto
|
||||
|
||||
package consensusproto
|
||||
|
||||
import (
|
||||
bytes "bytes"
|
||||
context "context"
|
||||
errors "errors"
|
||||
jsonpb "github.com/gogo/protobuf/jsonpb"
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
drpc "storj.io/drpc"
|
||||
drpcerr "storj.io/drpc/drpcerr"
|
||||
)
|
||||
|
||||
type drpcEncoding_File_consensus_consensusproto_protos_consensus_proto struct{}
|
||||
|
||||
func (drpcEncoding_File_consensus_consensusproto_protos_consensus_proto) Marshal(msg drpc.Message) ([]byte, error) {
|
||||
return proto.Marshal(msg.(proto.Message))
|
||||
}
|
||||
|
||||
func (drpcEncoding_File_consensus_consensusproto_protos_consensus_proto) Unmarshal(buf []byte, msg drpc.Message) error {
|
||||
return proto.Unmarshal(buf, msg.(proto.Message))
|
||||
}
|
||||
|
||||
func (drpcEncoding_File_consensus_consensusproto_protos_consensus_proto) JSONMarshal(msg drpc.Message) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
err := new(jsonpb.Marshaler).Marshal(&buf, msg.(proto.Message))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (drpcEncoding_File_consensus_consensusproto_protos_consensus_proto) JSONUnmarshal(buf []byte, msg drpc.Message) error {
|
||||
return jsonpb.Unmarshal(bytes.NewReader(buf), msg.(proto.Message))
|
||||
}
|
||||
|
||||
type DRPCConsensusClient interface {
|
||||
DRPCConn() drpc.Conn
|
||||
|
||||
LogAdd(ctx context.Context, in *LogAddRequest) (*Ok, error)
|
||||
RecordAdd(ctx context.Context, in *RecordAddRequest) (*RawRecordWithId, error)
|
||||
LogWatch(ctx context.Context) (DRPCConsensus_LogWatchClient, error)
|
||||
}
|
||||
|
||||
type drpcConsensusClient struct {
|
||||
cc drpc.Conn
|
||||
}
|
||||
|
||||
func NewDRPCConsensusClient(cc drpc.Conn) DRPCConsensusClient {
|
||||
return &drpcConsensusClient{cc}
|
||||
}
|
||||
|
||||
func (c *drpcConsensusClient) DRPCConn() drpc.Conn { return c.cc }
|
||||
|
||||
func (c *drpcConsensusClient) LogAdd(ctx context.Context, in *LogAddRequest) (*Ok, error) {
|
||||
out := new(Ok)
|
||||
err := c.cc.Invoke(ctx, "/consensusProto.Consensus/LogAdd", drpcEncoding_File_consensus_consensusproto_protos_consensus_proto{}, in, out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *drpcConsensusClient) RecordAdd(ctx context.Context, in *RecordAddRequest) (*RawRecordWithId, error) {
|
||||
out := new(RawRecordWithId)
|
||||
err := c.cc.Invoke(ctx, "/consensusProto.Consensus/RecordAdd", drpcEncoding_File_consensus_consensusproto_protos_consensus_proto{}, in, out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *drpcConsensusClient) LogWatch(ctx context.Context) (DRPCConsensus_LogWatchClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, "/consensusProto.Consensus/LogWatch", drpcEncoding_File_consensus_consensusproto_protos_consensus_proto{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &drpcConsensus_LogWatchClient{stream}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type DRPCConsensus_LogWatchClient interface {
|
||||
drpc.Stream
|
||||
Send(*LogWatchRequest) error
|
||||
Recv() (*LogWatchEvent, error)
|
||||
}
|
||||
|
||||
type drpcConsensus_LogWatchClient struct {
|
||||
drpc.Stream
|
||||
}
|
||||
|
||||
func (x *drpcConsensus_LogWatchClient) GetStream() drpc.Stream {
|
||||
return x.Stream
|
||||
}
|
||||
|
||||
func (x *drpcConsensus_LogWatchClient) Send(m *LogWatchRequest) error {
|
||||
return x.MsgSend(m, drpcEncoding_File_consensus_consensusproto_protos_consensus_proto{})
|
||||
}
|
||||
|
||||
func (x *drpcConsensus_LogWatchClient) Recv() (*LogWatchEvent, error) {
|
||||
m := new(LogWatchEvent)
|
||||
if err := x.MsgRecv(m, drpcEncoding_File_consensus_consensusproto_protos_consensus_proto{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (x *drpcConsensus_LogWatchClient) RecvMsg(m *LogWatchEvent) error {
|
||||
return x.MsgRecv(m, drpcEncoding_File_consensus_consensusproto_protos_consensus_proto{})
|
||||
}
|
||||
|
||||
type DRPCConsensusServer interface {
|
||||
LogAdd(context.Context, *LogAddRequest) (*Ok, error)
|
||||
RecordAdd(context.Context, *RecordAddRequest) (*RawRecordWithId, error)
|
||||
LogWatch(DRPCConsensus_LogWatchStream) error
|
||||
}
|
||||
|
||||
type DRPCConsensusUnimplementedServer struct{}
|
||||
|
||||
func (s *DRPCConsensusUnimplementedServer) LogAdd(context.Context, *LogAddRequest) (*Ok, error) {
|
||||
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
|
||||
}
|
||||
|
||||
func (s *DRPCConsensusUnimplementedServer) RecordAdd(context.Context, *RecordAddRequest) (*RawRecordWithId, error) {
|
||||
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
|
||||
}
|
||||
|
||||
func (s *DRPCConsensusUnimplementedServer) LogWatch(DRPCConsensus_LogWatchStream) error {
|
||||
return drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
|
||||
}
|
||||
|
||||
type DRPCConsensusDescription struct{}
|
||||
|
||||
func (DRPCConsensusDescription) NumMethods() int { return 3 }
|
||||
|
||||
func (DRPCConsensusDescription) Method(n int) (string, drpc.Encoding, drpc.Receiver, interface{}, bool) {
|
||||
switch n {
|
||||
case 0:
|
||||
return "/consensusProto.Consensus/LogAdd", drpcEncoding_File_consensus_consensusproto_protos_consensus_proto{},
|
||||
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
|
||||
return srv.(DRPCConsensusServer).
|
||||
LogAdd(
|
||||
ctx,
|
||||
in1.(*LogAddRequest),
|
||||
)
|
||||
}, DRPCConsensusServer.LogAdd, true
|
||||
case 1:
|
||||
return "/consensusProto.Consensus/RecordAdd", drpcEncoding_File_consensus_consensusproto_protos_consensus_proto{},
|
||||
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
|
||||
return srv.(DRPCConsensusServer).
|
||||
RecordAdd(
|
||||
ctx,
|
||||
in1.(*RecordAddRequest),
|
||||
)
|
||||
}, DRPCConsensusServer.RecordAdd, true
|
||||
case 2:
|
||||
return "/consensusProto.Consensus/LogWatch", drpcEncoding_File_consensus_consensusproto_protos_consensus_proto{},
|
||||
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
|
||||
return nil, srv.(DRPCConsensusServer).
|
||||
LogWatch(
|
||||
&drpcConsensus_LogWatchStream{in1.(drpc.Stream)},
|
||||
)
|
||||
}, DRPCConsensusServer.LogWatch, true
|
||||
default:
|
||||
return "", nil, nil, nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func DRPCRegisterConsensus(mux drpc.Mux, impl DRPCConsensusServer) error {
|
||||
return mux.Register(impl, DRPCConsensusDescription{})
|
||||
}
|
||||
|
||||
type DRPCConsensus_LogAddStream interface {
|
||||
drpc.Stream
|
||||
SendAndClose(*Ok) error
|
||||
}
|
||||
|
||||
type drpcConsensus_LogAddStream struct {
|
||||
drpc.Stream
|
||||
}
|
||||
|
||||
func (x *drpcConsensus_LogAddStream) SendAndClose(m *Ok) error {
|
||||
if err := x.MsgSend(m, drpcEncoding_File_consensus_consensusproto_protos_consensus_proto{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return x.CloseSend()
|
||||
}
|
||||
|
||||
type DRPCConsensus_RecordAddStream interface {
|
||||
drpc.Stream
|
||||
SendAndClose(*RawRecordWithId) error
|
||||
}
|
||||
|
||||
type drpcConsensus_RecordAddStream struct {
|
||||
drpc.Stream
|
||||
}
|
||||
|
||||
func (x *drpcConsensus_RecordAddStream) SendAndClose(m *RawRecordWithId) error {
|
||||
if err := x.MsgSend(m, drpcEncoding_File_consensus_consensusproto_protos_consensus_proto{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return x.CloseSend()
|
||||
}
|
||||
|
||||
type DRPCConsensus_LogWatchStream interface {
|
||||
drpc.Stream
|
||||
Send(*LogWatchEvent) error
|
||||
Recv() (*LogWatchRequest, error)
|
||||
}
|
||||
|
||||
type drpcConsensus_LogWatchStream struct {
|
||||
drpc.Stream
|
||||
}
|
||||
|
||||
func (x *drpcConsensus_LogWatchStream) Send(m *LogWatchEvent) error {
|
||||
return x.MsgSend(m, drpcEncoding_File_consensus_consensusproto_protos_consensus_proto{})
|
||||
}
|
||||
|
||||
func (x *drpcConsensus_LogWatchStream) Recv() (*LogWatchRequest, error) {
|
||||
m := new(LogWatchRequest)
|
||||
if err := x.MsgRecv(m, drpcEncoding_File_consensus_consensusproto_protos_consensus_proto{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (x *drpcConsensus_LogWatchStream) RecvMsg(m *LogWatchRequest) error {
|
||||
return x.MsgRecv(m, drpcEncoding_File_consensus_consensusproto_protos_consensus_proto{})
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package consensuserr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/anyproto/any-sync/net/rpc/rpcerr"
|
||||
)
|
||||
|
||||
var (
|
||||
errGroup = rpcerr.ErrGroup(consensusproto.ErrCodes_ErrorOffset)
|
||||
|
||||
ErrUnexpected = errGroup.Register(fmt.Errorf("unexpected consensus error"), uint64(consensusproto.ErrCodes_Unexpected))
|
||||
ErrConflict = errGroup.Register(fmt.Errorf("records conflict"), uint64(consensusproto.ErrCodes_RecordConflict))
|
||||
ErrLogExists = errGroup.Register(fmt.Errorf("log exists"), uint64(consensusproto.ErrCodes_LogExists))
|
||||
ErrLogNotFound = errGroup.Register(fmt.Errorf("log not found"), uint64(consensusproto.ErrCodes_LogNotFound))
|
||||
ErrForbidden = errGroup.Register(fmt.Errorf("forbidden"), uint64(consensusproto.ErrCodes_Forbidden))
|
||||
ErrInvalidPayload = errGroup.Register(fmt.Errorf("invalid payload"), uint64(consensusproto.ErrCodes_InvalidPayload))
|
||||
)
|
||||
@@ -0,0 +1,113 @@
|
||||
syntax = "proto3";
|
||||
package consensusProto;
|
||||
|
||||
option go_package = "consensus/consensusproto";
|
||||
|
||||
enum ErrCodes {
|
||||
Unexpected = 0;
|
||||
LogExists = 1;
|
||||
LogNotFound = 2;
|
||||
RecordConflict = 3;
|
||||
Forbidden = 4;
|
||||
InvalidPayload = 5;
|
||||
ErrorOffset = 500;
|
||||
}
|
||||
|
||||
|
||||
message Log {
|
||||
string id = 1;
|
||||
repeated RawRecordWithId records = 3;
|
||||
}
|
||||
|
||||
// RawRecord is a proto message containing the payload in bytes, signature of the account who added it and signature of the acceptor
|
||||
message RawRecord {
|
||||
bytes payload = 1;
|
||||
bytes signature = 2;
|
||||
bytes acceptorIdentity = 3;
|
||||
bytes acceptorSignature = 4;
|
||||
}
|
||||
|
||||
// RawRecordWithId is a raw record and the id for convenience
|
||||
message RawRecordWithId {
|
||||
bytes payload = 1;
|
||||
string id = 2;
|
||||
}
|
||||
|
||||
// Record is a record containing a data
|
||||
message Record {
|
||||
string prevId = 1;
|
||||
bytes identity = 2;
|
||||
bytes data = 3;
|
||||
int64 timestamp = 4;
|
||||
}
|
||||
|
||||
|
||||
service Consensus {
|
||||
// AddLog adds new log to consensus
|
||||
rpc LogAdd(LogAddRequest) returns (Ok);
|
||||
// AddRecord adds new record to log
|
||||
rpc RecordAdd(RecordAddRequest) returns (RawRecordWithId);
|
||||
// WatchLog fetches log and subscribes for a changes
|
||||
rpc LogWatch(stream LogWatchRequest) returns (stream LogWatchEvent);
|
||||
}
|
||||
|
||||
message Ok {}
|
||||
|
||||
message LogAddRequest {
|
||||
// first record in the log, consensus node not sign it
|
||||
RawRecordWithId record = 1;
|
||||
}
|
||||
|
||||
message RecordAddRequest {
|
||||
string logId = 1;
|
||||
RawRecord record = 2;
|
||||
}
|
||||
|
||||
message LogWatchRequest {
|
||||
repeated string watchIds = 1;
|
||||
repeated string unwatchIds = 2;
|
||||
}
|
||||
|
||||
message LogWatchEvent {
|
||||
string logId = 1;
|
||||
repeated RawRecordWithId records = 2;
|
||||
Err error = 3;
|
||||
}
|
||||
|
||||
message Err {
|
||||
ErrCodes error = 1;
|
||||
}
|
||||
|
||||
// LogSyncContentValue provides different types for log sync
|
||||
message LogSyncContentValue {
|
||||
oneof value {
|
||||
LogHeadUpdate headUpdate = 1;
|
||||
LogFullSyncRequest fullSyncRequest = 2;
|
||||
LogFullSyncResponse fullSyncResponse = 3;
|
||||
}
|
||||
}
|
||||
|
||||
// LogSyncMessage is a message sent when we are syncing logs
|
||||
message LogSyncMessage {
|
||||
string id = 1;
|
||||
bytes payload = 2;
|
||||
LogSyncContentValue content = 3;
|
||||
}
|
||||
|
||||
// LogHeadUpdate is a message sent on consensus log head update
|
||||
message LogHeadUpdate {
|
||||
string head = 1;
|
||||
repeated RawRecordWithId records = 2;
|
||||
}
|
||||
|
||||
// LogFullSyncRequest is a message sent when consensus log needs full sync
|
||||
message LogFullSyncRequest {
|
||||
string head = 1;
|
||||
repeated RawRecordWithId records = 2;
|
||||
}
|
||||
|
||||
// LogFullSyncResponse is a message sent as a response for a specific full sync
|
||||
message LogFullSyncResponse {
|
||||
string head = 1;
|
||||
repeated RawRecordWithId records = 2;
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
treechangeproto "github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
coordinatorclient "github.com/anyproto/any-sync/coordinator/coordinatorclient"
|
||||
coordinatorproto "github.com/anyproto/any-sync/coordinator/coordinatorproto"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockCoordinatorClient is a mock of CoordinatorClient interface.
|
||||
|
||||
@@ -12,7 +12,6 @@ require (
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/goccy/go-graphviz v0.1.1
|
||||
github.com/gogo/protobuf v1.3.2
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/hashicorp/yamux v0.1.1
|
||||
github.com/huandu/skiplist v1.2.0
|
||||
@@ -25,26 +24,26 @@ require (
|
||||
github.com/ipfs/go-ipld-format v0.5.0
|
||||
github.com/ipfs/go-merkledag v0.11.0
|
||||
github.com/ipfs/go-unixfs v0.4.6
|
||||
github.com/libp2p/go-libp2p v0.27.5
|
||||
github.com/libp2p/go-libp2p v0.28.1
|
||||
github.com/mr-tron/base58 v1.2.0
|
||||
github.com/multiformats/go-multibase v0.2.0
|
||||
github.com/multiformats/go-multihash v0.2.3
|
||||
github.com/prometheus/client_golang v1.15.1
|
||||
github.com/prometheus/client_golang v1.16.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/tyler-smith/go-bip39 v1.1.0
|
||||
github.com/zeebo/blake3 v0.2.3
|
||||
go.uber.org/atomic v1.11.0
|
||||
go.uber.org/mock v0.2.0
|
||||
go.uber.org/zap v1.24.0
|
||||
golang.org/x/crypto v0.9.0
|
||||
golang.org/x/crypto v0.10.0
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
|
||||
golang.org/x/net v0.10.0
|
||||
golang.org/x/net v0.11.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
storj.io/drpc v0.0.33
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
|
||||
github.com/benbjohnson/clock v1.3.5 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 // indirect
|
||||
@@ -56,9 +55,7 @@ require (
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/huin/goupnp v1.2.0 // indirect
|
||||
github.com/ipfs/bbloom v0.0.4 // indirect
|
||||
github.com/ipfs/go-bitfield v1.1.0 // indirect
|
||||
github.com/ipfs/go-datastore v0.6.0 // indirect
|
||||
@@ -75,29 +72,25 @@ require (
|
||||
github.com/ipld/go-ipld-prime v0.20.0 // indirect
|
||||
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
|
||||
github.com/jbenet/goprocess v0.1.4 // indirect
|
||||
github.com/klauspost/compress v1.16.5 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/miekg/dns v1.1.54 // indirect
|
||||
github.com/minio/sha256-simd v1.0.0 // indirect
|
||||
github.com/minio/sha256-simd v1.0.1 // indirect
|
||||
github.com/multiformats/go-base32 v0.1.0 // indirect
|
||||
github.com/multiformats/go-base36 v0.2.0 // indirect
|
||||
github.com/multiformats/go-multiaddr v0.9.0 // indirect
|
||||
github.com/multiformats/go-multicodec v0.9.0 // indirect
|
||||
github.com/multiformats/go-multistream v0.4.1 // indirect
|
||||
github.com/multiformats/go-varint v0.0.7 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.9.7 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/polydawn/refmt v0.0.0-20201211092308-30ac6d18308e // indirect
|
||||
github.com/prometheus/client_model v0.4.0 // indirect
|
||||
github.com/prometheus/common v0.44.0 // indirect
|
||||
github.com/prometheus/procfs v0.10.0 // indirect
|
||||
github.com/prometheus/procfs v0.10.1 // indirect
|
||||
github.com/quic-go/quic-go v0.35.1 // indirect
|
||||
github.com/quic-go/webtransport-go v0.5.3 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f // indirect
|
||||
github.com/zeebo/errs v1.3.0 // indirect
|
||||
@@ -106,7 +99,7 @@ require (
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/image v0.6.0 // indirect
|
||||
golang.org/x/sync v0.2.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/sys v0.9.0 // indirect
|
||||
golang.org/x/tools v0.9.3 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
lukechampine.com/blake3 v1.2.1 // indirect
|
||||
|
||||
@@ -13,7 +13,6 @@ github.com/anyproto/go-slip21 v1.0.0 h1:CI7lUqTIwmPOEGVAj4jyNLoICvueh++0U2HoAi3m
|
||||
github.com/anyproto/go-slip21 v1.0.0/go.mod h1:gbIJt7HAdr5DuT4f2pFTKCBSUWYsm/fysHBNqgsuxT0=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
|
||||
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
@@ -55,7 +54,6 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
@@ -65,7 +63,6 @@ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs=
|
||||
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
@@ -82,7 +79,6 @@ github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0Jr
|
||||
github.com/huandu/skiplist v1.2.0 h1:gox56QD77HzSC0w+Ws3MH3iie755GBJU1OER3h5VsYw=
|
||||
github.com/huandu/skiplist v1.2.0/go.mod h1:7v3iFjLcSAzO4fN5B8dvebvo/qsfumiLiDXMrPiHF9w=
|
||||
github.com/huin/goupnp v1.2.0 h1:uOKW26NG1hsSSbXIZ1IR7XP9Gjd1U8pnLaCMgntmkmY=
|
||||
github.com/huin/goupnp v1.2.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
|
||||
github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
|
||||
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
|
||||
github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
|
||||
@@ -156,11 +152,9 @@ github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpR
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
|
||||
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
@@ -171,13 +165,13 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
|
||||
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
|
||||
github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c=
|
||||
github.com/libp2p/go-libp2p v0.27.5 h1:KwA7pXKXpz8hG6Cr1fMA7UkgleogcwQj0sxl5qquWRg=
|
||||
github.com/libp2p/go-libp2p v0.27.5/go.mod h1:oMfQGTb9CHnrOuSM6yMmyK2lXz3qIhnkn2+oK3B1Y2g=
|
||||
github.com/libp2p/go-libp2p v0.28.1 h1:YurK+ZAI6cKfASLJBVFkpVBdl3wGhFi6fusOt725ii8=
|
||||
github.com/libp2p/go-libp2p v0.28.1/go.mod h1:s3Xabc9LSwOcnv9UD4nORnXKTsWkPMkIMB/JIGXVnzk=
|
||||
github.com/libp2p/go-libp2p-asn-util v0.3.0 h1:gMDcMyYiZKkocGXDQ5nsUQyquC9+H+iLEQHwOCZ7s8s=
|
||||
github.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0=
|
||||
github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA=
|
||||
github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0=
|
||||
github.com/libp2p/go-nat v0.1.0 h1:MfVsH6DLcpa04Xr+p8hmVRG4juse0s3J8HyNWYHffXg=
|
||||
github.com/libp2p/go-nat v0.2.0 h1:Tyz+bUFAYqGyJ/ppPPymMGbIgNRH+WqC5QrT5fKrrGk=
|
||||
github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU=
|
||||
github.com/libp2p/go-yamux/v4 v4.0.0 h1:+Y80dV2Yx/kv7Y7JKu0LECyVdMXm1VUoko+VQ9rBfZQ=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
@@ -186,12 +180,11 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI=
|
||||
github.com/miekg/dns v1.1.54/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
||||
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
|
||||
github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U=
|
||||
github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
|
||||
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
|
||||
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
|
||||
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
|
||||
github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8=
|
||||
github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
||||
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
||||
@@ -220,8 +213,8 @@ github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXS
|
||||
github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
|
||||
github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
|
||||
github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 h1:BvoENQQU+fZ9uukda/RzCAL/191HHwJA5b13R6diVlY=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss=
|
||||
github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0=
|
||||
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
|
||||
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -231,21 +224,20 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/polydawn/refmt v0.0.0-20201211092308-30ac6d18308e h1:ZOcivgkkFRnjfoTcGsDq3UQYiBmekwLA+qg0OjyB/ls=
|
||||
github.com/polydawn/refmt v0.0.0-20201211092308-30ac6d18308e/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o=
|
||||
github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI=
|
||||
github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
|
||||
github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
|
||||
github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
|
||||
github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
|
||||
github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
|
||||
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
|
||||
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
|
||||
github.com/prometheus/procfs v0.10.0 h1:UkG7GPYkO4UZyLnyXjaWYcgOSONqwdBqFUT95ugmt6I=
|
||||
github.com/prometheus/procfs v0.10.0/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
|
||||
github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
|
||||
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
|
||||
github.com/quic-go/quic-go v0.35.1 h1:b0kzj6b/cQAf05cT0CkQubHM31wiA+xH3IBkxP62poo=
|
||||
github.com/quic-go/quic-go v0.35.1/go.mod h1:+4CVgVppm0FNjpG3UcX8Joi/frKOH7/ciD5yGcwOO1g=
|
||||
github.com/quic-go/webtransport-go v0.5.3 h1:5XMlzemqB4qmOlgIus5zB45AcZ2kCgCy2EptUrfOPWU=
|
||||
github.com/quic-go/webtransport-go v0.5.3/go.mod h1:OhmmgJIzTTqXK5xvtuX0oBpLV2GkLWNDA+UeTGJXErU=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
@@ -288,6 +280,8 @@ go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
|
||||
go.uber.org/mock v0.2.0 h1:TaP3xedm7JaAgScZO7tlvlKrqT0p7I6OsdGB5YNSMDU=
|
||||
go.uber.org/mock v0.2.0/go.mod h1:J0y0rp9L3xiff1+ZBfKxlC1fz2+aO16tw0tsDOixfuM=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
@@ -305,8 +299,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4=
|
||||
@@ -328,8 +322,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
|
||||
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -350,12 +344,11 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
@@ -364,7 +357,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
@@ -373,7 +366,6 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type limiter struct {
|
||||
startThreshold int
|
||||
slowDownStep time.Duration
|
||||
}
|
||||
|
||||
func (l limiter) wait(count int) <-chan time.Time {
|
||||
if count > l.startThreshold {
|
||||
wait := l.slowDownStep * time.Duration(count-l.startThreshold)
|
||||
return time.After(wait)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
reflect "reflect"
|
||||
time "time"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
drpc "storj.io/drpc"
|
||||
)
|
||||
|
||||
|
||||
+65
-10
@@ -3,6 +3,12 @@ package peer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/app/ocache"
|
||||
"github.com/anyproto/any-sync/net/connutil"
|
||||
@@ -11,15 +17,11 @@ import (
|
||||
"github.com/anyproto/any-sync/net/secureservice/handshake/handshakeproto"
|
||||
"github.com/anyproto/any-sync/net/transport"
|
||||
"go.uber.org/zap"
|
||||
"io"
|
||||
"net"
|
||||
"storj.io/drpc"
|
||||
"storj.io/drpc/drpcconn"
|
||||
"storj.io/drpc/drpcmanager"
|
||||
"storj.io/drpc/drpcstream"
|
||||
"storj.io/drpc/drpcwire"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var log = logger.NewNamed("common.net.peer")
|
||||
@@ -35,8 +37,15 @@ func NewPeer(mc transport.MultiConn, ctrl connCtrl) (p Peer, err error) {
|
||||
active: map[*subConn]struct{}{},
|
||||
MultiConn: mc,
|
||||
ctrl: ctrl,
|
||||
created: time.Now(),
|
||||
limiter: limiter{
|
||||
// start throttling after 10 sub conns
|
||||
startThreshold: 10,
|
||||
slowDownStep: time.Millisecond * 100,
|
||||
},
|
||||
subConnRelease: make(chan drpc.Conn),
|
||||
created: time.Now(),
|
||||
}
|
||||
pr.acceptCtx, pr.acceptCtxCancel = context.WithCancel(context.Background())
|
||||
if pr.id, err = CtxPeerId(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -70,13 +79,22 @@ type peer struct {
|
||||
ctrl connCtrl
|
||||
|
||||
// drpc conn pool
|
||||
inactive []*subConn
|
||||
active map[*subConn]struct{}
|
||||
// outgoing
|
||||
inactive []*subConn
|
||||
active map[*subConn]struct{}
|
||||
subConnRelease chan drpc.Conn
|
||||
openingWaitCount atomic.Int32
|
||||
|
||||
incomingCount atomic.Int32
|
||||
acceptCtx context.Context
|
||||
|
||||
acceptCtxCancel context.CancelFunc
|
||||
|
||||
limiter limiter
|
||||
|
||||
mu sync.Mutex
|
||||
|
||||
created time.Time
|
||||
|
||||
transport.MultiConn
|
||||
}
|
||||
|
||||
@@ -87,7 +105,20 @@ func (p *peer) Id() string {
|
||||
func (p *peer) AcquireDrpcConn(ctx context.Context) (drpc.Conn, error) {
|
||||
p.mu.Lock()
|
||||
if len(p.inactive) == 0 {
|
||||
wait := p.limiter.wait(len(p.active) + int(p.openingWaitCount.Load()))
|
||||
p.mu.Unlock()
|
||||
if wait != nil {
|
||||
p.openingWaitCount.Add(1)
|
||||
defer p.openingWaitCount.Add(-1)
|
||||
// throttle new connection opening
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case dconn := <-p.subConnRelease:
|
||||
return dconn, nil
|
||||
case <-wait:
|
||||
}
|
||||
}
|
||||
dconn, err := p.openDrpcConn(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -110,6 +141,21 @@ func (p *peer) AcquireDrpcConn(ctx context.Context) (drpc.Conn, error) {
|
||||
}
|
||||
|
||||
func (p *peer) ReleaseDrpcConn(conn drpc.Conn) {
|
||||
// do nothing if it's closed connection
|
||||
select {
|
||||
case <-conn.Closed():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
// try to send this connection to acquire if anyone is waiting for it
|
||||
select {
|
||||
case p.subConnRelease <- conn:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
// return to pool
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
sc, ok := conn.(*subConn)
|
||||
@@ -143,7 +189,7 @@ func (p *peer) openDrpcConn(ctx context.Context) (dconn *subConn, err error) {
|
||||
tconn := connutil.NewLastUsageConn(conn)
|
||||
bufSize := p.ctrl.DrpcConfig().Stream.MaxMsgSizeMb * (1 << 20)
|
||||
return &subConn{
|
||||
Conn: drpcconn.NewWithOptions(conn, drpcconn.Options{
|
||||
Conn: drpcconn.NewWithOptions(tconn, drpcconn.Options{
|
||||
Manager: drpcmanager.Options{
|
||||
Reader: drpcwire.ReaderOptions{MaximumBufferSize: bufSize},
|
||||
Stream: drpcstream.Options{MaximumBufferSize: bufSize},
|
||||
@@ -162,12 +208,21 @@ func (p *peer) acceptLoop() {
|
||||
}
|
||||
}()
|
||||
for {
|
||||
if wait := p.limiter.wait(int(p.incomingCount.Load())); wait != nil {
|
||||
select {
|
||||
case <-wait:
|
||||
case <-p.acceptCtx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
conn, err := p.Accept()
|
||||
if err != nil {
|
||||
exitErr = err
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
p.incomingCount.Add(1)
|
||||
defer p.incomingCount.Add(-1)
|
||||
serveErr := p.serve(conn)
|
||||
if serveErr != io.EOF && serveErr != transport.ErrConnClosed {
|
||||
log.InfoCtx(p.Context(), "serve connection error", zap.Error(serveErr))
|
||||
@@ -241,7 +296,7 @@ func (p *peer) gc(ttl time.Duration) (aliveCount int) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return len(p.active) + len(p.inactive)
|
||||
return len(p.active) + len(p.inactive) + int(p.incomingCount.Load())
|
||||
}
|
||||
|
||||
func (p *peer) Close() (err error) {
|
||||
|
||||
+98
-22
@@ -6,12 +6,14 @@ import (
|
||||
"github.com/anyproto/any-sync/net/secureservice/handshake"
|
||||
"github.com/anyproto/any-sync/net/secureservice/handshake/handshakeproto"
|
||||
"github.com/anyproto/any-sync/net/transport/mock_transport"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
"io"
|
||||
"net"
|
||||
_ "net/http/pprof"
|
||||
"storj.io/drpc"
|
||||
"storj.io/drpc/drpcconn"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -19,32 +21,86 @@ import (
|
||||
var ctx = context.Background()
|
||||
|
||||
func TestPeer_AcquireDrpcConn(t *testing.T) {
|
||||
t.Run("generic", func(t *testing.T) {
|
||||
fx := newFixture(t, "p1")
|
||||
defer fx.finish()
|
||||
in, out := net.Pipe()
|
||||
go func() {
|
||||
handshake.IncomingProtoHandshake(ctx, out, defaultProtoChecker)
|
||||
}()
|
||||
defer out.Close()
|
||||
fx.mc.EXPECT().Open(gomock.Any()).Return(in, nil)
|
||||
dc, err := fx.AcquireDrpcConn(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, dc)
|
||||
defer dc.Close()
|
||||
|
||||
assert.Len(t, fx.active, 1)
|
||||
assert.Len(t, fx.inactive, 0)
|
||||
|
||||
fx.ReleaseDrpcConn(dc)
|
||||
|
||||
assert.Len(t, fx.active, 0)
|
||||
assert.Len(t, fx.inactive, 1)
|
||||
|
||||
dc, err = fx.AcquireDrpcConn(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, dc)
|
||||
assert.Len(t, fx.active, 1)
|
||||
assert.Len(t, fx.inactive, 0)
|
||||
})
|
||||
t.Run("closed sub conn", func(t *testing.T) {
|
||||
fx := newFixture(t, "p1")
|
||||
defer fx.finish()
|
||||
|
||||
closedIn, _ := net.Pipe()
|
||||
dc := drpcconn.New(closedIn)
|
||||
fx.ReleaseDrpcConn(&subConn{Conn: dc})
|
||||
dc.Close()
|
||||
|
||||
in, out := net.Pipe()
|
||||
go func() {
|
||||
handshake.IncomingProtoHandshake(ctx, out, defaultProtoChecker)
|
||||
}()
|
||||
defer out.Close()
|
||||
fx.mc.EXPECT().Open(gomock.Any()).Return(in, nil)
|
||||
_, err := fx.AcquireDrpcConn(ctx)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPeer_DrpcConn_OpenThrottling(t *testing.T) {
|
||||
fx := newFixture(t, "p1")
|
||||
defer fx.finish()
|
||||
in, out := net.Pipe()
|
||||
|
||||
acquire := func() (func(), drpc.Conn, error) {
|
||||
in, out := net.Pipe()
|
||||
go func() {
|
||||
_, err := handshake.IncomingProtoHandshake(ctx, out, defaultProtoChecker)
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
fx.mc.EXPECT().Open(gomock.Any()).Return(in, nil)
|
||||
dconn, err := fx.AcquireDrpcConn(ctx)
|
||||
return func() { out.Close() }, dconn, err
|
||||
}
|
||||
|
||||
var conCount = fx.limiter.startThreshold + 3
|
||||
var conns []drpc.Conn
|
||||
for i := 0; i < conCount; i++ {
|
||||
cc, dc, err := acquire()
|
||||
require.NoError(t, err)
|
||||
defer cc()
|
||||
conns = append(conns, dc)
|
||||
}
|
||||
|
||||
go func() {
|
||||
handshake.IncomingProtoHandshake(ctx, out, defaultProtoChecker)
|
||||
time.Sleep(fx.limiter.slowDownStep)
|
||||
fx.ReleaseDrpcConn(conns[0])
|
||||
conns = conns[1:]
|
||||
}()
|
||||
defer out.Close()
|
||||
fx.mc.EXPECT().Open(gomock.Any()).Return(in, nil)
|
||||
dc, err := fx.AcquireDrpcConn(ctx)
|
||||
_, err := fx.AcquireDrpcConn(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, dc)
|
||||
defer dc.Close()
|
||||
|
||||
assert.Len(t, fx.active, 1)
|
||||
assert.Len(t, fx.inactive, 0)
|
||||
|
||||
fx.ReleaseDrpcConn(dc)
|
||||
|
||||
assert.Len(t, fx.active, 0)
|
||||
assert.Len(t, fx.inactive, 1)
|
||||
|
||||
dc, err = fx.AcquireDrpcConn(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, dc)
|
||||
assert.Len(t, fx.active, 1)
|
||||
assert.Len(t, fx.inactive, 0)
|
||||
}
|
||||
|
||||
func TestPeerAccept(t *testing.T) {
|
||||
@@ -63,6 +119,26 @@ func TestPeerAccept(t *testing.T) {
|
||||
assert.NoError(t, <-outHandshakeCh)
|
||||
}
|
||||
|
||||
func TestPeer_DrpcConn_AcceptThrottling(t *testing.T) {
|
||||
fx := newFixture(t, "p1")
|
||||
defer fx.finish()
|
||||
|
||||
var conCount = fx.limiter.startThreshold + 3
|
||||
for i := 0; i < conCount; i++ {
|
||||
in, out := net.Pipe()
|
||||
defer out.Close()
|
||||
|
||||
var outHandshakeCh = make(chan error)
|
||||
go func() {
|
||||
outHandshakeCh <- handshake.OutgoingProtoHandshake(ctx, out, handshakeproto.ProtoType_DRPC)
|
||||
}()
|
||||
fx.acceptCh <- acceptedConn{conn: in}
|
||||
cn := <-fx.testCtrl.serveConn
|
||||
assert.Equal(t, in, cn)
|
||||
assert.NoError(t, <-outHandshakeCh)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPeer_TryClose(t *testing.T) {
|
||||
t.Run("not close in first minute", func(t *testing.T) {
|
||||
fx := newFixture(t, "p1")
|
||||
|
||||
Loaded 100 of 116 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user