1
0
Fork 0
mirror of https://github.com/anyproto/any-sync.git synced 2025-06-08 05:57:03 +09:00

Merge branch 'main' of github.com:anyproto/any-sync into add-content-with-validator

# Conflicts:
#	commonspace/object/tree/synctree/synctree_test.go
This commit is contained in:
Sergey 2024-08-26 15:07:33 +02:00
commit f56a3f1a08
No known key found for this signature in database
GPG key ID: 3B6BEF79160221C6
53 changed files with 2026 additions and 464 deletions

View file

@ -100,7 +100,7 @@ func (d *diffSyncer) Sync(ctx context.Context) error {
}
d.log.DebugCtx(ctx, "start diffsync", zap.Strings("peerIds", peerIds))
for _, p := range peers {
if err = d.syncWithPeer(peer.CtxWithPeerId(ctx, p.Id()), p); err != nil {
if err = d.syncWithPeer(peer.CtxWithPeerAddr(ctx, p.Id()), p); err != nil {
d.log.ErrorCtx(ctx, "can't sync with peer", zap.String("peer", p.Id()), zap.Error(err))
}
}
@ -152,13 +152,13 @@ func (d *diffSyncer) syncWithPeer(ctx context.Context, p peer.Peer) (err error)
// if we removed acl head from the list
if len(existingIds) < prevExistingLen {
if syncErr := d.syncAcl.SyncWithPeer(ctx, p.Id()); syncErr != nil {
if syncErr := d.syncAcl.SyncWithPeer(ctx, p); 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)
err = d.treeSyncer.SyncAll(ctx, p, existingIds, missingIds)
if err != nil {
return err
}

View file

@ -5,7 +5,6 @@ import (
"context"
"fmt"
"testing"
"time"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
@ -18,6 +17,7 @@ import (
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
"github.com/anyproto/any-sync/consensus/consensusproto"
"github.com/anyproto/any-sync/net/peer"
"github.com/anyproto/any-sync/net/rpc/rpctest"
)
type pushSpaceRequestMatcher struct {
@ -56,49 +56,6 @@ func (p pushSpaceRequestMatcher) String() string {
return ""
}
type mockPeer struct {
}
func (m mockPeer) CloseChan() <-chan struct{} {
return nil
}
func (m mockPeer) SetTTL(ttl time.Duration) {
return
}
func (m mockPeer) Id() string {
return "peerId"
}
func (m mockPeer) Context() context.Context {
return context.Background()
}
func (m mockPeer) AcquireDrpcConn(ctx context.Context) (drpc.Conn, error) {
return nil, nil
}
func (m mockPeer) ReleaseDrpcConn(conn drpc.Conn) {
return
}
func (m mockPeer) DoDrpc(ctx context.Context, do func(conn drpc.Conn) error) error {
return nil
}
func (m mockPeer) IsClosed() bool {
return false
}
func (m mockPeer) TryClose(objectTTL time.Duration) (res bool, err error) {
return false, err
}
func (m mockPeer) Close() (err error) {
return nil
}
func (fx *headSyncFixture) initDiffSyncer(t *testing.T) {
fx.init(t)
fx.diffSyncer = newDiffSyncer(fx.headSync).(*diffSyncer)
@ -116,7 +73,7 @@ func TestDiffSyncer(t *testing.T) {
fx := newHeadSyncFixture(t)
fx.initDiffSyncer(t)
defer fx.stop()
mPeer := mockPeer{}
mPeer := rpctest.MockPeer{}
remDiff := NewRemoteDiff(fx.spaceState.SpaceId, fx.clientMock)
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
fx.treeSyncerMock.EXPECT().ShouldSync(gomock.Any()).Return(true)
@ -131,7 +88,7 @@ func TestDiffSyncer(t *testing.T) {
fx.deletionStateMock.EXPECT().Filter([]string{"new"}).Return([]string{"new"}).Times(1)
fx.deletionStateMock.EXPECT().Filter([]string{"changed"}).Return([]string{"changed"}).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.treeSyncerMock.EXPECT().SyncAll(gomock.Any(), mPeer, []string{"changed"}, []string{"new"}).Return(nil)
require.NoError(t, fx.diffSyncer.Sync(ctx))
})
@ -139,7 +96,7 @@ func TestDiffSyncer(t *testing.T) {
fx := newHeadSyncFixture(t)
fx.initDiffSyncer(t)
defer fx.stop()
mPeer := mockPeer{}
mPeer := rpctest.MockPeer{}
remDiff := NewRemoteDiff(fx.spaceState.SpaceId, fx.clientMock)
fx.treeSyncerMock.EXPECT().ShouldSync(gomock.Any()).Return(true)
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
@ -154,8 +111,8 @@ func TestDiffSyncer(t *testing.T) {
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)
fx.treeSyncerMock.EXPECT().SyncAll(gomock.Any(), mPeer, []string{"changed"}, []string{"new"}).Return(nil)
fx.aclMock.EXPECT().SyncWithPeer(gomock.Any(), mPeer).Return(nil)
require.NoError(t, fx.diffSyncer.Sync(ctx))
})
@ -225,7 +182,7 @@ func TestDiffSyncer(t *testing.T) {
fx.peerManagerMock.EXPECT().
GetResponsiblePeers(gomock.Any()).
Return([]peer.Peer{mockPeer{}}, nil)
Return([]peer.Peer{rpctest.MockPeer{}}, nil)
fx.diffContainerMock.EXPECT().
DiffTypeCheck(gomock.Any(), gomock.Eq(remDiff)).Return(true, fx.diffMock, nil)
fx.diffMock.EXPECT().
@ -261,7 +218,7 @@ func TestDiffSyncer(t *testing.T) {
fx.treeSyncerMock.EXPECT().ShouldSync(gomock.Any()).Return(true)
fx.peerManagerMock.EXPECT().
GetResponsiblePeers(gomock.Any()).
Return([]peer.Peer{mockPeer{}}, nil)
Return([]peer.Peer{rpctest.MockPeer{}}, nil)
fx.diffContainerMock.EXPECT().
DiffTypeCheck(gomock.Any(), gomock.Eq(remDiff)).Return(true, fx.diffMock, nil)
fx.diffMock.EXPECT().
@ -275,7 +232,7 @@ func TestDiffSyncer(t *testing.T) {
fx := newHeadSyncFixture(t)
fx.initDiffSyncer(t)
defer fx.stop()
mPeer := mockPeer{}
mPeer := rpctest.MockPeer{}
remDiff := NewRemoteDiff(fx.spaceState.SpaceId, fx.clientMock)
fx.treeSyncerMock.EXPECT().ShouldSync(gomock.Any()).Return(true)
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")

View file

@ -18,6 +18,7 @@ import (
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"
peer "github.com/anyproto/any-sync/net/peer"
crypto "github.com/anyproto/any-sync/util/crypto"
gomock "go.uber.org/mock/gomock"
)
@ -132,17 +133,17 @@ func (mr *MockSyncAclMockRecorder) GetIndex(arg0 any) *gomock.Call {
}
// HandleMessage mocks base method.
func (m *MockSyncAcl) HandleMessage(arg0 context.Context, arg1 string, arg2 *spacesyncproto.ObjectSyncMessage) error {
func (m *MockSyncAcl) HandleMessage(arg0 context.Context, arg1 string, arg2 uint32, arg3 *spacesyncproto.ObjectSyncMessage) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HandleMessage", arg0, arg1, arg2)
ret := m.ctrl.Call(m, "HandleMessage", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(error)
return ret0
}
// HandleMessage indicates an expected call of HandleMessage.
func (mr *MockSyncAclMockRecorder) HandleMessage(arg0, arg1, arg2 any) *gomock.Call {
func (mr *MockSyncAclMockRecorder) HandleMessage(arg0, arg1, arg2, arg3 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleMessage", reflect.TypeOf((*MockSyncAcl)(nil).HandleMessage), arg0, arg1, arg2)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleMessage", reflect.TypeOf((*MockSyncAcl)(nil).HandleMessage), arg0, arg1, arg2, arg3)
}
// HandleRequest mocks base method.
@ -430,7 +431,7 @@ func (mr *MockSyncAclMockRecorder) SetHeadUpdater(arg0 any) *gomock.Call {
}
// SyncWithPeer mocks base method.
func (m *MockSyncAcl) SyncWithPeer(arg0 context.Context, arg1 string) error {
func (m *MockSyncAcl) SyncWithPeer(arg0 context.Context, arg1 peer.Peer) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SyncWithPeer", arg0, arg1)
ret0, _ := ret[0].(error)
@ -504,6 +505,20 @@ func (mr *MockSyncClientMockRecorder) Broadcast(arg0 any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Broadcast", reflect.TypeOf((*MockSyncClient)(nil).Broadcast), arg0)
}
// CreateEmptyFullSyncRequest mocks base method.
func (m *MockSyncClient) CreateEmptyFullSyncRequest(arg0 list.AclList) *consensusproto.LogSyncMessage {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateEmptyFullSyncRequest", arg0)
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
return ret0
}
// CreateEmptyFullSyncRequest indicates an expected call of CreateEmptyFullSyncRequest.
func (mr *MockSyncClientMockRecorder) CreateEmptyFullSyncRequest(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateEmptyFullSyncRequest", reflect.TypeOf((*MockSyncClient)(nil).CreateEmptyFullSyncRequest), arg0)
}
// CreateFullSyncRequest mocks base method.
func (m *MockSyncClient) CreateFullSyncRequest(arg0 list.AclList, arg1 string) (*consensusproto.LogSyncMessage, error) {
m.ctrl.T.Helper()
@ -614,6 +629,20 @@ func (m *MockRequestFactory) EXPECT() *MockRequestFactoryMockRecorder {
return m.recorder
}
// CreateEmptyFullSyncRequest mocks base method.
func (m *MockRequestFactory) CreateEmptyFullSyncRequest(arg0 list.AclList) *consensusproto.LogSyncMessage {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateEmptyFullSyncRequest", arg0)
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
return ret0
}
// CreateEmptyFullSyncRequest indicates an expected call of CreateEmptyFullSyncRequest.
func (mr *MockRequestFactoryMockRecorder) CreateEmptyFullSyncRequest(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateEmptyFullSyncRequest", reflect.TypeOf((*MockRequestFactory)(nil).CreateEmptyFullSyncRequest), arg0)
}
// CreateFullSyncRequest mocks base method.
func (m *MockRequestFactory) CreateFullSyncRequest(arg0 list.AclList, arg1 string) (*consensusproto.LogSyncMessage, error) {
m.ctrl.T.Helper()

View file

@ -9,6 +9,7 @@ import (
type RequestFactory interface {
CreateHeadUpdate(l list.AclList, added []*consensusproto.RawRecordWithId) (msg *consensusproto.LogSyncMessage)
CreateEmptyFullSyncRequest(l list.AclList) (req *consensusproto.LogSyncMessage)
CreateFullSyncRequest(l list.AclList, theirHead string) (req *consensusproto.LogSyncMessage, err error)
CreateFullSyncResponse(l list.AclList, theirHead string) (*consensusproto.LogSyncMessage, error)
}
@ -26,6 +27,13 @@ func (r *requestFactory) CreateHeadUpdate(l list.AclList, added []*consensusprot
}, l.Root())
}
func (r *requestFactory) CreateEmptyFullSyncRequest(l list.AclList) (req *consensusproto.LogSyncMessage) {
// this is only sent to newer versions of the protocol
return consensusproto.WrapFullRequest(&consensusproto.LogFullSyncRequest{
Head: l.Head().Id,
}, 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{

View file

@ -8,6 +8,8 @@ import (
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl/headupdater"
"github.com/anyproto/any-sync/commonspace/object/syncobjectgetter"
"github.com/anyproto/any-sync/net/peer"
"github.com/anyproto/any-sync/net/secureservice"
"github.com/anyproto/any-sync/accountservice"
"github.com/anyproto/any-sync/app"
@ -35,7 +37,7 @@ type SyncAcl interface {
list.AclList
syncobjectgetter.SyncObject
SetHeadUpdater(updater headupdater.HeadUpdater)
SyncWithPeer(ctx context.Context, peerId string) (err error)
SyncWithPeer(ctx context.Context, p peer.Peer) (err error)
SetAclUpdater(updater headupdater.AclUpdater)
}
@ -73,8 +75,8 @@ 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) HandleMessage(ctx context.Context, senderId string, protoVersion uint32, request *spacesyncproto.ObjectSyncMessage) (err error) {
return s.syncHandler.HandleMessage(ctx, senderId, protoVersion, request)
}
func (s *syncAcl) Init(a *app.App) (err error) {
@ -136,11 +138,18 @@ func (s *syncAcl) AddRawRecords(rawRecords []*consensusproto.RawRecordWithId) (e
return
}
func (s *syncAcl) SyncWithPeer(ctx context.Context, peerId string) (err error) {
func (s *syncAcl) SyncWithPeer(ctx context.Context, p peer.Peer) (err error) {
s.Lock()
defer s.Unlock()
protoVersion, err := peer.CtxProtoVersion(p.Context())
// this works with old protocol
if err != nil || protoVersion <= secureservice.ProtoVersion {
headUpdate := s.syncClient.CreateHeadUpdate(s, nil)
return s.syncClient.SendUpdate(peerId, headUpdate)
return s.syncClient.SendUpdate(p.Id(), headUpdate)
}
// for new protocol sending empty request
request := s.syncClient.CreateEmptyFullSyncRequest(s)
return s.syncClient.QueueRequest(p.Id(), request)
}
func (s *syncAcl) Close(ctx context.Context) (err error) {

View file

@ -0,0 +1,56 @@
package syncacl
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"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/anyproto/any-sync/net/peer"
"github.com/anyproto/any-sync/net/rpc/rpctest"
"github.com/anyproto/any-sync/net/secureservice"
)
var ctx = context.Background()
func TestSyncAcl_SyncWithPeer(t *testing.T) {
// this component will be rewritten, so no need for fixture now
t.Run("sync with old peer", func(t *testing.T) {
ctrl := gomock.NewController(t)
acl := mock_list.NewMockAclList(ctrl)
s := &syncAcl{AclList: acl}
defer ctrl.Finish()
mockClient := mock_syncacl.NewMockSyncClient(ctrl)
s.syncClient = mockClient
ctx := peer.CtxWithProtoVersion(ctx, secureservice.ProtoVersion)
pr := rpctest.MockPeer{Ctx: ctx}
retMsg := &consensusproto.LogSyncMessage{}
mockClient.EXPECT().CreateHeadUpdate(s, nil).Return(retMsg)
acl.EXPECT().Lock()
acl.EXPECT().Unlock()
mockClient.EXPECT().SendUpdate("peerId", retMsg).Return(nil)
err := s.SyncWithPeer(ctx, &pr)
require.NoError(t, err)
})
t.Run("sync with new peer", func(t *testing.T) {
ctrl := gomock.NewController(t)
acl := mock_list.NewMockAclList(ctrl)
s := &syncAcl{AclList: acl}
defer ctrl.Finish()
mockClient := mock_syncacl.NewMockSyncClient(ctrl)
s.syncClient = mockClient
ctx := peer.CtxWithProtoVersion(ctx, secureservice.NewSyncProtoVersion)
pr := rpctest.MockPeer{Ctx: ctx}
req := &consensusproto.LogSyncMessage{}
mockClient.EXPECT().CreateEmptyFullSyncRequest(s).Return(req)
acl.EXPECT().Lock()
acl.EXPECT().Unlock()
mockClient.EXPECT().QueueRequest("peerId", req).Return(nil)
err := s.SyncWithPeer(ctx, &pr)
require.NoError(t, err)
})
}

View file

@ -4,12 +4,14 @@ import (
"context"
"errors"
"github.com/gogo/protobuf/proto"
"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"
"github.com/anyproto/any-sync/net/secureservice"
)
var (
@ -35,7 +37,7 @@ func newSyncAclHandler(spaceId string, aclList list.AclList, syncClient SyncClie
}
}
func (s *syncAclHandler) HandleMessage(ctx context.Context, senderId string, message *spacesyncproto.ObjectSyncMessage) (err error) {
func (s *syncAclHandler) HandleMessage(ctx context.Context, senderId string, protoVersion uint32, message *spacesyncproto.ObjectSyncMessage) (err error) {
unmarshalled := &consensusproto.LogSyncMessage{}
err = proto.Unmarshal(message.Payload, unmarshalled)
if err != nil {
@ -57,7 +59,18 @@ func (s *syncAclHandler) HandleMessage(ctx context.Context, senderId string, mes
case content.GetFullSyncRequest() != nil:
return ErrMessageIsRequest
case content.GetFullSyncResponse() != nil:
return s.syncProtocol.FullSyncResponse(ctx, senderId, content.GetFullSyncResponse())
err := s.syncProtocol.FullSyncResponse(ctx, senderId, content.GetFullSyncResponse())
if err != nil {
return err
}
if protoVersion <= secureservice.ProtoVersion || s.aclList.Head().Id == head {
return nil
}
req, err := s.syncClient.CreateFullSyncRequest(s.aclList, head)
if err != nil {
return err
}
return s.syncClient.QueueRequest(senderId, req)
}
return
}

View file

@ -3,16 +3,20 @@ package syncacl
import (
"context"
"fmt"
"sync"
"testing"
"github.com/gogo/protobuf/proto"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"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/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"
"github.com/anyproto/any-sync/net/secureservice"
)
type testAclMock struct {
@ -110,7 +114,7 @@ func TestSyncAclHandler_HandleMessage(t *testing.T) {
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)
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, 0, objectMsg)
require.NoError(t, err)
})
t.Run("handle head update, no request", func(t *testing.T) {
@ -127,7 +131,7 @@ func TestSyncAclHandler_HandleMessage(t *testing.T) {
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)
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, 0, objectMsg)
require.NoError(t, err)
})
t.Run("handle head update, returned error", func(t *testing.T) {
@ -145,7 +149,7 @@ func TestSyncAclHandler_HandleMessage(t *testing.T) {
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)
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, 0, objectMsg)
require.Error(t, expectedErr, err)
})
t.Run("handle full sync request is forbidden", func(t *testing.T) {
@ -160,7 +164,7 @@ func TestSyncAclHandler_HandleMessage(t *testing.T) {
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, 0, objectMsg)
require.Error(t, ErrMessageIsRequest, err)
})
t.Run("handle full sync response, no error", func(t *testing.T) {
@ -177,7 +181,44 @@ func TestSyncAclHandler_HandleMessage(t *testing.T) {
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)
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, 0, objectMsg)
require.NoError(t, err)
})
t.Run("handle full sync response, new protocol, not equal heads", 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.aclMock.EXPECT().Head().AnyTimes().Return(&list.AclRecord{Id: "h2"})
fx.syncProtocolMock.EXPECT().FullSyncResponse(ctx, fx.senderId, gomock.Any()).Return(nil)
req := &consensusproto.LogSyncMessage{}
fx.syncClientMock.EXPECT().CreateFullSyncRequest(fx.aclMock, "h1").Return(req, nil)
fx.syncClientMock.EXPECT().QueueRequest(fx.senderId, req).Return(nil)
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, secureservice.NewSyncProtoVersion, objectMsg)
require.NoError(t, err)
})
t.Run("handle full sync response, new protocol, equal heads", 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.aclMock.EXPECT().Head().AnyTimes().Return(&list.AclRecord{Id: "h1"})
fx.syncProtocolMock.EXPECT().FullSyncResponse(ctx, fx.senderId, gomock.Any()).Return(nil)
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, secureservice.NewSyncProtoVersion, objectMsg)
require.NoError(t, err)
})
}

View file

@ -1,28 +0,0 @@
package exporter
import (
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
)
type TreeImportParams struct {
ListStorage liststorage.ListStorage
TreeStorage treestorage.TreeStorage
BeforeId string
IncludeBeforeId bool
}
func ImportHistoryTree(params TreeImportParams) (tree objecttree.ReadableObjectTree, err error) {
aclList, err := list.BuildAclList(params.ListStorage, list.NoOpAcceptorVerifier{})
if err != nil {
return
}
return objecttree.BuildNonVerifiableHistoryTree(objecttree.HistoryTreeParams{
TreeStorage: params.TreeStorage,
AclList: aclList,
BeforeId: params.BeforeId,
IncludeBeforeId: params.IncludeBeforeId,
})
}

View file

@ -27,34 +27,46 @@ func (h *historyTree) rebuildFromStorage(params HistoryTreeParams) (err error) {
}
func (h *historyTree) rebuild(params HistoryTreeParams) (err error) {
var (
beforeId = params.BeforeId
include = params.IncludeBeforeId
full = params.BuildFullTree
)
h.treeBuilder.Reset()
if full {
if len(params.Heads) == 0 {
h.tree, err = h.treeBuilder.BuildFull()
return
}
if beforeId == h.Id() && !include {
return ErrLoadBeforeRoot
return err
}
heads := []string{beforeId}
if beforeId == "" {
heads, err = h.treeStorage.Heads()
if err != nil {
return
if len(params.Heads) == 1 {
return h.rebuildWithSingleHead(params.IncludeBeforeId, params.Heads[0])
}
} else if !include {
beforeChange, err := h.treeBuilder.loadChange(beforeId)
h.tree, err = h.treeBuilder.build(params.Heads, nil, nil)
return err
}
func (h *historyTree) rebuildWithSingleHead(includeBeforeId bool, head string) (err error) {
if head == "" {
return h.rebuildWithEmptyHead()
}
if !includeBeforeId {
return h.rebuildWithPreviousHead(head)
}
h.tree, err = h.treeBuilder.build([]string{head}, nil, nil)
return err
}
func (h *historyTree) rebuildWithEmptyHead() (err error) {
heads, err := h.treeStorage.Heads()
if err != nil {
return err
}
heads = beforeChange.PreviousIds
}
h.tree, err = h.treeBuilder.build(heads, nil, nil)
return
return err
}
func (h *historyTree) rebuildWithPreviousHead(head string) (err error) {
change, err := h.treeBuilder.loadChange(head)
if err != nil {
return err
}
h.tree, err = h.treeBuilder.build(change.PreviousIds, nil, nil)
return err
}

View file

@ -1245,7 +1245,7 @@ func TestObjectTree(t *testing.T) {
}
deps.treeStorage.AddRawChangesSetHeads(rawChanges, []string{"6"})
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
BeforeId: "6",
Heads: []string{"6"},
IncludeBeforeId: false,
})
require.NoError(t, err)
@ -1276,9 +1276,7 @@ func TestObjectTree(t *testing.T) {
changeCreator.CreateRaw("6", aclList.Head().Id, "5", false, "5"),
}
deps.treeStorage.AddRawChangesSetHeads(rawChanges, []string{"6"})
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
BuildFullTree: true,
})
hTree, err := buildHistoryTree(deps, HistoryTreeParams{})
require.NoError(t, err)
// check tree heads
assert.Equal(t, []string{"6"}, hTree.Heads())
@ -1307,8 +1305,7 @@ func TestObjectTree(t *testing.T) {
}
deps.treeStorage.AddRawChangesSetHeads(rawChanges, []string{"6"})
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
BeforeId: "6",
IncludeBeforeId: true,
Heads: []string{"6"}, IncludeBeforeId: true,
})
require.NoError(t, err)
// check tree heads
@ -1328,7 +1325,7 @@ func TestObjectTree(t *testing.T) {
t.Run("test history tree root", func(t *testing.T) {
_, deps := prepareHistoryTreeDeps(aclList)
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
BeforeId: "0",
Heads: []string{"0"},
IncludeBeforeId: true,
})
require.NoError(t, err)

View file

@ -27,9 +27,8 @@ type ObjectTreeDerivePayload struct {
type HistoryTreeParams struct {
TreeStorage treestorage.TreeStorage
AclList list.AclList
BeforeId string
Heads []string
IncludeBeforeId bool
BuildFullTree bool
}
type objectTreeDeps struct {

View file

@ -20,6 +20,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"
peer "github.com/anyproto/any-sync/net/peer"
gomock "go.uber.org/mock/gomock"
)
@ -105,6 +106,21 @@ func (mr *MockSyncTreeMockRecorder) AddRawChanges(arg0, arg1 any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawChanges", reflect.TypeOf((*MockSyncTree)(nil).AddRawChanges), arg0, arg1)
}
// AddRawChangesFromPeer mocks base method.
func (m *MockSyncTree) AddRawChangesFromPeer(arg0 context.Context, arg1 string, arg2 objecttree.RawChangesPayload) (objecttree.AddResult, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AddRawChangesFromPeer", arg0, arg1, arg2)
ret0, _ := ret[0].(objecttree.AddResult)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AddRawChangesFromPeer indicates an expected call of AddRawChangesFromPeer.
func (mr *MockSyncTreeMockRecorder) AddRawChangesFromPeer(arg0, arg1, arg2 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawChangesFromPeer", reflect.TypeOf((*MockSyncTree)(nil).AddRawChangesFromPeer), arg0, arg1, arg2)
}
// ChangeInfo mocks base method.
func (m *MockSyncTree) ChangeInfo() *treechangeproto.TreeChangeInfo {
m.ctrl.T.Helper()
@ -207,17 +223,17 @@ func (mr *MockSyncTreeMockRecorder) GetChange(arg0 any) *gomock.Call {
}
// HandleMessage mocks base method.
func (m *MockSyncTree) HandleMessage(arg0 context.Context, arg1 string, arg2 *spacesyncproto.ObjectSyncMessage) error {
func (m *MockSyncTree) HandleMessage(arg0 context.Context, arg1 string, arg2 uint32, arg3 *spacesyncproto.ObjectSyncMessage) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HandleMessage", arg0, arg1, arg2)
ret := m.ctrl.Call(m, "HandleMessage", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(error)
return ret0
}
// HandleMessage indicates an expected call of HandleMessage.
func (mr *MockSyncTreeMockRecorder) HandleMessage(arg0, arg1, arg2 any) *gomock.Call {
func (mr *MockSyncTreeMockRecorder) HandleMessage(arg0, arg1, arg2, arg3 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleMessage", reflect.TypeOf((*MockSyncTree)(nil).HandleMessage), arg0, arg1, arg2)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleMessage", reflect.TypeOf((*MockSyncTree)(nil).HandleMessage), arg0, arg1, arg2, arg3)
}
// HandleRequest mocks base method.
@ -445,7 +461,7 @@ func (mr *MockSyncTreeMockRecorder) Storage() *gomock.Call {
}
// SyncWithPeer mocks base method.
func (m *MockSyncTree) SyncWithPeer(arg0 context.Context, arg1 string) error {
func (m *MockSyncTree) SyncWithPeer(arg0 context.Context, arg1 peer.Peer) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SyncWithPeer", arg0, arg1)
ret0, _ := ret[0].(error)
@ -663,6 +679,20 @@ func (mr *MockSyncClientMockRecorder) Broadcast(arg0 any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Broadcast", reflect.TypeOf((*MockSyncClient)(nil).Broadcast), arg0)
}
// CreateEmptyFullSyncRequest mocks base method.
func (m *MockSyncClient) CreateEmptyFullSyncRequest(arg0 objecttree.ObjectTree) *treechangeproto.TreeSyncMessage {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateEmptyFullSyncRequest", arg0)
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
return ret0
}
// CreateEmptyFullSyncRequest indicates an expected call of CreateEmptyFullSyncRequest.
func (mr *MockSyncClientMockRecorder) CreateEmptyFullSyncRequest(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateEmptyFullSyncRequest", reflect.TypeOf((*MockSyncClient)(nil).CreateEmptyFullSyncRequest), arg0)
}
// CreateFullSyncRequest mocks base method.
func (m *MockSyncClient) CreateFullSyncRequest(arg0 objecttree.ObjectTree, arg1, arg2 []string) (*treechangeproto.TreeSyncMessage, error) {
m.ctrl.T.Helper()
@ -787,6 +817,20 @@ func (m *MockRequestFactory) EXPECT() *MockRequestFactoryMockRecorder {
return m.recorder
}
// CreateEmptyFullSyncRequest mocks base method.
func (m *MockRequestFactory) CreateEmptyFullSyncRequest(arg0 objecttree.ObjectTree) *treechangeproto.TreeSyncMessage {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateEmptyFullSyncRequest", arg0)
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
return ret0
}
// CreateEmptyFullSyncRequest indicates an expected call of CreateEmptyFullSyncRequest.
func (mr *MockRequestFactoryMockRecorder) CreateEmptyFullSyncRequest(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateEmptyFullSyncRequest", reflect.TypeOf((*MockRequestFactory)(nil).CreateEmptyFullSyncRequest), arg0)
}
// CreateFullSyncRequest mocks base method.
func (m *MockRequestFactory) CreateFullSyncRequest(arg0 objecttree.ObjectTree, arg1, arg2 []string) (*treechangeproto.TreeSyncMessage, error) {
m.ctrl.T.Helper()
@ -898,16 +942,16 @@ func (mr *MockTreeSyncProtocolMockRecorder) FullSyncResponse(arg0, arg1, arg2 an
}
// HeadUpdate mocks base method.
func (m *MockTreeSyncProtocol) HeadUpdate(arg0 context.Context, arg1 string, arg2 *treechangeproto.TreeHeadUpdate) (*treechangeproto.TreeSyncMessage, error) {
func (m *MockTreeSyncProtocol) HeadUpdate(arg0 context.Context, arg1 string, arg2 uint32, arg3 *treechangeproto.TreeHeadUpdate) (*treechangeproto.TreeSyncMessage, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HeadUpdate", arg0, arg1, arg2)
ret := m.ctrl.Call(m, "HeadUpdate", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// HeadUpdate indicates an expected call of HeadUpdate.
func (mr *MockTreeSyncProtocolMockRecorder) HeadUpdate(arg0, arg1, arg2 any) *gomock.Call {
func (mr *MockTreeSyncProtocolMockRecorder) HeadUpdate(arg0, arg1, arg2, arg3 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeadUpdate", reflect.TypeOf((*MockTreeSyncProtocol)(nil).HeadUpdate), arg0, arg1, arg2)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeadUpdate", reflect.TypeOf((*MockTreeSyncProtocol)(nil).HeadUpdate), arg0, arg1, arg2, arg3)
}

View file

@ -2,6 +2,7 @@ package synctree
import (
"fmt"
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/util/slice"
@ -10,6 +11,7 @@ import (
type RequestFactory interface {
CreateHeadUpdate(t objecttree.ObjectTree, added []*treechangeproto.RawTreeChangeWithId) (msg *treechangeproto.TreeSyncMessage)
CreateNewTreeRequest() (msg *treechangeproto.TreeSyncMessage)
CreateEmptyFullSyncRequest(t objecttree.ObjectTree) (req *treechangeproto.TreeSyncMessage)
CreateFullSyncRequest(t objecttree.ObjectTree, theirHeads, theirSnapshotPath []string) (req *treechangeproto.TreeSyncMessage, err error)
CreateFullSyncResponse(t objecttree.ObjectTree, theirHeads, theirSnapshotPath []string) (*treechangeproto.TreeSyncMessage, error)
}
@ -32,6 +34,13 @@ func (r *requestFactory) CreateNewTreeRequest() (msg *treechangeproto.TreeSyncMe
return treechangeproto.WrapFullRequest(&treechangeproto.TreeFullSyncRequest{}, nil)
}
func (r *requestFactory) CreateEmptyFullSyncRequest(t objecttree.ObjectTree) (msg *treechangeproto.TreeSyncMessage) {
return treechangeproto.WrapFullRequest(&treechangeproto.TreeFullSyncRequest{
Heads: t.Heads(),
SnapshotPath: t.SnapshotPath(),
}, t.Header())
}
func (r *requestFactory) CreateFullSyncRequest(t objecttree.ObjectTree, theirHeads, theirSnapshotPath []string) (msg *treechangeproto.TreeSyncMessage, err error) {
req := &treechangeproto.TreeFullSyncRequest{}
if t == nil {

View file

@ -4,6 +4,7 @@ package synctree
import (
"context"
"errors"
"slices"
"time"
"go.uber.org/zap"
@ -18,7 +19,9 @@ import (
"github.com/anyproto/any-sync/commonspace/spacestorage"
"github.com/anyproto/any-sync/commonspace/syncstatus"
"github.com/anyproto/any-sync/net/peer"
"github.com/anyproto/any-sync/net/secureservice"
"github.com/anyproto/any-sync/nodeconf"
"github.com/anyproto/any-sync/util/slice"
)
var (
@ -34,11 +37,16 @@ type ListenerSetter interface {
SetListener(listener updatelistener.UpdateListener)
}
type SyncTree interface {
type peerSendableObjectTree interface {
objecttree.ObjectTree
AddRawChangesFromPeer(ctx context.Context, peerId string, changesPayload objecttree.RawChangesPayload) (res objecttree.AddResult, err error)
}
type SyncTree interface {
peerSendableObjectTree
synchandler.SyncHandler
ListenerSetter
SyncWithPeer(ctx context.Context, peerId string) (err error)
SyncWithPeer(ctx context.Context, p peer.Peer) (err error)
}
// SyncTree sends head updates to sync service and also sends new changes to update listener
@ -79,13 +87,13 @@ type BuildDeps struct {
func BuildSyncTreeOrGetRemote(ctx context.Context, id string, deps BuildDeps) (t SyncTree, err error) {
var (
remoteGetter = treeRemoteGetter{treeId: id, deps: deps}
isRemote bool
peerId string
)
deps.TreeStorage, isRemote, err = remoteGetter.getTree(ctx)
deps.TreeStorage, peerId, err = remoteGetter.getTree(ctx)
if err != nil {
return
}
return buildSyncTree(ctx, isRemote, deps)
return buildSyncTree(ctx, peerId, deps)
}
func PutSyncTree(ctx context.Context, payload treestorage.TreeStorageCreatePayload, deps BuildDeps) (t SyncTree, err error) {
@ -93,10 +101,10 @@ func PutSyncTree(ctx context.Context, payload treestorage.TreeStorageCreatePaylo
if err != nil {
return
}
return buildSyncTree(ctx, true, deps)
return buildSyncTree(ctx, peer.CtxResponsiblePeers, deps)
}
func buildSyncTree(ctx context.Context, sendUpdate bool, deps BuildDeps) (t SyncTree, err error) {
func buildSyncTree(ctx context.Context, peerId string, deps BuildDeps) (t SyncTree, err error) {
objTree, err := deps.BuildObjectTree(deps.TreeStorage, deps.AclList)
if err != nil {
return
@ -118,10 +126,13 @@ func buildSyncTree(ctx context.Context, sendUpdate bool, deps BuildDeps) (t Sync
syncTree.Unlock()
// don't send updates for empty derived trees, because they won't be accepted
if sendUpdate && !objecttree.IsEmptyDerivedTree(objTree) {
if peerId != "" && !objecttree.IsEmptyDerivedTree(objTree) {
headUpdate := syncTree.syncClient.CreateHeadUpdate(t, nil)
// send to everybody, because everybody should know that the node or client got new tree
syncTree.syncClient.Broadcast(headUpdate)
if peerId != peer.CtxResponsiblePeers {
deps.SyncStatus.ObjectReceive(peerId, syncTree.Id(), syncTree.Heads())
}
}
return
}
@ -166,6 +177,38 @@ func (s *syncTree) AddContentWithValidator(ctx context.Context, content objecttr
return
}
func (s *syncTree) hasHeads(ot objecttree.ObjectTree, heads []string) bool {
return slice.UnsortedEquals(ot.Heads(), heads) || ot.HasChanges(heads...)
}
func (s *syncTree) AddRawChangesFromPeer(ctx context.Context, peerId string, changesPayload objecttree.RawChangesPayload) (res objecttree.AddResult, err error) {
if s.hasHeads(s, changesPayload.NewHeads) {
s.syncStatus.HeadsApply(peerId, s.Id(), s.Heads(), true)
return objecttree.AddResult{
OldHeads: changesPayload.NewHeads,
Heads: changesPayload.NewHeads,
Mode: objecttree.Nothing,
}, nil
}
prevHeads := s.Heads()
res, err = s.AddRawChanges(ctx, changesPayload)
if err != nil {
return
}
curHeads := s.Heads()
allAdded := true
for _, head := range changesPayload.NewHeads {
if !slices.Contains(curHeads, head) {
allAdded = false
break
}
}
if !slice.UnsortedEquals(prevHeads, curHeads) {
s.syncStatus.HeadsApply(peerId, s.Id(), curHeads, allAdded)
}
return
}
func (s *syncTree) AddRawChanges(ctx context.Context, changesPayload objecttree.RawChangesPayload) (res objecttree.AddResult, err error) {
if err = s.checkAlive(); err != nil {
return
@ -251,14 +294,21 @@ func (s *syncTree) checkAlive() (err error) {
return
}
func (s *syncTree) SyncWithPeer(ctx context.Context, peerId string) (err error) {
func (s *syncTree) SyncWithPeer(ctx context.Context, p peer.Peer) (err error) {
s.Lock()
defer s.Unlock()
if objecttree.IsEmptyDerivedTree(s) {
return nil
if objecttree.IsEmptyDerivedTree(s.ObjectTree) {
return
}
protoVersion, err := peer.CtxProtoVersion(p.Context())
// this works with old protocol
if err != nil || protoVersion <= secureservice.ProtoVersion {
headUpdate := s.syncClient.CreateHeadUpdate(s, nil)
return s.syncClient.SendUpdate(peerId, headUpdate.RootChange.Id, headUpdate)
return s.syncClient.SendUpdate(p.Id(), headUpdate.RootChange.Id, headUpdate)
}
// for new protocol sending empty request
request := s.syncClient.CreateEmptyFullSyncRequest(s)
return s.syncClient.QueueRequest(p.Id(), s.Id(), request)
}
func (s *syncTree) afterBuild() {

View file

@ -60,7 +60,7 @@ func Test_BuildSyncTree(t *testing.T) {
}
headUpdate := &treechangeproto.TreeSyncMessage{}
t.Run("AddRawChanges update", func(t *testing.T) {
t.Run("AddRawChangesFromPeer update", func(t *testing.T) {
changes := []*treechangeproto.RawTreeChangeWithId{{Id: "some"}}
payload := objecttree.RawChangesPayload{
NewHeads: nil,
@ -70,6 +70,8 @@ func Test_BuildSyncTree(t *testing.T) {
Added: changes,
Mode: objecttree.Append,
}
objTreeMock.EXPECT().Heads().AnyTimes().Return([]string{"headId"})
objTreeMock.EXPECT().HasChanges(gomock.Any()).AnyTimes().Return(false)
objTreeMock.EXPECT().AddRawChanges(gomock.Any(), gomock.Eq(payload)).
Return(expectedRes, nil)
updateListenerMock.EXPECT().Update(tr)
@ -77,12 +79,12 @@ func Test_BuildSyncTree(t *testing.T) {
syncClientMock.EXPECT().CreateHeadUpdate(gomock.Eq(tr), gomock.Eq(changes)).Return(headUpdate)
syncClientMock.EXPECT().Broadcast(gomock.Eq(headUpdate))
objTreeMock.EXPECT().Flush()
res, err := tr.AddRawChanges(ctx, payload)
res, err := tr.AddRawChangesFromPeer(ctx, "peerId", payload)
require.NoError(t, err)
require.Equal(t, expectedRes, res)
})
t.Run("AddRawChanges rebuild", func(t *testing.T) {
t.Run("AddRawChangesFromPeer rebuild", func(t *testing.T) {
changes := []*treechangeproto.RawTreeChangeWithId{{Id: "some"}}
payload := objecttree.RawChangesPayload{
NewHeads: nil,
@ -93,6 +95,7 @@ func Test_BuildSyncTree(t *testing.T) {
Added: changes,
Mode: objecttree.Rebuild,
}
objTreeMock.EXPECT().Heads().AnyTimes().Return([]string{"headId"})
objTreeMock.EXPECT().AddRawChanges(gomock.Any(), gomock.Eq(payload)).
Return(expectedRes, nil)
updateListenerMock.EXPECT().Rebuild(tr)
@ -100,12 +103,12 @@ func Test_BuildSyncTree(t *testing.T) {
syncClientMock.EXPECT().CreateHeadUpdate(gomock.Eq(tr), gomock.Eq(changes)).Return(headUpdate)
syncClientMock.EXPECT().Broadcast(gomock.Eq(headUpdate))
objTreeMock.EXPECT().Flush()
res, err := tr.AddRawChanges(ctx, payload)
res, err := tr.AddRawChangesFromPeer(ctx, "peerId", payload)
require.NoError(t, err)
require.Equal(t, expectedRes, res)
})
t.Run("AddRawChanges nothing", func(t *testing.T) {
t.Run("AddRawChangesFromPeer nothing", func(t *testing.T) {
changes := []*treechangeproto.RawTreeChangeWithId{{Id: "some"}}
payload := objecttree.RawChangesPayload{
NewHeads: nil,
@ -115,10 +118,11 @@ func Test_BuildSyncTree(t *testing.T) {
Added: changes,
Mode: objecttree.Nothing,
}
objTreeMock.EXPECT().Heads().AnyTimes().Return([]string{"headId"})
objTreeMock.EXPECT().AddRawChanges(gomock.Any(), gomock.Eq(payload)).
Return(expectedRes, nil)
res, err := tr.AddRawChanges(ctx, payload)
res, err := tr.AddRawChangesFromPeer(ctx, "peerId", payload)
require.NoError(t, err)
require.Equal(t, expectedRes, res)
})

View file

@ -5,13 +5,15 @@ import (
"errors"
"sync"
"github.com/gogo/protobuf/proto"
"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/net/secureservice"
"github.com/anyproto/any-sync/util/slice"
"github.com/gogo/protobuf/proto"
)
var (
@ -34,10 +36,10 @@ type syncTreeHandler struct {
const maxQueueSize = 5
func newSyncTreeHandler(spaceId string, objTree objecttree.ObjectTree, syncClient SyncClient, syncStatus syncstatus.StatusUpdater) synchandler.SyncHandler {
func newSyncTreeHandler(spaceId string, objTree peerSendableObjectTree, syncClient SyncClient, syncStatus syncstatus.StatusUpdater) synchandler.SyncHandler {
return &syncTreeHandler{
objTree: objTree,
syncProtocol: newTreeSyncProtocol(spaceId, objTree, syncClient),
syncProtocol: newTreeSyncProtocol(spaceId, objTree, syncClient, syncStatus),
syncClient: syncClient,
syncStatus: syncStatus,
spaceId: spaceId,
@ -85,7 +87,7 @@ func (s *syncTreeHandler) handleRequest(ctx context.Context, senderId string, fu
return
}
func (s *syncTreeHandler) HandleMessage(ctx context.Context, senderId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
func (s *syncTreeHandler) HandleMessage(ctx context.Context, senderId string, protoVersion uint32, msg *spacesyncproto.ObjectSyncMessage) (err error) {
unmarshalled := &treechangeproto.TreeSyncMessage{}
err = proto.Unmarshal(msg.Payload, unmarshalled)
if err != nil {
@ -100,10 +102,10 @@ func (s *syncTreeHandler) HandleMessage(ctx context.Context, senderId string, ms
return
}
s.handlerLock.Unlock()
return s.handleMessage(ctx, unmarshalled, senderId)
return s.handleMessage(ctx, unmarshalled, protoVersion, senderId)
}
func (s *syncTreeHandler) handleMessage(ctx context.Context, msg *treechangeproto.TreeSyncMessage, senderId string) (err error) {
func (s *syncTreeHandler) handleMessage(ctx context.Context, msg *treechangeproto.TreeSyncMessage, protoVersion uint32, senderId string) (err error) {
s.objTree.Lock()
defer s.objTree.Unlock()
var (
@ -129,7 +131,7 @@ func (s *syncTreeHandler) handleMessage(ctx context.Context, msg *treechangeprot
switch {
case content.GetHeadUpdate() != nil:
var syncReq *treechangeproto.TreeSyncMessage
syncReq, err = s.syncProtocol.HeadUpdate(ctx, senderId, content.GetHeadUpdate())
syncReq, err = s.syncProtocol.HeadUpdate(ctx, senderId, protoVersion, content.GetHeadUpdate())
if err != nil || syncReq == nil {
return
}
@ -137,7 +139,27 @@ func (s *syncTreeHandler) handleMessage(ctx context.Context, msg *treechangeprot
case content.GetFullSyncRequest() != nil:
return ErrMessageIsRequest
case content.GetFullSyncResponse() != nil:
return s.syncProtocol.FullSyncResponse(ctx, senderId, content.GetFullSyncResponse())
err := s.syncProtocol.FullSyncResponse(ctx, senderId, content.GetFullSyncResponse())
if err != nil {
return err
}
cnt := content.GetFullSyncResponse()
if protoVersion <= secureservice.ProtoVersion || slice.UnsortedEquals(cnt.Heads, s.objTree.Heads()) {
return nil
}
req, err := s.syncClient.CreateFullSyncRequest(s.objTree, cnt.Heads, cnt.SnapshotPath)
if err != nil {
return err
}
return s.syncClient.QueueRequest(senderId, treeId, req)
default:
if protoVersion <= secureservice.ProtoVersion {
return nil
}
req, err := s.syncClient.CreateFullSyncRequest(s.objTree, nil, nil)
if err != nil {
return err
}
return s.syncClient.QueueRequest(senderId, treeId, req)
}
return
}

View file

@ -5,13 +5,16 @@ import (
"sync"
"testing"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"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/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/stretchr/testify/require"
"go.uber.org/mock/gomock"
"github.com/anyproto/any-sync/net/secureservice"
)
type testObjTreeMock struct {
@ -19,6 +22,10 @@ type testObjTreeMock struct {
m sync.RWMutex
}
func (t *testObjTreeMock) AddRawChangesFromPeer(ctx context.Context, peerId string, changesPayload objecttree.RawChangesPayload) (res objecttree.AddResult, err error) {
return t.MockObjectTree.AddRawChanges(ctx, changesPayload)
}
func newTestObjMock(mockTree *mock_objecttree.MockObjectTree) *testObjTreeMock {
return &testObjTreeMock{
MockObjectTree: mockTree,
@ -111,10 +118,10 @@ func TestSyncTreeHandler_HandleMessage(t *testing.T) {
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.syncProtocolMock.EXPECT().HeadUpdate(ctx, fx.senderId, uint32(0), gomock.Any()).Return(syncReq, nil)
fx.syncClientMock.EXPECT().QueueRequest(fx.senderId, fx.treeId, syncReq).Return(nil)
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, 0, objectMsg)
require.NoError(t, err)
require.Equal(t, []string{"h3"}, fx.syncHandler.heads)
})
@ -133,7 +140,7 @@ func TestSyncTreeHandler_HandleMessage(t *testing.T) {
fx.syncHandler.heads = []string{"h1"}
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, 0, objectMsg)
require.NoError(t, err)
})
@ -152,9 +159,9 @@ func TestSyncTreeHandler_HandleMessage(t *testing.T) {
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)
fx.syncProtocolMock.EXPECT().HeadUpdate(ctx, fx.senderId, uint32(0), gomock.Any()).Return(nil, nil)
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, 0, objectMsg)
require.NoError(t, err)
require.Equal(t, []string{"h3"}, fx.syncHandler.heads)
})
@ -174,7 +181,7 @@ func TestSyncTreeHandler_HandleMessage(t *testing.T) {
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)
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, 0, objectMsg)
require.Equal(t, err, ErrMessageIsRequest)
})
@ -195,7 +202,66 @@ func TestSyncTreeHandler_HandleMessage(t *testing.T) {
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)
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, 0, objectMsg)
require.NoError(t, err)
})
t.Run("handle full sync response new protocol heads not equal", func(t *testing.T) {
fx := newSyncHandlerFixture(t)
defer fx.stop()
treeId := "treeId"
chWithId := &treechangeproto.RawTreeChangeWithId{}
fullSyncResponse := &treechangeproto.TreeFullSyncResponse{
Heads: []string{"h3"},
SnapshotPath: []string{"h3"},
}
treeMsg := treechangeproto.WrapFullResponse(fullSyncResponse, chWithId)
objectMsg, _ := spacesyncproto.MarshallSyncMessage(treeMsg, "spaceId", treeId)
fx.syncHandler.heads = []string{"h2"}
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
fx.objectTreeMock.EXPECT().Heads().AnyTimes().Return([]string{"h2"})
fx.syncProtocolMock.EXPECT().FullSyncResponse(ctx, fx.senderId, gomock.Any()).Return(nil)
req := &treechangeproto.TreeSyncMessage{}
fx.syncClientMock.EXPECT().CreateFullSyncRequest(fx.objectTreeMock, fullSyncResponse.Heads, fullSyncResponse.SnapshotPath).Return(req, nil)
fx.syncClientMock.EXPECT().QueueRequest(fx.senderId, fx.treeId, req).Return(nil)
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, secureservice.NewSyncProtoVersion, objectMsg)
require.NoError(t, err)
})
t.Run("handle full sync response new protocol heads equal", func(t *testing.T) {
fx := newSyncHandlerFixture(t)
defer fx.stop()
treeId := "treeId"
chWithId := &treechangeproto.RawTreeChangeWithId{}
fullSyncResponse := &treechangeproto.TreeFullSyncResponse{
Heads: []string{"h3"},
SnapshotPath: []string{"h3"},
}
treeMsg := treechangeproto.WrapFullResponse(fullSyncResponse, 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(3).Return([]string{"h3"})
fx.syncProtocolMock.EXPECT().FullSyncResponse(ctx, fx.senderId, gomock.Any()).Return(nil)
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, secureservice.NewSyncProtoVersion, objectMsg)
require.NoError(t, err)
})
t.Run("handle full sync response new protocol empty message", func(t *testing.T) {
fx := newSyncHandlerFixture(t)
defer fx.stop()
treeId := "treeId"
objectMsg := &spacesyncproto.ObjectSyncMessage{ObjectId: treeId}
fx.syncHandler.heads = []string{"h2"}
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
fx.objectTreeMock.EXPECT().Heads().AnyTimes().Return([]string{"h2"})
req := &treechangeproto.TreeSyncMessage{}
fx.syncClientMock.EXPECT().CreateFullSyncRequest(fx.objectTreeMock, nil, nil).Return(req, nil)
fx.syncClientMock.EXPECT().QueueRequest(fx.senderId, fx.treeId, req).Return(nil)
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, secureservice.NewSyncProtoVersion, objectMsg)
require.NoError(t, err)
})
}

View file

@ -60,16 +60,20 @@ func (t treeRemoteGetter) treeRequest(ctx context.Context, peerId string) (msg *
return
}
func (t treeRemoteGetter) treeRequestLoop(ctx context.Context) (msg *treechangeproto.TreeSyncMessage, err error) {
func (t treeRemoteGetter) treeRequestLoop(ctx context.Context) (msg *treechangeproto.TreeSyncMessage, peerId string, err error) {
availablePeers, err := t.getPeers(ctx)
if err != nil {
return
}
// in future we will try to load from different peers
return t.treeRequest(ctx, availablePeers[0])
res, err := t.treeRequest(ctx, availablePeers[0])
if err != nil {
return
}
return res, availablePeers[0], nil
}
func (t treeRemoteGetter) getTree(ctx context.Context) (treeStorage treestorage.TreeStorage, isRemote bool, err error) {
func (t treeRemoteGetter) getTree(ctx context.Context) (treeStorage treestorage.TreeStorage, peerId string, err error) {
treeStorage, err = t.deps.SpaceStorage.TreeStorage(t.treeId)
if err == nil {
return
@ -87,8 +91,7 @@ func (t treeRemoteGetter) getTree(ctx context.Context) (treeStorage treestorage.
return
}
isRemote = true
resp, err := t.treeRequestLoop(ctx)
resp, peerId, err := t.treeRequestLoop(ctx)
if err != nil {
return
}

View file

@ -70,7 +70,7 @@ func TestTreeRemoteGetter(t *testing.T) {
fx.peerGetterMock.EXPECT().GetResponsiblePeers(tCtx).Return([]peer.Peer{mockPeer}, nil)
fx.syncClientMock.EXPECT().CreateNewTreeRequest().Return(treeRequest)
fx.syncClientMock.EXPECT().SendRequest(tCtx, peerId, fx.treeGetter.treeId, treeRequest).Return(objectResponse, nil)
resp, err := fx.treeGetter.treeRequestLoop(tCtx)
resp, _, err := fx.treeGetter.treeRequestLoop(tCtx)
require.NoError(t, err)
require.Equal(t, "id", resp.RootChange.Id)
})
@ -84,7 +84,7 @@ func TestTreeRemoteGetter(t *testing.T) {
mockPeer.EXPECT().Id().AnyTimes().Return(peerId)
fx.syncClientMock.EXPECT().CreateNewTreeRequest().Return(treeRequest)
fx.syncClientMock.EXPECT().SendRequest(tCtx, peerId, fx.treeGetter.treeId, treeRequest).AnyTimes().Return(nil, fmt.Errorf("some"))
_, err := fx.treeGetter.treeRequestLoop(tCtx)
_, _, err := fx.treeGetter.treeRequestLoop(tCtx)
require.Error(t, err)
})
}

View file

@ -3,15 +3,18 @@ package synctree
import (
"context"
"go.uber.org/zap"
"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"
"github.com/anyproto/any-sync/commonspace/syncstatus"
"github.com/anyproto/any-sync/net/secureservice"
"github.com/anyproto/any-sync/util/slice"
"go.uber.org/zap"
)
type TreeSyncProtocol interface {
HeadUpdate(ctx context.Context, senderId string, update *treechangeproto.TreeHeadUpdate) (request *treechangeproto.TreeSyncMessage, err error)
HeadUpdate(ctx context.Context, senderId string, protoVersion uint32, update *treechangeproto.TreeHeadUpdate) (request *treechangeproto.TreeSyncMessage, err error)
FullSyncRequest(ctx context.Context, senderId string, request *treechangeproto.TreeFullSyncRequest) (response *treechangeproto.TreeSyncMessage, err error)
FullSyncResponse(ctx context.Context, senderId string, response *treechangeproto.TreeFullSyncResponse) (err error)
}
@ -19,20 +22,22 @@ type TreeSyncProtocol interface {
type treeSyncProtocol struct {
log logger.CtxLogger
spaceId string
objTree objecttree.ObjectTree
objTree peerSendableObjectTree
syncStatus syncstatus.StatusUpdater
reqFactory RequestFactory
}
func newTreeSyncProtocol(spaceId string, objTree objecttree.ObjectTree, reqFactory RequestFactory) *treeSyncProtocol {
func newTreeSyncProtocol(spaceId string, objTree peerSendableObjectTree, reqFactory RequestFactory, syncStatus syncstatus.StatusUpdater) *treeSyncProtocol {
return &treeSyncProtocol{
log: log.With(zap.String("spaceId", spaceId), zap.String("treeId", objTree.Id())),
spaceId: spaceId,
objTree: objTree,
syncStatus: syncStatus,
reqFactory: reqFactory,
}
}
func (t *treeSyncProtocol) HeadUpdate(ctx context.Context, senderId string, update *treechangeproto.TreeHeadUpdate) (fullRequest *treechangeproto.TreeSyncMessage, err error) {
func (t *treeSyncProtocol) HeadUpdate(ctx context.Context, senderId string, protoVersion uint32, update *treechangeproto.TreeHeadUpdate) (fullRequest *treechangeproto.TreeSyncMessage, err error) {
var (
isEmptyUpdate = len(update.Changes) == 0
objTree = t.objTree
@ -62,6 +67,9 @@ func (t *treeSyncProtocol) HeadUpdate(ctx context.Context, senderId string, upda
headEquals := slice.UnsortedEquals(objTree.Heads(), update.Heads)
log.DebugCtx(ctx, "is empty update", zap.Bool("headEquals", headEquals))
if headEquals {
if protoVersion > secureservice.ProtoVersion {
t.syncStatus.HeadsApply(senderId, objTree.Id(), update.Heads, true)
}
return
}
@ -70,11 +78,7 @@ func (t *treeSyncProtocol) HeadUpdate(ctx context.Context, senderId string, upda
return
}
if t.hasHeads(objTree, update.Heads) {
return
}
_, err = objTree.AddRawChanges(ctx, objecttree.RawChangesPayload{
_, err = objTree.AddRawChangesFromPeer(ctx, senderId, objecttree.RawChangesPayload{
NewHeads: update.Heads,
RawChanges: update.Changes,
})
@ -109,8 +113,8 @@ func (t *treeSyncProtocol) FullSyncRequest(ctx context.Context, senderId string,
}
}()
if len(request.Changes) != 0 && !t.hasHeads(objTree, request.Heads) {
_, err = objTree.AddRawChanges(ctx, objecttree.RawChangesPayload{
if len(request.Changes) != 0 {
_, err = objTree.AddRawChangesFromPeer(ctx, senderId, objecttree.RawChangesPayload{
NewHeads: request.Heads,
RawChanges: request.Changes,
})
@ -137,11 +141,8 @@ func (t *treeSyncProtocol) FullSyncResponse(ctx context.Context, senderId string
log.DebugCtx(ctx, "full sync response succeeded")
}
}()
if t.hasHeads(objTree, response.Heads) {
return
}
_, err = objTree.AddRawChanges(ctx, objecttree.RawChangesPayload{
_, err = objTree.AddRawChangesFromPeer(ctx, senderId, objecttree.RawChangesPayload{
NewHeads: response.Heads,
RawChanges: response.Changes,
})

View file

@ -3,14 +3,17 @@ package synctree
import (
"context"
"fmt"
"testing"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"github.com/anyproto/any-sync/app/logger"
"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/object/tree/synctree/mock_synctree"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"testing"
"github.com/anyproto/any-sync/commonspace/syncstatus"
)
type treeSyncProtocolFixture struct {
@ -32,7 +35,7 @@ func newSyncProtocolFixture(t *testing.T) *treeSyncProtocolFixture {
spaceId := "spaceId"
reqFactory := mock_synctree.NewMockRequestFactory(ctrl)
objTree.EXPECT().Id().Return("treeId")
syncProtocol := newTreeSyncProtocol(spaceId, objTree, reqFactory)
syncProtocol := newTreeSyncProtocol(spaceId, objTree, reqFactory, syncstatus.NewNoOpSyncStatus())
return &treeSyncProtocolFixture{
log: log,
spaceId: spaceId,
@ -69,8 +72,7 @@ func TestTreeSyncProtocol_HeadUpdate(t *testing.T) {
SnapshotPath: []string{"h1"},
}
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).Times(2)
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(false)
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).AnyTimes()
fx.objectTreeMock.EXPECT().
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
NewHeads: []string{"h1"},
@ -79,7 +81,7 @@ func TestTreeSyncProtocol_HeadUpdate(t *testing.T) {
Return(objecttree.AddResult{}, nil)
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(true)
res, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
res, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, 0, headUpdate)
require.NoError(t, err)
require.Nil(t, res)
})
@ -96,8 +98,14 @@ func TestTreeSyncProtocol_HeadUpdate(t *testing.T) {
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h1"}).AnyTimes()
fx.objectTreeMock.EXPECT().
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
NewHeads: []string{"h1"},
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
})).
Return(objecttree.AddResult{}, nil)
res, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
res, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, 0, headUpdate)
require.NoError(t, err)
require.Nil(t, res)
})
@ -117,7 +125,7 @@ func TestTreeSyncProtocol_HeadUpdate(t *testing.T) {
CreateFullSyncRequest(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
Return(fullRequest, nil)
res, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
res, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, 0, headUpdate)
require.NoError(t, err)
require.Equal(t, fullRequest, res)
})
@ -134,7 +142,7 @@ func TestTreeSyncProtocol_HeadUpdate(t *testing.T) {
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h1"}).AnyTimes()
res, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
res, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, 0, headUpdate)
require.NoError(t, err)
require.Nil(t, res)
})
@ -161,7 +169,7 @@ func TestTreeSyncProtocol_FullSyncRequest(t *testing.T) {
}
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).AnyTimes()
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(false)
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).AnyTimes().Return(false)
fx.objectTreeMock.EXPECT().
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
NewHeads: []string{"h1"},
@ -190,6 +198,12 @@ func TestTreeSyncProtocol_FullSyncRequest(t *testing.T) {
fx.objectTreeMock.EXPECT().
Heads().
Return([]string{"h1"}).AnyTimes()
fx.objectTreeMock.EXPECT().
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
NewHeads: []string{"h1"},
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
})).
Return(objecttree.AddResult{}, nil)
fx.reqFactory.EXPECT().
CreateFullSyncResponse(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
Return(fullResponse, nil)
@ -228,7 +242,6 @@ func TestTreeSyncProtocol_FullSyncRequest(t *testing.T) {
}
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).AnyTimes()
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(false)
fx.objectTreeMock.EXPECT().
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
NewHeads: []string{"h1"},
@ -258,9 +271,6 @@ func TestTreeSyncProtocol_FullSyncResponse(t *testing.T) {
fx.objectTreeMock.EXPECT().
Heads().
Return([]string{"h2"}).AnyTimes()
fx.objectTreeMock.EXPECT().
HasChanges(gomock.Eq([]string{"h1"})).
Return(false)
fx.objectTreeMock.EXPECT().
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
NewHeads: []string{"h1"},
@ -286,6 +296,12 @@ func TestTreeSyncProtocol_FullSyncResponse(t *testing.T) {
fx.objectTreeMock.EXPECT().
Heads().
Return([]string{"h1"}).AnyTimes()
fx.objectTreeMock.EXPECT().
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
NewHeads: []string{"h1"},
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
})).
Return(objecttree.AddResult{}, nil)
err := fx.syncProtocol.FullSyncResponse(ctx, fx.senderId, fullSyncResponse)
require.NoError(t, err)

View file

@ -3,6 +3,16 @@ package synctree
import (
"context"
"fmt"
"math/rand"
"sync"
"testing"
"time"
"github.com/cheggaaa/mb/v3"
"github.com/gogo/protobuf/proto"
"github.com/stretchr/testify/require"
"golang.org/x/exp/slices"
"github.com/anyproto/any-sync/app"
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
@ -12,14 +22,6 @@ import (
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
"github.com/anyproto/any-sync/commonspace/syncstatus"
"github.com/anyproto/any-sync/net/peer"
"github.com/cheggaaa/mb/v3"
"github.com/gogo/protobuf/proto"
"github.com/stretchr/testify/require"
"golang.org/x/exp/slices"
"math/rand"
"sync"
"testing"
"time"
)
// protocolMsg is a message used in sync protocol tests
@ -210,7 +212,7 @@ func createEmptySyncHandler(peerId, spaceId string, builder objecttree.BuildObje
func (h *testSyncHandler) HandleMessage(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (err error) {
if h.SyncHandler != nil {
return h.SyncHandler.HandleMessage(ctx, senderId, request)
return h.SyncHandler.HandleMessage(ctx, senderId, 0, request)
}
unmarshalled := &treechangeproto.TreeSyncMessage{}
err = proto.Unmarshal(request.Payload, unmarshalled)
@ -317,6 +319,16 @@ func (b *broadcastTree) AddRawChanges(ctx context.Context, changes objecttree.Ra
return res, nil
}
func (b *broadcastTree) AddRawChangesFromPeer(ctx context.Context, peerId string, changes objecttree.RawChangesPayload) (objecttree.AddResult, error) {
res, err := b.ObjectTree.AddRawChanges(ctx, changes)
if err != nil {
return objecttree.AddResult{}, err
}
upd := b.SyncClient.CreateHeadUpdate(b.ObjectTree, res.Added)
b.SyncClient.Broadcast(upd)
return res, nil
}
func createStorage(treeId string, aclList list.AclList) treestorage.TreeStorage {
changeCreator := objecttree.NewMockChangeCreator()
st := changeCreator.CreateNewTreeStorage(treeId, aclList.Head().Id, false)

View file

@ -14,6 +14,7 @@ import (
reflect "reflect"
app "github.com/anyproto/any-sync/app"
peer "github.com/anyproto/any-sync/net/peer"
gomock "go.uber.org/mock/gomock"
)
@ -135,7 +136,7 @@ func (mr *MockTreeSyncerMockRecorder) StopSync() *gomock.Call {
}
// SyncAll mocks base method.
func (m *MockTreeSyncer) SyncAll(arg0 context.Context, arg1 string, arg2, arg3 []string) error {
func (m *MockTreeSyncer) SyncAll(arg0 context.Context, arg1 peer.Peer, arg2, arg3 []string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SyncAll", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(error)

View file

@ -5,6 +5,7 @@ import (
"context"
"github.com/anyproto/any-sync/app"
"github.com/anyproto/any-sync/net/peer"
)
const CName = "common.object.treesyncer"
@ -14,5 +15,5 @@ type TreeSyncer interface {
StartSync()
StopSync()
ShouldSync(peerId string) bool
SyncAll(ctx context.Context, peerId string, existing, missing []string) error
SyncAll(ctx context.Context, p peer.Peer, existing, missing []string) error
}

View file

@ -15,6 +15,7 @@ import (
"github.com/anyproto/any-sync/commonspace/spacestate"
"github.com/anyproto/any-sync/metric"
"github.com/anyproto/any-sync/net/peer"
"github.com/anyproto/any-sync/net/secureservice"
"github.com/anyproto/any-sync/util/multiqueue"
"go.uber.org/zap"
@ -128,6 +129,10 @@ func (s *objectSync) HandleMessage(ctx context.Context, hm HandleMessage) (err e
func (s *objectSync) processHandleMessage(msg HandleMessage) {
var err error
peerProtoVersion, err := peer.CtxProtoVersion(msg.PeerCtx)
if err != nil {
peerProtoVersion = secureservice.ProtoVersion
}
msg.StartHandlingTime = time.Now()
ctx := peer.CtxWithPeerId(context.Background(), msg.SenderId)
ctx = logger.CtxWithFields(ctx, zap.Uint64("msgId", msg.Id), zap.String("senderId", msg.SenderId))
@ -148,7 +153,7 @@ func (s *objectSync) processHandleMessage(msg HandleMessage) {
return
}
}
if err = s.handleMessage(ctx, msg.SenderId, msg.Message); err != nil {
if err = s.handleMessage(ctx, msg.SenderId, peerProtoVersion, msg.Message); err != nil {
if msg.Message.ObjectId != "" {
// cleanup thread on error
_ = s.handleQueue.CloseThread(msg.Message.ObjectId)
@ -170,7 +175,7 @@ func (s *objectSync) handleRequest(ctx context.Context, senderId string, msg *sp
return obj.HandleRequest(ctx, senderId, msg)
}
func (s *objectSync) handleMessage(ctx context.Context, senderId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
func (s *objectSync) handleMessage(ctx context.Context, senderId string, protoVersion uint32, msg *spacesyncproto.ObjectSyncMessage) (err error) {
log := log.With(zap.String("objectId", msg.ObjectId))
defer func() {
if p := recover(); p != nil {
@ -186,7 +191,7 @@ func (s *objectSync) handleMessage(ctx context.Context, senderId string, msg *sp
if err != nil {
return fmt.Errorf("failed to get object from cache: %w", err)
}
err = obj.HandleMessage(ctx, senderId, msg)
err = obj.HandleMessage(ctx, senderId, protoVersion, msg)
if err != nil {
return fmt.Errorf("failed to handle message: %w", err)
}

View file

@ -2,10 +2,11 @@ package synchandler
import (
"context"
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
)
type SyncHandler interface {
HandleMessage(ctx context.Context, senderId string, message *spacesyncproto.ObjectSyncMessage) (err error)
HandleMessage(ctx context.Context, senderId string, protoVersion uint32, message *spacesyncproto.ObjectSyncMessage) (err error)
HandleRequest(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (response *spacesyncproto.ObjectSyncMessage, err error)
}

View file

@ -39,9 +39,8 @@ var log = logger.NewNamed(CName)
var ErrSpaceClosed = errors.New("space is closed")
type HistoryTreeOpts struct {
BeforeId string
Heads []string
Include bool
BuildFullTree bool
}
type TreeBuilder interface {
@ -145,9 +144,8 @@ func (t *treeBuilder) BuildHistoryTree(ctx context.Context, id string, opts Hist
params := objecttree.HistoryTreeParams{
AclList: t.aclList,
BeforeId: opts.BeforeId,
Heads: opts.Heads,
IncludeBeforeId: opts.Include,
BuildFullTree: opts.BuildFullTree,
}
params.TreeStorage, err = t.spaceStorage.TreeStorage(id)
if err != nil {

View file

@ -13,7 +13,6 @@ import (
"github.com/anyproto/any-sync/commonspace/objectsync"
"github.com/anyproto/any-sync/commonspace/spacestate"
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
"github.com/anyproto/any-sync/net/peer"
"github.com/anyproto/any-sync/net/pool"
"github.com/anyproto/any-sync/net/rpc/rpcerr"
)
@ -112,7 +111,11 @@ func (r *requestManager) SendRequest(ctx context.Context, peerId string, req *sp
defer func() {
r.reqStat.RemoveSyncRequest(peerId, req)
}()
return r.doRequest(ctx, peerId, req)
res, err := r.doRequest(ctx, peerId, req)
if err != nil {
return nil, err
}
return res.resp, err
}
func (r *requestManager) QueueRequest(peerId string, req *spacesyncproto.ObjectSyncMessage) (err error) {
@ -142,27 +145,36 @@ var doRequestAndHandle = (*requestManager).requestAndHandle
func (r *requestManager) requestAndHandle(peerId string, req *spacesyncproto.ObjectSyncMessage) {
ctx := r.ctx
resp, err := r.doRequest(ctx, peerId, req)
res, err := r.doRequest(ctx, peerId, req)
if err != nil {
log.Warn("failed to send request", zap.Error(err))
return
}
ctx = peer.CtxWithPeerId(ctx, peerId)
_ = r.handler.HandleMessage(ctx, objectsync.HandleMessage{
SenderId: peerId,
Message: resp,
PeerCtx: ctx,
Message: res.resp,
PeerCtx: res.peerCtx,
})
}
func (r *requestManager) doRequest(ctx context.Context, peerId string, msg *spacesyncproto.ObjectSyncMessage) (resp *spacesyncproto.ObjectSyncMessage, err error) {
type result struct {
peerCtx context.Context
resp *spacesyncproto.ObjectSyncMessage
}
func (r *requestManager) doRequest(ctx context.Context, peerId string, msg *spacesyncproto.ObjectSyncMessage) (res result, err error) {
pr, err := r.peerPool.Get(ctx, peerId)
if err != nil {
return
}
res.peerCtx = pr.Context()
err = pr.DoDrpc(ctx, func(conn drpc.Conn) error {
cl := r.clientFactory.Client(conn)
resp, err = cl.ObjectSync(ctx, msg)
resp, err := cl.ObjectSync(ctx, msg)
if err != nil {
return err
}
res.resp = resp
return err
})
err = rpcerr.Unwrap(err)

View file

@ -66,12 +66,14 @@ func TestRequestManager_SyncRequest(t *testing.T) {
defer fx.stop()
peerId := "PeerId"
peerCtx := peer.CtxWithPeerId(ctx, peerId)
peerMock := mock_peer.NewMockPeer(fx.ctrl)
conn := &drpcconn.Conn{}
msg := &spacesyncproto.ObjectSyncMessage{}
resp := &spacesyncproto.ObjectSyncMessage{}
fx.peerPoolMock.EXPECT().Get(ctx, peerId).Return(peerMock, nil)
fx.clientMock.EXPECT().ObjectSync(ctx, msg).Return(resp, nil)
peerMock.EXPECT().Context().Return(peerCtx).AnyTimes()
peerMock.EXPECT().DoDrpc(ctx, gomock.Any()).DoAndReturn(func(ctx context.Context, drpcHandler func(conn drpc.Conn) error) {
drpcHandler(conn)
}).Return(nil)
@ -86,12 +88,14 @@ func TestRequestManager_SyncRequest(t *testing.T) {
ctx = fx.requestManager.ctx
peerId := "PeerId"
peerCtx := peer.CtxWithPeerId(ctx, peerId)
peerMock := mock_peer.NewMockPeer(fx.ctrl)
conn := &drpcconn.Conn{}
msg := &spacesyncproto.ObjectSyncMessage{}
resp := &spacesyncproto.ObjectSyncMessage{}
fx.peerPoolMock.EXPECT().Get(ctx, peerId).Return(peerMock, nil)
fx.clientMock.EXPECT().ObjectSync(ctx, msg).Return(resp, nil)
peerMock.EXPECT().Context().Return(peerCtx).AnyTimes()
peerMock.EXPECT().DoDrpc(ctx, gomock.Any()).DoAndReturn(func(ctx context.Context, drpcHandler func(conn drpc.Conn) error) {
drpcHandler(conn)
}).Return(nil)

View file

@ -46,7 +46,6 @@ var (
return objecttree.BuildHistoryTree(objecttree.HistoryTreeParams{
TreeStorage: objTree.Storage(),
AclList: objTree.AclList(),
BuildFullTree: true,
})
}
)

View file

@ -298,7 +298,7 @@ func (m mockTreeSyncer) StartSync() {
func (m mockTreeSyncer) StopSync() {
}
func (m mockTreeSyncer) SyncAll(ctx context.Context, peerId string, existing, missing []string) error {
func (m mockTreeSyncer) SyncAll(ctx context.Context, p peer.Peer, existing, missing []string) error {
return nil
}
@ -364,6 +364,10 @@ var _ coordinatorclient.CoordinatorClient = (*mockCoordinatorClient)(nil)
type mockCoordinatorClient struct {
}
func (m mockCoordinatorClient) IsNetworkNeedsUpdate(ctx context.Context) (bool, error) {
return false, nil
}
func (m mockCoordinatorClient) SpaceMakeShareable(ctx context.Context, spaceId string) (err error) {
return nil
}
@ -424,6 +428,10 @@ func (m mockCoordinatorClient) AclGetRecords(ctx context.Context, spaceId, aclHe
return
}
func (m mockCoordinatorClient) AclEventLog(ctx context.Context, accountId, lastRecordId string, limit int) (records []*coordinatorproto.AclEventLogRecord, err error) {
return
}
func (m mockCoordinatorClient) Init(a *app.App) (err error) {
return
}

View file

@ -2,6 +2,7 @@ package syncstatus
import (
"context"
"github.com/anyproto/any-sync/app"
)
@ -22,6 +23,12 @@ func (n *noOpSyncStatus) Name() (name string) {
func (n *noOpSyncStatus) HeadsChange(treeId string, heads []string) {
}
func (n *noOpSyncStatus) ObjectReceive(senderId, treeId string, heads []string) {
}
func (n *noOpSyncStatus) HeadsApply(senderId, treeId string, heads []string, allAdded bool) {
}
func (n *noOpSyncStatus) HeadsReceive(senderId, treeId string, heads []string) {
}

View file

@ -11,4 +11,6 @@ type StatusUpdater interface {
HeadsChange(treeId string, heads []string)
HeadsReceive(senderId, treeId string, heads []string)
ObjectReceive(senderId, treeId string, heads []string)
HeadsApply(senderId, treeId string, heads []string, allAdded bool)
}

View file

@ -14,6 +14,7 @@ import (
"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/net/secureservice"
"github.com/anyproto/any-sync/nodeconf"
"github.com/anyproto/any-sync/util/cidutil"
"github.com/anyproto/any-sync/util/crypto"
@ -40,6 +41,7 @@ type CoordinatorClient interface {
SpaceMakeShareable(ctx context.Context, spaceId string) (err error)
SpaceMakeUnshareable(ctx context.Context, spaceId, aclId string) (err error)
NetworkConfiguration(ctx context.Context, currentId string) (*coordinatorproto.NetworkConfigurationResponse, error)
IsNetworkNeedsUpdate(ctx context.Context) (bool, error)
DeletionLog(ctx context.Context, lastRecordId string, limit int) (records []*coordinatorproto.DeletionLogRecord, err error)
IdentityRepoPut(ctx context.Context, identity string, data []*identityrepoproto.Data) (err error)
@ -50,6 +52,8 @@ type CoordinatorClient interface {
AccountLimitsSet(ctx context.Context, req *coordinatorproto.AccountLimitsSetRequest) error
AclEventLog(ctx context.Context, accountId, lastRecordId string, limit int) (records []*coordinatorproto.AclEventLogRecord, err error)
app.Component
}
@ -332,6 +336,34 @@ func (c *coordinatorClient) SpaceMakeUnshareable(ctx context.Context, spaceId, a
})
}
func (c *coordinatorClient) AclEventLog(ctx context.Context, accountId, lastRecordId string, limit int) (records []*coordinatorproto.AclEventLogRecord, err error) {
err = c.doClient(ctx, func(cl coordinatorproto.DRPCCoordinatorClient) error {
resp, err := cl.AclEventLog(ctx, &coordinatorproto.AclEventLogRequest{
AccountIdentity: accountId,
AfterId: lastRecordId,
Limit: uint32(limit),
})
if err != nil {
return rpcerr.Unwrap(err)
}
records = resp.Records
return nil
})
return
}
func (c *coordinatorClient) IsNetworkNeedsUpdate(ctx context.Context) (bool, error) {
p, err := c.getPeer(ctx)
if err != nil {
return false, err
}
version, err := peer.CtxProtoVersion(p.Context())
if err != nil {
return false, err
}
return secureservice.ProtoVersion < version, nil
}
func (c *coordinatorClient) doClient(ctx context.Context, f func(cl coordinatorproto.DRPCCoordinatorClient) error) error {
p, err := c.getPeer(ctx)
if err != nil {

View file

@ -5,7 +5,6 @@
//
// mockgen -destination mock_coordinatorclient/mock_coordinatorclient.go github.com/anyproto/any-sync/coordinator/coordinatorclient CoordinatorClient
//
// Package mock_coordinatorclient is a generated GoMock package.
package mock_coordinatorclient
@ -102,6 +101,21 @@ func (mr *MockCoordinatorClientMockRecorder) AclAddRecord(arg0, arg1, arg2 any)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AclAddRecord", reflect.TypeOf((*MockCoordinatorClient)(nil).AclAddRecord), arg0, arg1, arg2)
}
// AclEventLog mocks base method.
func (m *MockCoordinatorClient) AclEventLog(arg0 context.Context, arg1, arg2 string, arg3 int) ([]*coordinatorproto.AclEventLogRecord, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AclEventLog", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].([]*coordinatorproto.AclEventLogRecord)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AclEventLog indicates an expected call of AclEventLog.
func (mr *MockCoordinatorClientMockRecorder) AclEventLog(arg0, arg1, arg2, arg3 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AclEventLog", reflect.TypeOf((*MockCoordinatorClient)(nil).AclEventLog), arg0, arg1, arg2, arg3)
}
// AclGetRecords mocks base method.
func (m *MockCoordinatorClient) AclGetRecords(arg0 context.Context, arg1, arg2 string) ([]*consensusproto.RawRecordWithId, error) {
m.ctrl.T.Helper()
@ -175,6 +189,21 @@ func (mr *MockCoordinatorClientMockRecorder) Init(arg0 any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockCoordinatorClient)(nil).Init), arg0)
}
// IsNetworkNeedsUpdate mocks base method.
func (m *MockCoordinatorClient) IsNetworkNeedsUpdate(arg0 context.Context) (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IsNetworkNeedsUpdate", arg0)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// IsNetworkNeedsUpdate indicates an expected call of IsNetworkNeedsUpdate.
func (mr *MockCoordinatorClientMockRecorder) IsNetworkNeedsUpdate(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsNetworkNeedsUpdate", reflect.TypeOf((*MockCoordinatorClient)(nil).IsNetworkNeedsUpdate), arg0)
}
// Name mocks base method.
func (m *MockCoordinatorClient) Name() string {
m.ctrl.T.Helper()

File diff suppressed because it is too large Load diff

View file

@ -54,6 +54,7 @@ type DRPCCoordinatorClient interface {
AclAddRecord(ctx context.Context, in *AclAddRecordRequest) (*AclAddRecordResponse, error)
AclGetRecords(ctx context.Context, in *AclGetRecordsRequest) (*AclGetRecordsResponse, error)
AccountLimitsSet(ctx context.Context, in *AccountLimitsSetRequest) (*AccountLimitsSetResponse, error)
AclEventLog(ctx context.Context, in *AclEventLogRequest) (*AclEventLogResponse, error)
}
type drpcCoordinatorClient struct {
@ -192,6 +193,15 @@ func (c *drpcCoordinatorClient) AccountLimitsSet(ctx context.Context, in *Accoun
return out, nil
}
func (c *drpcCoordinatorClient) AclEventLog(ctx context.Context, in *AclEventLogRequest) (*AclEventLogResponse, error) {
out := new(AclEventLogResponse)
err := c.cc.Invoke(ctx, "/coordinator.Coordinator/AclEventLog", drpcEncoding_File_coordinator_coordinatorproto_protos_coordinator_proto{}, in, out)
if err != nil {
return nil, err
}
return out, nil
}
type DRPCCoordinatorServer interface {
SpaceSign(context.Context, *SpaceSignRequest) (*SpaceSignResponse, error)
SpaceStatusCheck(context.Context, *SpaceStatusCheckRequest) (*SpaceStatusCheckResponse, error)
@ -207,6 +217,7 @@ type DRPCCoordinatorServer interface {
AclAddRecord(context.Context, *AclAddRecordRequest) (*AclAddRecordResponse, error)
AclGetRecords(context.Context, *AclGetRecordsRequest) (*AclGetRecordsResponse, error)
AccountLimitsSet(context.Context, *AccountLimitsSetRequest) (*AccountLimitsSetResponse, error)
AclEventLog(context.Context, *AclEventLogRequest) (*AclEventLogResponse, error)
}
type DRPCCoordinatorUnimplementedServer struct{}
@ -267,9 +278,13 @@ func (s *DRPCCoordinatorUnimplementedServer) AccountLimitsSet(context.Context, *
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
}
func (s *DRPCCoordinatorUnimplementedServer) AclEventLog(context.Context, *AclEventLogRequest) (*AclEventLogResponse, error) {
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
}
type DRPCCoordinatorDescription struct{}
func (DRPCCoordinatorDescription) NumMethods() int { return 14 }
func (DRPCCoordinatorDescription) NumMethods() int { return 15 }
func (DRPCCoordinatorDescription) Method(n int) (string, drpc.Encoding, drpc.Receiver, interface{}, bool) {
switch n {
@ -399,6 +414,15 @@ func (DRPCCoordinatorDescription) Method(n int) (string, drpc.Encoding, drpc.Rec
in1.(*AccountLimitsSetRequest),
)
}, DRPCCoordinatorServer.AccountLimitsSet, true
case 14:
return "/coordinator.Coordinator/AclEventLog", drpcEncoding_File_coordinator_coordinatorproto_protos_coordinator_proto{},
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
return srv.(DRPCCoordinatorServer).
AclEventLog(
ctx,
in1.(*AclEventLogRequest),
)
}, DRPCCoordinatorServer.AclEventLog, true
default:
return "", nil, nil, nil, false
}
@ -631,3 +655,19 @@ func (x *drpcCoordinator_AccountLimitsSetStream) SendAndClose(m *AccountLimitsSe
}
return x.CloseSend()
}
type DRPCCoordinator_AclEventLogStream interface {
drpc.Stream
SendAndClose(*AclEventLogResponse) error
}
type drpcCoordinator_AclEventLogStream struct {
drpc.Stream
}
func (x *drpcCoordinator_AclEventLogStream) SendAndClose(m *AclEventLogResponse) error {
if err := x.MsgSend(m, drpcEncoding_File_coordinator_coordinatorproto_protos_coordinator_proto{}); err != nil {
return err
}
return x.CloseSend()
}

View file

@ -43,6 +43,9 @@ service Coordinator {
rpc AclGetRecords(AclGetRecordsRequest) returns (AclGetRecordsResponse);
// AccountLimitsSet sets limits to the account. Can be used only by a network config member
rpc AccountLimitsSet(AccountLimitsSetRequest) returns (AccountLimitsSetResponse);
// EventLog gets the latest event log records
rpc AclEventLog(AclEventLogRequest) returns (AclEventLogResponse);
}
message SpaceSignRequest {
@ -364,5 +367,39 @@ message AccountLimitsSetRequest {
uint32 sharedSpacesLimit = 6;
}
message AccountLimitsSetResponse {}
message AclEventLogRequest {
string accountIdentity = 1;
// AfterId is the last known logId to request records after this id. If it is empty will be returned a list from the beginning.
string afterId = 2;
// Limit is a desired record count in response
uint32 limit = 3;
}
message AclEventLogResponse {
// AclEventLogRecord list of records, if there are no new records will be empty
repeated AclEventLogRecord records = 1;
// HasMore indicates if there are records left
bool hasMore = 2;
}
enum AclEventLogRecordType {
RecordTypeSpaceReceipt = 0;
RecordTypeSpaceShared = 1;
RecordTypeSpaceUnshared = 2;
RecordTypeSpaceAclAddRecord = 3;
}
message AclEventLogRecord {
// Id is a record id
string id = 1;
// SpaceId is a space identifier
string spaceId = 2;
// Timestamp is a unixtimestamp of record creation
int64 timestamp = 3;
// Type of current event
AclEventLogRecordType type = 4;
// only for RecordTypeSpaceAclAddRecord
string aclChangeId = 5;
}

View file

@ -66,6 +66,7 @@ func (n *nodeConfSource) GetLast(ctx context.Context, currentId string) (c nodec
Types: types,
}
}
return nodeconf.Configuration{
Id: res.ConfigurationId,
NetworkId: res.NetworkId,

22
go.mod
View file

@ -12,7 +12,7 @@ require (
github.com/cespare/xxhash v1.1.0
github.com/cheggaaa/mb/v3 v3.0.2
github.com/gobwas/glob v0.2.3
github.com/goccy/go-graphviz v0.1.2
github.com/goccy/go-graphviz v0.1.3
github.com/gogo/protobuf v1.3.2
github.com/google/uuid v1.6.0
github.com/hashicorp/yamux v0.1.1
@ -21,21 +21,21 @@ require (
github.com/ipfs/go-block-format v0.2.0
github.com/ipfs/go-cid v0.4.1
github.com/ipfs/go-ipld-format v0.6.0
github.com/libp2p/go-libp2p v0.33.2
github.com/libp2p/go-libp2p v0.35.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.19.1
github.com/quic-go/quic-go v0.44.0
github.com/quic-go/quic-go v0.46.0
github.com/stretchr/testify v1.9.0
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.4.0
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.23.0
golang.org/x/crypto v0.26.0
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
golang.org/x/net v0.25.0
golang.org/x/net v0.28.0
golang.org/x/time v0.5.0
gopkg.in/yaml.v3 v3.0.1
storj.io/drpc v0.0.34
@ -49,7 +49,7 @@ require (
github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
github.com/fogleman/gg v1.3.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
@ -57,7 +57,7 @@ require (
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/pprof v0.0.0-20240402174815-29b9bb013b0f // indirect
github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // 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,7 +75,7 @@ require (
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.12.3 // indirect
github.com/multiformats/go-multiaddr v0.12.4 // indirect
github.com/multiformats/go-multicodec v0.9.0 // indirect
github.com/multiformats/go-multistream v0.5.0 // indirect
github.com/multiformats/go-varint v0.0.7 // indirect
@ -83,7 +83,7 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/polydawn/refmt v0.89.0 // indirect
github.com/prometheus/client_model v0.6.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.48.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
@ -95,8 +95,8 @@ require (
golang.org/x/image v0.14.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/sys v0.23.0 // indirect
golang.org/x/tools v0.21.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
lukechampine.com/blake3 v1.2.1 // indirect
)

88
go.sum
View file

@ -52,8 +52,8 @@ github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U=
github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg=
github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
@ -73,8 +73,8 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/goccy/go-graphviz v0.1.2 h1:sWSJ6w13BCm/ZOUTHDVrdvbsxqN8yyzaFcHrH/hQ9Yg=
github.com/goccy/go-graphviz v0.1.2/go.mod h1:pMYpbAqJT10V8dzV1JN/g/wUlG/0imKPzn3ZsrchGCI=
github.com/goccy/go-graphviz v0.1.3 h1:Pkt8y4FBnBNI9tfSobpoN5qy1qMNqRXPQYvLhaSUasY=
github.com/goccy/go-graphviz v0.1.3/go.mod h1:pMYpbAqJT10V8dzV1JN/g/wUlG/0imKPzn3ZsrchGCI=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
@ -93,8 +93,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4=
github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
@ -154,8 +154,8 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
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/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
@ -170,8 +170,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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-libp2p v0.33.2 h1:vCdwnFxoGOXMKmaGHlDSnL4bM3fQeW8pgIa9DECnb40=
github.com/libp2p/go-libp2p v0.33.2/go.mod h1:zTeppLuCvUIkT118pFVzA8xzP/p2dJYOMApCkFh0Yww=
github.com/libp2p/go-libp2p v0.35.1 h1:Hm7Ub2BF+GCb14ojcsEK6WAy5it5smPDK02iXSZLl50=
github.com/libp2p/go-libp2p v0.35.1/go.mod h1:Dnkgba5hsfSv5dvvXC8nfqk44hH0gIKKno+HOMU0fdc=
github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94=
github.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8=
github.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0=
@ -202,8 +202,8 @@ github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aG
github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=
github.com/multiformats/go-multiaddr v0.12.3 h1:hVBXvPRcKG0w80VinQ23P5t7czWgg65BmIvQKjDydU8=
github.com/multiformats/go-multiaddr v0.12.3/go.mod h1:sBXrNzucqkFJhvKOiwwLyqamGa/P5EIXNPLovyhQCII=
github.com/multiformats/go-multiaddr v0.12.4 h1:rrKqpY9h+n80EwhhC/kkcunCZZ7URIF8yN1WEUt2Hvc=
github.com/multiformats/go-multiaddr v0.12.4/go.mod h1:sBXrNzucqkFJhvKOiwwLyqamGa/P5EIXNPLovyhQCII=
github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A=
github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk=
github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E=
@ -229,6 +229,38 @@ github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3Hig
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/pion/datachannel v1.5.6 h1:1IxKJntfSlYkpUj8LlYRSWpYiTTC02nUrOE8T3DqGeg=
github.com/pion/datachannel v1.5.6/go.mod h1:1eKT6Q85pRnr2mHiWHxJwO50SfZRtWHTsNIVb/NfGW4=
github.com/pion/dtls/v2 v2.2.11 h1:9U/dpCYl1ySttROPWJgqWKEylUdT0fXp/xst6JwY5Ks=
github.com/pion/dtls/v2 v2.2.11/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE=
github.com/pion/ice/v2 v2.3.25 h1:M5rJA07dqhi3nobJIg+uPtcVjFECTrhcR3n0ns8kDZs=
github.com/pion/ice/v2 v2.3.25/go.mod h1:KXJJcZK7E8WzrBEYnV4UtqEZsGeWfHxsNqhVcVvgjxw=
github.com/pion/interceptor v0.1.29 h1:39fsnlP1U8gw2JzOFWdfCU82vHvhW9o0rZnZF56wF+M=
github.com/pion/interceptor v0.1.29/go.mod h1:ri+LGNjRUc5xUNtDEPzfdkmSqISixVTBF/z/Zms/6T4=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/mdns v0.0.12 h1:CiMYlY+O0azojWDmxdNr7ADGrnZ+V6Ilfner+6mSVK8=
github.com/pion/mdns v0.0.12/go.mod h1:VExJjv8to/6Wqm1FXK+Ii/Z9tsVk/F5sD/N70cnYFbk=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.14 h1:KCkGV3vJ+4DAJmvP0vaQShsb0xkRfWkO540Gy102KyE=
github.com/pion/rtcp v1.2.14/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
github.com/pion/rtp v1.8.6 h1:MTmn/b0aWWsAzux2AmP8WGllusBVw4NPYPVFFd7jUPw=
github.com/pion/rtp v1.8.6/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
github.com/pion/sctp v1.8.16 h1:PKrMs+o9EMLRvFfXq59WFsC+V8mN1wnKzqrv+3D/gYY=
github.com/pion/sctp v1.8.16/go.mod h1:P6PbDVA++OJMrVNg2AL3XtYHV4uD6dvfyOovCgMs0PE=
github.com/pion/sdp/v3 v3.0.9 h1:pX++dCHoHUwq43kuwf3PyJfHlwIj4hXA7Vrifiq0IJY=
github.com/pion/sdp/v3 v3.0.9/go.mod h1:B5xmvENq5IXJimIO4zfp6LAe1fD9N+kFv+V/1lOdz8M=
github.com/pion/srtp/v2 v2.0.18 h1:vKpAXfawO9RtTRKZJbG4y0v1b11NZxQnxRl85kGuUlo=
github.com/pion/srtp/v2 v2.0.18/go.mod h1:0KJQjA99A6/a0DOVTu1PhDSw0CXF2jTkqOoMg3ODqdA=
github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=
github.com/pion/transport/v2 v2.2.5 h1:iyi25i/21gQck4hfRhomF6SktmUQjRsRW4WJdhfc3Kc=
github.com/pion/transport/v2 v2.2.5/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
github.com/pion/turn/v2 v2.1.6 h1:Xr2niVsiPTB0FPtt+yAWKFUkU1eotQbGgpTIld4x1Gc=
github.com/pion/turn/v2 v2.1.6/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
github.com/pion/webrtc/v3 v3.2.40 h1:Wtfi6AZMQg+624cvCXUuSmrKWepSB7zfgYDOYqsSOVU=
github.com/pion/webrtc/v3 v3.2.40/go.mod h1:M1RAe3TNTD1tzyvqHrbVODfwdPGSXOUo/OgpoGGJqFY=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -238,18 +270,18 @@ github.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4
github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw=
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/quic-go v0.44.0 h1:So5wOr7jyO4vzL2sd8/pD9Kesciv91zSk8BoFngItQ0=
github.com/quic-go/quic-go v0.44.0/go.mod h1:z4cx/9Ny9UtGITIPzmPTXh1ULfOyWh4qGQlpnPcWmek=
github.com/quic-go/webtransport-go v0.6.0 h1:CvNsKqc4W2HljHJnoT+rMmbRJybShZ0YPFDD3NxaZLY=
github.com/quic-go/webtransport-go v0.6.0/go.mod h1:9KjU4AEBqEQidGHNDkZrb8CAa1abRaosM2yGOyiikEc=
github.com/quic-go/quic-go v0.46.0 h1:uuwLClEEyk1DNvchH8uCByQVjo3yKL9opKulExNDs7Y=
github.com/quic-go/quic-go v0.46.0/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI=
github.com/quic-go/webtransport-go v0.8.0 h1:HxSrwun11U+LlmwpgM1kEqIqH90IT4N8auv/cD7QFJg=
github.com/quic-go/webtransport-go v0.8.0/go.mod h1:N99tjprW432Ut5ONql/aUhSLT0YVSlwHohQsuac9WaM=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@ -314,8 +346,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
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.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
@ -333,8 +365,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/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=
@ -353,13 +385,13 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/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.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -375,8 +407,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=

View file

@ -71,7 +71,7 @@ func New() AnyNsClientService {
func (s *service) doClient(ctx context.Context, fn func(cl nsp.DRPCAnynsClient) error) error {
if len(s.nodeconf.NamingNodePeers()) == 0 {
log.Error("no namingNode peers configured")
return errors.New("no namingNode peers configured")
return errors.New("no namingNode peers configured. Node config ID: " + s.nodeconf.Id())
}
// it will try to connect to the Naming Node

View file

@ -17,10 +17,12 @@ const (
contextKeyIdentity
contextKeyPeerAddr
contextKeyPeerClientVersion
contextKeyPeerProtoVersion
)
var (
ErrPeerIdNotFoundInContext = errors.New("peer id not found in context")
ErrProtoVersionNotFoundInContext = errors.New("proto version not found in context")
ErrIdentityNotFoundInContext = errors.New("identity not found in context")
)
@ -42,6 +44,19 @@ func CtxWithPeerId(ctx context.Context, peerId string) context.Context {
return context.WithValue(ctx, contextKeyPeerId, peerId)
}
// CtxWithProtoVersion sets peer protocol version
func CtxWithProtoVersion(ctx context.Context, version uint32) context.Context {
return context.WithValue(ctx, contextKeyPeerProtoVersion, version)
}
// CtxProtoVersion returns peer protocol version
func CtxProtoVersion(ctx context.Context) (uint32, error) {
if protoVersion, ok := ctx.Value(contextKeyPeerProtoVersion).(uint32); ok {
return protoVersion, nil
}
return 0, ErrProtoVersionNotFoundInContext
}
// CtxPeerAddr returns peer address
func CtxPeerAddr(ctx context.Context) string {
if p, ok := ctx.Value(contextKeyPeerAddr).(string); ok {

14
net/peer/context_test.go Normal file
View file

@ -0,0 +1,14 @@
package peer
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestCtxProtoVersion(t *testing.T) {
ctx := CtxWithProtoVersion(ctx, 1)
ver, err := CtxProtoVersion(ctx)
require.NoError(t, err)
require.Equal(t, uint32(1), ver)
}

View file

@ -2,6 +2,10 @@ package rpctest
import (
"context"
"time"
"storj.io/drpc"
"github.com/anyproto/any-sync/net/peer"
"github.com/anyproto/any-sync/net/rpc/rpctest/multiconntest"
"github.com/anyproto/any-sync/net/transport"
@ -10,3 +14,50 @@ import (
func MultiConnPair(peerIdServ, peerIdClient string) (serv, client transport.MultiConn) {
return multiconntest.MultiConnPair(peer.CtxWithPeerId(context.Background(), peerIdServ), peer.CtxWithPeerId(context.Background(), peerIdClient))
}
type MockPeer struct {
Ctx context.Context
}
func (m MockPeer) CloseChan() <-chan struct{} {
return nil
}
func (m MockPeer) SetTTL(ttl time.Duration) {
return
}
func (m MockPeer) Id() string {
return "peerId"
}
func (m MockPeer) Context() context.Context {
if m.Ctx != nil {
return m.Ctx
}
return context.Background()
}
func (m MockPeer) AcquireDrpcConn(ctx context.Context) (drpc.Conn, error) {
return nil, nil
}
func (m MockPeer) ReleaseDrpcConn(conn drpc.Conn) {
return
}
func (m MockPeer) DoDrpc(ctx context.Context, do func(conn drpc.Conn) error) error {
return nil
}
func (m MockPeer) IsClosed() bool {
return false
}
func (m MockPeer) TryClose(objectTTL time.Duration) (res bool, err error) {
return false, err
}
func (m MockPeer) Close() (err error) {
return nil
}

View file

@ -28,11 +28,13 @@ var (
// ProtoVersion 1 - version with yamux over tcp and quic
// ProtoVersion 2 - acl compatible version
// ProtoVersion 3 - acl with breaking changes / multiplayer
AclCompatibleVersion = uint32(2)
ProtoVersion = uint32(3)
NewSyncProtoVersion = uint32(4)
)
var (
compatibleVersions = []uint32{2, ProtoVersion}
compatibleVersions = []uint32{AclCompatibleVersion, ProtoVersion, NewSyncProtoVersion}
)
func New() SecureService {
@ -119,6 +121,7 @@ func (s *secureService) HandshakeInbound(ctx context.Context, conn io.ReadWriteC
cctx = peer.CtxWithPeerId(cctx, peerId)
cctx = peer.CtxWithIdentity(cctx, res.Identity)
cctx = peer.CtxWithClientVersion(cctx, res.ClientVersion)
cctx = peer.CtxWithProtoVersion(cctx, res.ProtoVersion)
return
}
@ -146,6 +149,7 @@ func (s *secureService) HandshakeOutbound(ctx context.Context, conn io.ReadWrite
cctx = peer.CtxWithPeerId(cctx, peerId)
cctx = peer.CtxWithIdentity(cctx, res.Identity)
cctx = peer.CtxWithClientVersion(cctx, res.ClientVersion)
cctx = peer.CtxWithProtoVersion(cctx, res.ProtoVersion)
return cctx, nil
}

View file

@ -13,6 +13,7 @@ import (
"github.com/anyproto/any-sync/app/debugstat"
"github.com/anyproto/any-sync/net"
"github.com/anyproto/any-sync/net/peer"
"github.com/anyproto/any-sync/net/secureservice"
)
// StreamHandler handles incoming messages from streams
@ -293,6 +294,11 @@ func (s *streamPool) openStream(ctx context.Context, p peer.Peer) *openingProces
// in case there was no peerId in context
ctx := peer.CtxWithPeerId(ctx, p.Id())
// open new stream and add to pool
peerProto, err := peer.CtxProtoVersion(p.Context())
if err != nil {
peerProto = secureservice.ProtoVersion
}
ctx = peer.CtxWithProtoVersion(ctx, peerProto)
st, tags, err := s.handler.OpenStream(ctx, p)
if err != nil {
op.err = err

View file

@ -3,6 +3,10 @@ package yamux
import (
"context"
"fmt"
"net"
"sync"
"time"
"github.com/anyproto/any-sync/app"
"github.com/anyproto/any-sync/app/logger"
"github.com/anyproto/any-sync/net/connutil"
@ -10,9 +14,6 @@ import (
"github.com/anyproto/any-sync/net/transport"
"github.com/hashicorp/yamux"
"go.uber.org/zap"
"net"
"sync"
"time"
)
const CName = "net.transport.yamux"
@ -157,18 +158,18 @@ func (y *yamuxTransport) accept(conn net.Conn) {
defer cancel()
cctx, err := y.secure.SecureInbound(ctx, conn)
if err != nil {
log.Warn("incoming connection handshake error", zap.Error(err))
log.Info("incoming connection handshake error", zap.Error(err), zap.String("remoteAddr", conn.RemoteAddr().String()))
return
}
luc := connutil.NewLastUsageConn(connutil.NewTimeout(conn, time.Duration(y.conf.WriteTimeoutSec)*time.Second))
sess, err := yamux.Server(luc, y.yamuxConf)
if err != nil {
log.Warn("incoming connection yamux session error", zap.Error(err))
log.Info("incoming connection yamux session error", zap.Error(err), zap.String("remoteAddr", conn.RemoteAddr().String()))
return
}
mc := NewMultiConn(cctx, luc, conn.RemoteAddr().String(), sess)
if err = y.accepter.Accept(mc); err != nil {
log.Warn("connection accept error", zap.Error(err))
log.Info("connection accept error", zap.Error(err), zap.String("remoteAddr", conn.RemoteAddr().String()))
}
}

View file

@ -61,5 +61,5 @@ func (n *nodeConfStore) SaveLast(ctx context.Context, c nodeconf.Configuration)
if err != nil {
return
}
return os.WriteFile(path, data, 0755)
return os.WriteFile(path, data, 0644)
}

View file

@ -3,6 +3,7 @@ package nodeconf
import (
"context"
"errors"
"sync"
commonaccount "github.com/anyproto/any-sync/accountservice"
@ -31,6 +32,7 @@ const (
NetworkCompatibilityStatusOk
NetworkCompatibilityStatusError
NetworkCompatibilityStatusIncompatible
NetworkCompatibilityStatusNeedsUpdate
)
func New() Service {
@ -43,6 +45,10 @@ type Service interface {
app.ComponentRunnable
}
type NetworkProtoVersionChecker interface {
IsNetworkNeedsUpdate(ctx context.Context) (bool, error)
}
type service struct {
accountId string
config Configuration
@ -53,6 +59,7 @@ type service struct {
sync periodicsync.PeriodicSync
compatibilityStatus NetworkCompatibilityStatus
networkProtoVersionChecker NetworkProtoVersionChecker
}
func (s *service) Init(a *app.App) (err error) {
@ -79,6 +86,7 @@ func (s *service) Init(a *app.App) (err error) {
}
return
}, log)
s.networkProtoVersionChecker = app.MustComponent[NetworkProtoVersionChecker](a)
return s.setLastConfiguration(lastStored)
}
@ -99,16 +107,68 @@ func (s *service) NetworkCompatibilityStatus() NetworkCompatibilityStatus {
func (s *service) updateConfiguration(ctx context.Context) (err error) {
last, err := s.source.GetLast(ctx, s.Configuration().Id)
if err != nil {
if err != nil && !errors.Is(err, ErrConfigurationNotChanged) {
s.setCompatibilityStatusByErr(err)
return
return err
}
if err = s.updateCompatibilityStatus(ctx); err != nil {
return err
}
if err = s.saveAndSetLastConfiguration(ctx, last); err != nil {
return err
}
return nil
}
func (s *service) updateCompatibilityStatus(ctx context.Context) error {
needsUpdate, checkErr := s.networkProtoVersionChecker.IsNetworkNeedsUpdate(ctx)
if checkErr != nil {
return checkErr
}
if needsUpdate {
s.setCompatibilityStatus(NetworkCompatibilityStatusNeedsUpdate)
} else {
s.setCompatibilityStatusByErr(nil)
s.setCompatibilityStatus(NetworkCompatibilityStatusOk)
}
if err = s.store.SaveLast(ctx, last); err != nil {
return
return nil
}
func (s *service) saveAndSetLastConfiguration(ctx context.Context, last Configuration) error {
if err := s.store.SaveLast(ctx, last); err != nil {
return err
}
return s.setLastConfiguration(last)
if err := s.setLastConfiguration(last); err != nil {
return err
}
return nil
}
func (s *service) setCompatibilityStatus(status NetworkCompatibilityStatus) {
s.mu.Lock()
defer s.mu.Unlock()
s.compatibilityStatus = status
}
func (s *service) setCompatibilityStatusByErr(err error) {
var status NetworkCompatibilityStatus
switch err {
case nil:
status = NetworkCompatibilityStatusOk
case handshake.ErrIncompatibleVersion:
status = NetworkCompatibilityStatusIncompatible
case net.ErrUnableToConnect:
status = NetworkCompatibilityStatusUnknown
default:
status = NetworkCompatibilityStatusError
}
s.setCompatibilityStatus(status)
}
func (s *service) setLastConfiguration(c Configuration) (err error) {
@ -137,21 +197,6 @@ func (s *service) setLastConfiguration(c Configuration) (err error) {
return
}
func (s *service) setCompatibilityStatusByErr(err error) {
s.mu.Lock()
defer s.mu.Unlock()
switch err {
case nil:
s.compatibilityStatus = NetworkCompatibilityStatusOk
case handshake.ErrIncompatibleVersion:
s.compatibilityStatus = NetworkCompatibilityStatusIncompatible
case net.ErrUnableToConnect:
s.compatibilityStatus = NetworkCompatibilityStatusUnknown
default:
s.compatibilityStatus = NetworkCompatibilityStatusError
}
}
func (s *service) Id() string {
s.mu.RLock()
defer s.mu.RUnlock()

View file

@ -54,17 +54,34 @@ func TestService_NetworkCompatibilityStatus(t *testing.T) {
time.Sleep(time.Millisecond * 10)
assert.Equal(t, NetworkCompatibilityStatusOk, fx.NetworkCompatibilityStatus())
})
t.Run("needs update", func(t *testing.T) {
fx := newFixture(t)
fx.testCoordinator.needsUpdate = true
defer fx.finish(t)
fx.run(t)
time.Sleep(time.Millisecond * 10)
assert.Equal(t, NetworkCompatibilityStatusNeedsUpdate, fx.NetworkCompatibilityStatus())
})
t.Run("network not changed update", func(t *testing.T) {
fx := newFixture(t)
fx.testSource.err = ErrConfigurationNotChanged
defer fx.finish(t)
fx.run(t)
time.Sleep(time.Millisecond * 10)
assert.Equal(t, NetworkCompatibilityStatusOk, fx.NetworkCompatibilityStatus())
})
}
func newFixture(t *testing.T) *fixture {
fx := &fixture{
Service: New(),
testCoordinator: &testCoordinator{},
a: new(app.App),
testStore: &testStore{},
testSource: &testSource{},
testConf: newTestConf(),
}
fx.a.Register(fx.testConf).Register(&accounttest.AccountTestService{}).Register(fx.Service).Register(fx.testSource).Register(fx.testStore)
fx.a.Register(fx.testConf).Register(&accounttest.AccountTestService{}).Register(fx.Service).Register(fx.testSource).Register(fx.testStore).Register(fx.testCoordinator)
return fx
}
@ -74,6 +91,7 @@ type fixture struct {
testStore *testStore
testSource *testSource
testConf *testConf
testCoordinator *testCoordinator
}
func (fx *fixture) run(t *testing.T) {
@ -84,6 +102,17 @@ func (fx *fixture) finish(t *testing.T) {
require.NoError(t, fx.a.Close(ctx))
}
type testCoordinator struct {
needsUpdate bool
}
func (t *testCoordinator) IsNetworkNeedsUpdate(ctx context.Context) (bool, error) {
return t.needsUpdate, nil
}
func (t *testCoordinator) Init(a *app.App) error { return nil }
func (t *testCoordinator) Name() string { return "testCoordinator" }
type testSource struct {
conf Configuration
err error

View file

@ -57,8 +57,7 @@ func New() AnyPpClientService {
func (s *service) doClient(ctx context.Context, fn func(cl pp.DRPCAnyPaymentProcessingClient) error) error {
if len(s.nodeconf.PaymentProcessingNodePeers()) == 0 {
log.Error("no payment processing peers configured")
return errors.New("no paymentProcessingNode peers configured")
return errors.New("no paymentProcessingNode peers configured. Node config ID: " + s.nodeconf.Id())
}
// it will try to connect to the Payment Node