Compare commits
No commits in common. "main" and "v0.0.33" have entirely different histories.
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@ -10,5 +10,5 @@ updates:
|
|||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
ignore:
|
ignore:
|
||||||
- dependency-name: "github.com/anyproto/go-chash"
|
- dependency-name: "github.com/anytypeio/go-chash"
|
||||||
|
|
||||||
|
|||||||
30
.github/workflows/coverage.yml
vendored
30
.github/workflows/coverage.yml
vendored
@ -7,7 +7,7 @@ jobs:
|
|||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
GOPRIVATE: github.com/anyproto
|
GOPRIVATE: github.com/anytypeio
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v3
|
||||||
@ -17,20 +17,20 @@ jobs:
|
|||||||
- name: git config
|
- name: git config
|
||||||
run: git config --global url.https://${{ secrets.ANYTYPE_PAT }}@github.com/.insteadOf https://github.com/
|
run: git config --global url.https://${{ secrets.ANYTYPE_PAT }}@github.com/.insteadOf https://github.com/
|
||||||
|
|
||||||
# # cache {{
|
# cache {{
|
||||||
# - id: go-cache-paths
|
- id: go-cache-paths
|
||||||
# run: |
|
run: |
|
||||||
# echo "GOCACHE=$(go env GOCACHE)" >> $GITHUB_OUTPUT
|
echo "GOCACHE=$(go env GOCACHE)" >> $GITHUB_OUTPUT
|
||||||
# echo "GOMODCACHE=$(go env GOMODCACHE)" >> $GITHUB_OUTPUT
|
echo "GOMODCACHE=$(go env GOMODCACHE)" >> $GITHUB_OUTPUT
|
||||||
# - uses: actions/cache@v3
|
- uses: actions/cache@v3
|
||||||
# with:
|
with:
|
||||||
# path: |
|
path: |
|
||||||
# ${{ steps.go-cache-paths.outputs.GOCACHE }}
|
${{ steps.go-cache-paths.outputs.GOCACHE }}
|
||||||
# ${{ steps.go-cache-paths.outputs.GOMODCACHE }}
|
${{ steps.go-cache-paths.outputs.GOMODCACHE }}
|
||||||
# key: ${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }}
|
key: ${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }}
|
||||||
# restore-keys: |
|
restore-keys: |
|
||||||
# ${{ runner.os }}-go-${{ matrix.go-version }}-
|
${{ runner.os }}-go-${{ matrix.go-version }}-
|
||||||
# # }}
|
# }}
|
||||||
|
|
||||||
- name: deps
|
- name: deps
|
||||||
run: make deps
|
run: make deps
|
||||||
|
|||||||
21
LICENSE
21
LICENSE
@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2023 Any Association
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
||||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
||||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
|
||||||
OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
7
Makefile
7
Makefile
@ -1,5 +1,5 @@
|
|||||||
.PHONY: proto test deps
|
.PHONY: proto test deps
|
||||||
export GOPRIVATE=github.com/anyproto
|
export GOPRIVATE=github.com/anytypeio
|
||||||
export PATH:=deps:$(PATH)
|
export PATH:=deps:$(PATH)
|
||||||
|
|
||||||
proto:
|
proto:
|
||||||
@ -8,8 +8,8 @@ proto:
|
|||||||
@$(eval P_ACL_RECORDS_PATH_PB := commonspace/object/acl/aclrecordproto)
|
@$(eval P_ACL_RECORDS_PATH_PB := commonspace/object/acl/aclrecordproto)
|
||||||
@$(eval P_TREE_CHANGES_PATH_PB := commonspace/object/tree/treechangeproto)
|
@$(eval P_TREE_CHANGES_PATH_PB := commonspace/object/tree/treechangeproto)
|
||||||
@$(eval P_CRYPTO_PATH_PB := util/crypto/cryptoproto)
|
@$(eval P_CRYPTO_PATH_PB := util/crypto/cryptoproto)
|
||||||
@$(eval P_ACL_RECORDS := M$(P_ACL_RECORDS_PATH_PB)/protos/aclrecord.proto=github.com/anyproto/any-sync/$(P_ACL_RECORDS_PATH_PB))
|
@$(eval P_ACL_RECORDS := M$(P_ACL_RECORDS_PATH_PB)/protos/aclrecord.proto=github.com/anytypeio/any-sync/$(P_ACL_RECORDS_PATH_PB))
|
||||||
@$(eval P_TREE_CHANGES := M$(P_TREE_CHANGES_PATH_PB)/protos/treechange.proto=github.com/anyproto/any-sync/$(P_TREE_CHANGES_PATH_PB))
|
@$(eval P_TREE_CHANGES := M$(P_TREE_CHANGES_PATH_PB)/protos/treechange.proto=github.com/anytypeio/any-sync/$(P_TREE_CHANGES_PATH_PB))
|
||||||
|
|
||||||
protoc --gogofaster_out=:. $(P_ACL_RECORDS_PATH_PB)/protos/*.proto
|
protoc --gogofaster_out=:. $(P_ACL_RECORDS_PATH_PB)/protos/*.proto
|
||||||
protoc --gogofaster_out=:. $(P_TREE_CHANGES_PATH_PB)/protos/*.proto
|
protoc --gogofaster_out=:. $(P_TREE_CHANGES_PATH_PB)/protos/*.proto
|
||||||
@ -20,7 +20,6 @@ proto:
|
|||||||
protoc --gogofaster_out=$(PKGMAP):. --go-drpc_out=protolib=github.com/gogo/protobuf:. net/streampool/testservice/protos/*.proto
|
protoc --gogofaster_out=$(PKGMAP):. --go-drpc_out=protolib=github.com/gogo/protobuf:. net/streampool/testservice/protos/*.proto
|
||||||
protoc --gogofaster_out=:. net/secureservice/handshake/handshakeproto/protos/*.proto
|
protoc --gogofaster_out=:. net/secureservice/handshake/handshakeproto/protos/*.proto
|
||||||
protoc --gogofaster_out=$(PKGMAP):. --go-drpc_out=protolib=github.com/gogo/protobuf:. coordinator/coordinatorproto/protos/*.proto
|
protoc --gogofaster_out=$(PKGMAP):. --go-drpc_out=protolib=github.com/gogo/protobuf:. coordinator/coordinatorproto/protos/*.proto
|
||||||
protoc --gogofaster_out=:. --go-drpc_out=protolib=github.com/gogo/protobuf:. consensus/consensusproto/protos/*.proto
|
|
||||||
|
|
||||||
deps:
|
deps:
|
||||||
go mod download
|
go mod download
|
||||||
|
|||||||
43
README.md
43
README.md
@ -1,43 +0,0 @@
|
|||||||
# Any-Sync
|
|
||||||
Any-Sync is an open-source protocol designed to create high-performance, local-first, peer-to-peer, end-to-end encrypted applications that facilitate seamless collaboration among multiple users and devices.
|
|
||||||
|
|
||||||
By utilizing this protocol, users can rest assured that they retain complete control over their data and digital experience. They are empowered to freely transition between various service providers, or even opt to self-host the applications.
|
|
||||||
|
|
||||||
This ensures utmost flexibility and autonomy for users in managing their personal information and digital interactions.
|
|
||||||
|
|
||||||
## Introduction
|
|
||||||
Most existing information management tools are implemented on centralized client-server architecture or designed for an offline-first single-user usage. Either way there are trade-offs for users: they can face restricted freedoms and privacy violations or compromise on the functionality of tools to avoid this.
|
|
||||||
|
|
||||||
We believe this goes against fundamental digital freedoms and that a new generation of software is needed that will respect these freedoms, while providing best in-class user experience.
|
|
||||||
|
|
||||||
Our goal with `any-sync` is to develop a protocol that will enable the deployment of this software.
|
|
||||||
|
|
||||||
Features:
|
|
||||||
- Conflict-free data replication across multiple devices and agents
|
|
||||||
- Built-in end-to-end encryption
|
|
||||||
- Cryptographically verifiable history of changes
|
|
||||||
- Adoption to frequent operations (high performance)
|
|
||||||
- Reliable and scalable infrastructure
|
|
||||||
- Simultaneous support of p2p and remote communication
|
|
||||||
|
|
||||||
## Protocol explanation
|
|
||||||
Plese read the [overview](https://tech.anytype.io/any-sync/overview) of protocol entities and design.
|
|
||||||
|
|
||||||
## Implementation
|
|
||||||
|
|
||||||
You can find the various parts of the protocol implemented in Go in the following repositories:
|
|
||||||
- [`any-sync-node`](https://github.com/anyproto/any-sync-node) — implementation of a sync node responsible for storing spaces and objects.
|
|
||||||
- [`any-sync-filenode`](https://github.com/anyproto/any-sync-filenode) — implementation of a file node responsible for storing files.
|
|
||||||
- [`any-sync-coordinator`](https://github.com/anyproto/any-sync-coordinator) — implementation of a coordinator node responsible for network configuration management.
|
|
||||||
|
|
||||||
## Contribution
|
|
||||||
Thank you for your desire to develop Anytype together.
|
|
||||||
|
|
||||||
Currently, we're not ready to accept PRs, but we will in the nearest future.
|
|
||||||
|
|
||||||
Follow us on [Github](https://github.com/anyproto) and join the [Contributors Community](https://github.com/orgs/anyproto/discussions).
|
|
||||||
|
|
||||||
---
|
|
||||||
Made by Any — a Swiss association 🇨🇭
|
|
||||||
|
|
||||||
Licensed under [MIT License](./LICENSE).
|
|
||||||
@ -1,9 +1,9 @@
|
|||||||
//go:generate mockgen -destination mock_accountservice/mock_accountservice.go github.com/anyproto/any-sync/accountservice Service
|
//go:generate mockgen -destination mock_accountservice/mock_accountservice.go github.com/anytypeio/any-sync/accountservice Service
|
||||||
package accountservice
|
package accountservice
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/anyproto/any-sync/app"
|
"github.com/anytypeio/any-sync/app"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||||
)
|
)
|
||||||
|
|
||||||
const CName = "common.accountservice"
|
const CName = "common.accountservice"
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
package mock_accountservice
|
package mock_accountservice
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/anyproto/any-sync/accountservice"
|
"github.com/anytypeio/any-sync/accountservice"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||||
"go.uber.org/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewAccountServiceWithAccount(ctrl *gomock.Controller, acc *accountdata.AccountKeys) *MockService {
|
func NewAccountServiceWithAccount(ctrl *gomock.Controller, acc *accountdata.AccountKeys) *MockService {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: github.com/anyproto/any-sync/accountservice (interfaces: Service)
|
// Source: github.com/anytypeio/any-sync/accountservice (interfaces: Service)
|
||||||
|
|
||||||
// Package mock_accountservice is a generated GoMock package.
|
// Package mock_accountservice is a generated GoMock package.
|
||||||
package mock_accountservice
|
package mock_accountservice
|
||||||
@ -7,9 +7,9 @@ package mock_accountservice
|
|||||||
import (
|
import (
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
app "github.com/anyproto/any-sync/app"
|
app "github.com/anytypeio/any-sync/app"
|
||||||
accountdata "github.com/anyproto/any-sync/commonspace/object/accountdata"
|
accountdata "github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||||
gomock "go.uber.org/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockService is a mock of Service interface.
|
// MockService is a mock of Service interface.
|
||||||
|
|||||||
105
app/app.go
105
app/app.go
@ -4,11 +4,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
"github.com/anytypeio/any-sync/app/logger"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/debug"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -16,8 +15,8 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
// values of this vars will be defined while compilation
|
// values of this vars will be defined while compilation
|
||||||
AppName, GitCommit, GitBranch, GitState, GitSummary, BuildDate string
|
GitCommit, GitBranch, GitState, GitSummary, BuildDate string
|
||||||
name string
|
name string
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -55,14 +54,11 @@ type ComponentStatable interface {
|
|||||||
// App is the central part of the application
|
// App is the central part of the application
|
||||||
// It contains and manages all components
|
// It contains and manages all components
|
||||||
type App struct {
|
type App struct {
|
||||||
parent *App
|
components []Component
|
||||||
components []Component
|
mu sync.RWMutex
|
||||||
mu sync.RWMutex
|
startStat Stat
|
||||||
startStat Stat
|
stopStat Stat
|
||||||
stopStat Stat
|
deviceState int
|
||||||
deviceState int
|
|
||||||
versionName string
|
|
||||||
anySyncVersion string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns app name
|
// Name returns app name
|
||||||
@ -70,28 +66,11 @@ func (app *App) Name() string {
|
|||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) AppName() string {
|
|
||||||
return AppName
|
|
||||||
}
|
|
||||||
|
|
||||||
// Version return app version
|
// Version return app version
|
||||||
func (app *App) Version() string {
|
func (app *App) Version() string {
|
||||||
return GitSummary
|
return GitSummary
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetVersionName sets the custom application version
|
|
||||||
func (app *App) SetVersionName(v string) {
|
|
||||||
app.versionName = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// VersionName returns a string with the settled app version or auto-generated version if it didn't set
|
|
||||||
func (app *App) VersionName() string {
|
|
||||||
if app.versionName != "" {
|
|
||||||
return app.versionName
|
|
||||||
}
|
|
||||||
return AppName + ":" + GitSummary + "/any-sync:" + app.AnySyncVersion()
|
|
||||||
}
|
|
||||||
|
|
||||||
type Stat struct {
|
type Stat struct {
|
||||||
SpentMsPerComp map[string]int64
|
SpentMsPerComp map[string]int64
|
||||||
SpentMsTotal int64
|
SpentMsTotal int64
|
||||||
@ -124,16 +103,6 @@ func VersionDescription() string {
|
|||||||
return fmt.Sprintf("build on %s from %s at #%s(%s)", BuildDate, GitBranch, GitCommit, GitState)
|
return fmt.Sprintf("build on %s from %s at #%s(%s)", BuildDate, GitBranch, GitCommit, GitState)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChildApp creates a child container which has access to parent's components
|
|
||||||
// It doesn't call Start on any of the parent's components
|
|
||||||
func (app *App) ChildApp() *App {
|
|
||||||
return &App{
|
|
||||||
parent: app,
|
|
||||||
deviceState: app.deviceState,
|
|
||||||
anySyncVersion: app.AnySyncVersion(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register adds service to registry
|
// Register adds service to registry
|
||||||
// All components will be started in the order they were registered
|
// All components will be started in the order they were registered
|
||||||
func (app *App) Register(s Component) *App {
|
func (app *App) Register(s Component) *App {
|
||||||
@ -153,14 +122,10 @@ func (app *App) Register(s Component) *App {
|
|||||||
func (app *App) Component(name string) Component {
|
func (app *App) Component(name string) Component {
|
||||||
app.mu.RLock()
|
app.mu.RLock()
|
||||||
defer app.mu.RUnlock()
|
defer app.mu.RUnlock()
|
||||||
current := app
|
for _, s := range app.components {
|
||||||
for current != nil {
|
if s.Name() == name {
|
||||||
for _, s := range current.components {
|
return s
|
||||||
if s.Name() == name {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
current = current.parent
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -178,14 +143,10 @@ func (app *App) MustComponent(name string) Component {
|
|||||||
func MustComponent[i any](app *App) i {
|
func MustComponent[i any](app *App) i {
|
||||||
app.mu.RLock()
|
app.mu.RLock()
|
||||||
defer app.mu.RUnlock()
|
defer app.mu.RUnlock()
|
||||||
current := app
|
for _, s := range app.components {
|
||||||
for current != nil {
|
if v, ok := s.(i); ok {
|
||||||
for _, s := range current.components {
|
return v
|
||||||
if v, ok := s.(i); ok {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
current = current.parent
|
|
||||||
}
|
}
|
||||||
empty := new(i)
|
empty := new(i)
|
||||||
panic(fmt.Errorf("component with interface %T is not found", empty))
|
panic(fmt.Errorf("component with interface %T is not found", empty))
|
||||||
@ -195,13 +156,9 @@ func MustComponent[i any](app *App) i {
|
|||||||
func (app *App) ComponentNames() (names []string) {
|
func (app *App) ComponentNames() (names []string) {
|
||||||
app.mu.RLock()
|
app.mu.RLock()
|
||||||
defer app.mu.RUnlock()
|
defer app.mu.RUnlock()
|
||||||
names = make([]string, 0, len(app.components))
|
names = make([]string, len(app.components))
|
||||||
current := app
|
for i, c := range app.components {
|
||||||
for current != nil {
|
names[i] = c.Name()
|
||||||
for _, c := range current.components {
|
|
||||||
names = append(names, c.Name())
|
|
||||||
}
|
|
||||||
current = current.parent
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -262,15 +219,6 @@ func (app *App) Start(ctx context.Context) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// IterateComponents iterates over all registered components. It's safe for concurrent use.
|
|
||||||
func (app *App) IterateComponents(fn func(Component)) {
|
|
||||||
app.mu.RLock()
|
|
||||||
defer app.mu.RUnlock()
|
|
||||||
for _, s := range app.components {
|
|
||||||
fn(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func stackAllGoroutines() []byte {
|
func stackAllGoroutines() []byte {
|
||||||
buf := make([]byte, 1024)
|
buf := make([]byte, 1024)
|
||||||
for {
|
for {
|
||||||
@ -309,7 +257,7 @@ func (app *App) Close(ctx context.Context) error {
|
|||||||
case <-time.After(StopWarningAfter):
|
case <-time.After(StopWarningAfter):
|
||||||
statLogger(app.stopStat, log).
|
statLogger(app.stopStat, log).
|
||||||
With(zap.String("in_progress", currentComponentStopping)).
|
With(zap.String("in_progress", currentComponentStopping)).
|
||||||
Warn("components close in progress")
|
Warn("components close in progress")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
go func() {
|
go func() {
|
||||||
@ -363,20 +311,3 @@ func (app *App) SetDeviceState(state int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var onceVersion sync.Once
|
|
||||||
|
|
||||||
func (app *App) AnySyncVersion() string {
|
|
||||||
onceVersion.Do(func() {
|
|
||||||
info, ok := debug.ReadBuildInfo()
|
|
||||||
if ok {
|
|
||||||
for _, mod := range info.Deps {
|
|
||||||
if mod.Path == "github.com/anyproto/any-sync" {
|
|
||||||
app.anySyncVersion = mod.Version
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return app.anySyncVersion
|
|
||||||
}
|
|
||||||
|
|||||||
@ -34,40 +34,6 @@ func TestAppServiceRegistry(t *testing.T) {
|
|||||||
names := app.ComponentNames()
|
names := app.ComponentNames()
|
||||||
assert.Equal(t, names, []string{"c1", "r1", "s1"})
|
assert.Equal(t, names, []string{"c1", "r1", "s1"})
|
||||||
})
|
})
|
||||||
t.Run("Child MustComponent", func(t *testing.T) {
|
|
||||||
app := app.ChildApp()
|
|
||||||
app.Register(newTestService(testTypeComponent, "x1", nil, nil))
|
|
||||||
for _, name := range []string{"c1", "r1", "s1", "x1"} {
|
|
||||||
assert.NotPanics(t, func() { app.MustComponent(name) }, name)
|
|
||||||
}
|
|
||||||
assert.Panics(t, func() { app.MustComponent("not-registered") })
|
|
||||||
})
|
|
||||||
t.Run("Child ComponentNames", func(t *testing.T) {
|
|
||||||
app := app.ChildApp()
|
|
||||||
app.Register(newTestService(testTypeComponent, "x1", nil, nil))
|
|
||||||
names := app.ComponentNames()
|
|
||||||
assert.Equal(t, names, []string{"x1", "c1", "r1", "s1"})
|
|
||||||
})
|
|
||||||
t.Run("Child override", func(t *testing.T) {
|
|
||||||
app := app.ChildApp()
|
|
||||||
app.Register(newTestService(testTypeRunnable, "s1", nil, nil))
|
|
||||||
_ = app.MustComponent("s1").(*testRunnable)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestApp_IterateComponents(t *testing.T) {
|
|
||||||
app := new(App)
|
|
||||||
|
|
||||||
app.Register(newTestService(testTypeRunnable, "c1", nil, nil))
|
|
||||||
app.Register(newTestService(testTypeRunnable, "r1", nil, nil))
|
|
||||||
app.Register(newTestService(testTypeComponent, "s1", nil, nil))
|
|
||||||
|
|
||||||
var got []string
|
|
||||||
app.IterateComponents(func(s Component) {
|
|
||||||
got = append(got, s.Name())
|
|
||||||
})
|
|
||||||
|
|
||||||
assert.ElementsMatch(t, []string{"c1", "r1", "s1"}, got)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAppStart(t *testing.T) {
|
func TestAppStart(t *testing.T) {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// Package ldiff provides a container of elements with fixed id and changeable content.
|
// Package ldiff provides a container of elements with fixed id and changeable content.
|
||||||
// Diff can calculate the difference with another diff container (you can make it remote) with minimum hops and traffic.
|
// Diff can calculate the difference with another diff container (you can make it remote) with minimum hops and traffic.
|
||||||
//
|
//
|
||||||
//go:generate mockgen -destination mock_ldiff/mock_ldiff.go github.com/anyproto/any-sync/app/ldiff Diff,Remote
|
//go:generate mockgen -destination mock_ldiff/mock_ldiff.go github.com/anytypeio/any-sync/app/ldiff Diff,Remote
|
||||||
package ldiff
|
package ldiff
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@ -3,9 +3,9 @@ package ldiff
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"gopkg.in/mgo.v2/bson"
|
||||||
"math"
|
"math"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
@ -44,7 +44,7 @@ func TestDiff_Diff(t *testing.T) {
|
|||||||
d2 := New(16, 16)
|
d2 := New(16, 16)
|
||||||
for i := 0; i < 1000; i++ {
|
for i := 0; i < 1000; i++ {
|
||||||
id := fmt.Sprint(i)
|
id := fmt.Sprint(i)
|
||||||
head := uuid.NewString()
|
head := bson.NewObjectId().Hex()
|
||||||
d1.Set(Element{
|
d1.Set(Element{
|
||||||
Id: id,
|
Id: id,
|
||||||
Head: head,
|
Head: head,
|
||||||
@ -92,7 +92,7 @@ func TestDiff_Diff(t *testing.T) {
|
|||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
d2.Set(Element{
|
d2.Set(Element{
|
||||||
Id: fmt.Sprint(i),
|
Id: fmt.Sprint(i),
|
||||||
Head: uuid.NewString(),
|
Head: bson.NewObjectId().Hex(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,7 +108,7 @@ func TestDiff_Diff(t *testing.T) {
|
|||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
d2.Set(Element{
|
d2.Set(Element{
|
||||||
Id: fmt.Sprint(i),
|
Id: fmt.Sprint(i),
|
||||||
Head: uuid.NewString(),
|
Head: bson.NewObjectId().Hex(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
var cancel func()
|
var cancel func()
|
||||||
@ -123,7 +123,7 @@ func BenchmarkDiff_Ranges(b *testing.B) {
|
|||||||
d := New(16, 16)
|
d := New(16, 16)
|
||||||
for i := 0; i < 10000; i++ {
|
for i := 0; i < 10000; i++ {
|
||||||
id := fmt.Sprint(i)
|
id := fmt.Sprint(i)
|
||||||
head := uuid.NewString()
|
head := bson.NewObjectId().Hex()
|
||||||
d.Set(Element{
|
d.Set(Element{
|
||||||
Id: id,
|
Id: id,
|
||||||
Head: head,
|
Head: head,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: github.com/anyproto/any-sync/app/ldiff (interfaces: Diff,Remote)
|
// Source: github.com/anytypeio/any-sync/app/ldiff (interfaces: Diff,Remote)
|
||||||
|
|
||||||
// Package mock_ldiff is a generated GoMock package.
|
// Package mock_ldiff is a generated GoMock package.
|
||||||
package mock_ldiff
|
package mock_ldiff
|
||||||
@ -8,8 +8,8 @@ import (
|
|||||||
context "context"
|
context "context"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
ldiff "github.com/anyproto/any-sync/app/ldiff"
|
ldiff "github.com/anytypeio/any-sync/app/ldiff"
|
||||||
gomock "go.uber.org/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockDiff is a mock of Diff interface.
|
// MockDiff is a mock of Diff interface.
|
||||||
|
|||||||
@ -1,13 +1,10 @@
|
|||||||
package logger
|
package logger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/util/slice"
|
"github.com/anytypeio/any-sync/util/slice"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LogFormat int
|
type LogFormat int
|
||||||
@ -18,19 +15,14 @@ const (
|
|||||||
JSONOutput
|
JSONOutput
|
||||||
)
|
)
|
||||||
|
|
||||||
type NamedLevel struct {
|
|
||||||
Name string `yaml:"name"`
|
|
||||||
Level string `yaml:"level"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Production bool `yaml:"production"`
|
Production bool `yaml:"production"`
|
||||||
DefaultLevel string `yaml:"defaultLevel"`
|
DefaultLevel string `yaml:"defaultLevel"`
|
||||||
Levels []NamedLevel `yaml:"levels"` // first match will be used
|
NamedLevels map[string]string `yaml:"namedLevels"`
|
||||||
AddOutputPaths []string `yaml:"outputPaths"`
|
AddOutputPaths []string `yaml:"outputPaths"`
|
||||||
DisableStdErr bool `yaml:"disableStdErr"`
|
DisableStdErr bool `yaml:"disableStdErr"`
|
||||||
Format LogFormat `yaml:"format"`
|
Format LogFormat `yaml:"format"`
|
||||||
ZapConfig *zap.Config `yaml:"-"` // optional, if set it will be used instead of other config options
|
ZapConfig *zap.Config `yaml:"-"` // optional, if set it will be used instead of other config options
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l Config) ApplyGlobal() {
|
func (l Config) ApplyGlobal() {
|
||||||
@ -76,49 +68,20 @@ func (l Config) ApplyGlobal() {
|
|||||||
conf.Level = defaultLevel
|
conf.Level = defaultLevel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, v := range l.Levels {
|
var lvl = make(map[string]zap.AtomicLevel)
|
||||||
if lev, err := zap.ParseAtomicLevel(v.Level); err == nil {
|
for k, v := range l.NamedLevels {
|
||||||
|
if lev, err := zap.ParseAtomicLevel(v); err == nil {
|
||||||
|
lvl[k] = lev
|
||||||
// we need to have a minimum level of all named loggers for the main logger
|
// we need to have a minimum level of all named loggers for the main logger
|
||||||
if lev.Level() < conf.Level.Level() {
|
if lev.Level() < conf.Level.Level() {
|
||||||
conf.Level.SetLevel(lev.Level())
|
conf.Level.SetLevel(lev.Level())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lg, err := conf.Build()
|
lg, err := conf.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Default().Fatal("can't build logger", zap.Error(err))
|
Default().Fatal("can't build logger", zap.Error(err))
|
||||||
}
|
}
|
||||||
SetDefault(lg)
|
SetDefault(lg)
|
||||||
SetNamedLevels(l.Levels)
|
SetNamedLevels(lvl)
|
||||||
}
|
|
||||||
|
|
||||||
// LevelsFromStr parses a string of the form "name1=DEBUG;prefix*=WARN;*=ERROR" into a slice of NamedLevel
|
|
||||||
// it may be useful to parse the log level from the OS env var
|
|
||||||
func LevelsFromStr(s string) (levels []NamedLevel) {
|
|
||||||
for _, kv := range strings.Split(s, ";") {
|
|
||||||
strings.TrimSpace(kv)
|
|
||||||
parts := strings.Split(kv, "=")
|
|
||||||
var key, value string
|
|
||||||
if len(parts) == 1 {
|
|
||||||
key = "*"
|
|
||||||
value = parts[0]
|
|
||||||
_, err := zap.ParseAtomicLevel(value)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Can't parse log level %s: %s\n", parts[0], err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
levels = append(levels, NamedLevel{Name: key, Level: value})
|
|
||||||
} else if len(parts) == 2 {
|
|
||||||
key = parts[0]
|
|
||||||
value = parts[1]
|
|
||||||
}
|
|
||||||
_, err := zap.ParseAtomicLevel(value)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Can't parse log level %s: %s\n", parts[0], err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
levels = append(levels, NamedLevel{Name: key, Level: value})
|
|
||||||
}
|
|
||||||
return levels
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,17 +11,12 @@ var (
|
|||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
loggerConfig zap.Config
|
loggerConfig zap.Config
|
||||||
namedLevels []namedLevel
|
namedLevels = make(map[string]zap.AtomicLevel)
|
||||||
namedGlobs = make(map[string]glob.Glob)
|
namedGlobs = make(map[string]glob.Glob)
|
||||||
namedLoggers = make(map[string]CtxLogger)
|
namedLoggers = make(map[string]CtxLogger)
|
||||||
namedSugarLoggers = make(map[string]*zap.SugaredLogger)
|
namedSugarLoggers = make(map[string]*zap.SugaredLogger)
|
||||||
)
|
)
|
||||||
|
|
||||||
type namedLevel struct {
|
|
||||||
name string
|
|
||||||
level zap.AtomicLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
loggerConfig = zap.NewDevelopmentConfig()
|
loggerConfig = zap.NewDevelopmentConfig()
|
||||||
logger, _ = loggerConfig.Build()
|
logger, _ = loggerConfig.Build()
|
||||||
@ -40,23 +35,18 @@ func SetDefault(l *zap.Logger) {
|
|||||||
// it also supports glob patterns for names, like "app*"
|
// it also supports glob patterns for names, like "app*"
|
||||||
// can be racy in case there are existing named loggers
|
// can be racy in case there are existing named loggers
|
||||||
// so consider to call only once at the beginning
|
// so consider to call only once at the beginning
|
||||||
func SetNamedLevels(nls []NamedLevel) {
|
func SetNamedLevels(l map[string]zap.AtomicLevel) {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
namedLevels = namedLevels[:0]
|
namedLevels = l
|
||||||
|
|
||||||
var minLevel = logger.Level()
|
var minLevel = logger.Level()
|
||||||
for _, nl := range nls {
|
for k, l := range namedLevels {
|
||||||
l, err := zap.ParseAtomicLevel(nl.Level)
|
g, err := glob.Compile(k)
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
namedLevels = append(namedLevels, namedLevel{name: nl.Name, level: l})
|
|
||||||
g, err := glob.Compile(nl.Name)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
namedGlobs[nl.Name] = g
|
namedGlobs[k] = g
|
||||||
}
|
}
|
||||||
|
namedLevels[k] = l
|
||||||
if l.Level() < minLevel {
|
if l.Level() < minLevel {
|
||||||
minLevel = l.Level()
|
minLevel = l.Level()
|
||||||
}
|
}
|
||||||
@ -91,18 +81,23 @@ func Default() *zap.Logger {
|
|||||||
return logger
|
return logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// getLevel returns the level for the given name
|
|
||||||
// it return the first matching name or glob pattern whatever comes first
|
|
||||||
func getLevel(name string) zap.AtomicLevel {
|
func getLevel(name string) zap.AtomicLevel {
|
||||||
for _, nl := range namedLevels {
|
level, ok := namedLevels[name]
|
||||||
if nl.name == name {
|
if !ok {
|
||||||
return nl.level
|
var found bool
|
||||||
|
for globName, glob := range namedGlobs {
|
||||||
|
if glob.Match(name) {
|
||||||
|
found = true
|
||||||
|
level, _ = namedLevels[globName]
|
||||||
|
// no need to check ok, because we know that globName exists
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if g, ok := namedGlobs[nl.name]; ok && g.Match(name) {
|
if !found {
|
||||||
return nl.level
|
level = loggerConfig.Level
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return zap.NewAtomicLevelAt(logger.Level())
|
return level
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNamed(name string, fields ...zap.Field) CtxLogger {
|
func NewNamed(name string, fields ...zap.Field) CtxLogger {
|
||||||
|
|||||||
@ -1,150 +0,0 @@
|
|||||||
package logger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_getLevel1(t *testing.T) {
|
|
||||||
SetNamedLevels([]NamedLevel{
|
|
||||||
{Name: "app", Level: "debug"},
|
|
||||||
{Name: "app*", Level: "info"},
|
|
||||||
{Name: "app.sub", Level: "warn"},
|
|
||||||
{Name: "*", Level: "fatal"},
|
|
||||||
})
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
want zap.AtomicLevel
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "app",
|
|
||||||
want: zap.NewAtomicLevelAt(zap.DebugLevel),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "app.aaa",
|
|
||||||
want: zap.NewAtomicLevelAt(zap.InfoLevel),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "app.sub",
|
|
||||||
want: zap.NewAtomicLevelAt(zap.InfoLevel),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "random",
|
|
||||||
want: zap.NewAtomicLevelAt(zap.FatalLevel),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if got := getLevel(tt.name); !reflect.DeepEqual(got, tt.want) {
|
|
||||||
t.Errorf("getLevel() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_getLevel2(t *testing.T) {
|
|
||||||
SetNamedLevels([]NamedLevel{
|
|
||||||
{Name: "*", Level: "ERROR"},
|
|
||||||
{Name: "app", Level: "info"},
|
|
||||||
{Name: "app.sub", Level: "warn"},
|
|
||||||
{Name: "*", Level: "fatal"},
|
|
||||||
})
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
want zap.AtomicLevel
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "app",
|
|
||||||
want: zap.NewAtomicLevelAt(zap.ErrorLevel),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "app.aaa",
|
|
||||||
want: zap.NewAtomicLevelAt(zap.ErrorLevel),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "app.sub",
|
|
||||||
want: zap.NewAtomicLevelAt(zap.ErrorLevel),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "random",
|
|
||||||
want: zap.NewAtomicLevelAt(zap.ErrorLevel),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if got := getLevel(tt.name); !reflect.DeepEqual(got, tt.want) {
|
|
||||||
t.Errorf("getLevel() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_getLevel3(t *testing.T) {
|
|
||||||
SetNamedLevels([]NamedLevel{
|
|
||||||
{Name: "app", Level: "info"},
|
|
||||||
{Name: "*.sub", Level: "warn"},
|
|
||||||
{Name: "*", Level: "fatal"},
|
|
||||||
})
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
want zap.AtomicLevel
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "app",
|
|
||||||
want: zap.NewAtomicLevelAt(zap.InfoLevel),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "app.sub",
|
|
||||||
want: zap.NewAtomicLevelAt(zap.WarnLevel),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "random",
|
|
||||||
want: zap.NewAtomicLevelAt(zap.FatalLevel),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if got := getLevel(tt.name); !reflect.DeepEqual(got, tt.want) {
|
|
||||||
t.Errorf("getLevel() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_getLevel4(t *testing.T) {
|
|
||||||
SetNamedLevels([]NamedLevel{
|
|
||||||
{Name: "*", Level: "invalid"},
|
|
||||||
{Name: "app", Level: "info"},
|
|
||||||
{Name: "b", Level: "invalid"},
|
|
||||||
})
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
want zap.AtomicLevel
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "app",
|
|
||||||
want: zap.NewAtomicLevelAt(zap.InfoLevel),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "app.sub",
|
|
||||||
want: zap.NewAtomicLevelAt(logger.Level()),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "b",
|
|
||||||
want: zap.NewAtomicLevelAt(logger.Level()),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if got := getLevel(tt.name); !reflect.DeepEqual(got, tt.want) {
|
|
||||||
t.Errorf("getLevel() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -2,10 +2,9 @@ package ocache
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"go.uber.org/zap"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type entryState int
|
type entryState int
|
||||||
@ -26,7 +25,6 @@ type entry struct {
|
|||||||
value Object
|
value Object
|
||||||
close chan struct{}
|
close chan struct{}
|
||||||
mx sync.Mutex
|
mx sync.Mutex
|
||||||
cancel context.CancelFunc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newEntry(id string, value Object, state entryState) *entry {
|
func newEntry(id string, value Object, state entryState) *entry {
|
||||||
@ -51,20 +49,6 @@ func (e *entry) isClosing() bool {
|
|||||||
return e.state == entryStateClosed || e.state == entryStateClosing
|
return e.state == entryStateClosed || e.state == entryStateClosing
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *entry) setCancel(cancel context.CancelFunc) {
|
|
||||||
e.mx.Lock()
|
|
||||||
defer e.mx.Unlock()
|
|
||||||
e.cancel = cancel
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *entry) cancelLoad() {
|
|
||||||
e.mx.Lock()
|
|
||||||
defer e.mx.Unlock()
|
|
||||||
if e.cancel != nil {
|
|
||||||
e.cancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *entry) waitLoad(ctx context.Context, id string) (value Object, err error) {
|
func (e *entry) waitLoad(ctx context.Context, id string) (value Object, err error) {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
|||||||
@ -6,9 +6,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func WithPrometheus(reg *prometheus.Registry, namespace, subsystem string) Option {
|
func WithPrometheus(reg *prometheus.Registry, namespace, subsystem string) Option {
|
||||||
if reg == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if subsystem == "" {
|
if subsystem == "" {
|
||||||
subsystem = "cache"
|
subsystem = "cache"
|
||||||
}
|
}
|
||||||
@ -16,7 +13,9 @@ func WithPrometheus(reg *prometheus.Registry, namespace, subsystem string) Optio
|
|||||||
subSplit := strings.Split(subsystem, ".")
|
subSplit := strings.Split(subsystem, ".")
|
||||||
namespace = strings.Join(nameSplit, "_")
|
namespace = strings.Join(nameSplit, "_")
|
||||||
subsystem = strings.Join(subSplit, "_")
|
subsystem = strings.Join(subSplit, "_")
|
||||||
|
if reg == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return func(cache *oCache) {
|
return func(cache *oCache) {
|
||||||
cache.metrics = &metrics{
|
cache.metrics = &metrics{
|
||||||
hit: prometheus.NewCounter(prometheus.CounterOpts{
|
hit: prometheus.NewCounter(prometheus.CounterOpts{
|
||||||
|
|||||||
@ -3,11 +3,10 @@ package ocache
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/anytypeio/any-sync/app/logger"
|
||||||
|
"go.uber.org/zap"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -158,10 +157,7 @@ func (c *oCache) Pick(ctx context.Context, id string) (value Object, err error)
|
|||||||
|
|
||||||
func (c *oCache) load(ctx context.Context, id string, e *entry) {
|
func (c *oCache) load(ctx context.Context, id string, e *entry) {
|
||||||
defer close(e.load)
|
defer close(e.load)
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
e.setCancel(cancel)
|
|
||||||
value, err := c.loadFunc(ctx, id)
|
value, err := c.loadFunc(ctx, id)
|
||||||
cancel()
|
|
||||||
|
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
@ -319,7 +315,6 @@ func (c *oCache) Close() (err error) {
|
|||||||
close(c.closeCh)
|
close(c.closeCh)
|
||||||
var toClose []*entry
|
var toClose []*entry
|
||||||
for _, e := range c.data {
|
for _, e := range c.data {
|
||||||
e.cancelLoad()
|
|
||||||
toClose = append(toClose, e)
|
toClose = append(toClose, e)
|
||||||
}
|
}
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
|
|||||||
@ -386,25 +386,6 @@ func Test_OCache_Remove(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOCacheCancelWhenRemove(t *testing.T) {
|
|
||||||
c := New(func(ctx context.Context, id string) (value Object, err error) {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, ctx.Err()
|
|
||||||
}
|
|
||||||
}, WithTTL(time.Millisecond*10))
|
|
||||||
stopLoad := make(chan struct{})
|
|
||||||
var err error
|
|
||||||
go func() {
|
|
||||||
_, err = c.Get(context.TODO(), "id")
|
|
||||||
stopLoad <- struct{}{}
|
|
||||||
}()
|
|
||||||
time.Sleep(time.Millisecond * 10)
|
|
||||||
c.Close()
|
|
||||||
<-stopLoad
|
|
||||||
require.Equal(t, context.Canceled, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOCacheFuzzy(t *testing.T) {
|
func TestOCacheFuzzy(t *testing.T) {
|
||||||
t.Run("test many objects gc, get and remove simultaneously, close after", func(t *testing.T) {
|
t.Run("test many objects gc, get and remove simultaneously, close after", func(t *testing.T) {
|
||||||
tryCloseIds := make(map[string]bool)
|
tryCloseIds := make(map[string]bool)
|
||||||
|
|||||||
@ -2,8 +2,8 @@ package fileblockstore
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
"github.com/anytypeio/any-sync/app/logger"
|
||||||
"github.com/anyproto/any-sync/commonfile/fileproto/fileprotoerr"
|
"github.com/anytypeio/any-sync/commonfile/fileproto/fileprotoerr"
|
||||||
blocks "github.com/ipfs/go-block-format"
|
blocks "github.com/ipfs/go-block-format"
|
||||||
"github.com/ipfs/go-cid"
|
"github.com/ipfs/go-cid"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -298,6 +298,94 @@ func (m *BlockPushResponse) XXX_DiscardUnknown() {
|
|||||||
|
|
||||||
var xxx_messageInfo_BlockPushResponse proto.InternalMessageInfo
|
var xxx_messageInfo_BlockPushResponse proto.InternalMessageInfo
|
||||||
|
|
||||||
|
type BlocksDeleteRequest struct {
|
||||||
|
SpaceId string `protobuf:"bytes,1,opt,name=spaceId,proto3" json:"spaceId,omitempty"`
|
||||||
|
Cids [][]byte `protobuf:"bytes,2,rep,name=cids,proto3" json:"cids,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BlocksDeleteRequest) Reset() { *m = BlocksDeleteRequest{} }
|
||||||
|
func (m *BlocksDeleteRequest) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*BlocksDeleteRequest) ProtoMessage() {}
|
||||||
|
func (*BlocksDeleteRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_fd665a7e11c833d5, []int{4}
|
||||||
|
}
|
||||||
|
func (m *BlocksDeleteRequest) XXX_Unmarshal(b []byte) error {
|
||||||
|
return m.Unmarshal(b)
|
||||||
|
}
|
||||||
|
func (m *BlocksDeleteRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
if deterministic {
|
||||||
|
return xxx_messageInfo_BlocksDeleteRequest.Marshal(b, m, deterministic)
|
||||||
|
} else {
|
||||||
|
b = b[:cap(b)]
|
||||||
|
n, err := m.MarshalToSizedBuffer(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b[:n], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (m *BlocksDeleteRequest) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_BlocksDeleteRequest.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *BlocksDeleteRequest) XXX_Size() int {
|
||||||
|
return m.Size()
|
||||||
|
}
|
||||||
|
func (m *BlocksDeleteRequest) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_BlocksDeleteRequest.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_BlocksDeleteRequest proto.InternalMessageInfo
|
||||||
|
|
||||||
|
func (m *BlocksDeleteRequest) GetSpaceId() string {
|
||||||
|
if m != nil {
|
||||||
|
return m.SpaceId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BlocksDeleteRequest) GetCids() [][]byte {
|
||||||
|
if m != nil {
|
||||||
|
return m.Cids
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type BlocksDeleteResponse struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BlocksDeleteResponse) Reset() { *m = BlocksDeleteResponse{} }
|
||||||
|
func (m *BlocksDeleteResponse) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*BlocksDeleteResponse) ProtoMessage() {}
|
||||||
|
func (*BlocksDeleteResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_fd665a7e11c833d5, []int{5}
|
||||||
|
}
|
||||||
|
func (m *BlocksDeleteResponse) XXX_Unmarshal(b []byte) error {
|
||||||
|
return m.Unmarshal(b)
|
||||||
|
}
|
||||||
|
func (m *BlocksDeleteResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
if deterministic {
|
||||||
|
return xxx_messageInfo_BlocksDeleteResponse.Marshal(b, m, deterministic)
|
||||||
|
} else {
|
||||||
|
b = b[:cap(b)]
|
||||||
|
n, err := m.MarshalToSizedBuffer(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b[:n], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (m *BlocksDeleteResponse) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_BlocksDeleteResponse.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *BlocksDeleteResponse) XXX_Size() int {
|
||||||
|
return m.Size()
|
||||||
|
}
|
||||||
|
func (m *BlocksDeleteResponse) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_BlocksDeleteResponse.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_BlocksDeleteResponse proto.InternalMessageInfo
|
||||||
|
|
||||||
type BlocksCheckRequest struct {
|
type BlocksCheckRequest struct {
|
||||||
SpaceId string `protobuf:"bytes,1,opt,name=spaceId,proto3" json:"spaceId,omitempty"`
|
SpaceId string `protobuf:"bytes,1,opt,name=spaceId,proto3" json:"spaceId,omitempty"`
|
||||||
Cids [][]byte `protobuf:"bytes,2,rep,name=cids,proto3" json:"cids,omitempty"`
|
Cids [][]byte `protobuf:"bytes,2,rep,name=cids,proto3" json:"cids,omitempty"`
|
||||||
@ -307,7 +395,7 @@ func (m *BlocksCheckRequest) Reset() { *m = BlocksCheckRequest{} }
|
|||||||
func (m *BlocksCheckRequest) String() string { return proto.CompactTextString(m) }
|
func (m *BlocksCheckRequest) String() string { return proto.CompactTextString(m) }
|
||||||
func (*BlocksCheckRequest) ProtoMessage() {}
|
func (*BlocksCheckRequest) ProtoMessage() {}
|
||||||
func (*BlocksCheckRequest) Descriptor() ([]byte, []int) {
|
func (*BlocksCheckRequest) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_fd665a7e11c833d5, []int{4}
|
return fileDescriptor_fd665a7e11c833d5, []int{6}
|
||||||
}
|
}
|
||||||
func (m *BlocksCheckRequest) XXX_Unmarshal(b []byte) error {
|
func (m *BlocksCheckRequest) XXX_Unmarshal(b []byte) error {
|
||||||
return m.Unmarshal(b)
|
return m.Unmarshal(b)
|
||||||
@ -358,7 +446,7 @@ func (m *BlocksCheckResponse) Reset() { *m = BlocksCheckResponse{} }
|
|||||||
func (m *BlocksCheckResponse) String() string { return proto.CompactTextString(m) }
|
func (m *BlocksCheckResponse) String() string { return proto.CompactTextString(m) }
|
||||||
func (*BlocksCheckResponse) ProtoMessage() {}
|
func (*BlocksCheckResponse) ProtoMessage() {}
|
||||||
func (*BlocksCheckResponse) Descriptor() ([]byte, []int) {
|
func (*BlocksCheckResponse) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_fd665a7e11c833d5, []int{5}
|
return fileDescriptor_fd665a7e11c833d5, []int{7}
|
||||||
}
|
}
|
||||||
func (m *BlocksCheckResponse) XXX_Unmarshal(b []byte) error {
|
func (m *BlocksCheckResponse) XXX_Unmarshal(b []byte) error {
|
||||||
return m.Unmarshal(b)
|
return m.Unmarshal(b)
|
||||||
@ -403,7 +491,7 @@ func (m *BlockAvailability) Reset() { *m = BlockAvailability{} }
|
|||||||
func (m *BlockAvailability) String() string { return proto.CompactTextString(m) }
|
func (m *BlockAvailability) String() string { return proto.CompactTextString(m) }
|
||||||
func (*BlockAvailability) ProtoMessage() {}
|
func (*BlockAvailability) ProtoMessage() {}
|
||||||
func (*BlockAvailability) Descriptor() ([]byte, []int) {
|
func (*BlockAvailability) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_fd665a7e11c833d5, []int{6}
|
return fileDescriptor_fd665a7e11c833d5, []int{8}
|
||||||
}
|
}
|
||||||
func (m *BlockAvailability) XXX_Unmarshal(b []byte) error {
|
func (m *BlockAvailability) XXX_Unmarshal(b []byte) error {
|
||||||
return m.Unmarshal(b)
|
return m.Unmarshal(b)
|
||||||
@ -456,7 +544,7 @@ func (m *BlocksBindRequest) Reset() { *m = BlocksBindRequest{} }
|
|||||||
func (m *BlocksBindRequest) String() string { return proto.CompactTextString(m) }
|
func (m *BlocksBindRequest) String() string { return proto.CompactTextString(m) }
|
||||||
func (*BlocksBindRequest) ProtoMessage() {}
|
func (*BlocksBindRequest) ProtoMessage() {}
|
||||||
func (*BlocksBindRequest) Descriptor() ([]byte, []int) {
|
func (*BlocksBindRequest) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_fd665a7e11c833d5, []int{7}
|
return fileDescriptor_fd665a7e11c833d5, []int{9}
|
||||||
}
|
}
|
||||||
func (m *BlocksBindRequest) XXX_Unmarshal(b []byte) error {
|
func (m *BlocksBindRequest) XXX_Unmarshal(b []byte) error {
|
||||||
return m.Unmarshal(b)
|
return m.Unmarshal(b)
|
||||||
@ -513,7 +601,7 @@ func (m *BlocksBindResponse) Reset() { *m = BlocksBindResponse{} }
|
|||||||
func (m *BlocksBindResponse) String() string { return proto.CompactTextString(m) }
|
func (m *BlocksBindResponse) String() string { return proto.CompactTextString(m) }
|
||||||
func (*BlocksBindResponse) ProtoMessage() {}
|
func (*BlocksBindResponse) ProtoMessage() {}
|
||||||
func (*BlocksBindResponse) Descriptor() ([]byte, []int) {
|
func (*BlocksBindResponse) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_fd665a7e11c833d5, []int{8}
|
return fileDescriptor_fd665a7e11c833d5, []int{10}
|
||||||
}
|
}
|
||||||
func (m *BlocksBindResponse) XXX_Unmarshal(b []byte) error {
|
func (m *BlocksBindResponse) XXX_Unmarshal(b []byte) error {
|
||||||
return m.Unmarshal(b)
|
return m.Unmarshal(b)
|
||||||
@ -551,7 +639,7 @@ func (m *FilesDeleteRequest) Reset() { *m = FilesDeleteRequest{} }
|
|||||||
func (m *FilesDeleteRequest) String() string { return proto.CompactTextString(m) }
|
func (m *FilesDeleteRequest) String() string { return proto.CompactTextString(m) }
|
||||||
func (*FilesDeleteRequest) ProtoMessage() {}
|
func (*FilesDeleteRequest) ProtoMessage() {}
|
||||||
func (*FilesDeleteRequest) Descriptor() ([]byte, []int) {
|
func (*FilesDeleteRequest) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_fd665a7e11c833d5, []int{9}
|
return fileDescriptor_fd665a7e11c833d5, []int{11}
|
||||||
}
|
}
|
||||||
func (m *FilesDeleteRequest) XXX_Unmarshal(b []byte) error {
|
func (m *FilesDeleteRequest) XXX_Unmarshal(b []byte) error {
|
||||||
return m.Unmarshal(b)
|
return m.Unmarshal(b)
|
||||||
@ -601,7 +689,7 @@ func (m *FilesDeleteResponse) Reset() { *m = FilesDeleteResponse{} }
|
|||||||
func (m *FilesDeleteResponse) String() string { return proto.CompactTextString(m) }
|
func (m *FilesDeleteResponse) String() string { return proto.CompactTextString(m) }
|
||||||
func (*FilesDeleteResponse) ProtoMessage() {}
|
func (*FilesDeleteResponse) ProtoMessage() {}
|
||||||
func (*FilesDeleteResponse) Descriptor() ([]byte, []int) {
|
func (*FilesDeleteResponse) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_fd665a7e11c833d5, []int{10}
|
return fileDescriptor_fd665a7e11c833d5, []int{12}
|
||||||
}
|
}
|
||||||
func (m *FilesDeleteResponse) XXX_Unmarshal(b []byte) error {
|
func (m *FilesDeleteResponse) XXX_Unmarshal(b []byte) error {
|
||||||
return m.Unmarshal(b)
|
return m.Unmarshal(b)
|
||||||
@ -639,7 +727,7 @@ func (m *FilesInfoRequest) Reset() { *m = FilesInfoRequest{} }
|
|||||||
func (m *FilesInfoRequest) String() string { return proto.CompactTextString(m) }
|
func (m *FilesInfoRequest) String() string { return proto.CompactTextString(m) }
|
||||||
func (*FilesInfoRequest) ProtoMessage() {}
|
func (*FilesInfoRequest) ProtoMessage() {}
|
||||||
func (*FilesInfoRequest) Descriptor() ([]byte, []int) {
|
func (*FilesInfoRequest) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_fd665a7e11c833d5, []int{11}
|
return fileDescriptor_fd665a7e11c833d5, []int{13}
|
||||||
}
|
}
|
||||||
func (m *FilesInfoRequest) XXX_Unmarshal(b []byte) error {
|
func (m *FilesInfoRequest) XXX_Unmarshal(b []byte) error {
|
||||||
return m.Unmarshal(b)
|
return m.Unmarshal(b)
|
||||||
@ -690,7 +778,7 @@ func (m *FilesInfoResponse) Reset() { *m = FilesInfoResponse{} }
|
|||||||
func (m *FilesInfoResponse) String() string { return proto.CompactTextString(m) }
|
func (m *FilesInfoResponse) String() string { return proto.CompactTextString(m) }
|
||||||
func (*FilesInfoResponse) ProtoMessage() {}
|
func (*FilesInfoResponse) ProtoMessage() {}
|
||||||
func (*FilesInfoResponse) Descriptor() ([]byte, []int) {
|
func (*FilesInfoResponse) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_fd665a7e11c833d5, []int{12}
|
return fileDescriptor_fd665a7e11c833d5, []int{14}
|
||||||
}
|
}
|
||||||
func (m *FilesInfoResponse) XXX_Unmarshal(b []byte) error {
|
func (m *FilesInfoResponse) XXX_Unmarshal(b []byte) error {
|
||||||
return m.Unmarshal(b)
|
return m.Unmarshal(b)
|
||||||
@ -736,7 +824,7 @@ func (m *FileInfo) Reset() { *m = FileInfo{} }
|
|||||||
func (m *FileInfo) String() string { return proto.CompactTextString(m) }
|
func (m *FileInfo) String() string { return proto.CompactTextString(m) }
|
||||||
func (*FileInfo) ProtoMessage() {}
|
func (*FileInfo) ProtoMessage() {}
|
||||||
func (*FileInfo) Descriptor() ([]byte, []int) {
|
func (*FileInfo) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_fd665a7e11c833d5, []int{13}
|
return fileDescriptor_fd665a7e11c833d5, []int{15}
|
||||||
}
|
}
|
||||||
func (m *FileInfo) XXX_Unmarshal(b []byte) error {
|
func (m *FileInfo) XXX_Unmarshal(b []byte) error {
|
||||||
return m.Unmarshal(b)
|
return m.Unmarshal(b)
|
||||||
@ -793,7 +881,7 @@ func (m *CheckRequest) Reset() { *m = CheckRequest{} }
|
|||||||
func (m *CheckRequest) String() string { return proto.CompactTextString(m) }
|
func (m *CheckRequest) String() string { return proto.CompactTextString(m) }
|
||||||
func (*CheckRequest) ProtoMessage() {}
|
func (*CheckRequest) ProtoMessage() {}
|
||||||
func (*CheckRequest) Descriptor() ([]byte, []int) {
|
func (*CheckRequest) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_fd665a7e11c833d5, []int{14}
|
return fileDescriptor_fd665a7e11c833d5, []int{16}
|
||||||
}
|
}
|
||||||
func (m *CheckRequest) XXX_Unmarshal(b []byte) error {
|
func (m *CheckRequest) XXX_Unmarshal(b []byte) error {
|
||||||
return m.Unmarshal(b)
|
return m.Unmarshal(b)
|
||||||
@ -831,7 +919,7 @@ func (m *CheckResponse) Reset() { *m = CheckResponse{} }
|
|||||||
func (m *CheckResponse) String() string { return proto.CompactTextString(m) }
|
func (m *CheckResponse) String() string { return proto.CompactTextString(m) }
|
||||||
func (*CheckResponse) ProtoMessage() {}
|
func (*CheckResponse) ProtoMessage() {}
|
||||||
func (*CheckResponse) Descriptor() ([]byte, []int) {
|
func (*CheckResponse) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_fd665a7e11c833d5, []int{15}
|
return fileDescriptor_fd665a7e11c833d5, []int{17}
|
||||||
}
|
}
|
||||||
func (m *CheckResponse) XXX_Unmarshal(b []byte) error {
|
func (m *CheckResponse) XXX_Unmarshal(b []byte) error {
|
||||||
return m.Unmarshal(b)
|
return m.Unmarshal(b)
|
||||||
@ -882,7 +970,7 @@ func (m *SpaceInfoRequest) Reset() { *m = SpaceInfoRequest{} }
|
|||||||
func (m *SpaceInfoRequest) String() string { return proto.CompactTextString(m) }
|
func (m *SpaceInfoRequest) String() string { return proto.CompactTextString(m) }
|
||||||
func (*SpaceInfoRequest) ProtoMessage() {}
|
func (*SpaceInfoRequest) ProtoMessage() {}
|
||||||
func (*SpaceInfoRequest) Descriptor() ([]byte, []int) {
|
func (*SpaceInfoRequest) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_fd665a7e11c833d5, []int{16}
|
return fileDescriptor_fd665a7e11c833d5, []int{18}
|
||||||
}
|
}
|
||||||
func (m *SpaceInfoRequest) XXX_Unmarshal(b []byte) error {
|
func (m *SpaceInfoRequest) XXX_Unmarshal(b []byte) error {
|
||||||
return m.Unmarshal(b)
|
return m.Unmarshal(b)
|
||||||
@ -929,7 +1017,7 @@ func (m *SpaceInfoResponse) Reset() { *m = SpaceInfoResponse{} }
|
|||||||
func (m *SpaceInfoResponse) String() string { return proto.CompactTextString(m) }
|
func (m *SpaceInfoResponse) String() string { return proto.CompactTextString(m) }
|
||||||
func (*SpaceInfoResponse) ProtoMessage() {}
|
func (*SpaceInfoResponse) ProtoMessage() {}
|
||||||
func (*SpaceInfoResponse) Descriptor() ([]byte, []int) {
|
func (*SpaceInfoResponse) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_fd665a7e11c833d5, []int{17}
|
return fileDescriptor_fd665a7e11c833d5, []int{19}
|
||||||
}
|
}
|
||||||
func (m *SpaceInfoResponse) XXX_Unmarshal(b []byte) error {
|
func (m *SpaceInfoResponse) XXX_Unmarshal(b []byte) error {
|
||||||
return m.Unmarshal(b)
|
return m.Unmarshal(b)
|
||||||
@ -993,6 +1081,8 @@ func init() {
|
|||||||
proto.RegisterType((*BlockGetResponse)(nil), "filesync.BlockGetResponse")
|
proto.RegisterType((*BlockGetResponse)(nil), "filesync.BlockGetResponse")
|
||||||
proto.RegisterType((*BlockPushRequest)(nil), "filesync.BlockPushRequest")
|
proto.RegisterType((*BlockPushRequest)(nil), "filesync.BlockPushRequest")
|
||||||
proto.RegisterType((*BlockPushResponse)(nil), "filesync.BlockPushResponse")
|
proto.RegisterType((*BlockPushResponse)(nil), "filesync.BlockPushResponse")
|
||||||
|
proto.RegisterType((*BlocksDeleteRequest)(nil), "filesync.BlocksDeleteRequest")
|
||||||
|
proto.RegisterType((*BlocksDeleteResponse)(nil), "filesync.BlocksDeleteResponse")
|
||||||
proto.RegisterType((*BlocksCheckRequest)(nil), "filesync.BlocksCheckRequest")
|
proto.RegisterType((*BlocksCheckRequest)(nil), "filesync.BlocksCheckRequest")
|
||||||
proto.RegisterType((*BlocksCheckResponse)(nil), "filesync.BlocksCheckResponse")
|
proto.RegisterType((*BlocksCheckResponse)(nil), "filesync.BlocksCheckResponse")
|
||||||
proto.RegisterType((*BlockAvailability)(nil), "filesync.BlockAvailability")
|
proto.RegisterType((*BlockAvailability)(nil), "filesync.BlockAvailability")
|
||||||
@ -1014,58 +1104,59 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var fileDescriptor_fd665a7e11c833d5 = []byte{
|
var fileDescriptor_fd665a7e11c833d5 = []byte{
|
||||||
// 805 bytes of a gzipped FileDescriptorProto
|
// 817 bytes of a gzipped FileDescriptorProto
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x56, 0xcd, 0x52, 0xeb, 0x36,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x56, 0x4f, 0x53, 0xfb, 0x44,
|
||||||
0x14, 0x8e, 0x13, 0x13, 0xe2, 0x13, 0x02, 0x8e, 0xf8, 0x69, 0x1a, 0x52, 0x4f, 0xc6, 0x8b, 0x0e,
|
0x18, 0x6e, 0xda, 0xfc, 0x4a, 0xf3, 0x96, 0x42, 0xba, 0x40, 0xad, 0xa5, 0x66, 0x3a, 0x39, 0x38,
|
||||||
0xc3, 0x74, 0x42, 0x87, 0x76, 0xc1, 0xa6, 0x0b, 0x12, 0x12, 0x48, 0xe9, 0xd0, 0xd6, 0x4c, 0x87,
|
0x0c, 0xe3, 0x14, 0x07, 0x3d, 0x70, 0xf1, 0x40, 0x4b, 0x0b, 0x15, 0x07, 0x35, 0x8c, 0xc3, 0xa8,
|
||||||
0x69, 0xbb, 0xa9, 0x63, 0x2b, 0xe0, 0x62, 0xac, 0xd4, 0x52, 0x5a, 0xd2, 0x47, 0xe8, 0xaa, 0x7d,
|
0x17, 0xd3, 0x64, 0x0b, 0x91, 0x90, 0xad, 0xd9, 0xad, 0x52, 0x3f, 0x82, 0x27, 0xfd, 0x32, 0x7e,
|
||||||
0x99, 0x3e, 0x03, 0x4b, 0x96, 0x77, 0x79, 0x07, 0x5e, 0xe4, 0x8e, 0x64, 0x39, 0x56, 0x7e, 0xee,
|
0x06, 0x8e, 0x1c, 0x3d, 0x3a, 0xf0, 0x45, 0x9c, 0xdd, 0x6c, 0x9a, 0xed, 0x1f, 0x07, 0xc4, 0x4b,
|
||||||
0x70, 0x7f, 0x36, 0x8e, 0xf4, 0xe9, 0x9c, 0xf3, 0x1d, 0x7d, 0x92, 0xbe, 0x09, 0x7c, 0xee, 0x91,
|
0xd8, 0x3c, 0xfb, 0xbe, 0xef, 0xf3, 0xee, 0xf3, 0x66, 0x1f, 0x0a, 0x1f, 0x7a, 0xe4, 0xee, 0x8e,
|
||||||
0xbb, 0x3b, 0x12, 0x0d, 0x83, 0x10, 0x1f, 0xf0, 0xcf, 0x28, 0x26, 0x8c, 0x1c, 0x88, 0x2f, 0x15,
|
0x44, 0xa3, 0x20, 0xc4, 0x07, 0xfc, 0x31, 0x8e, 0x09, 0x23, 0x07, 0xe2, 0x49, 0x05, 0xd0, 0x16,
|
||||||
0x40, 0x4b, 0x8c, 0x51, 0x89, 0x8f, 0xe9, 0x24, 0xf2, 0xec, 0x6f, 0x60, 0xa3, 0x1d, 0x12, 0xef,
|
0x6b, 0x54, 0xe2, 0x6b, 0x3a, 0x8d, 0x3c, 0xfb, 0x33, 0xd8, 0xec, 0x84, 0xc4, 0xbb, 0x3d, 0xc5,
|
||||||
0xf6, 0x14, 0x33, 0x07, 0xff, 0x31, 0xc6, 0x94, 0xa1, 0x1a, 0xac, 0xd2, 0x91, 0xeb, 0xe1, 0xbe,
|
0xcc, 0xc1, 0x3f, 0x4d, 0x30, 0x65, 0xa8, 0x0e, 0x6b, 0x74, 0xec, 0x7a, 0x78, 0xe0, 0xd7, 0xb5,
|
||||||
0x5f, 0xd3, 0x9a, 0xda, 0x9e, 0xe1, 0xa4, 0x53, 0x64, 0x42, 0xc1, 0x0b, 0xfc, 0x5a, 0xbe, 0xa9,
|
0x96, 0xb6, 0x67, 0x38, 0xe9, 0x2b, 0x32, 0xa1, 0xe0, 0x05, 0x7e, 0x3d, 0xdf, 0xd2, 0xf6, 0xd6,
|
||||||
0xed, 0xad, 0x39, 0x7c, 0x68, 0x1f, 0x81, 0x99, 0xa5, 0xd3, 0x11, 0x89, 0x28, 0x4e, 0xa3, 0xb4,
|
0x1d, 0xbe, 0xb4, 0x8f, 0xc0, 0xcc, 0xd2, 0xe9, 0x98, 0x44, 0x14, 0xa7, 0x51, 0xda, 0x2c, 0x0a,
|
||||||
0x69, 0x14, 0x42, 0xa0, 0xfb, 0x2e, 0x73, 0x65, 0xa2, 0x18, 0xdb, 0xbf, 0xcb, 0xcc, 0x1f, 0xc6,
|
0x21, 0xd0, 0x7d, 0x97, 0xb9, 0x32, 0x51, 0xac, 0xed, 0x1f, 0x65, 0xe6, 0x57, 0x13, 0x7a, 0xf3,
|
||||||
0xf4, 0xe6, 0x65, 0xe6, 0x1d, 0x28, 0xf2, 0x96, 0xfb, 0x09, 0xb9, 0xe1, 0xc8, 0x59, 0xca, 0x55,
|
0x32, 0x73, 0x0d, 0x8a, 0xbc, 0xe5, 0x41, 0x42, 0x6e, 0x38, 0xf2, 0x2d, 0xe5, 0x2a, 0x2c, 0x73,
|
||||||
0x58, 0xe4, 0xd2, 0x15, 0xae, 0x4d, 0xa8, 0x2a, 0x5c, 0x49, 0x9b, 0x76, 0x1b, 0x90, 0x00, 0x69,
|
0xe9, 0x0a, 0xd7, 0x16, 0x54, 0x15, 0xae, 0xa4, 0x4d, 0xbb, 0x0b, 0x5b, 0x02, 0xa4, 0x27, 0x38,
|
||||||
0xe7, 0x06, 0x7b, 0xb7, 0x2f, 0xb7, 0x80, 0x40, 0xf7, 0x02, 0x9f, 0xd6, 0xf2, 0xcd, 0x02, 0x2f,
|
0xc4, 0x0c, 0xbf, 0xdc, 0x03, 0x02, 0xdd, 0x0b, 0x7c, 0x5a, 0xcf, 0xb7, 0x0a, 0xbc, 0x32, 0x5f,
|
||||||
0xcc, 0xc7, 0xf6, 0x00, 0x36, 0x67, 0x6a, 0x48, 0x05, 0xce, 0x01, 0x0d, 0x04, 0x7c, 0xfc, 0xa7,
|
0xdb, 0x35, 0xd8, 0x9e, 0x2f, 0x22, 0x8b, 0x77, 0x00, 0x25, 0x78, 0xf7, 0x06, 0x7b, 0xb7, 0x6f,
|
||||||
0x1b, 0x84, 0xee, 0x20, 0x08, 0x03, 0x36, 0xa9, 0x69, 0xcd, 0xc2, 0x5e, 0xf9, 0x70, 0xb7, 0x95,
|
0xab, 0x3d, 0x4c, 0x1b, 0x94, 0x35, 0xa4, 0xbc, 0xe7, 0x80, 0x86, 0x02, 0x3e, 0xfe, 0xd9, 0x0d,
|
||||||
0x6a, 0xdf, 0x12, 0xa9, 0x6a, 0x88, 0xb3, 0x24, 0xcd, 0xfe, 0x55, 0x36, 0xaf, 0x82, 0x4b, 0x34,
|
0x42, 0x77, 0x18, 0x84, 0x01, 0x9b, 0xd6, 0xb5, 0x56, 0x61, 0xaf, 0x7c, 0xb8, 0xdb, 0x4e, 0x07,
|
||||||
0xfe, 0x1a, 0x8a, 0x94, 0xb9, 0x6c, 0x4c, 0x85, 0x42, 0xeb, 0x87, 0x8d, 0x8c, 0x47, 0xcd, 0xbc,
|
0xdb, 0x16, 0xa9, 0x6a, 0x88, 0xb3, 0x22, 0xcd, 0xfe, 0x5e, 0x2a, 0xa3, 0x82, 0x2b, 0x06, 0xf8,
|
||||||
0x14, 0x31, 0x8e, 0x8c, 0xb5, 0x7f, 0x96, 0xc5, 0x69, 0x3b, 0x88, 0xfc, 0x0f, 0x3f, 0x86, 0x54,
|
0x29, 0x14, 0x29, 0x73, 0xd9, 0x84, 0x0a, 0xf9, 0x37, 0x0e, 0x9b, 0x19, 0x8f, 0x9a, 0x79, 0x29,
|
||||||
0x9b, 0x82, 0xa2, 0xcd, 0x56, 0xaa, 0x6f, 0x52, 0x5a, 0xaa, 0x7e, 0x06, 0xa8, 0xc7, 0xfb, 0x3a,
|
0x62, 0x1c, 0x19, 0x6b, 0x7f, 0x2b, 0x8b, 0xd3, 0x4e, 0x10, 0xf9, 0x6f, 0x9f, 0x71, 0xaa, 0x4d,
|
||||||
0xc1, 0x21, 0x66, 0xf8, 0x65, 0xc6, 0x1a, 0xac, 0x26, 0x1c, 0x89, 0xf0, 0x86, 0x93, 0x4e, 0xed,
|
0x41, 0xd1, 0x66, 0x3b, 0xd5, 0x37, 0x29, 0x2d, 0x55, 0x3f, 0x03, 0xd4, 0xe7, 0x7d, 0xbd, 0x76,
|
||||||
0x6d, 0xd8, 0x9c, 0xa9, 0x24, 0x09, 0x7a, 0x60, 0x0a, 0xb8, 0x1f, 0x0d, 0xc9, 0xc7, 0x94, 0xef,
|
0xa2, 0x75, 0x58, 0x4b, 0x38, 0x12, 0xe1, 0x0d, 0x27, 0x7d, 0xb5, 0x77, 0x60, 0x6b, 0xae, 0x92,
|
||||||
0x42, 0x55, 0xa9, 0x23, 0x0f, 0xf6, 0x4b, 0x30, 0x86, 0x29, 0x28, 0xcf, 0x13, 0x65, 0x3a, 0xf3,
|
0x24, 0xe8, 0x83, 0x29, 0xe0, 0x41, 0x34, 0x22, 0xff, 0xa7, 0x7c, 0x0f, 0xaa, 0x4a, 0x1d, 0x39,
|
||||||
0x78, 0x11, 0x9e, 0x05, 0xd9, 0xbf, 0x41, 0x29, 0x85, 0x15, 0xf5, 0xb4, 0x19, 0xf5, 0x2c, 0x80,
|
0xd8, 0x8f, 0xc1, 0x18, 0xa5, 0xa0, 0x9c, 0x27, 0xca, 0x74, 0xe6, 0xf1, 0x22, 0x3c, 0x0b, 0xb2,
|
||||||
0x31, 0x75, 0xaf, 0x71, 0x7b, 0xc2, 0x70, 0x72, 0x7c, 0xba, 0xa3, 0x20, 0xa8, 0x01, 0x06, 0x57,
|
0x7f, 0x80, 0x52, 0x0a, 0x2b, 0xea, 0x69, 0x73, 0xea, 0x59, 0x00, 0x13, 0xea, 0x5e, 0xe3, 0xce,
|
||||||
0xb4, 0x43, 0xc6, 0x11, 0x13, 0x57, 0xbd, 0xe2, 0x64, 0x80, 0xbd, 0x0e, 0x6b, 0xea, 0x0d, 0xb6,
|
0x94, 0xe1, 0x64, 0x7c, 0xba, 0xa3, 0x20, 0xa8, 0x09, 0x06, 0x57, 0xb4, 0x4b, 0x26, 0x11, 0x13,
|
||||||
0xcf, 0xa1, 0x32, 0x7b, 0x1b, 0xeb, 0x50, 0x92, 0xdb, 0xa5, 0xa2, 0x67, 0xc3, 0x99, 0xce, 0x39,
|
0xf7, 0xa8, 0xe2, 0x64, 0x80, 0xbd, 0x01, 0xeb, 0xea, 0x17, 0x6c, 0x9f, 0x43, 0x65, 0xfe, 0x6b,
|
||||||
0xb5, 0x1b, 0x86, 0xe4, 0xaf, 0xab, 0x38, 0x60, 0x58, 0x50, 0x97, 0x1c, 0x05, 0xb1, 0xbf, 0x00,
|
0x6c, 0x40, 0x49, 0x1e, 0x97, 0x8a, 0x9e, 0x0d, 0x67, 0xf6, 0xce, 0xa9, 0xdd, 0x30, 0x24, 0xbf,
|
||||||
0xf3, 0x52, 0xc4, 0xbe, 0x8b, 0x9a, 0xf6, 0x7f, 0x1a, 0x54, 0x95, 0x70, 0xc9, 0x6f, 0x01, 0x84,
|
0x5c, 0xc5, 0x01, 0xc3, 0x82, 0xba, 0xe4, 0x28, 0x88, 0xfd, 0x11, 0x98, 0x97, 0x22, 0xf6, 0x35,
|
||||||
0xc1, 0x5d, 0xc0, 0x92, 0xed, 0x69, 0xc9, 0xf6, 0x32, 0xe4, 0xfd, 0xb7, 0xaf, 0x2b, 0xdb, 0xe7,
|
0x6a, 0xda, 0x7f, 0x68, 0x50, 0x55, 0xc2, 0x25, 0xbf, 0x05, 0x10, 0x06, 0x77, 0x01, 0x4b, 0x8e,
|
||||||
0xd9, 0x42, 0xed, 0x64, 0x59, 0x4f, 0xb2, 0x33, 0x64, 0xff, 0x1f, 0x0d, 0x4a, 0xdd, 0x38, 0xee,
|
0xa7, 0x25, 0xc7, 0xcb, 0x90, 0xff, 0x7e, 0x7c, 0x5d, 0x39, 0x3e, 0xcf, 0x16, 0x6a, 0x27, 0xdb,
|
||||||
0x10, 0x1f, 0x53, 0xb4, 0x0e, 0xf0, 0x53, 0x84, 0xef, 0x47, 0xd8, 0x63, 0xd8, 0x37, 0x73, 0x68,
|
0x7a, 0x92, 0x9d, 0x21, 0xfb, 0xbf, 0x69, 0x50, 0xea, 0xc5, 0x71, 0x97, 0xf8, 0x98, 0xa2, 0x0d,
|
||||||
0x03, 0xca, 0x9d, 0xfe, 0xc9, 0x05, 0x61, 0x3d, 0x32, 0x8e, 0x7c, 0x53, 0x43, 0x15, 0x30, 0x7a,
|
0x80, 0x6f, 0x22, 0x7c, 0x3f, 0xc6, 0x1e, 0xc3, 0xbe, 0x99, 0x43, 0x9b, 0x50, 0xee, 0x0e, 0x4e,
|
||||||
0x24, 0x1e, 0x04, 0xbe, 0x8f, 0x23, 0x33, 0x8f, 0x76, 0x00, 0x89, 0xfd, 0x7c, 0xc7, 0xbb, 0xed,
|
0x2e, 0x08, 0xeb, 0x93, 0x49, 0xe4, 0x9b, 0x1a, 0xaa, 0x80, 0xd1, 0x27, 0xf1, 0x30, 0xf0, 0x7d,
|
||||||
0xde, 0x7b, 0x18, 0xfb, 0xd8, 0x37, 0x0b, 0x68, 0x1b, 0xaa, 0x3f, 0x8e, 0x71, 0x3c, 0xb9, 0x0c,
|
0x1c, 0x99, 0x79, 0x54, 0x03, 0x24, 0xce, 0xf3, 0x05, 0xef, 0xb6, 0x77, 0xef, 0x61, 0xec, 0x63,
|
||||||
0xfe, 0xc6, 0x53, 0x58, 0xe7, 0xd9, 0x57, 0x31, 0x89, 0xae, 0xcf, 0x5c, 0x7a, 0x63, 0xae, 0x20,
|
0xdf, 0x2c, 0xa0, 0x1d, 0xa8, 0x7e, 0x3d, 0xc1, 0xf1, 0xf4, 0x32, 0xf8, 0x15, 0xcf, 0x60, 0x9d,
|
||||||
0x13, 0xca, 0xdd, 0x38, 0x26, 0xf1, 0xf7, 0xc3, 0x21, 0xc5, 0xcc, 0x7c, 0xd0, 0xf6, 0xdb, 0x80,
|
0x67, 0x5f, 0xc5, 0x24, 0xba, 0x3e, 0x73, 0xe9, 0x8d, 0xf9, 0x0e, 0x99, 0x50, 0xee, 0xc5, 0x31,
|
||||||
0x16, 0x1f, 0x23, 0x4f, 0xbb, 0x20, 0xac, 0x7b, 0x1f, 0x50, 0x46, 0xcd, 0x1c, 0x02, 0x28, 0xca,
|
0x89, 0xbf, 0x1c, 0x8d, 0x28, 0x66, 0xe6, 0x83, 0xb6, 0xdf, 0x01, 0xb4, 0x7c, 0x19, 0x79, 0xda,
|
||||||
0xb1, 0x86, 0xaa, 0x50, 0x49, 0xc6, 0xfd, 0x48, 0x34, 0x62, 0xe6, 0x0f, 0xff, 0xd7, 0x41, 0xe7,
|
0x05, 0x61, 0xbd, 0xfb, 0x80, 0x32, 0x6a, 0xe6, 0x10, 0x40, 0x51, 0xae, 0x35, 0x54, 0x85, 0x4a,
|
||||||
0x57, 0x0a, 0x1d, 0x43, 0x29, 0xf5, 0x5e, 0xf4, 0xe9, 0x9c, 0xab, 0x64, 0x76, 0x5e, 0xaf, 0x2f,
|
0xb2, 0x1e, 0x44, 0xa2, 0x11, 0x33, 0x7f, 0xf8, 0xa7, 0x0e, 0x3a, 0xff, 0xa4, 0xd0, 0x31, 0x94,
|
||||||
0x5b, 0x92, 0x47, 0x73, 0x02, 0xc6, 0xd4, 0x18, 0xd1, 0x7c, 0xa0, 0xe2, 0xcc, 0xf5, 0xdd, 0xa5,
|
0x52, 0x63, 0x47, 0xef, 0x2f, 0xb8, 0x4a, 0xf6, 0xbf, 0xa2, 0xd1, 0x58, 0xb5, 0x25, 0x47, 0x73,
|
||||||
0x6b, 0xb2, 0xca, 0xb7, 0x50, 0x56, 0x5c, 0x10, 0x35, 0xe6, 0x62, 0x67, 0x0c, 0xb6, 0xfe, 0xd9,
|
0x02, 0xc6, 0xcc, 0x75, 0xd1, 0x62, 0xa0, 0x62, 0xfb, 0x8d, 0xdd, 0x95, 0x7b, 0xb2, 0xca, 0xe7,
|
||||||
0x5b, 0x56, 0x65, 0xad, 0x53, 0x80, 0xcc, 0x35, 0xd0, 0x3c, 0xad, 0x6a, 0x53, 0xf5, 0xc6, 0xf2,
|
0x50, 0x56, 0x5c, 0x10, 0x35, 0x17, 0x62, 0xe7, 0x0c, 0xb6, 0xf1, 0xc1, 0xbf, 0xec, 0xca, 0x5a,
|
||||||
0xc5, 0xac, 0x29, 0xc5, 0x1e, 0xd4, 0xa6, 0x16, 0xfd, 0x47, 0x6d, 0x6a, 0x89, 0xa7, 0x70, 0x99,
|
0xa7, 0x00, 0x99, 0x6b, 0xa0, 0x45, 0x5a, 0xd5, 0xa6, 0x1a, 0xcd, 0xd5, 0x9b, 0x59, 0x53, 0x8a,
|
||||||
0xa6, 0x5e, 0xa0, 0xca, 0x34, 0x6f, 0x34, 0xaa, 0x4c, 0x8b, 0xe6, 0x71, 0x04, 0x2b, 0x89, 0x40,
|
0x3d, 0xa8, 0x4d, 0x2d, 0xfb, 0x8f, 0xda, 0xd4, 0x0a, 0x4f, 0xe1, 0x32, 0xcd, 0xbc, 0x40, 0x95,
|
||||||
0x3b, 0x59, 0xd4, 0x8c, 0x34, 0x9f, 0x2c, 0xe0, 0x19, 0xff, 0xf4, 0x59, 0xa9, 0xfc, 0xf3, 0x4f,
|
0x69, 0xd1, 0x68, 0x54, 0x99, 0x96, 0xcd, 0xe3, 0x08, 0xde, 0x25, 0x02, 0xd5, 0xb2, 0xa8, 0x39,
|
||||||
0x53, 0xe5, 0x5f, 0x78, 0x87, 0xed, 0xd6, 0xc3, 0x93, 0xa5, 0x3d, 0x3e, 0x59, 0xda, 0xeb, 0x27,
|
0x69, 0xde, 0x5b, 0xc2, 0x33, 0xfe, 0xd9, 0xb5, 0x52, 0xf9, 0x17, 0xaf, 0xa6, 0xca, 0xbf, 0x74,
|
||||||
0x4b, 0xfb, 0xf7, 0xd9, 0xca, 0x3d, 0x3e, 0x5b, 0xb9, 0x57, 0xcf, 0x56, 0xee, 0x97, 0xad, 0x65,
|
0x0f, 0x3b, 0xed, 0x87, 0x27, 0x4b, 0x7b, 0x7c, 0xb2, 0xb4, 0xbf, 0x9f, 0x2c, 0xed, 0xf7, 0x67,
|
||||||
0x7f, 0x1b, 0x06, 0x45, 0xf1, 0xf3, 0xd5, 0x9b, 0x00, 0x00, 0x00, 0xff, 0xff, 0x45, 0x87, 0x27,
|
0x2b, 0xf7, 0xf8, 0x6c, 0xe5, 0xfe, 0x7a, 0xb6, 0x72, 0xdf, 0x6d, 0xaf, 0xfa, 0x4d, 0x32, 0x2c,
|
||||||
0x98, 0x55, 0x08, 0x00, 0x00,
|
0x8a, 0x3f, 0x9f, 0xfc, 0x13, 0x00, 0x00, 0xff, 0xff, 0x26, 0x23, 0x7f, 0x83, 0xb2, 0x08, 0x00,
|
||||||
|
0x00,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *BlockGetRequest) Marshal() (dAtA []byte, err error) {
|
func (m *BlockGetRequest) Marshal() (dAtA []byte, err error) {
|
||||||
@ -1216,6 +1307,68 @@ func (m *BlockPushResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
|||||||
return len(dAtA) - i, nil
|
return len(dAtA) - i, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *BlocksDeleteRequest) Marshal() (dAtA []byte, err error) {
|
||||||
|
size := m.Size()
|
||||||
|
dAtA = make([]byte, size)
|
||||||
|
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dAtA[:n], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BlocksDeleteRequest) MarshalTo(dAtA []byte) (int, error) {
|
||||||
|
size := m.Size()
|
||||||
|
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BlocksDeleteRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||||
|
i := len(dAtA)
|
||||||
|
_ = i
|
||||||
|
var l int
|
||||||
|
_ = l
|
||||||
|
if len(m.Cids) > 0 {
|
||||||
|
for iNdEx := len(m.Cids) - 1; iNdEx >= 0; iNdEx-- {
|
||||||
|
i -= len(m.Cids[iNdEx])
|
||||||
|
copy(dAtA[i:], m.Cids[iNdEx])
|
||||||
|
i = encodeVarintFile(dAtA, i, uint64(len(m.Cids[iNdEx])))
|
||||||
|
i--
|
||||||
|
dAtA[i] = 0x12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(m.SpaceId) > 0 {
|
||||||
|
i -= len(m.SpaceId)
|
||||||
|
copy(dAtA[i:], m.SpaceId)
|
||||||
|
i = encodeVarintFile(dAtA, i, uint64(len(m.SpaceId)))
|
||||||
|
i--
|
||||||
|
dAtA[i] = 0xa
|
||||||
|
}
|
||||||
|
return len(dAtA) - i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BlocksDeleteResponse) Marshal() (dAtA []byte, err error) {
|
||||||
|
size := m.Size()
|
||||||
|
dAtA = make([]byte, size)
|
||||||
|
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dAtA[:n], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BlocksDeleteResponse) MarshalTo(dAtA []byte) (int, error) {
|
||||||
|
size := m.Size()
|
||||||
|
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BlocksDeleteResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||||
|
i := len(dAtA)
|
||||||
|
_ = i
|
||||||
|
var l int
|
||||||
|
_ = l
|
||||||
|
return len(dAtA) - i, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (m *BlocksCheckRequest) Marshal() (dAtA []byte, err error) {
|
func (m *BlocksCheckRequest) Marshal() (dAtA []byte, err error) {
|
||||||
size := m.Size()
|
size := m.Size()
|
||||||
dAtA = make([]byte, size)
|
dAtA = make([]byte, size)
|
||||||
@ -1791,6 +1944,34 @@ func (m *BlockPushResponse) Size() (n int) {
|
|||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *BlocksDeleteRequest) Size() (n int) {
|
||||||
|
if m == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var l int
|
||||||
|
_ = l
|
||||||
|
l = len(m.SpaceId)
|
||||||
|
if l > 0 {
|
||||||
|
n += 1 + l + sovFile(uint64(l))
|
||||||
|
}
|
||||||
|
if len(m.Cids) > 0 {
|
||||||
|
for _, b := range m.Cids {
|
||||||
|
l = len(b)
|
||||||
|
n += 1 + l + sovFile(uint64(l))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BlocksDeleteResponse) Size() (n int) {
|
||||||
|
if m == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var l int
|
||||||
|
_ = l
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
func (m *BlocksCheckRequest) Size() (n int) {
|
func (m *BlocksCheckRequest) Size() (n int) {
|
||||||
if m == nil {
|
if m == nil {
|
||||||
return 0
|
return 0
|
||||||
@ -2487,6 +2668,170 @@ func (m *BlockPushResponse) Unmarshal(dAtA []byte) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
func (m *BlocksDeleteRequest) Unmarshal(dAtA []byte) error {
|
||||||
|
l := len(dAtA)
|
||||||
|
iNdEx := 0
|
||||||
|
for iNdEx < l {
|
||||||
|
preIndex := iNdEx
|
||||||
|
var wire uint64
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return ErrIntOverflowFile
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
wire |= uint64(b&0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fieldNum := int32(wire >> 3)
|
||||||
|
wireType := int(wire & 0x7)
|
||||||
|
if wireType == 4 {
|
||||||
|
return fmt.Errorf("proto: BlocksDeleteRequest: wiretype end group for non-group")
|
||||||
|
}
|
||||||
|
if fieldNum <= 0 {
|
||||||
|
return fmt.Errorf("proto: BlocksDeleteRequest: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||||
|
}
|
||||||
|
switch fieldNum {
|
||||||
|
case 1:
|
||||||
|
if wireType != 2 {
|
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field SpaceId", wireType)
|
||||||
|
}
|
||||||
|
var stringLen uint64
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return ErrIntOverflowFile
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
stringLen |= uint64(b&0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
intStringLen := int(stringLen)
|
||||||
|
if intStringLen < 0 {
|
||||||
|
return ErrInvalidLengthFile
|
||||||
|
}
|
||||||
|
postIndex := iNdEx + intStringLen
|
||||||
|
if postIndex < 0 {
|
||||||
|
return ErrInvalidLengthFile
|
||||||
|
}
|
||||||
|
if postIndex > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
m.SpaceId = string(dAtA[iNdEx:postIndex])
|
||||||
|
iNdEx = postIndex
|
||||||
|
case 2:
|
||||||
|
if wireType != 2 {
|
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field Cids", wireType)
|
||||||
|
}
|
||||||
|
var byteLen int
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return ErrIntOverflowFile
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
byteLen |= int(b&0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if byteLen < 0 {
|
||||||
|
return ErrInvalidLengthFile
|
||||||
|
}
|
||||||
|
postIndex := iNdEx + byteLen
|
||||||
|
if postIndex < 0 {
|
||||||
|
return ErrInvalidLengthFile
|
||||||
|
}
|
||||||
|
if postIndex > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
m.Cids = append(m.Cids, make([]byte, postIndex-iNdEx))
|
||||||
|
copy(m.Cids[len(m.Cids)-1], dAtA[iNdEx:postIndex])
|
||||||
|
iNdEx = postIndex
|
||||||
|
default:
|
||||||
|
iNdEx = preIndex
|
||||||
|
skippy, err := skipFile(dAtA[iNdEx:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||||
|
return ErrInvalidLengthFile
|
||||||
|
}
|
||||||
|
if (iNdEx + skippy) > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
iNdEx += skippy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if iNdEx > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (m *BlocksDeleteResponse) Unmarshal(dAtA []byte) error {
|
||||||
|
l := len(dAtA)
|
||||||
|
iNdEx := 0
|
||||||
|
for iNdEx < l {
|
||||||
|
preIndex := iNdEx
|
||||||
|
var wire uint64
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return ErrIntOverflowFile
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
wire |= uint64(b&0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fieldNum := int32(wire >> 3)
|
||||||
|
wireType := int(wire & 0x7)
|
||||||
|
if wireType == 4 {
|
||||||
|
return fmt.Errorf("proto: BlocksDeleteResponse: wiretype end group for non-group")
|
||||||
|
}
|
||||||
|
if fieldNum <= 0 {
|
||||||
|
return fmt.Errorf("proto: BlocksDeleteResponse: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||||
|
}
|
||||||
|
switch fieldNum {
|
||||||
|
default:
|
||||||
|
iNdEx = preIndex
|
||||||
|
skippy, err := skipFile(dAtA[iNdEx:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||||
|
return ErrInvalidLengthFile
|
||||||
|
}
|
||||||
|
if (iNdEx + skippy) > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
iNdEx += skippy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if iNdEx > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
func (m *BlocksCheckRequest) Unmarshal(dAtA []byte) error {
|
func (m *BlocksCheckRequest) Unmarshal(dAtA []byte) error {
|
||||||
l := len(dAtA)
|
l := len(dAtA)
|
||||||
iNdEx := 0
|
iNdEx := 0
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
// Code generated by protoc-gen-go-drpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-drpc. DO NOT EDIT.
|
||||||
// protoc-gen-go-drpc version: v0.0.33
|
// protoc-gen-go-drpc version: v0.0.32
|
||||||
// source: commonfile/fileproto/protos/file.proto
|
// source: commonfile/fileproto/protos/file.proto
|
||||||
|
|
||||||
package fileproto
|
package fileproto
|
||||||
|
|||||||
@ -2,8 +2,8 @@ package fileprotoerr
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/anyproto/any-sync/commonfile/fileproto"
|
"github.com/anytypeio/any-sync/commonfile/fileproto"
|
||||||
"github.com/anyproto/any-sync/net/rpc/rpcerr"
|
"github.com/anytypeio/any-sync/net/rpc/rpcerr"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@ -51,6 +51,13 @@ message BlockPushRequest {
|
|||||||
|
|
||||||
message BlockPushResponse {}
|
message BlockPushResponse {}
|
||||||
|
|
||||||
|
message BlocksDeleteRequest {
|
||||||
|
string spaceId = 1;
|
||||||
|
repeated bytes cids = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BlocksDeleteResponse {}
|
||||||
|
|
||||||
|
|
||||||
message BlocksCheckRequest {
|
message BlocksCheckRequest {
|
||||||
string spaceId = 1;
|
string spaceId = 1;
|
||||||
|
|||||||
@ -2,7 +2,7 @@ package fileservice
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/anyproto/any-sync/commonfile/fileblockstore"
|
"github.com/anytypeio/any-sync/commonfile/fileblockstore"
|
||||||
blocks "github.com/ipfs/go-block-format"
|
blocks "github.com/ipfs/go-block-format"
|
||||||
"github.com/ipfs/go-blockservice"
|
"github.com/ipfs/go-blockservice"
|
||||||
"github.com/ipfs/go-cid"
|
"github.com/ipfs/go-cid"
|
||||||
|
|||||||
@ -3,9 +3,9 @@ package fileservice
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/anyproto/any-sync/app"
|
"github.com/anytypeio/any-sync/app"
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
"github.com/anytypeio/any-sync/app/logger"
|
||||||
"github.com/anyproto/any-sync/commonfile/fileblockstore"
|
"github.com/anytypeio/any-sync/commonfile/fileblockstore"
|
||||||
"github.com/ipfs/go-cid"
|
"github.com/ipfs/go-cid"
|
||||||
chunker "github.com/ipfs/go-ipfs-chunker"
|
chunker "github.com/ipfs/go-ipfs-chunker"
|
||||||
ipld "github.com/ipfs/go-ipld-format"
|
ipld "github.com/ipfs/go-ipld-format"
|
||||||
|
|||||||
62
commonspace/commongetter.go
Normal file
62
commonspace/commongetter.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package commonspace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/object/syncobjectgetter"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/object/treegetter"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
type commonGetter struct {
|
||||||
|
treegetter.TreeGetter
|
||||||
|
spaceId string
|
||||||
|
reservedObjects []syncobjectgetter.SyncObject
|
||||||
|
spaceIsClosed *atomic.Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCommonGetter(spaceId string, getter treegetter.TreeGetter, spaceIsClosed *atomic.Bool) *commonGetter {
|
||||||
|
return &commonGetter{
|
||||||
|
TreeGetter: getter,
|
||||||
|
spaceId: spaceId,
|
||||||
|
spaceIsClosed: spaceIsClosed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *commonGetter) AddObject(object syncobjectgetter.SyncObject) {
|
||||||
|
c.reservedObjects = append(c.reservedObjects, object)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *commonGetter) GetTree(ctx context.Context, spaceId, treeId string) (objecttree.ObjectTree, error) {
|
||||||
|
if c.spaceIsClosed.Load() {
|
||||||
|
return nil, ErrSpaceClosed
|
||||||
|
}
|
||||||
|
if obj := c.getReservedObject(treeId); obj != nil {
|
||||||
|
return obj.(objecttree.ObjectTree), nil
|
||||||
|
}
|
||||||
|
return c.TreeGetter.GetTree(ctx, spaceId, treeId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *commonGetter) getReservedObject(id string) syncobjectgetter.SyncObject {
|
||||||
|
for _, obj := range c.reservedObjects {
|
||||||
|
if obj != nil && obj.Id() == id {
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *commonGetter) GetObject(ctx context.Context, objectId string) (obj syncobjectgetter.SyncObject, err error) {
|
||||||
|
if c.spaceIsClosed.Load() {
|
||||||
|
return nil, ErrSpaceClosed
|
||||||
|
}
|
||||||
|
if obj := c.getReservedObject(objectId); obj != nil {
|
||||||
|
return obj, nil
|
||||||
|
}
|
||||||
|
t, err := c.TreeGetter.GetTree(ctx, c.spaceId, objectId)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
obj = t.(syncobjectgetter.SyncObject)
|
||||||
|
return
|
||||||
|
}
|
||||||
@ -1,8 +1,8 @@
|
|||||||
package commonspace
|
package commonspace
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
"github.com/anytypeio/any-sync/commonspace/spacestorage"
|
||||||
)
|
)
|
||||||
|
|
||||||
type commonStorage struct {
|
type commonStorage struct {
|
||||||
|
|||||||
10
commonspace/config.go
Normal file
10
commonspace/config.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package commonspace
|
||||||
|
|
||||||
|
type ConfigGetter interface {
|
||||||
|
GetSpace() Config
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
GCTTL int `yaml:"gcTTL"`
|
||||||
|
SyncPeriod int `yaml:"syncPeriod"`
|
||||||
|
}
|
||||||
@ -1,11 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
type ConfigGetter interface {
|
|
||||||
GetSpace() Config
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
GCTTL int `yaml:"gcTTL"`
|
|
||||||
SyncPeriod int `yaml:"syncPeriod"`
|
|
||||||
KeepTreeDataInMemory bool `yaml:"keepTreeDataInMemory"`
|
|
||||||
}
|
|
||||||
@ -1,10 +1,9 @@
|
|||||||
//go:generate mockgen -destination mock_credentialprovider/mock_credentialprovider.go github.com/anyproto/any-sync/commonspace/credentialprovider CredentialProvider
|
//go:generate mockgen -destination mock_credentialprovider/mock_credentialprovider.go github.com/anytypeio/any-sync/commonspace/credentialprovider CredentialProvider
|
||||||
package credentialprovider
|
package credentialprovider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/anyproto/any-sync/app"
|
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const CName = "common.commonspace.credentialprovider"
|
const CName = "common.commonspace.credentialprovider"
|
||||||
@ -14,21 +13,12 @@ func NewNoOp() CredentialProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CredentialProvider interface {
|
type CredentialProvider interface {
|
||||||
app.Component
|
|
||||||
GetCredential(ctx context.Context, spaceHeader *spacesyncproto.RawSpaceHeaderWithId) ([]byte, error)
|
GetCredential(ctx context.Context, spaceHeader *spacesyncproto.RawSpaceHeaderWithId) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type noOpProvider struct {
|
type noOpProvider struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n noOpProvider) Init(a *app.App) (err error) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n noOpProvider) Name() (name string) {
|
|
||||||
return CName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n noOpProvider) GetCredential(ctx context.Context, spaceHeader *spacesyncproto.RawSpaceHeaderWithId) ([]byte, error) {
|
func (n noOpProvider) GetCredential(ctx context.Context, spaceHeader *spacesyncproto.RawSpaceHeaderWithId) ([]byte, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: github.com/anyproto/any-sync/commonspace/credentialprovider (interfaces: CredentialProvider)
|
// Source: github.com/anytypeio/any-sync/commonspace/credentialprovider (interfaces: CredentialProvider)
|
||||||
|
|
||||||
// Package mock_credentialprovider is a generated GoMock package.
|
// Package mock_credentialprovider is a generated GoMock package.
|
||||||
package mock_credentialprovider
|
package mock_credentialprovider
|
||||||
@ -8,9 +8,8 @@ import (
|
|||||||
context "context"
|
context "context"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
app "github.com/anyproto/any-sync/app"
|
spacesyncproto "github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||||
spacesyncproto "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
gomock "github.com/golang/mock/gomock"
|
||||||
gomock "go.uber.org/mock/gomock"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockCredentialProvider is a mock of CredentialProvider interface.
|
// MockCredentialProvider is a mock of CredentialProvider interface.
|
||||||
@ -50,31 +49,3 @@ func (mr *MockCredentialProviderMockRecorder) GetCredential(arg0, arg1 interface
|
|||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCredential", reflect.TypeOf((*MockCredentialProvider)(nil).GetCredential), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCredential", reflect.TypeOf((*MockCredentialProvider)(nil).GetCredential), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init mocks base method.
|
|
||||||
func (m *MockCredentialProvider) Init(arg0 *app.App) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Init", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init indicates an expected call of Init.
|
|
||||||
func (mr *MockCredentialProviderMockRecorder) Init(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockCredentialProvider)(nil).Init), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name mocks base method.
|
|
||||||
func (m *MockCredentialProvider) Name() string {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Name")
|
|
||||||
ret0, _ := ret[0].(string)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name indicates an expected call of Name.
|
|
||||||
func (mr *MockCredentialProviderMockRecorder) Name() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockCredentialProvider)(nil).Name))
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,278 +0,0 @@
|
|||||||
package commonspace
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/settings"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"math/rand"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func addIncorrectSnapshot(settingsObject settings.SettingsObject, acc *accountdata.AccountKeys, partialIds map[string]struct{}, newId string) (err error) {
|
|
||||||
factory := settingsstate.NewChangeFactory()
|
|
||||||
bytes, err := factory.CreateObjectDeleteChange(newId, &settingsstate.State{DeletedIds: partialIds}, true)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ch, err := settingsObject.PrepareChange(objecttree.SignableChangeContent{
|
|
||||||
Data: bytes,
|
|
||||||
Key: acc.SignKey,
|
|
||||||
IsSnapshot: true,
|
|
||||||
IsEncrypted: false,
|
|
||||||
Timestamp: time.Now().Unix(),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
res, err := settingsObject.AddRawChanges(context.Background(), objecttree.RawChangesPayload{
|
|
||||||
NewHeads: []string{ch.Id},
|
|
||||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{ch},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if res.Mode != objecttree.Rebuild {
|
|
||||||
return fmt.Errorf("incorrect mode: %d", res.Mode)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSpaceDeleteIds(t *testing.T) {
|
|
||||||
fx := newFixture(t)
|
|
||||||
acc := fx.account.Account()
|
|
||||||
rk := crypto.NewAES()
|
|
||||||
ctx := context.Background()
|
|
||||||
totalObjs := 1500
|
|
||||||
|
|
||||||
// creating space
|
|
||||||
sp, err := fx.spaceService.CreateSpace(ctx, SpaceCreatePayload{
|
|
||||||
SigningKey: acc.SignKey,
|
|
||||||
SpaceType: "type",
|
|
||||||
ReadKey: rk.Bytes(),
|
|
||||||
ReplicationKey: 10,
|
|
||||||
MasterKey: acc.PeerKey,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, sp)
|
|
||||||
|
|
||||||
// initializing space
|
|
||||||
spc, err := fx.spaceService.NewSpace(ctx, sp)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, spc)
|
|
||||||
// adding space to tree manager
|
|
||||||
fx.treeManager.space = spc
|
|
||||||
err = spc.Init(ctx)
|
|
||||||
require.NoError(t, err)
|
|
||||||
close(fx.treeManager.waitLoad)
|
|
||||||
|
|
||||||
var ids []string
|
|
||||||
for i := 0; i < totalObjs; i++ {
|
|
||||||
// creating a tree
|
|
||||||
bytes := make([]byte, 32)
|
|
||||||
rand.Read(bytes)
|
|
||||||
doc, err := spc.TreeBuilder().CreateTree(ctx, objecttree.ObjectTreeCreatePayload{
|
|
||||||
PrivKey: acc.SignKey,
|
|
||||||
ChangeType: "some",
|
|
||||||
SpaceId: spc.Id(),
|
|
||||||
IsEncrypted: false,
|
|
||||||
Seed: bytes,
|
|
||||||
Timestamp: time.Now().Unix(),
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
tr, err := spc.TreeBuilder().PutTree(ctx, doc, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
ids = append(ids, tr.Id())
|
|
||||||
tr.Close()
|
|
||||||
}
|
|
||||||
// deleting trees
|
|
||||||
for _, id := range ids {
|
|
||||||
err = spc.DeleteTree(ctx, id)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
time.Sleep(3 * time.Second)
|
|
||||||
spc.Close()
|
|
||||||
require.Equal(t, len(ids), len(fx.treeManager.deletedIds))
|
|
||||||
}
|
|
||||||
|
|
||||||
func createTree(t *testing.T, ctx context.Context, spc Space, acc *accountdata.AccountKeys) string {
|
|
||||||
bytes := make([]byte, 32)
|
|
||||||
rand.Read(bytes)
|
|
||||||
doc, err := spc.TreeBuilder().CreateTree(ctx, objecttree.ObjectTreeCreatePayload{
|
|
||||||
PrivKey: acc.SignKey,
|
|
||||||
ChangeType: "some",
|
|
||||||
SpaceId: spc.Id(),
|
|
||||||
IsEncrypted: false,
|
|
||||||
Seed: bytes,
|
|
||||||
Timestamp: time.Now().Unix(),
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
tr, err := spc.TreeBuilder().PutTree(ctx, doc, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
tr.Close()
|
|
||||||
return tr.Id()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSpaceDeleteIdsIncorrectSnapshot(t *testing.T) {
|
|
||||||
fx := newFixture(t)
|
|
||||||
acc := fx.account.Account()
|
|
||||||
rk := crypto.NewAES()
|
|
||||||
ctx := context.Background()
|
|
||||||
totalObjs := 1500
|
|
||||||
partialObjs := 300
|
|
||||||
|
|
||||||
// creating space
|
|
||||||
sp, err := fx.spaceService.CreateSpace(ctx, SpaceCreatePayload{
|
|
||||||
SigningKey: acc.SignKey,
|
|
||||||
SpaceType: "type",
|
|
||||||
ReadKey: rk.Bytes(),
|
|
||||||
ReplicationKey: 10,
|
|
||||||
MasterKey: acc.PeerKey,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, sp)
|
|
||||||
|
|
||||||
// initializing space
|
|
||||||
spc, err := fx.spaceService.NewSpace(ctx, sp)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, spc)
|
|
||||||
// adding space to tree manager
|
|
||||||
fx.treeManager.space = spc
|
|
||||||
err = spc.Init(ctx)
|
|
||||||
close(fx.treeManager.waitLoad)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
settingsObject := spc.(*space).app.MustComponent(settings.CName).(settings.Settings).SettingsObject()
|
|
||||||
var ids []string
|
|
||||||
for i := 0; i < totalObjs; i++ {
|
|
||||||
id := createTree(t, ctx, spc, acc)
|
|
||||||
ids = append(ids, id)
|
|
||||||
}
|
|
||||||
// copying storage, so we will have all the trees locally
|
|
||||||
inmemory := spc.Storage().(*commonStorage).SpaceStorage.(*spacestorage.InMemorySpaceStorage)
|
|
||||||
storageCopy := inmemory.CopyStorage()
|
|
||||||
treesCopy := inmemory.AllTrees()
|
|
||||||
|
|
||||||
// deleting trees
|
|
||||||
for _, id := range ids {
|
|
||||||
err = spc.DeleteTree(ctx, id)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
mapIds := map[string]struct{}{}
|
|
||||||
for _, id := range ids[:partialObjs] {
|
|
||||||
mapIds[id] = struct{}{}
|
|
||||||
}
|
|
||||||
// adding snapshot that breaks the state
|
|
||||||
err = addIncorrectSnapshot(settingsObject, acc, mapIds, ids[partialObjs])
|
|
||||||
require.NoError(t, err)
|
|
||||||
// copying the contents of the settings tree
|
|
||||||
treesCopy[settingsObject.Id()] = settingsObject.Storage()
|
|
||||||
storageCopy.SetTrees(treesCopy)
|
|
||||||
spc.Close()
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
// now we replace the storage, so the trees are back, but the settings object says that they are deleted
|
|
||||||
fx.storageProvider.(*spacestorage.InMemorySpaceStorageProvider).SetStorage(storageCopy)
|
|
||||||
|
|
||||||
spc, err = fx.spaceService.NewSpace(ctx, sp)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, spc)
|
|
||||||
fx.treeManager.waitLoad = make(chan struct{})
|
|
||||||
fx.treeManager.space = spc
|
|
||||||
fx.treeManager.deletedIds = nil
|
|
||||||
err = spc.Init(ctx)
|
|
||||||
require.NoError(t, err)
|
|
||||||
close(fx.treeManager.waitLoad)
|
|
||||||
|
|
||||||
// waiting until everything is deleted
|
|
||||||
time.Sleep(3 * time.Second)
|
|
||||||
require.Equal(t, len(ids), len(fx.treeManager.deletedIds))
|
|
||||||
|
|
||||||
// checking that new snapshot will contain all the changes
|
|
||||||
settingsObject = spc.(*space).app.MustComponent(settings.CName).(settings.Settings).SettingsObject()
|
|
||||||
settings.DoSnapshot = func(treeLen int) bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
id := createTree(t, ctx, spc, acc)
|
|
||||||
err = spc.DeleteTree(ctx, id)
|
|
||||||
require.NoError(t, err)
|
|
||||||
delIds := settingsObject.Root().Model.(*spacesyncproto.SettingsData).Snapshot.DeletedIds
|
|
||||||
require.Equal(t, totalObjs+1, len(delIds))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSpaceDeleteIdsMarkDeleted(t *testing.T) {
|
|
||||||
fx := newFixture(t)
|
|
||||||
acc := fx.account.Account()
|
|
||||||
rk := crypto.NewAES()
|
|
||||||
ctx := context.Background()
|
|
||||||
totalObjs := 1500
|
|
||||||
|
|
||||||
// creating space
|
|
||||||
sp, err := fx.spaceService.CreateSpace(ctx, SpaceCreatePayload{
|
|
||||||
SigningKey: acc.SignKey,
|
|
||||||
SpaceType: "type",
|
|
||||||
ReadKey: rk.Bytes(),
|
|
||||||
ReplicationKey: 10,
|
|
||||||
MasterKey: acc.PeerKey,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, sp)
|
|
||||||
|
|
||||||
// initializing space
|
|
||||||
spc, err := fx.spaceService.NewSpace(ctx, sp)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, spc)
|
|
||||||
// adding space to tree manager
|
|
||||||
fx.treeManager.space = spc
|
|
||||||
err = spc.Init(ctx)
|
|
||||||
require.NoError(t, err)
|
|
||||||
close(fx.treeManager.waitLoad)
|
|
||||||
|
|
||||||
settingsObject := spc.(*space).app.MustComponent(settings.CName).(settings.Settings).SettingsObject()
|
|
||||||
var ids []string
|
|
||||||
for i := 0; i < totalObjs; i++ {
|
|
||||||
id := createTree(t, ctx, spc, acc)
|
|
||||||
ids = append(ids, id)
|
|
||||||
}
|
|
||||||
// copying storage, so we will have the same contents, except for empty trees
|
|
||||||
inmemory := spc.Storage().(*commonStorage).SpaceStorage.(*spacestorage.InMemorySpaceStorage)
|
|
||||||
storageCopy := inmemory.CopyStorage()
|
|
||||||
|
|
||||||
// deleting trees, this will prepare the document to have all the deletion changes
|
|
||||||
for _, id := range ids {
|
|
||||||
err = spc.DeleteTree(ctx, id)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
treesMap := map[string]treestorage.TreeStorage{}
|
|
||||||
// copying the contents of the settings tree
|
|
||||||
treesMap[settingsObject.Id()] = settingsObject.Storage()
|
|
||||||
storageCopy.SetTrees(treesMap)
|
|
||||||
spc.Close()
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
// now we replace the storage, so the trees are back, but the settings object says that they are deleted
|
|
||||||
fx.storageProvider.(*spacestorage.InMemorySpaceStorageProvider).SetStorage(storageCopy)
|
|
||||||
|
|
||||||
spc, err = fx.spaceService.NewSpace(ctx, sp)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, spc)
|
|
||||||
fx.treeManager.space = spc
|
|
||||||
fx.treeManager.waitLoad = make(chan struct{})
|
|
||||||
fx.treeManager.deletedIds = nil
|
|
||||||
fx.treeManager.markedIds = nil
|
|
||||||
err = spc.Init(ctx)
|
|
||||||
require.NoError(t, err)
|
|
||||||
close(fx.treeManager.waitLoad)
|
|
||||||
|
|
||||||
// waiting until everything is deleted
|
|
||||||
time.Sleep(3 * time.Second)
|
|
||||||
require.Equal(t, len(ids), len(fx.treeManager.markedIds))
|
|
||||||
require.Zero(t, len(fx.treeManager.deletedIds))
|
|
||||||
}
|
|
||||||
@ -1,144 +0,0 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
|
||||||
// Source: github.com/anyproto/any-sync/commonspace/deletionstate (interfaces: ObjectDeletionState)
|
|
||||||
|
|
||||||
// Package mock_deletionstate is a generated GoMock package.
|
|
||||||
package mock_deletionstate
|
|
||||||
|
|
||||||
import (
|
|
||||||
reflect "reflect"
|
|
||||||
|
|
||||||
app "github.com/anyproto/any-sync/app"
|
|
||||||
deletionstate "github.com/anyproto/any-sync/commonspace/deletionstate"
|
|
||||||
gomock "go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockObjectDeletionState is a mock of ObjectDeletionState interface.
|
|
||||||
type MockObjectDeletionState struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockObjectDeletionStateMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockObjectDeletionStateMockRecorder is the mock recorder for MockObjectDeletionState.
|
|
||||||
type MockObjectDeletionStateMockRecorder struct {
|
|
||||||
mock *MockObjectDeletionState
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockObjectDeletionState creates a new mock instance.
|
|
||||||
func NewMockObjectDeletionState(ctrl *gomock.Controller) *MockObjectDeletionState {
|
|
||||||
mock := &MockObjectDeletionState{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockObjectDeletionStateMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockObjectDeletionState) EXPECT() *MockObjectDeletionStateMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add mocks base method.
|
|
||||||
func (m *MockObjectDeletionState) Add(arg0 map[string]struct{}) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "Add", arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add indicates an expected call of Add.
|
|
||||||
func (mr *MockObjectDeletionStateMockRecorder) Add(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockObjectDeletionState)(nil).Add), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddObserver mocks base method.
|
|
||||||
func (m *MockObjectDeletionState) AddObserver(arg0 deletionstate.StateUpdateObserver) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "AddObserver", arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddObserver indicates an expected call of AddObserver.
|
|
||||||
func (mr *MockObjectDeletionStateMockRecorder) AddObserver(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddObserver", reflect.TypeOf((*MockObjectDeletionState)(nil).AddObserver), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete mocks base method.
|
|
||||||
func (m *MockObjectDeletionState) Delete(arg0 string) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Delete", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete indicates an expected call of Delete.
|
|
||||||
func (mr *MockObjectDeletionStateMockRecorder) Delete(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockObjectDeletionState)(nil).Delete), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exists mocks base method.
|
|
||||||
func (m *MockObjectDeletionState) Exists(arg0 string) bool {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Exists", arg0)
|
|
||||||
ret0, _ := ret[0].(bool)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exists indicates an expected call of Exists.
|
|
||||||
func (mr *MockObjectDeletionStateMockRecorder) Exists(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exists", reflect.TypeOf((*MockObjectDeletionState)(nil).Exists), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter mocks base method.
|
|
||||||
func (m *MockObjectDeletionState) Filter(arg0 []string) []string {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Filter", arg0)
|
|
||||||
ret0, _ := ret[0].([]string)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter indicates an expected call of Filter.
|
|
||||||
func (mr *MockObjectDeletionStateMockRecorder) Filter(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Filter", reflect.TypeOf((*MockObjectDeletionState)(nil).Filter), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetQueued mocks base method.
|
|
||||||
func (m *MockObjectDeletionState) GetQueued() []string {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "GetQueued")
|
|
||||||
ret0, _ := ret[0].([]string)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetQueued indicates an expected call of GetQueued.
|
|
||||||
func (mr *MockObjectDeletionStateMockRecorder) GetQueued() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetQueued", reflect.TypeOf((*MockObjectDeletionState)(nil).GetQueued))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init mocks base method.
|
|
||||||
func (m *MockObjectDeletionState) Init(arg0 *app.App) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Init", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init indicates an expected call of Init.
|
|
||||||
func (mr *MockObjectDeletionStateMockRecorder) Init(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockObjectDeletionState)(nil).Init), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name mocks base method.
|
|
||||||
func (m *MockObjectDeletionState) Name() string {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Name")
|
|
||||||
ret0, _ := ret[0].(string)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name indicates an expected call of Name.
|
|
||||||
func (mr *MockObjectDeletionStateMockRecorder) Name() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockObjectDeletionState)(nil).Name))
|
|
||||||
}
|
|
||||||
@ -3,45 +3,49 @@ package headsync
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"github.com/anytypeio/any-sync/app/ldiff"
|
||||||
|
"github.com/anytypeio/any-sync/app/logger"
|
||||||
"github.com/anyproto/any-sync/app/ldiff"
|
"github.com/anytypeio/any-sync/commonspace/credentialprovider"
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/synctree"
|
||||||
"github.com/anyproto/any-sync/commonspace/credentialprovider"
|
"github.com/anytypeio/any-sync/commonspace/object/treegetter"
|
||||||
"github.com/anyproto/any-sync/commonspace/deletionstate"
|
"github.com/anytypeio/any-sync/commonspace/peermanager"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl"
|
"github.com/anytypeio/any-sync/commonspace/settings/settingsstate"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
"github.com/anytypeio/any-sync/commonspace/spacestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
"github.com/anytypeio/any-sync/commonspace/syncstatus"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anytypeio/any-sync/net/peer"
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
"github.com/anytypeio/any-sync/net/rpc/rpcerr"
|
||||||
"github.com/anyproto/any-sync/net/peer"
|
|
||||||
"github.com/anyproto/any-sync/net/rpc/rpcerr"
|
|
||||||
"github.com/anyproto/any-sync/util/slice"
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DiffSyncer interface {
|
type DiffSyncer interface {
|
||||||
Sync(ctx context.Context) error
|
Sync(ctx context.Context) error
|
||||||
RemoveObjects(ids []string)
|
RemoveObjects(ids []string)
|
||||||
UpdateHeads(id string, heads []string)
|
UpdateHeads(id string, heads []string)
|
||||||
Init()
|
Init(deletionState settingsstate.ObjectDeletionState)
|
||||||
Close() error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDiffSyncer(hs *headSync) DiffSyncer {
|
func newDiffSyncer(
|
||||||
|
spaceId string,
|
||||||
|
diff ldiff.Diff,
|
||||||
|
peerManager peermanager.PeerManager,
|
||||||
|
cache treegetter.TreeGetter,
|
||||||
|
storage spacestorage.SpaceStorage,
|
||||||
|
clientFactory spacesyncproto.ClientFactory,
|
||||||
|
syncStatus syncstatus.StatusUpdater,
|
||||||
|
credentialProvider credentialprovider.CredentialProvider,
|
||||||
|
log logger.CtxLogger) DiffSyncer {
|
||||||
return &diffSyncer{
|
return &diffSyncer{
|
||||||
diff: hs.diff,
|
diff: diff,
|
||||||
spaceId: hs.spaceId,
|
spaceId: spaceId,
|
||||||
treeManager: hs.treeManager,
|
cache: cache,
|
||||||
storage: hs.storage,
|
storage: storage,
|
||||||
peerManager: hs.peerManager,
|
peerManager: peerManager,
|
||||||
clientFactory: spacesyncproto.ClientFactoryFunc(spacesyncproto.NewDRPCSpaceSyncClient),
|
clientFactory: clientFactory,
|
||||||
credentialProvider: hs.credentialProvider,
|
credentialProvider: credentialProvider,
|
||||||
log: log,
|
log: log,
|
||||||
syncStatus: hs.syncStatus,
|
syncStatus: syncStatus,
|
||||||
deletionState: hs.deletionState,
|
|
||||||
syncAcl: hs.syncAcl,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,20 +53,18 @@ type diffSyncer struct {
|
|||||||
spaceId string
|
spaceId string
|
||||||
diff ldiff.Diff
|
diff ldiff.Diff
|
||||||
peerManager peermanager.PeerManager
|
peerManager peermanager.PeerManager
|
||||||
treeManager treemanager.TreeManager
|
cache treegetter.TreeGetter
|
||||||
storage spacestorage.SpaceStorage
|
storage spacestorage.SpaceStorage
|
||||||
clientFactory spacesyncproto.ClientFactory
|
clientFactory spacesyncproto.ClientFactory
|
||||||
log logger.CtxLogger
|
log logger.CtxLogger
|
||||||
deletionState deletionstate.ObjectDeletionState
|
deletionState settingsstate.ObjectDeletionState
|
||||||
credentialProvider credentialprovider.CredentialProvider
|
credentialProvider credentialprovider.CredentialProvider
|
||||||
syncStatus syncstatus.StatusUpdater
|
syncStatus syncstatus.StatusUpdater
|
||||||
treeSyncer treemanager.TreeSyncer
|
|
||||||
syncAcl syncacl.SyncAcl
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *diffSyncer) Init() {
|
func (d *diffSyncer) Init(deletionState settingsstate.ObjectDeletionState) {
|
||||||
|
d.deletionState = deletionState
|
||||||
d.deletionState.AddObserver(d.RemoveObjects)
|
d.deletionState.AddObserver(d.RemoveObjects)
|
||||||
d.treeSyncer = d.treeManager.NewTreeSyncer(d.spaceId, d.treeManager)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *diffSyncer) RemoveObjects(ids []string) {
|
func (d *diffSyncer) RemoveObjects(ids []string) {
|
||||||
@ -111,17 +113,10 @@ func (d *diffSyncer) Sync(ctx context.Context) error {
|
|||||||
|
|
||||||
func (d *diffSyncer) syncWithPeer(ctx context.Context, p peer.Peer) (err error) {
|
func (d *diffSyncer) syncWithPeer(ctx context.Context, p peer.Peer) (err error) {
|
||||||
ctx = logger.CtxWithFields(ctx, zap.String("peerId", p.Id()))
|
ctx = logger.CtxWithFields(ctx, zap.String("peerId", p.Id()))
|
||||||
conn, err := p.AcquireDrpcConn(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer p.ReleaseDrpcConn(conn)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cl = d.clientFactory.Client(conn)
|
cl = d.clientFactory.Client(p)
|
||||||
rdiff = NewRemoteDiff(d.spaceId, cl)
|
rdiff = NewRemoteDiff(d.spaceId, cl)
|
||||||
stateCounter = d.syncStatus.StateCounter()
|
stateCounter = d.syncStatus.StateCounter()
|
||||||
syncAclId = d.syncAcl.Id()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
newIds, changedIds, removedIds, err := d.diff.Diff(ctx, rdiff)
|
newIds, changedIds, removedIds, err := d.diff.Diff(ctx, rdiff)
|
||||||
@ -129,7 +124,7 @@ func (d *diffSyncer) syncWithPeer(ctx context.Context, p peer.Peer) (err error)
|
|||||||
if err != nil && err != spacesyncproto.ErrSpaceMissing {
|
if err != nil && err != spacesyncproto.ErrSpaceMissing {
|
||||||
if err == spacesyncproto.ErrSpaceIsDeleted {
|
if err == spacesyncproto.ErrSpaceIsDeleted {
|
||||||
d.log.Debug("got space deleted while syncing")
|
d.log.Debug("got space deleted while syncing")
|
||||||
d.treeSyncer.SyncAll(ctx, p.Id(), []string{d.storage.SpaceSettingsId()}, nil)
|
d.syncTrees(ctx, p.Id(), []string{d.storage.SpaceSettingsId()})
|
||||||
}
|
}
|
||||||
d.syncStatus.SetNodesOnline(p.Id(), false)
|
d.syncStatus.SetNodesOnline(p.Id(), false)
|
||||||
return fmt.Errorf("diff error: %v", err)
|
return fmt.Errorf("diff error: %v", err)
|
||||||
@ -142,36 +137,45 @@ func (d *diffSyncer) syncWithPeer(ctx context.Context, p peer.Peer) (err error)
|
|||||||
|
|
||||||
totalLen := len(newIds) + len(changedIds) + len(removedIds)
|
totalLen := len(newIds) + len(changedIds) + len(removedIds)
|
||||||
// not syncing ids which were removed through settings document
|
// not syncing ids which were removed through settings document
|
||||||
missingIds := d.deletionState.Filter(newIds)
|
filteredIds := d.deletionState.FilterJoin(newIds, changedIds, removedIds)
|
||||||
existingIds := append(d.deletionState.Filter(removedIds), d.deletionState.Filter(changedIds)...)
|
|
||||||
d.syncStatus.RemoveAllExcept(p.Id(), existingIds, stateCounter)
|
|
||||||
|
|
||||||
prevExistingLen := len(existingIds)
|
d.syncStatus.RemoveAllExcept(p.Id(), filteredIds, stateCounter)
|
||||||
existingIds = slice.DiscardFromSlice(existingIds, func(s string) bool {
|
|
||||||
return s == syncAclId
|
|
||||||
})
|
|
||||||
// if we removed acl head from the list
|
|
||||||
if len(existingIds) < prevExistingLen {
|
|
||||||
if syncErr := d.syncAcl.SyncWithPeer(ctx, p.Id()); syncErr != nil {
|
|
||||||
log.Warn("failed to send acl sync message to peer", zap.String("aclId", syncAclId))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// treeSyncer should not get acl id, that's why we filter existing ids before
|
d.syncTrees(ctx, p.Id(), filteredIds)
|
||||||
err = d.treeSyncer.SyncAll(ctx, p.Id(), existingIds, missingIds)
|
|
||||||
if err != nil {
|
d.log.Info("sync done:", zap.Int("newIds", len(newIds)),
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.log.Info("sync done:",
|
|
||||||
zap.Int("newIds", len(newIds)),
|
|
||||||
zap.Int("changedIds", len(changedIds)),
|
zap.Int("changedIds", len(changedIds)),
|
||||||
zap.Int("removedIds", len(removedIds)),
|
zap.Int("removedIds", len(removedIds)),
|
||||||
zap.Int("already deleted ids", totalLen-prevExistingLen-len(missingIds)),
|
zap.Int("already deleted ids", totalLen-len(filteredIds)),
|
||||||
zap.String("peerId", p.Id()),
|
zap.String("peerId", p.Id()),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *diffSyncer) syncTrees(ctx context.Context, peerId string, trees []string) {
|
||||||
|
for _, tId := range trees {
|
||||||
|
tree, err := d.cache.GetTree(ctx, d.spaceId, tId)
|
||||||
|
if err != nil {
|
||||||
|
d.log.InfoCtx(ctx, "can't load tree", zap.Error(err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
syncTree, ok := tree.(synctree.SyncTree)
|
||||||
|
if !ok {
|
||||||
|
d.log.InfoCtx(ctx, "not a sync tree", zap.String("objectId", tId))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// the idea why we call it directly is that if we try to get it from cache
|
||||||
|
// it may be already there (i.e. loaded)
|
||||||
|
// and build func will not be called, thus we won't sync the tree
|
||||||
|
// therefore we just do it manually
|
||||||
|
if err = syncTree.SyncWithPeer(ctx, peerId); err != nil {
|
||||||
|
d.log.WarnCtx(ctx, "synctree.SyncWithPeer error", zap.Error(err), zap.String("treeId", tId))
|
||||||
|
} else {
|
||||||
|
d.log.DebugCtx(ctx, "success synctree.SyncWithPeer", zap.String("treeId", tId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (d *diffSyncer) sendPushSpaceRequest(ctx context.Context, peerId string, cl spacesyncproto.DRPCSpaceSyncClient) (err error) {
|
func (d *diffSyncer) sendPushSpaceRequest(ctx context.Context, peerId string, cl spacesyncproto.DRPCSpaceSyncClient) (err error) {
|
||||||
aclStorage, err := d.storage.AclStorage()
|
aclStorage, err := d.storage.AclStorage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -234,7 +238,3 @@ func (d *diffSyncer) subscribe(ctx context.Context, peerId string) (err error) {
|
|||||||
Payload: payload,
|
Payload: payload,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *diffSyncer) Close() error {
|
|
||||||
return d.treeSyncer.Close()
|
|
||||||
}
|
|
||||||
|
|||||||
@ -4,19 +4,28 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/anytypeio/any-sync/app/ldiff"
|
||||||
|
"github.com/anytypeio/any-sync/app/ldiff/mock_ldiff"
|
||||||
|
"github.com/anytypeio/any-sync/app/logger"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/credentialprovider/mock_credentialprovider"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/object/acl/liststorage/mock_liststorage"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
|
mock_treestorage "github.com/anytypeio/any-sync/commonspace/object/tree/treestorage/mock_treestorage"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/object/treegetter/mock_treegetter"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/peermanager/mock_peermanager"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/settings/settingsstate/mock_settingsstate"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/spacestorage/mock_spacestorage"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/spacesyncproto/mock_spacesyncproto"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/syncstatus"
|
||||||
|
"github.com/anytypeio/any-sync/net/peer"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/libp2p/go-libp2p/core/sec"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"storj.io/drpc"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/app/ldiff"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage/mock_liststorage"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage/mock_treestorage"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
"github.com/anyproto/any-sync/net/peer"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
"storj.io/drpc"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type pushSpaceRequestMatcher struct {
|
type pushSpaceRequestMatcher struct {
|
||||||
@ -27,6 +36,56 @@ type pushSpaceRequestMatcher struct {
|
|||||||
spaceHeader *spacesyncproto.RawSpaceHeaderWithId
|
spaceHeader *spacesyncproto.RawSpaceHeaderWithId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p pushSpaceRequestMatcher) Matches(x interface{}) bool {
|
||||||
|
res, ok := x.(*spacesyncproto.SpacePushRequest)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.Payload.AclPayloadId == p.aclRootId && res.Payload.SpaceHeader == p.spaceHeader && res.Payload.SpaceSettingsPayloadId == p.settingsId && bytes.Equal(p.credential, res.Credential)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p pushSpaceRequestMatcher) String() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockPeer struct{}
|
||||||
|
|
||||||
|
func (m mockPeer) TryClose(objectTTL time.Duration) (res bool, err error) {
|
||||||
|
return true, m.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockPeer) Id() string {
|
||||||
|
return "mockId"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockPeer) LastUsage() time.Time {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockPeer) Secure() sec.SecureConn {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockPeer) UpdateLastUsage() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockPeer) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockPeer) Closed() <-chan struct{} {
|
||||||
|
return make(chan struct{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockPeer) Invoke(ctx context.Context, rpc string, enc drpc.Encoding, in, out drpc.Message) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockPeer) NewStream(ctx context.Context, rpc string, enc drpc.Encoding) (drpc.Stream, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
func newPushSpaceRequestMatcher(
|
func newPushSpaceRequestMatcher(
|
||||||
spaceId string,
|
spaceId string,
|
||||||
aclRootId string,
|
aclRootId string,
|
||||||
@ -42,159 +101,80 @@ func newPushSpaceRequestMatcher(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p pushSpaceRequestMatcher) Matches(x interface{}) bool {
|
func TestDiffSyncer_Sync(t *testing.T) {
|
||||||
res, ok := x.(*spacesyncproto.SpacePushRequest)
|
// setup
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.Payload.AclPayloadId == p.aclRootId && res.Payload.SpaceHeader == p.spaceHeader && res.Payload.SpaceSettingsPayloadId == p.settingsId && bytes.Equal(p.credential, res.Credential)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p pushSpaceRequestMatcher) String() string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockPeer struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
fx.diffSyncer.clientFactory = spacesyncproto.ClientFactoryFunc(func(cc drpc.Conn) spacesyncproto.DRPCSpaceSyncClient {
|
|
||||||
return fx.clientMock
|
|
||||||
})
|
|
||||||
fx.deletionStateMock.EXPECT().AddObserver(gomock.Any())
|
|
||||||
fx.treeManagerMock.EXPECT().NewTreeSyncer(fx.spaceState.SpaceId, fx.treeManagerMock).Return(fx.treeSyncerMock)
|
|
||||||
fx.diffSyncer.Init()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDiffSyncer(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
diffMock := mock_ldiff.NewMockDiff(ctrl)
|
||||||
|
peerManagerMock := mock_peermanager.NewMockPeerManager(ctrl)
|
||||||
|
cacheMock := mock_treegetter.NewMockTreeGetter(ctrl)
|
||||||
|
stMock := mock_spacestorage.NewMockSpaceStorage(ctrl)
|
||||||
|
clientMock := mock_spacesyncproto.NewMockDRPCSpaceSyncClient(ctrl)
|
||||||
|
factory := spacesyncproto.ClientFactoryFunc(func(cc drpc.Conn) spacesyncproto.DRPCSpaceSyncClient {
|
||||||
|
return clientMock
|
||||||
|
})
|
||||||
|
credentialProvider := mock_credentialprovider.NewMockCredentialProvider(ctrl)
|
||||||
|
delState := mock_settingsstate.NewMockObjectDeletionState(ctrl)
|
||||||
|
spaceId := "spaceId"
|
||||||
|
aclRootId := "aclRootId"
|
||||||
|
l := logger.NewNamed(spaceId)
|
||||||
|
diffSyncer := newDiffSyncer(spaceId, diffMock, peerManagerMock, cacheMock, stMock, factory, syncstatus.NewNoOpSyncStatus(), credentialProvider, l)
|
||||||
|
delState.EXPECT().AddObserver(gomock.Any())
|
||||||
|
diffSyncer.Init(delState)
|
||||||
|
|
||||||
t.Run("diff syncer sync", func(t *testing.T) {
|
t.Run("diff syncer sync", func(t *testing.T) {
|
||||||
fx := newHeadSyncFixture(t)
|
peerManagerMock.EXPECT().
|
||||||
fx.initDiffSyncer(t)
|
|
||||||
defer fx.stop()
|
|
||||||
mPeer := mockPeer{}
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
|
||||||
fx.peerManagerMock.EXPECT().
|
|
||||||
GetResponsiblePeers(gomock.Any()).
|
GetResponsiblePeers(gomock.Any()).
|
||||||
Return([]peer.Peer{mPeer}, nil)
|
Return([]peer.Peer{mockPeer{}}, nil)
|
||||||
fx.diffMock.EXPECT().
|
diffMock.EXPECT().
|
||||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(fx.spaceState.SpaceId, fx.clientMock))).
|
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(spaceId, clientMock))).
|
||||||
Return([]string{"new"}, []string{"changed"}, nil, nil)
|
Return([]string{"new"}, []string{"changed"}, nil, nil)
|
||||||
fx.deletionStateMock.EXPECT().Filter([]string{"new"}).Return([]string{"new"}).Times(1)
|
delState.EXPECT().FilterJoin(gomock.Any()).Return([]string{"new", "changed"})
|
||||||
fx.deletionStateMock.EXPECT().Filter([]string{"changed"}).Return([]string{"changed"}).Times(1)
|
for _, arg := range []string{"new", "changed"} {
|
||||||
fx.deletionStateMock.EXPECT().Filter(nil).Return(nil).Times(1)
|
cacheMock.EXPECT().
|
||||||
fx.treeSyncerMock.EXPECT().SyncAll(gomock.Any(), mPeer.Id(), []string{"changed"}, []string{"new"}).Return(nil)
|
GetTree(gomock.Any(), spaceId, arg).
|
||||||
require.NoError(t, fx.diffSyncer.Sync(ctx))
|
Return(nil, nil)
|
||||||
})
|
}
|
||||||
|
require.NoError(t, diffSyncer.Sync(ctx))
|
||||||
t.Run("diff syncer sync, acl changed", func(t *testing.T) {
|
|
||||||
fx := newHeadSyncFixture(t)
|
|
||||||
fx.initDiffSyncer(t)
|
|
||||||
defer fx.stop()
|
|
||||||
mPeer := mockPeer{}
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
|
||||||
fx.peerManagerMock.EXPECT().
|
|
||||||
GetResponsiblePeers(gomock.Any()).
|
|
||||||
Return([]peer.Peer{mPeer}, nil)
|
|
||||||
fx.diffMock.EXPECT().
|
|
||||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(fx.spaceState.SpaceId, fx.clientMock))).
|
|
||||||
Return([]string{"new"}, []string{"changed"}, nil, nil)
|
|
||||||
fx.deletionStateMock.EXPECT().Filter([]string{"new"}).Return([]string{"new"}).Times(1)
|
|
||||||
fx.deletionStateMock.EXPECT().Filter([]string{"changed"}).Return([]string{"changed", "aclId"}).Times(1)
|
|
||||||
fx.deletionStateMock.EXPECT().Filter(nil).Return(nil).Times(1)
|
|
||||||
fx.treeSyncerMock.EXPECT().SyncAll(gomock.Any(), mPeer.Id(), []string{"changed"}, []string{"new"}).Return(nil)
|
|
||||||
fx.aclMock.EXPECT().SyncWithPeer(gomock.Any(), mPeer.Id()).Return(nil)
|
|
||||||
require.NoError(t, fx.diffSyncer.Sync(ctx))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("diff syncer sync conf error", func(t *testing.T) {
|
t.Run("diff syncer sync conf error", func(t *testing.T) {
|
||||||
fx := newHeadSyncFixture(t)
|
peerManagerMock.EXPECT().
|
||||||
fx.initDiffSyncer(t)
|
|
||||||
defer fx.stop()
|
|
||||||
ctx := context.Background()
|
|
||||||
fx.peerManagerMock.EXPECT().
|
|
||||||
GetResponsiblePeers(gomock.Any()).
|
GetResponsiblePeers(gomock.Any()).
|
||||||
Return(nil, fmt.Errorf("some error"))
|
Return(nil, fmt.Errorf("some error"))
|
||||||
|
|
||||||
require.Error(t, fx.diffSyncer.Sync(ctx))
|
require.Error(t, diffSyncer.Sync(ctx))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("deletion state remove objects", func(t *testing.T) {
|
t.Run("deletion state remove objects", func(t *testing.T) {
|
||||||
fx := newHeadSyncFixture(t)
|
|
||||||
fx.initDiffSyncer(t)
|
|
||||||
defer fx.stop()
|
|
||||||
deletedId := "id"
|
deletedId := "id"
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
delState.EXPECT().Exists(deletedId).Return(true)
|
||||||
fx.deletionStateMock.EXPECT().Exists(deletedId).Return(true)
|
|
||||||
|
|
||||||
// this should not result in any mock being called
|
// this should not result in any mock being called
|
||||||
fx.diffSyncer.UpdateHeads(deletedId, []string{"someHead"})
|
diffSyncer.UpdateHeads(deletedId, []string{"someHead"})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("update heads updates diff", func(t *testing.T) {
|
t.Run("update heads updates diff", func(t *testing.T) {
|
||||||
fx := newHeadSyncFixture(t)
|
|
||||||
fx.initDiffSyncer(t)
|
|
||||||
defer fx.stop()
|
|
||||||
newId := "newId"
|
newId := "newId"
|
||||||
newHeads := []string{"h1", "h2"}
|
newHeads := []string{"h1", "h2"}
|
||||||
hash := "hash"
|
hash := "hash"
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
diffMock.EXPECT().Set(ldiff.Element{
|
||||||
fx.diffMock.EXPECT().Set(ldiff.Element{
|
|
||||||
Id: newId,
|
Id: newId,
|
||||||
Head: concatStrings(newHeads),
|
Head: concatStrings(newHeads),
|
||||||
})
|
})
|
||||||
fx.diffMock.EXPECT().Hash().Return(hash)
|
diffMock.EXPECT().Hash().Return(hash)
|
||||||
fx.deletionStateMock.EXPECT().Exists(newId).Return(false)
|
delState.EXPECT().Exists(newId).Return(false)
|
||||||
fx.storageMock.EXPECT().WriteSpaceHash(hash)
|
stMock.EXPECT().WriteSpaceHash(hash)
|
||||||
fx.diffSyncer.UpdateHeads(newId, newHeads)
|
diffSyncer.UpdateHeads(newId, newHeads)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("diff syncer sync space missing", func(t *testing.T) {
|
t.Run("diff syncer sync space missing", func(t *testing.T) {
|
||||||
fx := newHeadSyncFixture(t)
|
aclStorageMock := mock_liststorage.NewMockListStorage(ctrl)
|
||||||
fx.initDiffSyncer(t)
|
settingsStorage := mock_treestorage.NewMockTreeStorage(ctrl)
|
||||||
defer fx.stop()
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
|
||||||
aclStorageMock := mock_liststorage.NewMockListStorage(fx.ctrl)
|
|
||||||
settingsStorage := mock_treestorage.NewMockTreeStorage(fx.ctrl)
|
|
||||||
settingsId := "settingsId"
|
settingsId := "settingsId"
|
||||||
aclRootId := "aclRootId"
|
aclRoot := &aclrecordproto.RawAclRecordWithId{
|
||||||
aclRoot := &consensusproto.RawRecordWithId{
|
|
||||||
Id: aclRootId,
|
Id: aclRootId,
|
||||||
}
|
}
|
||||||
settingsRoot := &treechangeproto.RawTreeChangeWithId{
|
settingsRoot := &treechangeproto.RawTreeChangeWithId{
|
||||||
@ -204,63 +184,56 @@ func TestDiffSyncer(t *testing.T) {
|
|||||||
spaceSettingsId := "spaceSettingsId"
|
spaceSettingsId := "spaceSettingsId"
|
||||||
credential := []byte("credential")
|
credential := []byte("credential")
|
||||||
|
|
||||||
fx.peerManagerMock.EXPECT().
|
peerManagerMock.EXPECT().
|
||||||
GetResponsiblePeers(gomock.Any()).
|
GetResponsiblePeers(gomock.Any()).
|
||||||
Return([]peer.Peer{mockPeer{}}, nil)
|
Return([]peer.Peer{mockPeer{}}, nil)
|
||||||
fx.diffMock.EXPECT().
|
diffMock.EXPECT().
|
||||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(fx.spaceState.SpaceId, fx.clientMock))).
|
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(spaceId, clientMock))).
|
||||||
Return(nil, nil, nil, spacesyncproto.ErrSpaceMissing)
|
Return(nil, nil, nil, spacesyncproto.ErrSpaceMissing)
|
||||||
|
|
||||||
fx.storageMock.EXPECT().AclStorage().Return(aclStorageMock, nil)
|
stMock.EXPECT().AclStorage().Return(aclStorageMock, nil)
|
||||||
fx.storageMock.EXPECT().SpaceHeader().Return(spaceHeader, nil)
|
stMock.EXPECT().SpaceHeader().Return(spaceHeader, nil)
|
||||||
fx.storageMock.EXPECT().SpaceSettingsId().Return(spaceSettingsId)
|
stMock.EXPECT().SpaceSettingsId().Return(spaceSettingsId)
|
||||||
fx.storageMock.EXPECT().TreeStorage(spaceSettingsId).Return(settingsStorage, nil)
|
stMock.EXPECT().TreeStorage(spaceSettingsId).Return(settingsStorage, nil)
|
||||||
|
|
||||||
settingsStorage.EXPECT().Root().Return(settingsRoot, nil)
|
settingsStorage.EXPECT().Root().Return(settingsRoot, nil)
|
||||||
aclStorageMock.EXPECT().
|
aclStorageMock.EXPECT().
|
||||||
Root().
|
Root().
|
||||||
Return(aclRoot, nil)
|
Return(aclRoot, nil)
|
||||||
fx.credentialProviderMock.EXPECT().
|
credentialProvider.EXPECT().
|
||||||
GetCredential(gomock.Any(), spaceHeader).
|
GetCredential(gomock.Any(), spaceHeader).
|
||||||
Return(credential, nil)
|
Return(credential, nil)
|
||||||
fx.clientMock.EXPECT().
|
clientMock.EXPECT().
|
||||||
SpacePush(gomock.Any(), newPushSpaceRequestMatcher(fx.spaceState.SpaceId, aclRootId, settingsId, credential, spaceHeader)).
|
SpacePush(gomock.Any(), newPushSpaceRequestMatcher(spaceId, aclRootId, settingsId, credential, spaceHeader)).
|
||||||
Return(nil, nil)
|
Return(nil, nil)
|
||||||
fx.peerManagerMock.EXPECT().SendPeer(gomock.Any(), "peerId", gomock.Any())
|
peerManagerMock.EXPECT().SendPeer(gomock.Any(), "mockId", gomock.Any())
|
||||||
|
|
||||||
require.NoError(t, fx.diffSyncer.Sync(ctx))
|
require.NoError(t, diffSyncer.Sync(ctx))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("diff syncer sync unexpected", func(t *testing.T) {
|
t.Run("diff syncer sync unexpected", func(t *testing.T) {
|
||||||
fx := newHeadSyncFixture(t)
|
peerManagerMock.EXPECT().
|
||||||
fx.initDiffSyncer(t)
|
|
||||||
defer fx.stop()
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
|
||||||
fx.peerManagerMock.EXPECT().
|
|
||||||
GetResponsiblePeers(gomock.Any()).
|
GetResponsiblePeers(gomock.Any()).
|
||||||
Return([]peer.Peer{mockPeer{}}, nil)
|
Return([]peer.Peer{mockPeer{}}, nil)
|
||||||
fx.diffMock.EXPECT().
|
diffMock.EXPECT().
|
||||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(fx.spaceState.SpaceId, fx.clientMock))).
|
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(spaceId, clientMock))).
|
||||||
Return(nil, nil, nil, spacesyncproto.ErrUnexpected)
|
Return(nil, nil, nil, spacesyncproto.ErrUnexpected)
|
||||||
|
|
||||||
require.NoError(t, fx.diffSyncer.Sync(ctx))
|
require.NoError(t, diffSyncer.Sync(ctx))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("diff syncer sync space is deleted error", func(t *testing.T) {
|
t.Run("diff syncer sync space is deleted error", func(t *testing.T) {
|
||||||
fx := newHeadSyncFixture(t)
|
peerManagerMock.EXPECT().
|
||||||
fx.initDiffSyncer(t)
|
|
||||||
defer fx.stop()
|
|
||||||
mPeer := mockPeer{}
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
|
||||||
fx.peerManagerMock.EXPECT().
|
|
||||||
GetResponsiblePeers(gomock.Any()).
|
GetResponsiblePeers(gomock.Any()).
|
||||||
Return([]peer.Peer{mPeer}, nil)
|
Return([]peer.Peer{mockPeer{}}, nil)
|
||||||
fx.diffMock.EXPECT().
|
diffMock.EXPECT().
|
||||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(fx.spaceState.SpaceId, fx.clientMock))).
|
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(spaceId, clientMock))).
|
||||||
Return(nil, nil, nil, spacesyncproto.ErrSpaceIsDeleted)
|
Return(nil, nil, nil, spacesyncproto.ErrSpaceIsDeleted)
|
||||||
fx.storageMock.EXPECT().SpaceSettingsId().Return("settingsId")
|
stMock.EXPECT().SpaceSettingsId().Return("settingsId")
|
||||||
fx.treeSyncerMock.EXPECT().SyncAll(gomock.Any(), mPeer.Id(), []string{"settingsId"}, nil).Return(nil)
|
cacheMock.EXPECT().
|
||||||
|
GetTree(gomock.Any(), spaceId, "settingsId").
|
||||||
|
Return(nil, nil)
|
||||||
|
|
||||||
require.NoError(t, fx.diffSyncer.Sync(ctx))
|
require.NoError(t, diffSyncer.Sync(ctx))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,152 +1,125 @@
|
|||||||
//go:generate mockgen -destination mock_headsync/mock_headsync.go github.com/anyproto/any-sync/commonspace/headsync DiffSyncer
|
//go:generate mockgen -destination mock_headsync/mock_headsync.go github.com/anytypeio/any-sync/commonspace/headsync DiffSyncer
|
||||||
package headsync
|
package headsync
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"sync/atomic"
|
"github.com/anytypeio/any-sync/app/ldiff"
|
||||||
"time"
|
"github.com/anytypeio/any-sync/app/logger"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/credentialprovider"
|
||||||
"github.com/anyproto/any-sync/app"
|
"github.com/anytypeio/any-sync/commonspace/object/treegetter"
|
||||||
"github.com/anyproto/any-sync/app/ldiff"
|
"github.com/anytypeio/any-sync/commonspace/peermanager"
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
"github.com/anytypeio/any-sync/commonspace/settings/settingsstate"
|
||||||
config2 "github.com/anyproto/any-sync/commonspace/config"
|
"github.com/anytypeio/any-sync/commonspace/spacestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/credentialprovider"
|
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/deletionstate"
|
"github.com/anytypeio/any-sync/commonspace/syncstatus"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl"
|
"github.com/anytypeio/any-sync/net/peer"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
"github.com/anytypeio/any-sync/nodeconf"
|
||||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
"github.com/anytypeio/any-sync/util/periodicsync"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestate"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
|
||||||
"github.com/anyproto/any-sync/net/peer"
|
|
||||||
"github.com/anyproto/any-sync/nodeconf"
|
|
||||||
"github.com/anyproto/any-sync/util/periodicsync"
|
|
||||||
"github.com/anyproto/any-sync/util/slice"
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var log = logger.NewNamed(CName)
|
|
||||||
|
|
||||||
const CName = "common.commonspace.headsync"
|
|
||||||
|
|
||||||
type TreeHeads struct {
|
type TreeHeads struct {
|
||||||
Id string
|
Id string
|
||||||
Heads []string
|
Heads []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type HeadSync interface {
|
type HeadSync interface {
|
||||||
app.ComponentRunnable
|
Init(objectIds []string, deletionState settingsstate.ObjectDeletionState)
|
||||||
ExternalIds() []string
|
|
||||||
DebugAllHeads() (res []TreeHeads)
|
|
||||||
AllIds() []string
|
|
||||||
UpdateHeads(id string, heads []string)
|
UpdateHeads(id string, heads []string)
|
||||||
HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error)
|
HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error)
|
||||||
RemoveObjects(ids []string)
|
RemoveObjects(ids []string)
|
||||||
|
AllIds() []string
|
||||||
|
DebugAllHeads() (res []TreeHeads)
|
||||||
|
|
||||||
|
Close() (err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type headSync struct {
|
type headSync struct {
|
||||||
spaceId string
|
spaceId string
|
||||||
|
periodicSync periodicsync.PeriodicSync
|
||||||
|
storage spacestorage.SpaceStorage
|
||||||
|
diff ldiff.Diff
|
||||||
|
log logger.CtxLogger
|
||||||
|
syncer DiffSyncer
|
||||||
|
configuration nodeconf.Configuration
|
||||||
spaceIsDeleted *atomic.Bool
|
spaceIsDeleted *atomic.Bool
|
||||||
syncPeriod int
|
|
||||||
|
|
||||||
periodicSync periodicsync.PeriodicSync
|
syncPeriod int
|
||||||
storage spacestorage.SpaceStorage
|
|
||||||
diff ldiff.Diff
|
|
||||||
log logger.CtxLogger
|
|
||||||
syncer DiffSyncer
|
|
||||||
configuration nodeconf.NodeConf
|
|
||||||
peerManager peermanager.PeerManager
|
|
||||||
treeManager treemanager.TreeManager
|
|
||||||
credentialProvider credentialprovider.CredentialProvider
|
|
||||||
syncStatus syncstatus.StatusService
|
|
||||||
deletionState deletionstate.ObjectDeletionState
|
|
||||||
syncAcl syncacl.SyncAcl
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() HeadSync {
|
func NewHeadSync(
|
||||||
return &headSync{}
|
spaceId string,
|
||||||
}
|
spaceIsDeleted *atomic.Bool,
|
||||||
|
syncPeriod int,
|
||||||
|
configuration nodeconf.Configuration,
|
||||||
|
storage spacestorage.SpaceStorage,
|
||||||
|
peerManager peermanager.PeerManager,
|
||||||
|
cache treegetter.TreeGetter,
|
||||||
|
syncStatus syncstatus.StatusUpdater,
|
||||||
|
credentialProvider credentialprovider.CredentialProvider,
|
||||||
|
log logger.CtxLogger) HeadSync {
|
||||||
|
|
||||||
var createDiffSyncer = newDiffSyncer
|
diff := ldiff.New(16, 16)
|
||||||
|
l := log.With(zap.String("spaceId", spaceId))
|
||||||
func (h *headSync) Init(a *app.App) (err error) {
|
factory := spacesyncproto.ClientFactoryFunc(spacesyncproto.NewDRPCSpaceSyncClient)
|
||||||
shared := a.MustComponent(spacestate.CName).(*spacestate.SpaceState)
|
syncer := newDiffSyncer(spaceId, diff, peerManager, cache, storage, factory, syncStatus, credentialProvider, l)
|
||||||
cfg := a.MustComponent("config").(config2.ConfigGetter)
|
|
||||||
h.syncAcl = a.MustComponent(syncacl.CName).(syncacl.SyncAcl)
|
|
||||||
h.spaceId = shared.SpaceId
|
|
||||||
h.spaceIsDeleted = shared.SpaceIsDeleted
|
|
||||||
h.syncPeriod = cfg.GetSpace().SyncPeriod
|
|
||||||
h.configuration = a.MustComponent(nodeconf.CName).(nodeconf.NodeConf)
|
|
||||||
h.log = log.With(zap.String("spaceId", h.spaceId))
|
|
||||||
h.storage = a.MustComponent(spacestorage.CName).(spacestorage.SpaceStorage)
|
|
||||||
h.diff = ldiff.New(16, 16)
|
|
||||||
h.peerManager = a.MustComponent(peermanager.CName).(peermanager.PeerManager)
|
|
||||||
h.credentialProvider = a.MustComponent(credentialprovider.CName).(credentialprovider.CredentialProvider)
|
|
||||||
h.syncStatus = a.MustComponent(syncstatus.CName).(syncstatus.StatusService)
|
|
||||||
h.treeManager = a.MustComponent(treemanager.CName).(treemanager.TreeManager)
|
|
||||||
h.deletionState = a.MustComponent(deletionstate.CName).(deletionstate.ObjectDeletionState)
|
|
||||||
h.syncer = createDiffSyncer(h)
|
|
||||||
sync := func(ctx context.Context) (err error) {
|
sync := func(ctx context.Context) (err error) {
|
||||||
// for clients cancelling the sync process
|
// for clients cancelling the sync process
|
||||||
if h.spaceIsDeleted.Load() && !h.configuration.IsResponsible(h.spaceId) {
|
if spaceIsDeleted.Load() && !configuration.IsResponsible(spaceId) {
|
||||||
return spacesyncproto.ErrSpaceIsDeleted
|
return spacesyncproto.ErrSpaceIsDeleted
|
||||||
}
|
}
|
||||||
return h.syncer.Sync(ctx)
|
return syncer.Sync(ctx)
|
||||||
}
|
}
|
||||||
h.periodicSync = periodicsync.NewPeriodicSync(h.syncPeriod, time.Minute, sync, h.log)
|
periodicSync := periodicsync.NewPeriodicSync(syncPeriod, time.Minute, sync, l)
|
||||||
h.syncAcl.SetHeadUpdater(h)
|
|
||||||
// TODO: move to run?
|
|
||||||
h.syncer.Init()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *headSync) Name() (name string) {
|
return &headSync{
|
||||||
return CName
|
spaceId: spaceId,
|
||||||
}
|
storage: storage,
|
||||||
|
syncer: syncer,
|
||||||
func (h *headSync) Run(ctx context.Context) (err error) {
|
periodicSync: periodicSync,
|
||||||
initialIds, err := h.storage.StoredIds()
|
diff: diff,
|
||||||
if err != nil {
|
log: log,
|
||||||
return
|
syncPeriod: syncPeriod,
|
||||||
|
configuration: configuration,
|
||||||
|
spaceIsDeleted: spaceIsDeleted,
|
||||||
}
|
}
|
||||||
h.fillDiff(initialIds)
|
|
||||||
h.periodicSync.Run()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *headSync) HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error) {
|
func (d *headSync) Init(objectIds []string, deletionState settingsstate.ObjectDeletionState) {
|
||||||
if h.spaceIsDeleted.Load() {
|
d.fillDiff(objectIds)
|
||||||
|
d.syncer.Init(deletionState)
|
||||||
|
d.periodicSync.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *headSync) HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error) {
|
||||||
|
if d.spaceIsDeleted.Load() {
|
||||||
peerId, err := peer.CtxPeerId(ctx)
|
peerId, err := peer.CtxPeerId(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// stop receiving all request for sync from clients
|
// stop receiving all request for sync from clients
|
||||||
if !slices.Contains(h.configuration.NodeIds(h.spaceId), peerId) {
|
if !slices.Contains(d.configuration.NodeIds(d.spaceId), peerId) {
|
||||||
return nil, spacesyncproto.ErrSpaceIsDeleted
|
return nil, spacesyncproto.ErrSpaceIsDeleted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return HandleRangeRequest(ctx, h.diff, req)
|
return HandleRangeRequest(ctx, d.diff, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *headSync) UpdateHeads(id string, heads []string) {
|
func (d *headSync) UpdateHeads(id string, heads []string) {
|
||||||
h.syncer.UpdateHeads(id, heads)
|
d.syncer.UpdateHeads(id, heads)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *headSync) AllIds() []string {
|
func (d *headSync) AllIds() []string {
|
||||||
return h.diff.Ids()
|
return d.diff.Ids()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *headSync) ExternalIds() []string {
|
func (d *headSync) DebugAllHeads() (res []TreeHeads) {
|
||||||
settingsId := h.storage.SpaceSettingsId()
|
els := d.diff.Elements()
|
||||||
return slice.DiscardFromSlice(h.AllIds(), func(id string) bool {
|
|
||||||
return id == settingsId
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *headSync) DebugAllHeads() (res []TreeHeads) {
|
|
||||||
els := h.diff.Elements()
|
|
||||||
for _, el := range els {
|
for _, el := range els {
|
||||||
idHead := TreeHeads{
|
idHead := TreeHeads{
|
||||||
Id: el.Id,
|
Id: el.Id,
|
||||||
@ -157,19 +130,19 @@ func (h *headSync) DebugAllHeads() (res []TreeHeads) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *headSync) RemoveObjects(ids []string) {
|
func (d *headSync) RemoveObjects(ids []string) {
|
||||||
h.syncer.RemoveObjects(ids)
|
d.syncer.RemoveObjects(ids)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *headSync) Close(ctx context.Context) (err error) {
|
func (d *headSync) Close() (err error) {
|
||||||
h.periodicSync.Close()
|
d.periodicSync.Close()
|
||||||
return h.syncer.Close()
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *headSync) fillDiff(objectIds []string) {
|
func (d *headSync) fillDiff(objectIds []string) {
|
||||||
var els = make([]ldiff.Element, 0, len(objectIds))
|
var els = make([]ldiff.Element, 0, len(objectIds))
|
||||||
for _, id := range objectIds {
|
for _, id := range objectIds {
|
||||||
st, err := h.storage.TreeStorage(id)
|
st, err := d.storage.TreeStorage(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -182,12 +155,32 @@ func (h *headSync) fillDiff(objectIds []string) {
|
|||||||
Head: concatStrings(heads),
|
Head: concatStrings(heads),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
els = append(els, ldiff.Element{
|
d.diff.Set(els...)
|
||||||
Id: h.syncAcl.Id(),
|
if err := d.storage.WriteSpaceHash(d.diff.Hash()); err != nil {
|
||||||
Head: h.syncAcl.Head().Id,
|
d.log.Error("can't write space hash", zap.Error(err))
|
||||||
})
|
|
||||||
h.diff.Set(els...)
|
|
||||||
if err := h.storage.WriteSpaceHash(h.diff.Hash()); err != nil {
|
|
||||||
h.log.Error("can't write space hash", zap.Error(err))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func concatStrings(strs []string) string {
|
||||||
|
var (
|
||||||
|
b strings.Builder
|
||||||
|
totalLen int
|
||||||
|
)
|
||||||
|
for _, s := range strs {
|
||||||
|
totalLen += len(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Grow(totalLen)
|
||||||
|
for _, s := range strs {
|
||||||
|
b.WriteString(s)
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitString(str string) (res []string) {
|
||||||
|
const cidLen = 59
|
||||||
|
for i := 0; i < len(str); i += cidLen {
|
||||||
|
res = append(res, str[i:i+cidLen])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@ -1,190 +1,70 @@
|
|||||||
package headsync
|
package headsync
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"github.com/anytypeio/any-sync/app/ldiff"
|
||||||
"github.com/anyproto/any-sync/app"
|
"github.com/anytypeio/any-sync/app/ldiff/mock_ldiff"
|
||||||
"github.com/anyproto/any-sync/app/ldiff"
|
"github.com/anytypeio/any-sync/app/logger"
|
||||||
"github.com/anyproto/any-sync/app/ldiff/mock_ldiff"
|
"github.com/anytypeio/any-sync/commonspace/headsync/mock_headsync"
|
||||||
"github.com/anyproto/any-sync/commonspace/config"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage/mock_treestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/credentialprovider"
|
"github.com/anytypeio/any-sync/commonspace/settings/settingsstate/mock_settingsstate"
|
||||||
"github.com/anyproto/any-sync/commonspace/credentialprovider/mock_credentialprovider"
|
"github.com/anytypeio/any-sync/commonspace/spacestorage/mock_spacestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/deletionstate"
|
"github.com/anytypeio/any-sync/util/periodicsync/mock_periodicsync"
|
||||||
"github.com/anyproto/any-sync/commonspace/deletionstate/mock_deletionstate"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/anyproto/any-sync/commonspace/headsync/mock_headsync"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl/mock_syncacl"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage/mock_treestorage"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/treemanager/mock_treemanager"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/peermanager/mock_peermanager"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestate"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage/mock_spacestorage"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto/mock_spacesyncproto"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
|
||||||
"github.com/anyproto/any-sync/nodeconf"
|
|
||||||
"github.com/anyproto/any-sync/nodeconf/mock_nodeconf"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
"sync/atomic"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockConfig struct {
|
func TestDiffService(t *testing.T) {
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockConfig) Init(a *app.App) (err error) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockConfig) Name() (name string) {
|
|
||||||
return "config"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockConfig) GetSpace() config.Config {
|
|
||||||
return config.Config{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type headSyncFixture struct {
|
|
||||||
spaceState *spacestate.SpaceState
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
app *app.App
|
|
||||||
|
|
||||||
configurationMock *mock_nodeconf.MockService
|
|
||||||
storageMock *mock_spacestorage.MockSpaceStorage
|
|
||||||
peerManagerMock *mock_peermanager.MockPeerManager
|
|
||||||
credentialProviderMock *mock_credentialprovider.MockCredentialProvider
|
|
||||||
syncStatus syncstatus.StatusService
|
|
||||||
treeManagerMock *mock_treemanager.MockTreeManager
|
|
||||||
deletionStateMock *mock_deletionstate.MockObjectDeletionState
|
|
||||||
diffSyncerMock *mock_headsync.MockDiffSyncer
|
|
||||||
treeSyncerMock *mock_treemanager.MockTreeSyncer
|
|
||||||
diffMock *mock_ldiff.MockDiff
|
|
||||||
clientMock *mock_spacesyncproto.MockDRPCSpaceSyncClient
|
|
||||||
aclMock *mock_syncacl.MockSyncAcl
|
|
||||||
headSync *headSync
|
|
||||||
diffSyncer *diffSyncer
|
|
||||||
}
|
|
||||||
|
|
||||||
func newHeadSyncFixture(t *testing.T) *headSyncFixture {
|
|
||||||
spaceState := &spacestate.SpaceState{
|
|
||||||
SpaceId: "spaceId",
|
|
||||||
SpaceIsDeleted: &atomic.Bool{},
|
|
||||||
}
|
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
configurationMock := mock_nodeconf.NewMockService(ctrl)
|
defer ctrl.Finish()
|
||||||
configurationMock.EXPECT().Name().AnyTimes().Return(nodeconf.CName)
|
|
||||||
|
spaceId := "spaceId"
|
||||||
|
l := logger.NewNamed("sync")
|
||||||
|
pSyncMock := mock_periodicsync.NewMockPeriodicSync(ctrl)
|
||||||
storageMock := mock_spacestorage.NewMockSpaceStorage(ctrl)
|
storageMock := mock_spacestorage.NewMockSpaceStorage(ctrl)
|
||||||
storageMock.EXPECT().Name().AnyTimes().Return(spacestorage.CName)
|
treeStorageMock := mock_treestorage.NewMockTreeStorage(ctrl)
|
||||||
peerManagerMock := mock_peermanager.NewMockPeerManager(ctrl)
|
|
||||||
peerManagerMock.EXPECT().Name().AnyTimes().Return(peermanager.CName)
|
|
||||||
credentialProviderMock := mock_credentialprovider.NewMockCredentialProvider(ctrl)
|
|
||||||
credentialProviderMock.EXPECT().Name().AnyTimes().Return(credentialprovider.CName)
|
|
||||||
syncStatus := syncstatus.NewNoOpSyncStatus()
|
|
||||||
treeManagerMock := mock_treemanager.NewMockTreeManager(ctrl)
|
|
||||||
treeManagerMock.EXPECT().Name().AnyTimes().Return(treemanager.CName)
|
|
||||||
deletionStateMock := mock_deletionstate.NewMockObjectDeletionState(ctrl)
|
|
||||||
deletionStateMock.EXPECT().Name().AnyTimes().Return(deletionstate.CName)
|
|
||||||
diffSyncerMock := mock_headsync.NewMockDiffSyncer(ctrl)
|
|
||||||
treeSyncerMock := mock_treemanager.NewMockTreeSyncer(ctrl)
|
|
||||||
diffMock := mock_ldiff.NewMockDiff(ctrl)
|
diffMock := mock_ldiff.NewMockDiff(ctrl)
|
||||||
clientMock := mock_spacesyncproto.NewMockDRPCSpaceSyncClient(ctrl)
|
syncer := mock_headsync.NewMockDiffSyncer(ctrl)
|
||||||
aclMock := mock_syncacl.NewMockSyncAcl(ctrl)
|
delState := mock_settingsstate.NewMockObjectDeletionState(ctrl)
|
||||||
aclMock.EXPECT().Name().AnyTimes().Return(syncacl.CName)
|
syncPeriod := 1
|
||||||
aclMock.EXPECT().SetHeadUpdater(gomock.Any()).AnyTimes()
|
initId := "initId"
|
||||||
hs := &headSync{}
|
|
||||||
a := &app.App{}
|
service := &headSync{
|
||||||
a.Register(spaceState).
|
spaceId: spaceId,
|
||||||
Register(aclMock).
|
storage: storageMock,
|
||||||
Register(mockConfig{}).
|
periodicSync: pSyncMock,
|
||||||
Register(configurationMock).
|
syncer: syncer,
|
||||||
Register(storageMock).
|
diff: diffMock,
|
||||||
Register(peerManagerMock).
|
log: l,
|
||||||
Register(credentialProviderMock).
|
syncPeriod: syncPeriod,
|
||||||
Register(syncStatus).
|
|
||||||
Register(treeManagerMock).
|
|
||||||
Register(deletionStateMock).
|
|
||||||
Register(hs)
|
|
||||||
return &headSyncFixture{
|
|
||||||
spaceState: spaceState,
|
|
||||||
ctrl: ctrl,
|
|
||||||
app: a,
|
|
||||||
configurationMock: configurationMock,
|
|
||||||
storageMock: storageMock,
|
|
||||||
peerManagerMock: peerManagerMock,
|
|
||||||
credentialProviderMock: credentialProviderMock,
|
|
||||||
syncStatus: syncStatus,
|
|
||||||
treeManagerMock: treeManagerMock,
|
|
||||||
deletionStateMock: deletionStateMock,
|
|
||||||
headSync: hs,
|
|
||||||
diffSyncerMock: diffSyncerMock,
|
|
||||||
treeSyncerMock: treeSyncerMock,
|
|
||||||
diffMock: diffMock,
|
|
||||||
clientMock: clientMock,
|
|
||||||
aclMock: aclMock,
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func (fx *headSyncFixture) init(t *testing.T) {
|
t.Run("init", func(t *testing.T) {
|
||||||
createDiffSyncer = func(hs *headSync) DiffSyncer {
|
storageMock.EXPECT().TreeStorage(initId).Return(treeStorageMock, nil)
|
||||||
return fx.diffSyncerMock
|
treeStorageMock.EXPECT().Heads().Return([]string{"h1", "h2"}, nil)
|
||||||
}
|
syncer.EXPECT().Init(delState)
|
||||||
fx.diffSyncerMock.EXPECT().Init()
|
diffMock.EXPECT().Set(ldiff.Element{
|
||||||
err := fx.headSync.Init(fx.app)
|
Id: initId,
|
||||||
require.NoError(t, err)
|
|
||||||
fx.headSync.diff = fx.diffMock
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fx *headSyncFixture) stop() {
|
|
||||||
fx.ctrl.Finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHeadSync(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
t.Run("run close", func(t *testing.T) {
|
|
||||||
fx := newHeadSyncFixture(t)
|
|
||||||
fx.init(t)
|
|
||||||
defer fx.stop()
|
|
||||||
|
|
||||||
ids := []string{"id1"}
|
|
||||||
treeMock := mock_treestorage.NewMockTreeStorage(fx.ctrl)
|
|
||||||
fx.storageMock.EXPECT().StoredIds().Return(ids, nil)
|
|
||||||
fx.storageMock.EXPECT().TreeStorage(ids[0]).Return(treeMock, nil)
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
|
||||||
fx.aclMock.EXPECT().Head().AnyTimes().Return(&list.AclRecord{Id: "headId"})
|
|
||||||
treeMock.EXPECT().Heads().Return([]string{"h1", "h2"}, nil)
|
|
||||||
fx.diffMock.EXPECT().Set(ldiff.Element{
|
|
||||||
Id: "id1",
|
|
||||||
Head: "h1h2",
|
Head: "h1h2",
|
||||||
})
|
})
|
||||||
fx.diffMock.EXPECT().Hash().Return("hash")
|
hash := "123"
|
||||||
fx.storageMock.EXPECT().WriteSpaceHash("hash").Return(nil)
|
diffMock.EXPECT().Hash().Return(hash)
|
||||||
fx.diffSyncerMock.EXPECT().Sync(gomock.Any()).Return(nil)
|
storageMock.EXPECT().WriteSpaceHash(hash)
|
||||||
fx.diffSyncerMock.EXPECT().Close().Return(nil)
|
pSyncMock.EXPECT().Run()
|
||||||
err := fx.headSync.Run(ctx)
|
service.Init([]string{initId}, delState)
|
||||||
require.NoError(t, err)
|
|
||||||
err = fx.headSync.Close(ctx)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("update heads", func(t *testing.T) {
|
t.Run("update heads", func(t *testing.T) {
|
||||||
fx := newHeadSyncFixture(t)
|
syncer.EXPECT().UpdateHeads(initId, []string{"h1", "h2"})
|
||||||
fx.init(t)
|
service.UpdateHeads(initId, []string{"h1", "h2"})
|
||||||
defer fx.stop()
|
|
||||||
|
|
||||||
fx.diffSyncerMock.EXPECT().UpdateHeads("id1", []string{"h1"})
|
|
||||||
fx.headSync.UpdateHeads("id1", []string{"h1"})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("remove objects", func(t *testing.T) {
|
t.Run("remove objects", func(t *testing.T) {
|
||||||
fx := newHeadSyncFixture(t)
|
syncer.EXPECT().RemoveObjects([]string{"h1", "h2"})
|
||||||
fx.init(t)
|
service.RemoveObjects([]string{"h1", "h2"})
|
||||||
defer fx.stop()
|
})
|
||||||
|
|
||||||
fx.diffSyncerMock.EXPECT().RemoveObjects([]string{"id1"})
|
t.Run("close", func(t *testing.T) {
|
||||||
fx.headSync.RemoveObjects([]string{"id1"})
|
pSyncMock.EXPECT().Close()
|
||||||
|
service.Close()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: github.com/anyproto/any-sync/commonspace/headsync (interfaces: DiffSyncer)
|
// Source: github.com/anytypeio/any-sync/commonspace/headsync (interfaces: DiffSyncer)
|
||||||
|
|
||||||
// Package mock_headsync is a generated GoMock package.
|
// Package mock_headsync is a generated GoMock package.
|
||||||
package mock_headsync
|
package mock_headsync
|
||||||
@ -8,7 +8,8 @@ import (
|
|||||||
context "context"
|
context "context"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
gomock "go.uber.org/mock/gomock"
|
settingsstate "github.com/anytypeio/any-sync/commonspace/settings/settingsstate"
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockDiffSyncer is a mock of DiffSyncer interface.
|
// MockDiffSyncer is a mock of DiffSyncer interface.
|
||||||
@ -34,30 +35,16 @@ func (m *MockDiffSyncer) EXPECT() *MockDiffSyncerMockRecorder {
|
|||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close mocks base method.
|
|
||||||
func (m *MockDiffSyncer) Close() error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Close")
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close indicates an expected call of Close.
|
|
||||||
func (mr *MockDiffSyncerMockRecorder) Close() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockDiffSyncer)(nil).Close))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init mocks base method.
|
// Init mocks base method.
|
||||||
func (m *MockDiffSyncer) Init() {
|
func (m *MockDiffSyncer) Init(arg0 settingsstate.ObjectDeletionState) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "Init")
|
m.ctrl.Call(m, "Init", arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init indicates an expected call of Init.
|
// Init indicates an expected call of Init.
|
||||||
func (mr *MockDiffSyncerMockRecorder) Init() *gomock.Call {
|
func (mr *MockDiffSyncerMockRecorder) Init(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockDiffSyncer)(nil).Init))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockDiffSyncer)(nil).Init), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveObjects mocks base method.
|
// RemoveObjects mocks base method.
|
||||||
|
|||||||
@ -2,8 +2,8 @@ package headsync
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/anyproto/any-sync/app/ldiff"
|
"github.com/anytypeio/any-sync/app/ldiff"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client interface {
|
type Client interface {
|
||||||
|
|||||||
@ -3,8 +3,8 @@ package headsync
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/anyproto/any-sync/app/ldiff"
|
"github.com/anytypeio/any-sync/app/ldiff"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"testing"
|
"testing"
|
||||||
|
|||||||
@ -1,27 +0,0 @@
|
|||||||
package headsync
|
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
func concatStrings(strs []string) string {
|
|
||||||
var (
|
|
||||||
b strings.Builder
|
|
||||||
totalLen int
|
|
||||||
)
|
|
||||||
for _, s := range strs {
|
|
||||||
totalLen += len(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Grow(totalLen)
|
|
||||||
for _, s := range strs {
|
|
||||||
b.WriteString(s)
|
|
||||||
}
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitString(str string) (res []string) {
|
|
||||||
const cidLen = 59
|
|
||||||
for i := 0; i < len(str); i += cidLen {
|
|
||||||
res = append(res, str[i:i+cidLen])
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@ -2,7 +2,7 @@ package accountdata
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
"github.com/anytypeio/any-sync/util/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AccountKeys struct {
|
type AccountKeys struct {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,26 @@ syntax = "proto3";
|
|||||||
package aclrecord;
|
package aclrecord;
|
||||||
option go_package = "commonspace/object/acl/aclrecordproto";
|
option go_package = "commonspace/object/acl/aclrecordproto";
|
||||||
|
|
||||||
// AclRoot is a root of access control list
|
message RawAclRecord {
|
||||||
|
bytes payload = 1;
|
||||||
|
bytes signature = 2;
|
||||||
|
bytes acceptorIdentity = 3;
|
||||||
|
bytes acceptorSignature = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RawAclRecordWithId {
|
||||||
|
bytes payload = 1;
|
||||||
|
string id = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AclRecord {
|
||||||
|
string prevId = 1;
|
||||||
|
bytes identity = 2;
|
||||||
|
bytes data = 3;
|
||||||
|
string readKeyId = 4;
|
||||||
|
int64 timestamp = 5;
|
||||||
|
}
|
||||||
|
|
||||||
message AclRoot {
|
message AclRoot {
|
||||||
bytes identity = 1;
|
bytes identity = 1;
|
||||||
bytes masterKey = 2;
|
bytes masterKey = 2;
|
||||||
@ -12,95 +31,82 @@ message AclRoot {
|
|||||||
bytes identitySignature = 6;
|
bytes identitySignature = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
// AclAccountInvite contains the public invite key, the private part of which is sent to the user directly
|
|
||||||
message AclAccountInvite {
|
|
||||||
bytes inviteKey = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AclAccountRequestJoin contains the reference to the invite record and the data of the person who wants to join, confirmed by the private invite key
|
|
||||||
message AclAccountRequestJoin {
|
|
||||||
bytes inviteIdentity = 1;
|
|
||||||
string inviteRecordId = 2;
|
|
||||||
bytes inviteIdentitySignature = 3;
|
|
||||||
bytes metadata = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AclAccountRequestAccept contains the reference to join record and all read keys, encrypted with the identity of the requestor
|
|
||||||
message AclAccountRequestAccept {
|
|
||||||
bytes identity = 1;
|
|
||||||
string requestRecordId = 2;
|
|
||||||
repeated AclReadKeyWithRecord encryptedReadKeys = 3;
|
|
||||||
AclUserPermissions permissions = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AclAccountRequestDecline contains the reference to join record
|
|
||||||
message AclAccountRequestDecline {
|
|
||||||
string requestRecordId = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AclAccountInviteRevoke revokes the invite record
|
|
||||||
message AclAccountInviteRevoke {
|
|
||||||
string inviteRecordId = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AclReadKeys are a read key with record id
|
|
||||||
message AclReadKeyWithRecord {
|
|
||||||
string recordId = 1;
|
|
||||||
bytes encryptedReadKey = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AclEncryptedReadKeys are new key for specific identity
|
|
||||||
message AclEncryptedReadKey {
|
|
||||||
bytes identity = 1;
|
|
||||||
bytes encryptedReadKey = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AclAccountPermissionChange changes permissions of specific account
|
|
||||||
message AclAccountPermissionChange {
|
|
||||||
bytes identity = 1;
|
|
||||||
AclUserPermissions permissions = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AclReadKeyChange changes the key for a space
|
|
||||||
message AclReadKeyChange {
|
|
||||||
repeated AclEncryptedReadKey accountKeys = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AclAccountRemove removes an account and changes read key for space
|
|
||||||
message AclAccountRemove {
|
|
||||||
repeated bytes identities = 1;
|
|
||||||
repeated AclEncryptedReadKey accountKeys = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AclAccountRequestRemove adds a request to remove an account
|
|
||||||
message AclAccountRequestRemove {
|
|
||||||
}
|
|
||||||
|
|
||||||
// AclContentValue contains possible values for Acl
|
|
||||||
message AclContentValue {
|
message AclContentValue {
|
||||||
oneof value {
|
oneof value {
|
||||||
AclAccountInvite invite = 1;
|
AclUserAdd userAdd = 1;
|
||||||
AclAccountInviteRevoke inviteRevoke = 2;
|
AclUserRemove userRemove = 2;
|
||||||
AclAccountRequestJoin requestJoin = 3;
|
AclUserPermissionChange userPermissionChange = 3;
|
||||||
AclAccountRequestAccept requestAccept = 4;
|
AclUserInvite userInvite = 4;
|
||||||
AclAccountPermissionChange permissionChange = 5;
|
AclUserJoin userJoin = 5;
|
||||||
AclAccountRemove accountRemove = 6;
|
|
||||||
AclReadKeyChange readKeyChange = 7;
|
|
||||||
AclAccountRequestDecline requestDecline = 8;
|
|
||||||
AclAccountRequestRemove accountRequestRemove = 9;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AclData contains different acl content
|
|
||||||
message AclData {
|
message AclData {
|
||||||
repeated AclContentValue aclContent = 1;
|
repeated AclContentValue aclContent = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// AclUserPermissions contains different possible user roles
|
message AclState {
|
||||||
enum AclUserPermissions {
|
repeated string readKeyIds = 1;
|
||||||
None = 0;
|
repeated AclUserState userStates = 2;
|
||||||
Owner = 1;
|
map<string, AclUserInvite> invites = 3;
|
||||||
Admin = 2;
|
|
||||||
Writer = 3;
|
|
||||||
Reader = 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message AclUserState {
|
||||||
|
bytes identity = 1;
|
||||||
|
AclUserPermissions permissions = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AclUserAdd {
|
||||||
|
bytes identity = 1;
|
||||||
|
repeated bytes encryptedReadKeys = 2;
|
||||||
|
AclUserPermissions permissions = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AclUserInvite {
|
||||||
|
bytes acceptPublicKey = 1;
|
||||||
|
repeated bytes encryptedReadKeys = 2;
|
||||||
|
AclUserPermissions permissions = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AclUserJoin {
|
||||||
|
bytes identity = 1;
|
||||||
|
bytes acceptSignature = 2;
|
||||||
|
bytes acceptPubKey = 3;
|
||||||
|
repeated bytes encryptedReadKeys = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AclUserRemove {
|
||||||
|
bytes identity = 1;
|
||||||
|
repeated AclReadKeyReplace readKeyReplaces = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AclReadKeyReplace {
|
||||||
|
bytes identity = 1;
|
||||||
|
bytes encryptedReadKey = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AclUserPermissionChange {
|
||||||
|
bytes identity = 1;
|
||||||
|
AclUserPermissions permissions = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AclUserPermissions {
|
||||||
|
Admin = 0;
|
||||||
|
Writer = 1;
|
||||||
|
Reader = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AclSyncMessage {
|
||||||
|
AclSyncContentValue content = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// AclSyncContentValue provides different types for acl sync
|
||||||
|
message AclSyncContentValue {
|
||||||
|
oneof value {
|
||||||
|
AclAddRecords addRecords = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message AclAddRecords {
|
||||||
|
repeated RawAclRecordWithId records = 1;
|
||||||
|
}
|
||||||
@ -1,14 +1,11 @@
|
|||||||
package list
|
package list
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||||
|
"github.com/anytypeio/any-sync/util/cidutil"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
"github.com/anytypeio/any-sync/util/crypto"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
"github.com/anyproto/any-sync/util/cidutil"
|
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
|
||||||
"github.com/gogo/protobuf/proto"
|
"github.com/gogo/protobuf/proto"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RootContent struct {
|
type RootContent struct {
|
||||||
@ -18,387 +15,26 @@ type RootContent struct {
|
|||||||
EncryptedReadKey []byte
|
EncryptedReadKey []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type RequestJoinPayload struct {
|
|
||||||
InviteRecordId string
|
|
||||||
InviteKey crypto.PrivKey
|
|
||||||
Metadata []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type RequestAcceptPayload struct {
|
|
||||||
RequestRecordId string
|
|
||||||
Permissions AclPermissions
|
|
||||||
}
|
|
||||||
|
|
||||||
type PermissionChangePayload struct {
|
|
||||||
Identity crypto.PubKey
|
|
||||||
Permissions AclPermissions
|
|
||||||
}
|
|
||||||
|
|
||||||
type AccountRemovePayload struct {
|
|
||||||
Identities []crypto.PubKey
|
|
||||||
ReadKey crypto.SymKey
|
|
||||||
}
|
|
||||||
|
|
||||||
type InviteResult struct {
|
|
||||||
InviteRec *consensusproto.RawRecord
|
|
||||||
InviteKey crypto.PrivKey
|
|
||||||
}
|
|
||||||
|
|
||||||
type AclRecordBuilder interface {
|
type AclRecordBuilder interface {
|
||||||
UnmarshallWithId(rawIdRecord *consensusproto.RawRecordWithId) (rec *AclRecord, err error)
|
Unmarshall(rawIdRecord *aclrecordproto.RawAclRecordWithId) (rec *AclRecord, err error)
|
||||||
Unmarshall(rawRecord *consensusproto.RawRecord) (rec *AclRecord, err error)
|
BuildRoot(content RootContent) (rec *aclrecordproto.RawAclRecordWithId, err error)
|
||||||
|
|
||||||
BuildRoot(content RootContent) (rec *consensusproto.RawRecordWithId, err error)
|
|
||||||
BuildInvite() (res InviteResult, err error)
|
|
||||||
BuildInviteRevoke(inviteRecordId string) (rawRecord *consensusproto.RawRecord, err error)
|
|
||||||
BuildRequestJoin(payload RequestJoinPayload) (rawRecord *consensusproto.RawRecord, err error)
|
|
||||||
BuildRequestAccept(payload RequestAcceptPayload) (rawRecord *consensusproto.RawRecord, err error)
|
|
||||||
BuildRequestDecline(requestRecordId string) (rawRecord *consensusproto.RawRecord, err error)
|
|
||||||
BuildRequestRemove() (rawRecord *consensusproto.RawRecord, err error)
|
|
||||||
BuildPermissionChange(payload PermissionChangePayload) (rawRecord *consensusproto.RawRecord, err error)
|
|
||||||
BuildReadKeyChange(newKey crypto.SymKey) (rawRecord *consensusproto.RawRecord, err error)
|
|
||||||
BuildAccountRemove(payload AccountRemovePayload) (rawRecord *consensusproto.RawRecord, err error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type aclRecordBuilder struct {
|
type aclRecordBuilder struct {
|
||||||
id string
|
id string
|
||||||
keyStorage crypto.KeyStorage
|
keyStorage crypto.KeyStorage
|
||||||
accountKeys *accountdata.AccountKeys
|
|
||||||
verifier AcceptorVerifier
|
|
||||||
state *AclState
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAclRecordBuilder(id string, keyStorage crypto.KeyStorage, keys *accountdata.AccountKeys, verifier AcceptorVerifier) AclRecordBuilder {
|
func NewAclRecordBuilder(id string, keyStorage crypto.KeyStorage) AclRecordBuilder {
|
||||||
return &aclRecordBuilder{
|
return &aclRecordBuilder{
|
||||||
id: id,
|
id: id,
|
||||||
keyStorage: keyStorage,
|
keyStorage: keyStorage,
|
||||||
accountKeys: keys,
|
|
||||||
verifier: verifier,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *aclRecordBuilder) buildRecord(aclContent *aclrecordproto.AclContentValue) (rawRec *consensusproto.RawRecord, err error) {
|
func (a *aclRecordBuilder) Unmarshall(rawIdRecord *aclrecordproto.RawAclRecordWithId) (rec *AclRecord, err error) {
|
||||||
aclData := &aclrecordproto.AclData{AclContent: []*aclrecordproto.AclContentValue{
|
|
||||||
aclContent,
|
|
||||||
}}
|
|
||||||
marshalledData, err := aclData.Marshal()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
protoKey, err := a.accountKeys.SignKey.GetPublic().Marshall()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rec := &consensusproto.Record{
|
|
||||||
PrevId: a.state.lastRecordId,
|
|
||||||
Identity: protoKey,
|
|
||||||
Data: marshalledData,
|
|
||||||
Timestamp: time.Now().Unix(),
|
|
||||||
}
|
|
||||||
marshalledRec, err := rec.Marshal()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
signature, err := a.accountKeys.SignKey.Sign(marshalledRec)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rawRec = &consensusproto.RawRecord{
|
|
||||||
Payload: marshalledRec,
|
|
||||||
Signature: signature,
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclRecordBuilder) BuildInvite() (res InviteResult, err error) {
|
|
||||||
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
|
||||||
err = ErrInsufficientPermissions
|
|
||||||
return
|
|
||||||
}
|
|
||||||
privKey, pubKey, err := crypto.GenerateRandomEd25519KeyPair()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
invitePubKey, err := pubKey.Marshall()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
inviteRec := &aclrecordproto.AclAccountInvite{InviteKey: invitePubKey}
|
|
||||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_Invite{Invite: inviteRec}}
|
|
||||||
rawRec, err := a.buildRecord(content)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
res.InviteKey = privKey
|
|
||||||
res.InviteRec = rawRec
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclRecordBuilder) BuildInviteRevoke(inviteRecordId string) (rawRecord *consensusproto.RawRecord, err error) {
|
|
||||||
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
|
||||||
err = ErrInsufficientPermissions
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, exists := a.state.inviteKeys[inviteRecordId]
|
|
||||||
if !exists {
|
|
||||||
err = ErrNoSuchInvite
|
|
||||||
return
|
|
||||||
}
|
|
||||||
revokeRec := &aclrecordproto.AclAccountInviteRevoke{InviteRecordId: inviteRecordId}
|
|
||||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_InviteRevoke{InviteRevoke: revokeRec}}
|
|
||||||
return a.buildRecord(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclRecordBuilder) BuildRequestJoin(payload RequestJoinPayload) (rawRecord *consensusproto.RawRecord, err error) {
|
|
||||||
key, exists := a.state.inviteKeys[payload.InviteRecordId]
|
|
||||||
if !exists {
|
|
||||||
err = ErrNoSuchInvite
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !payload.InviteKey.GetPublic().Equals(key) {
|
|
||||||
err = ErrIncorrectInviteKey
|
|
||||||
}
|
|
||||||
rawIdentity, err := a.accountKeys.SignKey.GetPublic().Raw()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
signature, err := payload.InviteKey.Sign(rawIdentity)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
protoIdentity, err := a.accountKeys.SignKey.GetPublic().Marshall()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
joinRec := &aclrecordproto.AclAccountRequestJoin{
|
|
||||||
InviteIdentity: protoIdentity,
|
|
||||||
InviteRecordId: payload.InviteRecordId,
|
|
||||||
InviteIdentitySignature: signature,
|
|
||||||
Metadata: payload.Metadata,
|
|
||||||
}
|
|
||||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_RequestJoin{RequestJoin: joinRec}}
|
|
||||||
return a.buildRecord(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclRecordBuilder) BuildRequestAccept(payload RequestAcceptPayload) (rawRecord *consensusproto.RawRecord, err error) {
|
|
||||||
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
|
||||||
err = ErrInsufficientPermissions
|
|
||||||
return
|
|
||||||
}
|
|
||||||
request, exists := a.state.requestRecords[payload.RequestRecordId]
|
|
||||||
if !exists {
|
|
||||||
err = ErrNoSuchRequest
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var encryptedReadKeys []*aclrecordproto.AclReadKeyWithRecord
|
|
||||||
for keyId, key := range a.state.userReadKeys {
|
|
||||||
rawKey, err := key.Raw()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
enc, err := request.RequestIdentity.Encrypt(rawKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
encryptedReadKeys = append(encryptedReadKeys, &aclrecordproto.AclReadKeyWithRecord{
|
|
||||||
RecordId: keyId,
|
|
||||||
EncryptedReadKey: enc,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
requestIdentityProto, err := request.RequestIdentity.Marshall()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
acceptRec := &aclrecordproto.AclAccountRequestAccept{
|
|
||||||
Identity: requestIdentityProto,
|
|
||||||
RequestRecordId: payload.RequestRecordId,
|
|
||||||
EncryptedReadKeys: encryptedReadKeys,
|
|
||||||
Permissions: aclrecordproto.AclUserPermissions(payload.Permissions),
|
|
||||||
}
|
|
||||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_RequestAccept{RequestAccept: acceptRec}}
|
|
||||||
return a.buildRecord(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclRecordBuilder) BuildRequestDecline(requestRecordId string) (rawRecord *consensusproto.RawRecord, err error) {
|
|
||||||
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
|
||||||
err = ErrInsufficientPermissions
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, exists := a.state.requestRecords[requestRecordId]
|
|
||||||
if !exists {
|
|
||||||
err = ErrNoSuchRequest
|
|
||||||
return
|
|
||||||
}
|
|
||||||
declineRec := &aclrecordproto.AclAccountRequestDecline{RequestRecordId: requestRecordId}
|
|
||||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_RequestDecline{RequestDecline: declineRec}}
|
|
||||||
return a.buildRecord(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclRecordBuilder) BuildPermissionChange(payload PermissionChangePayload) (rawRecord *consensusproto.RawRecord, err error) {
|
|
||||||
permissions := a.state.Permissions(a.state.pubKey)
|
|
||||||
if !permissions.CanManageAccounts() || payload.Identity.Equals(a.state.pubKey) {
|
|
||||||
err = ErrInsufficientPermissions
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if payload.Permissions.IsOwner() {
|
|
||||||
err = ErrIsOwner
|
|
||||||
return
|
|
||||||
}
|
|
||||||
protoIdentity, err := payload.Identity.Marshall()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
permissionRec := &aclrecordproto.AclAccountPermissionChange{
|
|
||||||
Identity: protoIdentity,
|
|
||||||
Permissions: aclrecordproto.AclUserPermissions(payload.Permissions),
|
|
||||||
}
|
|
||||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_PermissionChange{PermissionChange: permissionRec}}
|
|
||||||
return a.buildRecord(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclRecordBuilder) BuildReadKeyChange(newKey crypto.SymKey) (rawRecord *consensusproto.RawRecord, err error) {
|
|
||||||
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
|
||||||
err = ErrInsufficientPermissions
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rawKey, err := newKey.Raw()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(rawKey) != crypto.KeyBytes {
|
|
||||||
err = ErrIncorrectReadKey
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var aclReadKeys []*aclrecordproto.AclEncryptedReadKey
|
|
||||||
for _, st := range a.state.userStates {
|
|
||||||
protoIdentity, err := st.PubKey.Marshall()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
enc, err := st.PubKey.Encrypt(rawKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
aclReadKeys = append(aclReadKeys, &aclrecordproto.AclEncryptedReadKey{
|
|
||||||
Identity: protoIdentity,
|
|
||||||
EncryptedReadKey: enc,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
readRec := &aclrecordproto.AclReadKeyChange{AccountKeys: aclReadKeys}
|
|
||||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_ReadKeyChange{ReadKeyChange: readRec}}
|
|
||||||
return a.buildRecord(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclRecordBuilder) BuildAccountRemove(payload AccountRemovePayload) (rawRecord *consensusproto.RawRecord, err error) {
|
|
||||||
deletedMap := map[string]struct{}{}
|
|
||||||
for _, key := range payload.Identities {
|
|
||||||
permissions := a.state.Permissions(key)
|
|
||||||
if permissions.IsOwner() {
|
|
||||||
return nil, ErrInsufficientPermissions
|
|
||||||
}
|
|
||||||
if permissions.NoPermissions() {
|
|
||||||
return nil, ErrNoSuchAccount
|
|
||||||
}
|
|
||||||
deletedMap[mapKeyFromPubKey(key)] = struct{}{}
|
|
||||||
}
|
|
||||||
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
|
||||||
err = ErrInsufficientPermissions
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rawKey, err := payload.ReadKey.Raw()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(rawKey) != crypto.KeyBytes {
|
|
||||||
err = ErrIncorrectReadKey
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var aclReadKeys []*aclrecordproto.AclEncryptedReadKey
|
|
||||||
for _, st := range a.state.userStates {
|
|
||||||
if _, exists := deletedMap[mapKeyFromPubKey(st.PubKey)]; exists {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
protoIdentity, err := st.PubKey.Marshall()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
enc, err := st.PubKey.Encrypt(rawKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
aclReadKeys = append(aclReadKeys, &aclrecordproto.AclEncryptedReadKey{
|
|
||||||
Identity: protoIdentity,
|
|
||||||
EncryptedReadKey: enc,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
var marshalledIdentities [][]byte
|
|
||||||
for _, key := range payload.Identities {
|
|
||||||
protoIdentity, err := key.Marshall()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
marshalledIdentities = append(marshalledIdentities, protoIdentity)
|
|
||||||
}
|
|
||||||
removeRec := &aclrecordproto.AclAccountRemove{AccountKeys: aclReadKeys, Identities: marshalledIdentities}
|
|
||||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_AccountRemove{AccountRemove: removeRec}}
|
|
||||||
return a.buildRecord(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclRecordBuilder) BuildRequestRemove() (rawRecord *consensusproto.RawRecord, err error) {
|
|
||||||
permissions := a.state.Permissions(a.state.pubKey)
|
|
||||||
if permissions.NoPermissions() {
|
|
||||||
err = ErrNoSuchAccount
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if permissions.IsOwner() {
|
|
||||||
err = ErrIsOwner
|
|
||||||
return
|
|
||||||
}
|
|
||||||
removeRec := &aclrecordproto.AclAccountRequestRemove{}
|
|
||||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_AccountRequestRemove{AccountRequestRemove: removeRec}}
|
|
||||||
return a.buildRecord(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclRecordBuilder) Unmarshall(rawRecord *consensusproto.RawRecord) (rec *AclRecord, err error) {
|
|
||||||
aclRecord := &consensusproto.Record{}
|
|
||||||
err = proto.Unmarshal(rawRecord.Payload, aclRecord)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pubKey, err := a.keyStorage.PubKeyFromProto(aclRecord.Identity)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
aclData := &aclrecordproto.AclData{}
|
|
||||||
err = proto.Unmarshal(aclRecord.Data, aclData)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rec = &AclRecord{
|
|
||||||
PrevId: aclRecord.PrevId,
|
|
||||||
Timestamp: aclRecord.Timestamp,
|
|
||||||
Data: aclRecord.Data,
|
|
||||||
Signature: rawRecord.Signature,
|
|
||||||
Identity: pubKey,
|
|
||||||
Model: aclData,
|
|
||||||
}
|
|
||||||
res, err := pubKey.Verify(rawRecord.Payload, rawRecord.Signature)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !res {
|
|
||||||
err = ErrInvalidSignature
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclRecordBuilder) UnmarshallWithId(rawIdRecord *consensusproto.RawRecordWithId) (rec *AclRecord, err error) {
|
|
||||||
var (
|
var (
|
||||||
rawRec = &consensusproto.RawRecord{}
|
rawRec = &aclrecordproto.RawAclRecord{}
|
||||||
pubKey crypto.PubKey
|
pubKey crypto.PubKey
|
||||||
)
|
)
|
||||||
err = proto.Unmarshal(rawIdRecord.Payload, rawRec)
|
err = proto.Unmarshal(rawIdRecord.Payload, rawRec)
|
||||||
@ -417,17 +53,14 @@ func (a *aclRecordBuilder) UnmarshallWithId(rawIdRecord *consensusproto.RawRecor
|
|||||||
}
|
}
|
||||||
rec = &AclRecord{
|
rec = &AclRecord{
|
||||||
Id: rawIdRecord.Id,
|
Id: rawIdRecord.Id,
|
||||||
|
ReadKeyId: rawIdRecord.Id,
|
||||||
Timestamp: aclRoot.Timestamp,
|
Timestamp: aclRoot.Timestamp,
|
||||||
Signature: rawRec.Signature,
|
Signature: rawRec.Signature,
|
||||||
Identity: pubKey,
|
Identity: pubKey,
|
||||||
Model: aclRoot,
|
Model: aclRoot,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = a.verifier.VerifyAcceptor(rawRec)
|
aclRecord := &aclrecordproto.AclRecord{}
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
aclRecord := &consensusproto.Record{}
|
|
||||||
err = proto.Unmarshal(rawRec.Payload, aclRecord)
|
err = proto.Unmarshal(rawRec.Payload, aclRecord)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -436,19 +69,14 @@ func (a *aclRecordBuilder) UnmarshallWithId(rawIdRecord *consensusproto.RawRecor
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
aclData := &aclrecordproto.AclData{}
|
|
||||||
err = proto.Unmarshal(aclRecord.Data, aclData)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rec = &AclRecord{
|
rec = &AclRecord{
|
||||||
Id: rawIdRecord.Id,
|
Id: rawIdRecord.Id,
|
||||||
PrevId: aclRecord.PrevId,
|
PrevId: aclRecord.PrevId,
|
||||||
|
ReadKeyId: aclRecord.ReadKeyId,
|
||||||
Timestamp: aclRecord.Timestamp,
|
Timestamp: aclRecord.Timestamp,
|
||||||
Data: aclRecord.Data,
|
Data: aclRecord.Data,
|
||||||
Signature: rawRec.Signature,
|
Signature: rawRec.Signature,
|
||||||
Identity: pubKey,
|
Identity: pubKey,
|
||||||
Model: aclData,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -456,7 +84,7 @@ func (a *aclRecordBuilder) UnmarshallWithId(rawIdRecord *consensusproto.RawRecor
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *aclRecordBuilder) BuildRoot(content RootContent) (rec *consensusproto.RawRecordWithId, err error) {
|
func (a *aclRecordBuilder) BuildRoot(content RootContent) (rec *aclrecordproto.RawAclRecordWithId, err error) {
|
||||||
rawIdentity, err := content.PrivKey.GetPublic().Raw()
|
rawIdentity, err := content.PrivKey.GetPublic().Raw()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -490,8 +118,8 @@ func (a *aclRecordBuilder) BuildRoot(content RootContent) (rec *consensusproto.R
|
|||||||
|
|
||||||
func verifyRaw(
|
func verifyRaw(
|
||||||
pubKey crypto.PubKey,
|
pubKey crypto.PubKey,
|
||||||
rawRec *consensusproto.RawRecord,
|
rawRec *aclrecordproto.RawAclRecord,
|
||||||
recWithId *consensusproto.RawRecordWithId) (err error) {
|
recWithId *aclrecordproto.RawAclRecordWithId) (err error) {
|
||||||
// verifying signature
|
// verifying signature
|
||||||
res, err := pubKey.Verify(rawRec.Payload, rawRec.Signature)
|
res, err := pubKey.Verify(rawRec.Payload, rawRec.Signature)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -509,7 +137,7 @@ func verifyRaw(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func marshalAclRoot(aclRoot *aclrecordproto.AclRoot, key crypto.PrivKey) (rawWithId *consensusproto.RawRecordWithId, err error) {
|
func marshalAclRoot(aclRoot *aclrecordproto.AclRoot, key crypto.PrivKey) (rawWithId *aclrecordproto.RawAclRecordWithId, err error) {
|
||||||
marshalledRoot, err := aclRoot.Marshal()
|
marshalledRoot, err := aclRoot.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -518,7 +146,7 @@ func marshalAclRoot(aclRoot *aclrecordproto.AclRoot, key crypto.PrivKey) (rawWit
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
raw := &consensusproto.RawRecord{
|
raw := &aclrecordproto.RawAclRecord{
|
||||||
Payload: marshalledRoot,
|
Payload: marshalledRoot,
|
||||||
Signature: signature,
|
Signature: signature,
|
||||||
}
|
}
|
||||||
@ -530,7 +158,7 @@ func marshalAclRoot(aclRoot *aclrecordproto.AclRoot, key crypto.PrivKey) (rawWit
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rawWithId = &consensusproto.RawRecordWithId{
|
rawWithId = &aclrecordproto.RawAclRecordWithId{
|
||||||
Payload: marshalledRaw,
|
Payload: marshalledRaw,
|
||||||
Id: aclHeadId,
|
Id: aclHeadId,
|
||||||
}
|
}
|
||||||
|
|||||||
9
commonspace/object/acl/list/aclrecordbuilder_test.go
Normal file
9
commonspace/object/acl/list/aclrecordbuilder_test.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package list
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAclRecordBuilder_BuildUserJoin(t *testing.T) {
|
||||||
|
return
|
||||||
|
}
|
||||||
@ -2,10 +2,10 @@ package list
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
"github.com/anytypeio/any-sync/app/logger"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
"github.com/anytypeio/any-sync/util/crypto"
|
||||||
"github.com/gogo/protobuf/proto"
|
"github.com/gogo/protobuf/proto"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
@ -13,24 +13,19 @@ import (
|
|||||||
var log = logger.NewNamedSugared("common.commonspace.acllist")
|
var log = logger.NewNamedSugared("common.commonspace.acllist")
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrNoSuchAccount = errors.New("no such account")
|
ErrNoSuchUser = errors.New("no such user")
|
||||||
ErrPendingRequest = errors.New("already exists pending request")
|
ErrFailedToDecrypt = errors.New("failed to decrypt key")
|
||||||
ErrUnexpectedContentType = errors.New("unexpected content type")
|
ErrUserRemoved = errors.New("user was removed from the document")
|
||||||
ErrIncorrectIdentity = errors.New("incorrect identity")
|
ErrDocumentForbidden = errors.New("your user was forbidden access to the document")
|
||||||
ErrIncorrectInviteKey = errors.New("incorrect invite key")
|
ErrUserAlreadyExists = errors.New("user already exists")
|
||||||
ErrFailedToDecrypt = errors.New("failed to decrypt key")
|
ErrNoSuchRecord = errors.New("no such record")
|
||||||
ErrNoSuchRecord = errors.New("no such record")
|
ErrNoSuchInvite = errors.New("no such invite")
|
||||||
ErrNoSuchRequest = errors.New("no such request")
|
ErrOldInvite = errors.New("invite is too old")
|
||||||
ErrNoSuchInvite = errors.New("no such invite")
|
ErrInsufficientPermissions = errors.New("insufficient permissions")
|
||||||
ErrInsufficientPermissions = errors.New("insufficient permissions")
|
ErrNoReadKey = errors.New("acl state doesn't have a read key")
|
||||||
ErrIsOwner = errors.New("can't be made by owner")
|
ErrInvalidSignature = errors.New("signature is invalid")
|
||||||
ErrIncorrectNumberOfAccounts = errors.New("incorrect number of accounts")
|
ErrIncorrectRoot = errors.New("incorrect root")
|
||||||
ErrDuplicateAccounts = errors.New("duplicate accounts")
|
ErrIncorrectRecordSequence = errors.New("incorrect prev id of a record")
|
||||||
ErrNoReadKey = errors.New("acl state doesn't have a read key")
|
|
||||||
ErrIncorrectReadKey = errors.New("incorrect read key")
|
|
||||||
ErrInvalidSignature = errors.New("signature is invalid")
|
|
||||||
ErrIncorrectRoot = errors.New("incorrect root")
|
|
||||||
ErrIncorrectRecordSequence = errors.New("incorrect prev id of a record")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserPermissionPair struct {
|
type UserPermissionPair struct {
|
||||||
@ -41,71 +36,37 @@ type UserPermissionPair struct {
|
|||||||
type AclState struct {
|
type AclState struct {
|
||||||
id string
|
id string
|
||||||
currentReadKeyId string
|
currentReadKeyId string
|
||||||
// userReadKeys is a map recordId -> read key which tells us about every read key
|
userReadKeys map[string]crypto.SymKey
|
||||||
userReadKeys map[string]crypto.SymKey
|
userStates map[string]AclUserState
|
||||||
// userStates is a map pubKey -> state which defines current user state
|
statesAtRecord map[string][]AclUserState
|
||||||
userStates map[string]AclUserState
|
key crypto.PrivKey
|
||||||
// statesAtRecord is a map recordId -> state which define user state at particular record
|
pubKey crypto.PubKey
|
||||||
// probably this can grow rather large at some point, so we can maybe optimise later to have:
|
keyStore crypto.KeyStorage
|
||||||
// - map pubKey -> []recordIds (where recordIds is an array where such identity permissions were changed)
|
totalReadKeys int
|
||||||
statesAtRecord map[string][]AclUserState
|
|
||||||
// inviteKeys is a map recordId -> invite
|
|
||||||
inviteKeys map[string]crypto.PubKey
|
|
||||||
// requestRecords is a map recordId -> RequestRecord
|
|
||||||
requestRecords map[string]RequestRecord
|
|
||||||
// pendingRequests is a map pubKey -> recordId
|
|
||||||
pendingRequests map[string]string
|
|
||||||
key crypto.PrivKey
|
|
||||||
pubKey crypto.PubKey
|
|
||||||
keyStore crypto.KeyStorage
|
|
||||||
totalReadKeys int
|
|
||||||
|
|
||||||
lastRecordId string
|
lastRecordId string
|
||||||
contentValidator ContentValidator
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAclStateWithKeys(
|
func newAclStateWithKeys(
|
||||||
id string,
|
id string,
|
||||||
key crypto.PrivKey) (*AclState, error) {
|
key crypto.PrivKey) (*AclState, error) {
|
||||||
st := &AclState{
|
return &AclState{
|
||||||
id: id,
|
id: id,
|
||||||
key: key,
|
key: key,
|
||||||
pubKey: key.GetPublic(),
|
pubKey: key.GetPublic(),
|
||||||
userReadKeys: make(map[string]crypto.SymKey),
|
userReadKeys: make(map[string]crypto.SymKey),
|
||||||
userStates: make(map[string]AclUserState),
|
userStates: make(map[string]AclUserState),
|
||||||
statesAtRecord: make(map[string][]AclUserState),
|
statesAtRecord: make(map[string][]AclUserState),
|
||||||
inviteKeys: make(map[string]crypto.PubKey),
|
}, nil
|
||||||
requestRecords: make(map[string]RequestRecord),
|
|
||||||
pendingRequests: make(map[string]string),
|
|
||||||
keyStore: crypto.NewKeyStorage(),
|
|
||||||
}
|
|
||||||
st.contentValidator = &contentValidator{
|
|
||||||
keyStore: st.keyStore,
|
|
||||||
aclState: st,
|
|
||||||
}
|
|
||||||
return st, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAclState(id string) *AclState {
|
func newAclState(id string) *AclState {
|
||||||
st := &AclState{
|
return &AclState{
|
||||||
id: id,
|
id: id,
|
||||||
userReadKeys: make(map[string]crypto.SymKey),
|
userReadKeys: make(map[string]crypto.SymKey),
|
||||||
userStates: make(map[string]AclUserState),
|
userStates: make(map[string]AclUserState),
|
||||||
statesAtRecord: make(map[string][]AclUserState),
|
statesAtRecord: make(map[string][]AclUserState),
|
||||||
inviteKeys: make(map[string]crypto.PubKey),
|
|
||||||
requestRecords: make(map[string]RequestRecord),
|
|
||||||
pendingRequests: make(map[string]string),
|
|
||||||
keyStore: crypto.NewKeyStorage(),
|
|
||||||
}
|
}
|
||||||
st.contentValidator = &contentValidator{
|
|
||||||
keyStore: st.keyStore,
|
|
||||||
aclState: st,
|
|
||||||
}
|
|
||||||
return st
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *AclState) Validator() ContentValidator {
|
|
||||||
return st.contentValidator
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) CurrentReadKeyId() string {
|
func (st *AclState) CurrentReadKeyId() string {
|
||||||
@ -113,7 +74,7 @@ func (st *AclState) CurrentReadKeyId() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) CurrentReadKey() (crypto.SymKey, error) {
|
func (st *AclState) CurrentReadKey() (crypto.SymKey, error) {
|
||||||
key, exists := st.userReadKeys[st.CurrentReadKeyId()]
|
key, exists := st.userReadKeys[st.currentReadKeyId]
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, ErrNoReadKey
|
return nil, ErrNoReadKey
|
||||||
}
|
}
|
||||||
@ -136,7 +97,7 @@ func (st *AclState) StateAtRecord(id string, pubKey crypto.PubKey) (AclUserState
|
|||||||
return perm, nil
|
return perm, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return AclUserState{}, ErrNoSuchAccount
|
return AclUserState{}, ErrNoSuchUser
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) applyRecord(record *AclRecord) (err error) {
|
func (st *AclState) applyRecord(record *AclRecord) (err error) {
|
||||||
@ -149,18 +110,17 @@ func (st *AclState) applyRecord(record *AclRecord) (err error) {
|
|||||||
err = ErrIncorrectRecordSequence
|
err = ErrIncorrectRecordSequence
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// if the record is root record
|
|
||||||
if record.Id == st.id {
|
if record.Id == st.id {
|
||||||
err = st.applyRoot(record)
|
err = st.applyRoot(record)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
st.statesAtRecord[record.Id] = []AclUserState{
|
st.statesAtRecord[record.Id] = []AclUserState{
|
||||||
st.userStates[mapKeyFromPubKey(record.Identity)],
|
{PubKey: record.Identity, Permissions: aclrecordproto.AclUserPermissions_Admin},
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// if the model is not cached
|
|
||||||
if record.Model == nil {
|
if record.Model == nil {
|
||||||
aclData := &aclrecordproto.AclData{}
|
aclData := &aclrecordproto.AclData{}
|
||||||
err = proto.Unmarshal(record.Data, aclData)
|
err = proto.Unmarshal(record.Data, aclData)
|
||||||
@ -169,16 +129,18 @@ func (st *AclState) applyRecord(record *AclRecord) (err error) {
|
|||||||
}
|
}
|
||||||
record.Model = aclData
|
record.Model = aclData
|
||||||
}
|
}
|
||||||
// applying records contents
|
|
||||||
err = st.applyChangeData(record)
|
err = st.applyChangeData(record)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// getting all states for users at record and saving them
|
|
||||||
|
// getting all states for users at record
|
||||||
var states []AclUserState
|
var states []AclUserState
|
||||||
for _, state := range st.userStates {
|
for _, state := range st.userStates {
|
||||||
states = append(states, state)
|
states = append(states, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
st.statesAtRecord[record.Id] = states
|
st.statesAtRecord[record.Id] = states
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -194,9 +156,9 @@ func (st *AclState) applyRoot(record *AclRecord) (err error) {
|
|||||||
// adding user to the list
|
// adding user to the list
|
||||||
userState := AclUserState{
|
userState := AclUserState{
|
||||||
PubKey: record.Identity,
|
PubKey: record.Identity,
|
||||||
Permissions: AclPermissions(aclrecordproto.AclUserPermissions_Owner),
|
Permissions: aclrecordproto.AclUserPermissions_Admin,
|
||||||
}
|
}
|
||||||
st.currentReadKeyId = record.Id
|
st.currentReadKeyId = record.ReadKeyId
|
||||||
st.userStates[mapKeyFromPubKey(record.Identity)] = userState
|
st.userStates[mapKeyFromPubKey(record.Identity)] = userState
|
||||||
st.totalReadKeys++
|
st.totalReadKeys++
|
||||||
return
|
return
|
||||||
@ -219,191 +181,92 @@ func (st *AclState) saveReadKeyFromRoot(record *AclRecord) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
st.userReadKeys[record.Id] = readKey
|
st.userReadKeys[record.Id] = readKey
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) applyChangeData(record *AclRecord) (err error) {
|
func (st *AclState) applyChangeData(record *AclRecord) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if record.ReadKeyId != st.currentReadKeyId {
|
||||||
|
st.totalReadKeys++
|
||||||
|
st.currentReadKeyId = record.ReadKeyId
|
||||||
|
}
|
||||||
|
}()
|
||||||
model := record.Model.(*aclrecordproto.AclData)
|
model := record.Model.(*aclrecordproto.AclData)
|
||||||
|
if !st.isUserJoin(model) {
|
||||||
|
// we check signature when we add this to the List, so no need to do it here
|
||||||
|
if _, exists := st.userStates[mapKeyFromPubKey(record.Identity)]; !exists {
|
||||||
|
err = ErrNoSuchUser
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// only Admins can do non-user join changes
|
||||||
|
if !st.HasPermission(record.Identity, aclrecordproto.AclUserPermissions_Admin) {
|
||||||
|
// TODO: add string encoding
|
||||||
|
err = fmt.Errorf("user %s must have admin permissions", record.Identity.Account())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, ch := range model.GetAclContent() {
|
for _, ch := range model.GetAclContent() {
|
||||||
if err = st.applyChangeContent(ch, record.Id, record.Identity); err != nil {
|
if err = st.applyChangeContent(ch, record.Id); err != nil {
|
||||||
log.Info("error while applying changes: %v; ignore", zap.Error(err))
|
log.Info("error while applying changes: %v; ignore", zap.Error(err))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) applyChangeContent(ch *aclrecordproto.AclContentValue, recordId string, authorIdentity crypto.PubKey) error {
|
func (st *AclState) applyChangeContent(ch *aclrecordproto.AclContentValue, recordId string) error {
|
||||||
switch {
|
switch {
|
||||||
case ch.GetPermissionChange() != nil:
|
case ch.GetUserPermissionChange() != nil:
|
||||||
return st.applyPermissionChange(ch.GetPermissionChange(), recordId, authorIdentity)
|
return st.applyUserPermissionChange(ch.GetUserPermissionChange(), recordId)
|
||||||
case ch.GetInvite() != nil:
|
case ch.GetUserAdd() != nil:
|
||||||
return st.applyInvite(ch.GetInvite(), recordId, authorIdentity)
|
return st.applyUserAdd(ch.GetUserAdd(), recordId)
|
||||||
case ch.GetInviteRevoke() != nil:
|
case ch.GetUserRemove() != nil:
|
||||||
return st.applyInviteRevoke(ch.GetInviteRevoke(), recordId, authorIdentity)
|
return st.applyUserRemove(ch.GetUserRemove(), recordId)
|
||||||
case ch.GetRequestJoin() != nil:
|
case ch.GetUserInvite() != nil:
|
||||||
return st.applyRequestJoin(ch.GetRequestJoin(), recordId, authorIdentity)
|
return st.applyUserInvite(ch.GetUserInvite(), recordId)
|
||||||
case ch.GetRequestAccept() != nil:
|
case ch.GetUserJoin() != nil:
|
||||||
return st.applyRequestAccept(ch.GetRequestAccept(), recordId, authorIdentity)
|
return st.applyUserJoin(ch.GetUserJoin(), recordId)
|
||||||
case ch.GetRequestDecline() != nil:
|
|
||||||
return st.applyRequestDecline(ch.GetRequestDecline(), recordId, authorIdentity)
|
|
||||||
case ch.GetAccountRemove() != nil:
|
|
||||||
return st.applyAccountRemove(ch.GetAccountRemove(), recordId, authorIdentity)
|
|
||||||
case ch.GetReadKeyChange() != nil:
|
|
||||||
return st.applyReadKeyChange(ch.GetReadKeyChange(), recordId, authorIdentity)
|
|
||||||
case ch.GetAccountRequestRemove() != nil:
|
|
||||||
return st.applyRequestRemove(ch.GetAccountRequestRemove(), recordId, authorIdentity)
|
|
||||||
default:
|
default:
|
||||||
return ErrUnexpectedContentType
|
return fmt.Errorf("unexpected change type: %v", ch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) applyPermissionChange(ch *aclrecordproto.AclAccountPermissionChange, recordId string, authorIdentity crypto.PubKey) error {
|
func (st *AclState) applyUserPermissionChange(ch *aclrecordproto.AclUserPermissionChange, recordId string) error {
|
||||||
chIdentity, err := st.keyStore.PubKeyFromProto(ch.Identity)
|
chIdentity, err := st.keyStore.PubKeyFromProto(ch.Identity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = st.contentValidator.ValidatePermissionChange(ch, authorIdentity)
|
state, exists := st.userStates[mapKeyFromPubKey(chIdentity)]
|
||||||
if err != nil {
|
if !exists {
|
||||||
return err
|
return ErrNoSuchUser
|
||||||
}
|
}
|
||||||
stringKey := mapKeyFromPubKey(chIdentity)
|
|
||||||
state, _ := st.userStates[stringKey]
|
state.Permissions = ch.Permissions
|
||||||
state.Permissions = AclPermissions(ch.Permissions)
|
|
||||||
st.userStates[stringKey] = state
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) applyInvite(ch *aclrecordproto.AclAccountInvite, recordId string, authorIdentity crypto.PubKey) error {
|
func (st *AclState) applyUserInvite(ch *aclrecordproto.AclUserInvite, recordId string) error {
|
||||||
inviteKey, err := st.keyStore.PubKeyFromProto(ch.InviteKey)
|
// TODO: check old code and bring it back :-)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = st.contentValidator.ValidateInvite(ch, authorIdentity)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
st.inviteKeys[recordId] = inviteKey
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) applyInviteRevoke(ch *aclrecordproto.AclAccountInviteRevoke, recordId string, authorIdentity crypto.PubKey) error {
|
func (st *AclState) applyUserJoin(ch *aclrecordproto.AclUserJoin, recordId string) error {
|
||||||
err := st.contentValidator.ValidateInviteRevoke(ch, authorIdentity)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
delete(st.inviteKeys, ch.InviteRecordId)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) applyRequestJoin(ch *aclrecordproto.AclAccountRequestJoin, recordId string, authorIdentity crypto.PubKey) error {
|
func (st *AclState) applyUserAdd(ch *aclrecordproto.AclUserAdd, recordId string) error {
|
||||||
err := st.contentValidator.ValidateRequestJoin(ch, authorIdentity)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
st.pendingRequests[mapKeyFromPubKey(authorIdentity)] = recordId
|
|
||||||
st.requestRecords[recordId] = RequestRecord{
|
|
||||||
RequestIdentity: authorIdentity,
|
|
||||||
RequestMetadata: ch.Metadata,
|
|
||||||
Type: RequestTypeJoin,
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) applyRequestAccept(ch *aclrecordproto.AclAccountRequestAccept, recordId string, authorIdentity crypto.PubKey) error {
|
func (st *AclState) applyUserRemove(ch *aclrecordproto.AclUserRemove, recordId string) error {
|
||||||
err := st.contentValidator.ValidateRequestAccept(ch, authorIdentity)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
acceptIdentity, err := st.keyStore.PubKeyFromProto(ch.Identity)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
record, _ := st.requestRecords[ch.RequestRecordId]
|
|
||||||
st.userStates[mapKeyFromPubKey(acceptIdentity)] = AclUserState{
|
|
||||||
PubKey: acceptIdentity,
|
|
||||||
Permissions: AclPermissions(ch.Permissions),
|
|
||||||
RequestMetadata: record.RequestMetadata,
|
|
||||||
}
|
|
||||||
delete(st.pendingRequests, mapKeyFromPubKey(st.requestRecords[ch.RequestRecordId].RequestIdentity))
|
|
||||||
if !st.pubKey.Equals(acceptIdentity) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for _, key := range ch.EncryptedReadKeys {
|
|
||||||
decrypted, err := st.key.Decrypt(key.EncryptedReadKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sym, err := crypto.UnmarshallAESKey(decrypted)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
st.userReadKeys[key.RecordId] = sym
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *AclState) applyRequestDecline(ch *aclrecordproto.AclAccountRequestDecline, recordId string, authorIdentity crypto.PubKey) error {
|
|
||||||
err := st.contentValidator.ValidateRequestDecline(ch, authorIdentity)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
delete(st.pendingRequests, mapKeyFromPubKey(st.requestRecords[ch.RequestRecordId].RequestIdentity))
|
|
||||||
delete(st.requestRecords, ch.RequestRecordId)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *AclState) applyRequestRemove(ch *aclrecordproto.AclAccountRequestRemove, recordId string, authorIdentity crypto.PubKey) error {
|
|
||||||
err := st.contentValidator.ValidateRequestRemove(ch, authorIdentity)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
st.requestRecords[recordId] = RequestRecord{
|
|
||||||
RequestIdentity: authorIdentity,
|
|
||||||
Type: RequestTypeRemove,
|
|
||||||
}
|
|
||||||
st.pendingRequests[mapKeyFromPubKey(authorIdentity)] = recordId
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *AclState) applyAccountRemove(ch *aclrecordproto.AclAccountRemove, recordId string, authorIdentity crypto.PubKey) error {
|
|
||||||
err := st.contentValidator.ValidateAccountRemove(ch, authorIdentity)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, rawIdentity := range ch.Identities {
|
|
||||||
identity, err := st.keyStore.PubKeyFromProto(rawIdentity)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
idKey := mapKeyFromPubKey(identity)
|
|
||||||
delete(st.userStates, idKey)
|
|
||||||
delete(st.pendingRequests, idKey)
|
|
||||||
}
|
|
||||||
return st.updateReadKey(ch.AccountKeys, recordId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *AclState) applyReadKeyChange(ch *aclrecordproto.AclReadKeyChange, recordId string, authorIdentity crypto.PubKey) error {
|
|
||||||
err := st.contentValidator.ValidateReadKeyChange(ch, authorIdentity)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return st.updateReadKey(ch.AccountKeys, recordId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *AclState) updateReadKey(keys []*aclrecordproto.AclEncryptedReadKey, recordId string) error {
|
|
||||||
for _, accKey := range keys {
|
|
||||||
identity, _ := st.keyStore.PubKeyFromProto(accKey.Identity)
|
|
||||||
if st.pubKey.Equals(identity) {
|
|
||||||
res, err := st.decryptReadKey(accKey.EncryptedReadKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
st.userReadKeys[recordId] = res
|
|
||||||
}
|
|
||||||
}
|
|
||||||
st.currentReadKeyId = recordId
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -412,6 +275,7 @@ func (st *AclState) decryptReadKey(msg []byte) (crypto.SymKey, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrFailedToDecrypt
|
return nil, ErrFailedToDecrypt
|
||||||
}
|
}
|
||||||
|
|
||||||
key, err := crypto.UnmarshallAESKey(decrypted)
|
key, err := crypto.UnmarshallAESKey(decrypted)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrFailedToDecrypt
|
return nil, ErrFailedToDecrypt
|
||||||
@ -419,31 +283,29 @@ func (st *AclState) decryptReadKey(msg []byte) (crypto.SymKey, error) {
|
|||||||
return key, nil
|
return key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) Permissions(identity crypto.PubKey) AclPermissions {
|
func (st *AclState) HasPermission(identity crypto.PubKey, permission aclrecordproto.AclUserPermissions) bool {
|
||||||
state, exists := st.userStates[mapKeyFromPubKey(identity)]
|
state, exists := st.userStates[mapKeyFromPubKey(identity)]
|
||||||
if !exists {
|
if !exists {
|
||||||
return AclPermissions(aclrecordproto.AclUserPermissions_None)
|
return false
|
||||||
}
|
}
|
||||||
return state.Permissions
|
|
||||||
|
return state.Permissions == permission
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) JoinRecords() (records []RequestRecord) {
|
func (st *AclState) isUserJoin(data *aclrecordproto.AclData) bool {
|
||||||
for _, recId := range st.pendingRequests {
|
// if we have a UserJoin, then it should always be the first one applied
|
||||||
rec := st.requestRecords[recId]
|
return data.GetAclContent() != nil && data.GetAclContent()[0].GetUserJoin() != nil
|
||||||
if rec.Type == RequestTypeJoin {
|
|
||||||
records = append(records, rec)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *AclState) RemoveRecords() (records []RequestRecord) {
|
func (st *AclState) isUserAdd(data *aclrecordproto.AclData, identity []byte) bool {
|
||||||
for _, recId := range st.pendingRequests {
|
return false
|
||||||
rec := st.requestRecords[recId]
|
}
|
||||||
if rec.Type == RequestTypeRemove {
|
|
||||||
records = append(records, rec)
|
func (st *AclState) UserStates() map[string]AclUserState {
|
||||||
}
|
return st.userStates
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (st *AclState) Invite(acceptPubKey []byte) (invite *aclrecordproto.AclUserInvite, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
package list
|
package list
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
"github.com/anytypeio/any-sync/util/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
type aclStateBuilder struct {
|
type aclStateBuilder struct {
|
||||||
|
|||||||
@ -1,25 +1,20 @@
|
|||||||
//go:generate mockgen -destination mock_list/mock_list.go github.com/anyproto/any-sync/commonspace/object/acl/list AclList
|
//go:generate mockgen -destination mock_list/mock_list.go github.com/anytypeio/any-sync/commonspace/object/acl/list AclList
|
||||||
package list
|
package list
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/object/acl/liststorage"
|
||||||
|
"github.com/anytypeio/any-sync/util/crypto"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
|
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
"github.com/anyproto/any-sync/util/cidutil"
|
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type IterFunc = func(record *AclRecord) (IsContinue bool)
|
type IterFunc = func(record *AclRecord) (IsContinue bool)
|
||||||
|
|
||||||
var (
|
var ErrIncorrectCID = errors.New("incorrect CID")
|
||||||
ErrIncorrectCID = errors.New("incorrect CID")
|
|
||||||
ErrRecordAlreadyExists = errors.New("record already exists")
|
|
||||||
)
|
|
||||||
|
|
||||||
type RWLocker interface {
|
type RWLocker interface {
|
||||||
sync.Locker
|
sync.Locker
|
||||||
@ -27,45 +22,26 @@ type RWLocker interface {
|
|||||||
RUnlock()
|
RUnlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
type AcceptorVerifier interface {
|
|
||||||
VerifyAcceptor(rec *consensusproto.RawRecord) (err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type NoOpAcceptorVerifier struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n NoOpAcceptorVerifier) VerifyAcceptor(rec *consensusproto.RawRecord) (err error) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type AclList interface {
|
type AclList interface {
|
||||||
RWLocker
|
RWLocker
|
||||||
Id() string
|
Id() string
|
||||||
Root() *consensusproto.RawRecordWithId
|
Root() *aclrecordproto.RawAclRecordWithId
|
||||||
Records() []*AclRecord
|
Records() []*AclRecord
|
||||||
AclState() *AclState
|
AclState() *AclState
|
||||||
IsAfter(first string, second string) (bool, error)
|
IsAfter(first string, second string) (bool, error)
|
||||||
HasHead(head string) bool
|
|
||||||
Head() *AclRecord
|
Head() *AclRecord
|
||||||
|
|
||||||
RecordsAfter(ctx context.Context, id string) (records []*consensusproto.RawRecordWithId, err error)
|
|
||||||
Get(id string) (*AclRecord, error)
|
Get(id string) (*AclRecord, error)
|
||||||
GetIndex(idx int) (*AclRecord, error)
|
|
||||||
Iterate(iterFunc IterFunc)
|
Iterate(iterFunc IterFunc)
|
||||||
IterateFrom(startId string, iterFunc IterFunc)
|
IterateFrom(startId string, iterFunc IterFunc)
|
||||||
|
|
||||||
KeyStorage() crypto.KeyStorage
|
KeyStorage() crypto.KeyStorage
|
||||||
RecordBuilder() AclRecordBuilder
|
|
||||||
|
|
||||||
ValidateRawRecord(record *consensusproto.RawRecord) (err error)
|
AddRawRecord(rawRec *aclrecordproto.RawAclRecordWithId) (added bool, err error)
|
||||||
AddRawRecord(rawRec *consensusproto.RawRecordWithId) (err error)
|
|
||||||
AddRawRecords(rawRecords []*consensusproto.RawRecordWithId) (err error)
|
|
||||||
|
|
||||||
Close(ctx context.Context) (err error)
|
Close() (err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type aclList struct {
|
type aclList struct {
|
||||||
root *consensusproto.RawRecordWithId
|
root *aclrecordproto.RawAclRecordWithId
|
||||||
records []*AclRecord
|
records []*AclRecord
|
||||||
indexes map[string]int
|
indexes map[string]int
|
||||||
id string
|
id string
|
||||||
@ -79,45 +55,18 @@ type aclList struct {
|
|||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
type internalDeps struct {
|
func BuildAclListWithIdentity(acc *accountdata.AccountKeys, storage liststorage.ListStorage) (AclList, error) {
|
||||||
storage liststorage.ListStorage
|
builder := newAclStateBuilderWithIdentity(acc)
|
||||||
keyStorage crypto.KeyStorage
|
|
||||||
stateBuilder *aclStateBuilder
|
|
||||||
recordBuilder AclRecordBuilder
|
|
||||||
acceptorVerifier AcceptorVerifier
|
|
||||||
}
|
|
||||||
|
|
||||||
func BuildAclListWithIdentity(acc *accountdata.AccountKeys, storage liststorage.ListStorage, verifier AcceptorVerifier) (AclList, error) {
|
|
||||||
keyStorage := crypto.NewKeyStorage()
|
keyStorage := crypto.NewKeyStorage()
|
||||||
deps := internalDeps{
|
return build(storage.Id(), keyStorage, builder, NewAclRecordBuilder(storage.Id(), keyStorage), storage)
|
||||||
storage: storage,
|
|
||||||
keyStorage: keyStorage,
|
|
||||||
stateBuilder: newAclStateBuilderWithIdentity(acc),
|
|
||||||
recordBuilder: NewAclRecordBuilder(storage.Id(), keyStorage, acc, verifier),
|
|
||||||
acceptorVerifier: verifier,
|
|
||||||
}
|
|
||||||
return build(deps)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildAclList(storage liststorage.ListStorage, verifier AcceptorVerifier) (AclList, error) {
|
func BuildAclList(storage liststorage.ListStorage) (AclList, error) {
|
||||||
keyStorage := crypto.NewKeyStorage()
|
keyStorage := crypto.NewKeyStorage()
|
||||||
deps := internalDeps{
|
return build(storage.Id(), keyStorage, newAclStateBuilder(), NewAclRecordBuilder(storage.Id(), crypto.NewKeyStorage()), storage)
|
||||||
storage: storage,
|
|
||||||
keyStorage: keyStorage,
|
|
||||||
stateBuilder: newAclStateBuilder(),
|
|
||||||
recordBuilder: NewAclRecordBuilder(storage.Id(), keyStorage, nil, verifier),
|
|
||||||
acceptorVerifier: verifier,
|
|
||||||
}
|
|
||||||
return build(deps)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func build(deps internalDeps) (list AclList, err error) {
|
func build(id string, keyStorage crypto.KeyStorage, stateBuilder *aclStateBuilder, recBuilder AclRecordBuilder, storage liststorage.ListStorage) (list AclList, err error) {
|
||||||
var (
|
|
||||||
storage = deps.storage
|
|
||||||
id = deps.storage.Id()
|
|
||||||
recBuilder = deps.recordBuilder
|
|
||||||
stateBuilder = deps.stateBuilder
|
|
||||||
)
|
|
||||||
head, err := storage.Head()
|
head, err := storage.Head()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -128,7 +77,7 @@ func build(deps internalDeps) (list AclList, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
record, err := recBuilder.UnmarshallWithId(rawRecordWithId)
|
record, err := recBuilder.Unmarshall(rawRecordWithId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -140,7 +89,7 @@ func build(deps internalDeps) (list AclList, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
record, err = recBuilder.UnmarshallWithId(rawRecordWithId)
|
record, err = recBuilder.Unmarshall(rawRecordWithId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -170,7 +119,6 @@ func build(deps internalDeps) (list AclList, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
recBuilder.(*aclRecordBuilder).state = state
|
|
||||||
list = &aclList{
|
list = &aclList{
|
||||||
root: rootWithId,
|
root: rootWithId,
|
||||||
records: records,
|
records: records,
|
||||||
@ -184,37 +132,15 @@ func build(deps internalDeps) (list AclList, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *aclList) RecordBuilder() AclRecordBuilder {
|
|
||||||
return a.recordBuilder
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclList) Records() []*AclRecord {
|
func (a *aclList) Records() []*AclRecord {
|
||||||
return a.records
|
return a.records
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *aclList) ValidateRawRecord(rawRec *consensusproto.RawRecord) (err error) {
|
func (a *aclList) AddRawRecord(rawRec *aclrecordproto.RawAclRecordWithId) (added bool, err error) {
|
||||||
record, err := a.recordBuilder.Unmarshall(rawRec)
|
if _, ok := a.indexes[rawRec.Id]; ok {
|
||||||
if err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return a.aclState.Validator().ValidateAclRecordContents(record)
|
record, err := a.recordBuilder.Unmarshall(rawRec)
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclList) AddRawRecords(rawRecords []*consensusproto.RawRecordWithId) (err error) {
|
|
||||||
for _, rec := range rawRecords {
|
|
||||||
err = a.AddRawRecord(rec)
|
|
||||||
if err != nil && err != ErrRecordAlreadyExists {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclList) AddRawRecord(rawRec *consensusproto.RawRecordWithId) (err error) {
|
|
||||||
if _, ok := a.indexes[rawRec.Id]; ok {
|
|
||||||
return ErrRecordAlreadyExists
|
|
||||||
}
|
|
||||||
record, err := a.recordBuilder.UnmarshallWithId(rawRec)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -229,6 +155,15 @@ func (a *aclList) AddRawRecord(rawRec *consensusproto.RawRecordWithId) (err erro
|
|||||||
if err = a.storage.SetHead(rawRec.Id); err != nil {
|
if err = a.storage.SetHead(rawRec.Id); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclList) IsValidNext(rawRec *aclrecordproto.RawAclRecordWithId) (err error) {
|
||||||
|
_, err = a.recordBuilder.Unmarshall(rawRec)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO: change state and add "check" method for records
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,7 +171,7 @@ func (a *aclList) Id() string {
|
|||||||
return a.id
|
return a.id
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *aclList) Root() *consensusproto.RawRecordWithId {
|
func (a *aclList) Root() *aclrecordproto.RawAclRecordWithId {
|
||||||
return a.root
|
return a.root
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,27 +196,14 @@ func (a *aclList) Head() *AclRecord {
|
|||||||
return a.records[len(a.records)-1]
|
return a.records[len(a.records)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *aclList) HasHead(head string) bool {
|
|
||||||
_, exists := a.indexes[head]
|
|
||||||
return exists
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclList) Get(id string) (*AclRecord, error) {
|
func (a *aclList) Get(id string) (*AclRecord, error) {
|
||||||
recIdx, ok := a.indexes[id]
|
recIdx, ok := a.indexes[id]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, ErrNoSuchRecord
|
return nil, fmt.Errorf("no such record")
|
||||||
}
|
}
|
||||||
return a.records[recIdx], nil
|
return a.records[recIdx], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *aclList) GetIndex(idx int) (*AclRecord, error) {
|
|
||||||
// TODO: when we add snapshots we will have to monitor record num in snapshots
|
|
||||||
if idx < 0 || idx >= len(a.records) {
|
|
||||||
return nil, ErrNoSuchRecord
|
|
||||||
}
|
|
||||||
return a.records[idx], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclList) Iterate(iterFunc IterFunc) {
|
func (a *aclList) Iterate(iterFunc IterFunc) {
|
||||||
for _, rec := range a.records {
|
for _, rec := range a.records {
|
||||||
if !iterFunc(rec) {
|
if !iterFunc(rec) {
|
||||||
@ -290,21 +212,6 @@ func (a *aclList) Iterate(iterFunc IterFunc) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *aclList) RecordsAfter(ctx context.Context, id string) (records []*consensusproto.RawRecordWithId, err error) {
|
|
||||||
recIdx, ok := a.indexes[id]
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrNoSuchRecord
|
|
||||||
}
|
|
||||||
for i := recIdx + 1; i < len(a.records); i++ {
|
|
||||||
rawRec, err := a.storage.GetRawRecord(ctx, a.records[i].Id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
records = append(records, rawRec)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclList) IterateFrom(startId string, iterFunc IterFunc) {
|
func (a *aclList) IterateFrom(startId string, iterFunc IterFunc) {
|
||||||
recIdx, ok := a.indexes[startId]
|
recIdx, ok := a.indexes[startId]
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -317,21 +224,6 @@ func (a *aclList) IterateFrom(startId string, iterFunc IterFunc) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *aclList) Close(ctx context.Context) (err error) {
|
func (a *aclList) Close() (err error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func WrapAclRecord(rawRec *consensusproto.RawRecord) *consensusproto.RawRecordWithId {
|
|
||||||
payload, err := rawRec.Marshal()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
id, err := cidutil.NewCidFromBytes(payload)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return &consensusproto.RawRecordWithId{
|
|
||||||
Payload: payload,
|
|
||||||
Id: id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -2,98 +2,11 @@ package list
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
type aclFixture struct {
|
|
||||||
ownerKeys *accountdata.AccountKeys
|
|
||||||
accountKeys *accountdata.AccountKeys
|
|
||||||
ownerAcl *aclList
|
|
||||||
accountAcl *aclList
|
|
||||||
spaceId string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFixture(t *testing.T) *aclFixture {
|
|
||||||
ownerKeys, err := accountdata.NewRandom()
|
|
||||||
require.NoError(t, err)
|
|
||||||
accountKeys, err := accountdata.NewRandom()
|
|
||||||
require.NoError(t, err)
|
|
||||||
spaceId := "spaceId"
|
|
||||||
ownerAcl, err := NewTestDerivedAcl(spaceId, ownerKeys)
|
|
||||||
require.NoError(t, err)
|
|
||||||
accountAcl, err := NewTestAclWithRoot(accountKeys, ownerAcl.Root())
|
|
||||||
require.NoError(t, err)
|
|
||||||
return &aclFixture{
|
|
||||||
ownerKeys: ownerKeys,
|
|
||||||
accountKeys: accountKeys,
|
|
||||||
ownerAcl: ownerAcl.(*aclList),
|
|
||||||
accountAcl: accountAcl.(*aclList),
|
|
||||||
spaceId: spaceId,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fx *aclFixture) addRec(t *testing.T, rec *consensusproto.RawRecordWithId) {
|
|
||||||
err := fx.ownerAcl.AddRawRecord(rec)
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = fx.accountAcl.AddRawRecord(rec)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fx *aclFixture) inviteAccount(t *testing.T, perms AclPermissions) {
|
|
||||||
var (
|
|
||||||
ownerAcl = fx.ownerAcl
|
|
||||||
ownerState = fx.ownerAcl.aclState
|
|
||||||
accountAcl = fx.accountAcl
|
|
||||||
accountState = fx.accountAcl.aclState
|
|
||||||
)
|
|
||||||
// building invite
|
|
||||||
inv, err := ownerAcl.RecordBuilder().BuildInvite()
|
|
||||||
require.NoError(t, err)
|
|
||||||
inviteRec := WrapAclRecord(inv.InviteRec)
|
|
||||||
fx.addRec(t, inviteRec)
|
|
||||||
|
|
||||||
// building request join
|
|
||||||
requestJoin, err := accountAcl.RecordBuilder().BuildRequestJoin(RequestJoinPayload{
|
|
||||||
InviteRecordId: inviteRec.Id,
|
|
||||||
InviteKey: inv.InviteKey,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
requestJoinRec := WrapAclRecord(requestJoin)
|
|
||||||
fx.addRec(t, requestJoinRec)
|
|
||||||
|
|
||||||
// building request accept
|
|
||||||
requestAccept, err := ownerAcl.RecordBuilder().BuildRequestAccept(RequestAcceptPayload{
|
|
||||||
RequestRecordId: requestJoinRec.Id,
|
|
||||||
Permissions: perms,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
// validate
|
|
||||||
err = ownerAcl.ValidateRawRecord(requestAccept)
|
|
||||||
require.NoError(t, err)
|
|
||||||
requestAcceptRec := WrapAclRecord(requestAccept)
|
|
||||||
fx.addRec(t, requestAcceptRec)
|
|
||||||
|
|
||||||
// checking acl state
|
|
||||||
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
|
||||||
require.True(t, ownerState.Permissions(accountState.pubKey).CanWrite())
|
|
||||||
require.Equal(t, 0, len(ownerState.pendingRequests))
|
|
||||||
require.Equal(t, 0, len(accountState.pendingRequests))
|
|
||||||
require.True(t, accountState.Permissions(ownerState.pubKey).IsOwner())
|
|
||||||
require.True(t, accountState.Permissions(accountState.pubKey).CanWrite())
|
|
||||||
|
|
||||||
_, err = ownerState.StateAtRecord(requestJoinRec.Id, accountState.pubKey)
|
|
||||||
require.Equal(t, ErrNoSuchAccount, err)
|
|
||||||
stateAtRec, err := ownerState.StateAtRecord(requestAcceptRec.Id, accountState.pubKey)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, stateAtRec.Permissions == perms)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAclList_BuildRoot(t *testing.T) {
|
func TestAclList_BuildRoot(t *testing.T) {
|
||||||
randomKeys, err := accountdata.NewRandom()
|
randomKeys, err := accountdata.NewRandom()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -101,193 +14,3 @@ func TestAclList_BuildRoot(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
fmt.Println(randomAcl.Id())
|
fmt.Println(randomAcl.Id())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAclList_InvitePipeline(t *testing.T) {
|
|
||||||
fx := newFixture(t)
|
|
||||||
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Writer))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAclList_InviteRevoke(t *testing.T) {
|
|
||||||
fx := newFixture(t)
|
|
||||||
var (
|
|
||||||
ownerState = fx.ownerAcl.aclState
|
|
||||||
accountState = fx.accountAcl.aclState
|
|
||||||
)
|
|
||||||
// building invite
|
|
||||||
inv, err := fx.ownerAcl.RecordBuilder().BuildInvite()
|
|
||||||
require.NoError(t, err)
|
|
||||||
inviteRec := WrapAclRecord(inv.InviteRec)
|
|
||||||
fx.addRec(t, inviteRec)
|
|
||||||
|
|
||||||
// building invite revoke
|
|
||||||
inviteRevoke, err := fx.ownerAcl.RecordBuilder().BuildInviteRevoke(ownerState.lastRecordId)
|
|
||||||
require.NoError(t, err)
|
|
||||||
inviteRevokeRec := WrapAclRecord(inviteRevoke)
|
|
||||||
fx.addRec(t, inviteRevokeRec)
|
|
||||||
|
|
||||||
// checking acl state
|
|
||||||
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
|
||||||
require.True(t, ownerState.Permissions(accountState.pubKey).NoPermissions())
|
|
||||||
require.Empty(t, ownerState.inviteKeys)
|
|
||||||
require.Empty(t, accountState.inviteKeys)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAclList_RequestDecline(t *testing.T) {
|
|
||||||
fx := newFixture(t)
|
|
||||||
var (
|
|
||||||
ownerAcl = fx.ownerAcl
|
|
||||||
ownerState = fx.ownerAcl.aclState
|
|
||||||
accountAcl = fx.accountAcl
|
|
||||||
accountState = fx.accountAcl.aclState
|
|
||||||
)
|
|
||||||
// building invite
|
|
||||||
inv, err := ownerAcl.RecordBuilder().BuildInvite()
|
|
||||||
require.NoError(t, err)
|
|
||||||
inviteRec := WrapAclRecord(inv.InviteRec)
|
|
||||||
fx.addRec(t, inviteRec)
|
|
||||||
|
|
||||||
// building request join
|
|
||||||
requestJoin, err := accountAcl.RecordBuilder().BuildRequestJoin(RequestJoinPayload{
|
|
||||||
InviteRecordId: inviteRec.Id,
|
|
||||||
InviteKey: inv.InviteKey,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
requestJoinRec := WrapAclRecord(requestJoin)
|
|
||||||
fx.addRec(t, requestJoinRec)
|
|
||||||
|
|
||||||
// building request decline
|
|
||||||
requestDecline, err := ownerAcl.RecordBuilder().BuildRequestDecline(ownerState.lastRecordId)
|
|
||||||
require.NoError(t, err)
|
|
||||||
requestDeclineRec := WrapAclRecord(requestDecline)
|
|
||||||
fx.addRec(t, requestDeclineRec)
|
|
||||||
|
|
||||||
// checking acl state
|
|
||||||
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
|
||||||
require.True(t, ownerState.Permissions(accountState.pubKey).NoPermissions())
|
|
||||||
require.Empty(t, ownerState.pendingRequests)
|
|
||||||
require.Empty(t, accountState.pendingRequests)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAclList_Remove(t *testing.T) {
|
|
||||||
fx := newFixture(t)
|
|
||||||
var (
|
|
||||||
ownerState = fx.ownerAcl.aclState
|
|
||||||
accountState = fx.accountAcl.aclState
|
|
||||||
)
|
|
||||||
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Writer))
|
|
||||||
|
|
||||||
newReadKey := crypto.NewAES()
|
|
||||||
remove, err := fx.ownerAcl.RecordBuilder().BuildAccountRemove(AccountRemovePayload{
|
|
||||||
Identities: []crypto.PubKey{fx.accountKeys.SignKey.GetPublic()},
|
|
||||||
ReadKey: newReadKey,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
removeRec := WrapAclRecord(remove)
|
|
||||||
fx.addRec(t, removeRec)
|
|
||||||
|
|
||||||
// checking acl state
|
|
||||||
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
|
||||||
require.True(t, ownerState.Permissions(accountState.pubKey).NoPermissions())
|
|
||||||
require.True(t, ownerState.userReadKeys[removeRec.Id].Equals(newReadKey))
|
|
||||||
require.NotNil(t, ownerState.userReadKeys[fx.ownerAcl.Id()])
|
|
||||||
require.Equal(t, 0, len(ownerState.pendingRequests))
|
|
||||||
require.Equal(t, 0, len(accountState.pendingRequests))
|
|
||||||
require.True(t, accountState.Permissions(ownerState.pubKey).IsOwner())
|
|
||||||
require.True(t, accountState.Permissions(accountState.pubKey).NoPermissions())
|
|
||||||
require.Nil(t, accountState.userReadKeys[removeRec.Id])
|
|
||||||
require.NotNil(t, accountState.userReadKeys[fx.ownerAcl.Id()])
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAclList_ReadKeyChange(t *testing.T) {
|
|
||||||
fx := newFixture(t)
|
|
||||||
var (
|
|
||||||
ownerState = fx.ownerAcl.aclState
|
|
||||||
accountState = fx.accountAcl.aclState
|
|
||||||
)
|
|
||||||
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Admin))
|
|
||||||
|
|
||||||
newReadKey := crypto.NewAES()
|
|
||||||
readKeyChange, err := fx.ownerAcl.RecordBuilder().BuildReadKeyChange(newReadKey)
|
|
||||||
require.NoError(t, err)
|
|
||||||
readKeyRec := WrapAclRecord(readKeyChange)
|
|
||||||
fx.addRec(t, readKeyRec)
|
|
||||||
|
|
||||||
// checking acl state
|
|
||||||
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
|
||||||
require.True(t, ownerState.Permissions(accountState.pubKey).CanManageAccounts())
|
|
||||||
require.True(t, ownerState.userReadKeys[readKeyRec.Id].Equals(newReadKey))
|
|
||||||
require.True(t, accountState.userReadKeys[readKeyRec.Id].Equals(newReadKey))
|
|
||||||
require.NotNil(t, ownerState.userReadKeys[fx.ownerAcl.Id()])
|
|
||||||
require.NotNil(t, accountState.userReadKeys[fx.ownerAcl.Id()])
|
|
||||||
readKey, err := ownerState.CurrentReadKey()
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, newReadKey.Equals(readKey))
|
|
||||||
require.Equal(t, 0, len(ownerState.pendingRequests))
|
|
||||||
require.Equal(t, 0, len(accountState.pendingRequests))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAclList_PermissionChange(t *testing.T) {
|
|
||||||
fx := newFixture(t)
|
|
||||||
var (
|
|
||||||
ownerState = fx.ownerAcl.aclState
|
|
||||||
accountState = fx.accountAcl.aclState
|
|
||||||
)
|
|
||||||
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Admin))
|
|
||||||
|
|
||||||
permissionChange, err := fx.ownerAcl.RecordBuilder().BuildPermissionChange(PermissionChangePayload{
|
|
||||||
Identity: fx.accountKeys.SignKey.GetPublic(),
|
|
||||||
Permissions: AclPermissions(aclrecordproto.AclUserPermissions_Writer),
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
permissionChangeRec := WrapAclRecord(permissionChange)
|
|
||||||
fx.addRec(t, permissionChangeRec)
|
|
||||||
|
|
||||||
// checking acl state
|
|
||||||
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
|
||||||
require.True(t, ownerState.Permissions(accountState.pubKey) == AclPermissions(aclrecordproto.AclUserPermissions_Writer))
|
|
||||||
require.True(t, accountState.Permissions(ownerState.pubKey).IsOwner())
|
|
||||||
require.True(t, accountState.Permissions(accountState.pubKey) == AclPermissions(aclrecordproto.AclUserPermissions_Writer))
|
|
||||||
require.NotNil(t, ownerState.userReadKeys[fx.ownerAcl.Id()])
|
|
||||||
require.NotNil(t, accountState.userReadKeys[fx.ownerAcl.Id()])
|
|
||||||
require.Equal(t, 0, len(ownerState.pendingRequests))
|
|
||||||
require.Equal(t, 0, len(accountState.pendingRequests))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAclList_RequestRemove(t *testing.T) {
|
|
||||||
fx := newFixture(t)
|
|
||||||
var (
|
|
||||||
ownerState = fx.ownerAcl.aclState
|
|
||||||
accountState = fx.accountAcl.aclState
|
|
||||||
)
|
|
||||||
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Writer))
|
|
||||||
|
|
||||||
removeRequest, err := fx.accountAcl.RecordBuilder().BuildRequestRemove()
|
|
||||||
require.NoError(t, err)
|
|
||||||
removeRequestRec := WrapAclRecord(removeRequest)
|
|
||||||
fx.addRec(t, removeRequestRec)
|
|
||||||
|
|
||||||
recs := fx.accountAcl.AclState().RemoveRecords()
|
|
||||||
require.Len(t, recs, 1)
|
|
||||||
require.True(t, accountState.pubKey.Equals(recs[0].RequestIdentity))
|
|
||||||
|
|
||||||
newReadKey := crypto.NewAES()
|
|
||||||
remove, err := fx.ownerAcl.RecordBuilder().BuildAccountRemove(AccountRemovePayload{
|
|
||||||
Identities: []crypto.PubKey{recs[0].RequestIdentity},
|
|
||||||
ReadKey: newReadKey,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
removeRec := WrapAclRecord(remove)
|
|
||||||
fx.addRec(t, removeRec)
|
|
||||||
|
|
||||||
// checking acl state
|
|
||||||
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
|
||||||
require.True(t, ownerState.Permissions(accountState.pubKey).NoPermissions())
|
|
||||||
require.True(t, ownerState.userReadKeys[removeRec.Id].Equals(newReadKey))
|
|
||||||
require.NotNil(t, ownerState.userReadKeys[fx.ownerAcl.Id()])
|
|
||||||
require.Equal(t, 0, len(ownerState.pendingRequests))
|
|
||||||
require.Equal(t, 0, len(accountState.pendingRequests))
|
|
||||||
require.True(t, accountState.Permissions(ownerState.pubKey).IsOwner())
|
|
||||||
require.True(t, accountState.Permissions(accountState.pubKey).NoPermissions())
|
|
||||||
require.Nil(t, accountState.userReadKeys[removeRec.Id])
|
|
||||||
require.NotNil(t, accountState.userReadKeys[fx.ownerAcl.Id()])
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
package list
|
package list
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
|
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
"github.com/anytypeio/any-sync/commonspace/object/acl/liststorage"
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
"github.com/anytypeio/any-sync/util/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewTestDerivedAcl(spaceId string, keys *accountdata.AccountKeys) (AclList, error) {
|
func NewTestDerivedAcl(spaceId string, keys *accountdata.AccountKeys) (AclList, error) {
|
||||||
builder := NewAclRecordBuilder("", crypto.NewKeyStorage(), keys, NoOpAcceptorVerifier{})
|
builder := NewAclRecordBuilder("", crypto.NewKeyStorage())
|
||||||
masterKey, _, err := crypto.GenerateRandomEd25519KeyPair()
|
masterKey, _, err := crypto.GenerateRandomEd25519KeyPair()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -21,21 +21,11 @@ func NewTestDerivedAcl(spaceId string, keys *accountdata.AccountKeys) (AclList,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
st, err := liststorage.NewInMemoryAclListStorage(root.Id, []*consensusproto.RawRecordWithId{
|
st, err := liststorage.NewInMemoryAclListStorage(root.Id, []*aclrecordproto.RawAclRecordWithId{
|
||||||
root,
|
root,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return BuildAclListWithIdentity(keys, st, NoOpAcceptorVerifier{})
|
return BuildAclListWithIdentity(keys, st)
|
||||||
}
|
|
||||||
|
|
||||||
func NewTestAclWithRoot(keys *accountdata.AccountKeys, root *consensusproto.RawRecordWithId) (AclList, error) {
|
|
||||||
st, err := liststorage.NewInMemoryAclListStorage(root.Id, []*consensusproto.RawRecordWithId{
|
|
||||||
root,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return BuildAclListWithIdentity(keys, st, NoOpAcceptorVerifier{})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,16 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: github.com/anyproto/any-sync/commonspace/object/acl/list (interfaces: AclList)
|
// Source: github.com/anytypeio/any-sync/commonspace/object/acl/list (interfaces: AclList)
|
||||||
|
|
||||||
// Package mock_list is a generated GoMock package.
|
// Package mock_list is a generated GoMock package.
|
||||||
package mock_list
|
package mock_list
|
||||||
|
|
||||||
import (
|
import (
|
||||||
context "context"
|
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
list "github.com/anyproto/any-sync/commonspace/object/acl/list"
|
aclrecordproto "github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||||
consensusproto "github.com/anyproto/any-sync/consensus/consensusproto"
|
list "github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||||
crypto "github.com/anyproto/any-sync/util/crypto"
|
crypto "github.com/anytypeio/any-sync/util/crypto"
|
||||||
gomock "go.uber.org/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockAclList is a mock of AclList interface.
|
// MockAclList is a mock of AclList interface.
|
||||||
@ -52,11 +51,12 @@ func (mr *MockAclListMockRecorder) AclState() *gomock.Call {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddRawRecord mocks base method.
|
// AddRawRecord mocks base method.
|
||||||
func (m *MockAclList) AddRawRecord(arg0 *consensusproto.RawRecordWithId) error {
|
func (m *MockAclList) AddRawRecord(arg0 *aclrecordproto.RawAclRecordWithId) (bool, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "AddRawRecord", arg0)
|
ret := m.ctrl.Call(m, "AddRawRecord", arg0)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(bool)
|
||||||
return ret0
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddRawRecord indicates an expected call of AddRawRecord.
|
// AddRawRecord indicates an expected call of AddRawRecord.
|
||||||
@ -65,32 +65,18 @@ func (mr *MockAclListMockRecorder) AddRawRecord(arg0 interface{}) *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawRecord", reflect.TypeOf((*MockAclList)(nil).AddRawRecord), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawRecord", reflect.TypeOf((*MockAclList)(nil).AddRawRecord), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddRawRecords mocks base method.
|
|
||||||
func (m *MockAclList) AddRawRecords(arg0 []*consensusproto.RawRecordWithId) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "AddRawRecords", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddRawRecords indicates an expected call of AddRawRecords.
|
|
||||||
func (mr *MockAclListMockRecorder) AddRawRecords(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawRecords", reflect.TypeOf((*MockAclList)(nil).AddRawRecords), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close mocks base method.
|
// Close mocks base method.
|
||||||
func (m *MockAclList) Close(arg0 context.Context) error {
|
func (m *MockAclList) Close() error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Close", arg0)
|
ret := m.ctrl.Call(m, "Close")
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close indicates an expected call of Close.
|
// Close indicates an expected call of Close.
|
||||||
func (mr *MockAclListMockRecorder) Close(arg0 interface{}) *gomock.Call {
|
func (mr *MockAclListMockRecorder) Close() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockAclList)(nil).Close), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockAclList)(nil).Close))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get mocks base method.
|
// Get mocks base method.
|
||||||
@ -108,35 +94,6 @@ func (mr *MockAclListMockRecorder) Get(arg0 interface{}) *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockAclList)(nil).Get), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockAclList)(nil).Get), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetIndex mocks base method.
|
|
||||||
func (m *MockAclList) GetIndex(arg0 int) (*list.AclRecord, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "GetIndex", arg0)
|
|
||||||
ret0, _ := ret[0].(*list.AclRecord)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetIndex indicates an expected call of GetIndex.
|
|
||||||
func (mr *MockAclListMockRecorder) GetIndex(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIndex", reflect.TypeOf((*MockAclList)(nil).GetIndex), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasHead mocks base method.
|
|
||||||
func (m *MockAclList) HasHead(arg0 string) bool {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "HasHead", arg0)
|
|
||||||
ret0, _ := ret[0].(bool)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasHead indicates an expected call of HasHead.
|
|
||||||
func (mr *MockAclListMockRecorder) HasHead(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasHead", reflect.TypeOf((*MockAclList)(nil).HasHead), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Head mocks base method.
|
// Head mocks base method.
|
||||||
func (m *MockAclList) Head() *list.AclRecord {
|
func (m *MockAclList) Head() *list.AclRecord {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@ -254,20 +211,6 @@ func (mr *MockAclListMockRecorder) RUnlock() *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RUnlock", reflect.TypeOf((*MockAclList)(nil).RUnlock))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RUnlock", reflect.TypeOf((*MockAclList)(nil).RUnlock))
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecordBuilder mocks base method.
|
|
||||||
func (m *MockAclList) RecordBuilder() list.AclRecordBuilder {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "RecordBuilder")
|
|
||||||
ret0, _ := ret[0].(list.AclRecordBuilder)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordBuilder indicates an expected call of RecordBuilder.
|
|
||||||
func (mr *MockAclListMockRecorder) RecordBuilder() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordBuilder", reflect.TypeOf((*MockAclList)(nil).RecordBuilder))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Records mocks base method.
|
// Records mocks base method.
|
||||||
func (m *MockAclList) Records() []*list.AclRecord {
|
func (m *MockAclList) Records() []*list.AclRecord {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@ -282,26 +225,11 @@ func (mr *MockAclListMockRecorder) Records() *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Records", reflect.TypeOf((*MockAclList)(nil).Records))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Records", reflect.TypeOf((*MockAclList)(nil).Records))
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecordsAfter mocks base method.
|
|
||||||
func (m *MockAclList) RecordsAfter(arg0 context.Context, arg1 string) ([]*consensusproto.RawRecordWithId, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "RecordsAfter", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].([]*consensusproto.RawRecordWithId)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordsAfter indicates an expected call of RecordsAfter.
|
|
||||||
func (mr *MockAclListMockRecorder) RecordsAfter(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordsAfter", reflect.TypeOf((*MockAclList)(nil).RecordsAfter), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Root mocks base method.
|
// Root mocks base method.
|
||||||
func (m *MockAclList) Root() *consensusproto.RawRecordWithId {
|
func (m *MockAclList) Root() *aclrecordproto.RawAclRecordWithId {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Root")
|
ret := m.ctrl.Call(m, "Root")
|
||||||
ret0, _ := ret[0].(*consensusproto.RawRecordWithId)
|
ret0, _ := ret[0].(*aclrecordproto.RawAclRecordWithId)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,17 +250,3 @@ func (mr *MockAclListMockRecorder) Unlock() *gomock.Call {
|
|||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockAclList)(nil).Unlock))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockAclList)(nil).Unlock))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateRawRecord mocks base method.
|
|
||||||
func (m *MockAclList) ValidateRawRecord(arg0 *consensusproto.RawRecord) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "ValidateRawRecord", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateRawRecord indicates an expected call of ValidateRawRecord.
|
|
||||||
func (mr *MockAclListMockRecorder) ValidateRawRecord(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateRawRecord", reflect.TypeOf((*MockAclList)(nil).ValidateRawRecord), arg0)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
package list
|
package list
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
"github.com/anytypeio/any-sync/util/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AclRecord struct {
|
type AclRecord struct {
|
||||||
Id string
|
Id string
|
||||||
PrevId string
|
PrevId string
|
||||||
|
ReadKeyId string
|
||||||
Timestamp int64
|
Timestamp int64
|
||||||
Data []byte
|
Data []byte
|
||||||
Identity crypto.PubKey
|
Identity crypto.PubKey
|
||||||
@ -15,55 +16,7 @@ type AclRecord struct {
|
|||||||
Signature []byte
|
Signature []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type RequestRecord struct {
|
|
||||||
RequestIdentity crypto.PubKey
|
|
||||||
RequestMetadata []byte
|
|
||||||
Type RequestType
|
|
||||||
}
|
|
||||||
|
|
||||||
type AclUserState struct {
|
type AclUserState struct {
|
||||||
PubKey crypto.PubKey
|
PubKey crypto.PubKey
|
||||||
Permissions AclPermissions
|
Permissions aclrecordproto.AclUserPermissions
|
||||||
RequestMetadata []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type RequestType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
RequestTypeRemove RequestType = iota
|
|
||||||
RequestTypeJoin
|
|
||||||
)
|
|
||||||
|
|
||||||
type AclPermissions aclrecordproto.AclUserPermissions
|
|
||||||
|
|
||||||
func (p AclPermissions) NoPermissions() bool {
|
|
||||||
return aclrecordproto.AclUserPermissions(p) == aclrecordproto.AclUserPermissions_None
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p AclPermissions) IsOwner() bool {
|
|
||||||
return aclrecordproto.AclUserPermissions(p) == aclrecordproto.AclUserPermissions_Owner
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p AclPermissions) CanWrite() bool {
|
|
||||||
switch aclrecordproto.AclUserPermissions(p) {
|
|
||||||
case aclrecordproto.AclUserPermissions_Admin:
|
|
||||||
return true
|
|
||||||
case aclrecordproto.AclUserPermissions_Writer:
|
|
||||||
return true
|
|
||||||
case aclrecordproto.AclUserPermissions_Owner:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p AclPermissions) CanManageAccounts() bool {
|
|
||||||
switch aclrecordproto.AclUserPermissions(p) {
|
|
||||||
case aclrecordproto.AclUserPermissions_Admin:
|
|
||||||
return true
|
|
||||||
case aclrecordproto.AclUserPermissions_Owner:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,218 +0,0 @@
|
|||||||
package list
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ContentValidator interface {
|
|
||||||
ValidateAclRecordContents(ch *AclRecord) (err error)
|
|
||||||
ValidatePermissionChange(ch *aclrecordproto.AclAccountPermissionChange, authorIdentity crypto.PubKey) (err error)
|
|
||||||
ValidateInvite(ch *aclrecordproto.AclAccountInvite, authorIdentity crypto.PubKey) (err error)
|
|
||||||
ValidateInviteRevoke(ch *aclrecordproto.AclAccountInviteRevoke, authorIdentity crypto.PubKey) (err error)
|
|
||||||
ValidateRequestJoin(ch *aclrecordproto.AclAccountRequestJoin, authorIdentity crypto.PubKey) (err error)
|
|
||||||
ValidateRequestAccept(ch *aclrecordproto.AclAccountRequestAccept, authorIdentity crypto.PubKey) (err error)
|
|
||||||
ValidateRequestDecline(ch *aclrecordproto.AclAccountRequestDecline, authorIdentity crypto.PubKey) (err error)
|
|
||||||
ValidateAccountRemove(ch *aclrecordproto.AclAccountRemove, authorIdentity crypto.PubKey) (err error)
|
|
||||||
ValidateRequestRemove(ch *aclrecordproto.AclAccountRequestRemove, authorIdentity crypto.PubKey) (err error)
|
|
||||||
ValidateReadKeyChange(ch *aclrecordproto.AclReadKeyChange, authorIdentity crypto.PubKey) (err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type contentValidator struct {
|
|
||||||
keyStore crypto.KeyStorage
|
|
||||||
aclState *AclState
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *contentValidator) ValidateAclRecordContents(ch *AclRecord) (err error) {
|
|
||||||
if ch.PrevId != c.aclState.lastRecordId {
|
|
||||||
return ErrIncorrectRecordSequence
|
|
||||||
}
|
|
||||||
aclData := ch.Model.(*aclrecordproto.AclData)
|
|
||||||
for _, content := range aclData.AclContent {
|
|
||||||
err = c.validateAclRecordContent(content, ch.Identity)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *contentValidator) validateAclRecordContent(ch *aclrecordproto.AclContentValue, authorIdentity crypto.PubKey) (err error) {
|
|
||||||
switch {
|
|
||||||
case ch.GetPermissionChange() != nil:
|
|
||||||
return c.ValidatePermissionChange(ch.GetPermissionChange(), authorIdentity)
|
|
||||||
case ch.GetInvite() != nil:
|
|
||||||
return c.ValidateInvite(ch.GetInvite(), authorIdentity)
|
|
||||||
case ch.GetInviteRevoke() != nil:
|
|
||||||
return c.ValidateInviteRevoke(ch.GetInviteRevoke(), authorIdentity)
|
|
||||||
case ch.GetRequestJoin() != nil:
|
|
||||||
return c.ValidateRequestJoin(ch.GetRequestJoin(), authorIdentity)
|
|
||||||
case ch.GetRequestAccept() != nil:
|
|
||||||
return c.ValidateRequestAccept(ch.GetRequestAccept(), authorIdentity)
|
|
||||||
case ch.GetRequestDecline() != nil:
|
|
||||||
return c.ValidateRequestDecline(ch.GetRequestDecline(), authorIdentity)
|
|
||||||
case ch.GetAccountRemove() != nil:
|
|
||||||
return c.ValidateAccountRemove(ch.GetAccountRemove(), authorIdentity)
|
|
||||||
case ch.GetAccountRequestRemove() != nil:
|
|
||||||
return c.ValidateRequestRemove(ch.GetAccountRequestRemove(), authorIdentity)
|
|
||||||
case ch.GetReadKeyChange() != nil:
|
|
||||||
return c.ValidateReadKeyChange(ch.GetReadKeyChange(), authorIdentity)
|
|
||||||
default:
|
|
||||||
return ErrUnexpectedContentType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *contentValidator) ValidatePermissionChange(ch *aclrecordproto.AclAccountPermissionChange, authorIdentity crypto.PubKey) (err error) {
|
|
||||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
|
||||||
return ErrInsufficientPermissions
|
|
||||||
}
|
|
||||||
chIdentity, err := c.keyStore.PubKeyFromProto(ch.Identity)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, exists := c.aclState.userStates[mapKeyFromPubKey(chIdentity)]
|
|
||||||
if !exists {
|
|
||||||
return ErrNoSuchAccount
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *contentValidator) ValidateInvite(ch *aclrecordproto.AclAccountInvite, authorIdentity crypto.PubKey) (err error) {
|
|
||||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
|
||||||
return ErrInsufficientPermissions
|
|
||||||
}
|
|
||||||
_, err = c.keyStore.PubKeyFromProto(ch.InviteKey)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *contentValidator) ValidateInviteRevoke(ch *aclrecordproto.AclAccountInviteRevoke, authorIdentity crypto.PubKey) (err error) {
|
|
||||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
|
||||||
return ErrInsufficientPermissions
|
|
||||||
}
|
|
||||||
_, exists := c.aclState.inviteKeys[ch.InviteRecordId]
|
|
||||||
if !exists {
|
|
||||||
return ErrNoSuchInvite
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *contentValidator) ValidateRequestJoin(ch *aclrecordproto.AclAccountRequestJoin, authorIdentity crypto.PubKey) (err error) {
|
|
||||||
inviteKey, exists := c.aclState.inviteKeys[ch.InviteRecordId]
|
|
||||||
if !exists {
|
|
||||||
return ErrNoSuchInvite
|
|
||||||
}
|
|
||||||
inviteIdentity, err := c.keyStore.PubKeyFromProto(ch.InviteIdentity)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, exists := c.aclState.pendingRequests[mapKeyFromPubKey(inviteIdentity)]; exists {
|
|
||||||
return ErrPendingRequest
|
|
||||||
}
|
|
||||||
if !authorIdentity.Equals(inviteIdentity) {
|
|
||||||
return ErrIncorrectIdentity
|
|
||||||
}
|
|
||||||
rawInviteIdentity, err := inviteIdentity.Raw()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ok, err := inviteKey.Verify(rawInviteIdentity, ch.InviteIdentitySignature)
|
|
||||||
if err != nil {
|
|
||||||
return ErrInvalidSignature
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
return ErrInvalidSignature
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *contentValidator) ValidateRequestAccept(ch *aclrecordproto.AclAccountRequestAccept, authorIdentity crypto.PubKey) (err error) {
|
|
||||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
|
||||||
return ErrInsufficientPermissions
|
|
||||||
}
|
|
||||||
record, exists := c.aclState.requestRecords[ch.RequestRecordId]
|
|
||||||
if !exists {
|
|
||||||
return ErrNoSuchRequest
|
|
||||||
}
|
|
||||||
acceptIdentity, err := c.keyStore.PubKeyFromProto(ch.Identity)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !acceptIdentity.Equals(record.RequestIdentity) {
|
|
||||||
return ErrIncorrectIdentity
|
|
||||||
}
|
|
||||||
if ch.Permissions == aclrecordproto.AclUserPermissions_Owner {
|
|
||||||
return ErrInsufficientPermissions
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *contentValidator) ValidateRequestDecline(ch *aclrecordproto.AclAccountRequestDecline, authorIdentity crypto.PubKey) (err error) {
|
|
||||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
|
||||||
return ErrInsufficientPermissions
|
|
||||||
}
|
|
||||||
_, exists := c.aclState.requestRecords[ch.RequestRecordId]
|
|
||||||
if !exists {
|
|
||||||
return ErrNoSuchRequest
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *contentValidator) ValidateAccountRemove(ch *aclrecordproto.AclAccountRemove, authorIdentity crypto.PubKey) (err error) {
|
|
||||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
|
||||||
return ErrInsufficientPermissions
|
|
||||||
}
|
|
||||||
seenIdentities := map[string]struct{}{}
|
|
||||||
for _, rawIdentity := range ch.Identities {
|
|
||||||
identity, err := c.keyStore.PubKeyFromProto(rawIdentity)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if identity.Equals(authorIdentity) {
|
|
||||||
return ErrInsufficientPermissions
|
|
||||||
}
|
|
||||||
permissions := c.aclState.Permissions(identity)
|
|
||||||
if permissions.NoPermissions() {
|
|
||||||
return ErrNoSuchAccount
|
|
||||||
}
|
|
||||||
if permissions.IsOwner() {
|
|
||||||
return ErrInsufficientPermissions
|
|
||||||
}
|
|
||||||
idKey := mapKeyFromPubKey(identity)
|
|
||||||
if _, exists := seenIdentities[idKey]; exists {
|
|
||||||
return ErrDuplicateAccounts
|
|
||||||
}
|
|
||||||
seenIdentities[mapKeyFromPubKey(identity)] = struct{}{}
|
|
||||||
}
|
|
||||||
return c.validateAccountReadKeys(ch.AccountKeys, len(c.aclState.userStates)-len(ch.Identities))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *contentValidator) ValidateRequestRemove(ch *aclrecordproto.AclAccountRequestRemove, authorIdentity crypto.PubKey) (err error) {
|
|
||||||
if c.aclState.Permissions(authorIdentity).NoPermissions() {
|
|
||||||
return ErrInsufficientPermissions
|
|
||||||
}
|
|
||||||
if _, exists := c.aclState.pendingRequests[mapKeyFromPubKey(authorIdentity)]; exists {
|
|
||||||
return ErrPendingRequest
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *contentValidator) ValidateReadKeyChange(ch *aclrecordproto.AclReadKeyChange, authorIdentity crypto.PubKey) (err error) {
|
|
||||||
return c.validateAccountReadKeys(ch.AccountKeys, len(c.aclState.userStates))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *contentValidator) validateAccountReadKeys(accountKeys []*aclrecordproto.AclEncryptedReadKey, usersNum int) (err error) {
|
|
||||||
if len(accountKeys) != usersNum {
|
|
||||||
return ErrIncorrectNumberOfAccounts
|
|
||||||
}
|
|
||||||
for _, encKeys := range accountKeys {
|
|
||||||
identity, err := c.keyStore.PubKeyFromProto(encKeys.Identity)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, exists := c.aclState.userStates[mapKeyFromPubKey(identity)]
|
|
||||||
if !exists {
|
|
||||||
return ErrNoSuchAccount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@ -3,26 +3,24 @@ package liststorage
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
|
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
type inMemoryAclListStorage struct {
|
type inMemoryAclListStorage struct {
|
||||||
id string
|
id string
|
||||||
root *consensusproto.RawRecordWithId
|
root *aclrecordproto.RawAclRecordWithId
|
||||||
head string
|
head string
|
||||||
records map[string]*consensusproto.RawRecordWithId
|
records map[string]*aclrecordproto.RawAclRecordWithId
|
||||||
|
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewInMemoryAclListStorage(
|
func NewInMemoryAclListStorage(
|
||||||
id string,
|
id string,
|
||||||
records []*consensusproto.RawRecordWithId) (ListStorage, error) {
|
records []*aclrecordproto.RawAclRecordWithId) (ListStorage, error) {
|
||||||
|
|
||||||
allRecords := make(map[string]*consensusproto.RawRecordWithId)
|
allRecords := make(map[string]*aclrecordproto.RawAclRecordWithId)
|
||||||
for _, ch := range records {
|
for _, ch := range records {
|
||||||
allRecords[ch.Id] = ch
|
allRecords[ch.Id] = ch
|
||||||
}
|
}
|
||||||
@ -43,7 +41,7 @@ func (t *inMemoryAclListStorage) Id() string {
|
|||||||
return t.id
|
return t.id
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *inMemoryAclListStorage) Root() (*consensusproto.RawRecordWithId, error) {
|
func (t *inMemoryAclListStorage) Root() (*aclrecordproto.RawAclRecordWithId, error) {
|
||||||
t.RLock()
|
t.RLock()
|
||||||
defer t.RUnlock()
|
defer t.RUnlock()
|
||||||
return t.root, nil
|
return t.root, nil
|
||||||
@ -62,7 +60,7 @@ func (t *inMemoryAclListStorage) SetHead(head string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *inMemoryAclListStorage) AddRawRecord(ctx context.Context, record *consensusproto.RawRecordWithId) error {
|
func (t *inMemoryAclListStorage) AddRawRecord(ctx context.Context, record *aclrecordproto.RawAclRecordWithId) error {
|
||||||
t.Lock()
|
t.Lock()
|
||||||
defer t.Unlock()
|
defer t.Unlock()
|
||||||
// TODO: better to do deep copy
|
// TODO: better to do deep copy
|
||||||
@ -70,7 +68,7 @@ func (t *inMemoryAclListStorage) AddRawRecord(ctx context.Context, record *conse
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *inMemoryAclListStorage) GetRawRecord(ctx context.Context, recordId string) (*consensusproto.RawRecordWithId, error) {
|
func (t *inMemoryAclListStorage) GetRawRecord(ctx context.Context, recordId string) (*aclrecordproto.RawAclRecordWithId, error) {
|
||||||
t.RLock()
|
t.RLock()
|
||||||
defer t.RUnlock()
|
defer t.RUnlock()
|
||||||
if res, exists := t.records[recordId]; exists {
|
if res, exists := t.records[recordId]; exists {
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
//go:generate mockgen -destination mock_liststorage/mock_liststorage.go github.com/anyproto/any-sync/commonspace/object/acl/liststorage ListStorage
|
//go:generate mockgen -destination mock_liststorage/mock_liststorage.go github.com/anytypeio/any-sync/commonspace/object/acl/liststorage ListStorage
|
||||||
package liststorage
|
package liststorage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -15,15 +14,15 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Exporter interface {
|
type Exporter interface {
|
||||||
ListStorage(root *consensusproto.RawRecordWithId) (ListStorage, error)
|
ListStorage(root *aclrecordproto.RawAclRecordWithId) (ListStorage, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListStorage interface {
|
type ListStorage interface {
|
||||||
Id() string
|
Id() string
|
||||||
Root() (*consensusproto.RawRecordWithId, error)
|
Root() (*aclrecordproto.RawAclRecordWithId, error)
|
||||||
Head() (string, error)
|
Head() (string, error)
|
||||||
SetHead(headId string) error
|
SetHead(headId string) error
|
||||||
|
|
||||||
GetRawRecord(ctx context.Context, id string) (*consensusproto.RawRecordWithId, error)
|
GetRawRecord(ctx context.Context, id string) (*aclrecordproto.RawAclRecordWithId, error)
|
||||||
AddRawRecord(ctx context.Context, rec *consensusproto.RawRecordWithId) error
|
AddRawRecord(ctx context.Context, rec *aclrecordproto.RawAclRecordWithId) error
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: github.com/anyproto/any-sync/commonspace/object/acl/liststorage (interfaces: ListStorage)
|
// Source: github.com/anytypeio/any-sync/commonspace/object/acl/liststorage (interfaces: ListStorage)
|
||||||
|
|
||||||
// Package mock_liststorage is a generated GoMock package.
|
// Package mock_liststorage is a generated GoMock package.
|
||||||
package mock_liststorage
|
package mock_liststorage
|
||||||
@ -8,8 +8,8 @@ import (
|
|||||||
context "context"
|
context "context"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
consensusproto "github.com/anyproto/any-sync/consensus/consensusproto"
|
aclrecordproto "github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||||
gomock "go.uber.org/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockListStorage is a mock of ListStorage interface.
|
// MockListStorage is a mock of ListStorage interface.
|
||||||
@ -36,7 +36,7 @@ func (m *MockListStorage) EXPECT() *MockListStorageMockRecorder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddRawRecord mocks base method.
|
// AddRawRecord mocks base method.
|
||||||
func (m *MockListStorage) AddRawRecord(arg0 context.Context, arg1 *consensusproto.RawRecordWithId) error {
|
func (m *MockListStorage) AddRawRecord(arg0 context.Context, arg1 *aclrecordproto.RawAclRecordWithId) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "AddRawRecord", arg0, arg1)
|
ret := m.ctrl.Call(m, "AddRawRecord", arg0, arg1)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
@ -50,10 +50,10 @@ func (mr *MockListStorageMockRecorder) AddRawRecord(arg0, arg1 interface{}) *gom
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetRawRecord mocks base method.
|
// GetRawRecord mocks base method.
|
||||||
func (m *MockListStorage) GetRawRecord(arg0 context.Context, arg1 string) (*consensusproto.RawRecordWithId, error) {
|
func (m *MockListStorage) GetRawRecord(arg0 context.Context, arg1 string) (*aclrecordproto.RawAclRecordWithId, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetRawRecord", arg0, arg1)
|
ret := m.ctrl.Call(m, "GetRawRecord", arg0, arg1)
|
||||||
ret0, _ := ret[0].(*consensusproto.RawRecordWithId)
|
ret0, _ := ret[0].(*aclrecordproto.RawAclRecordWithId)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
@ -94,10 +94,10 @@ func (mr *MockListStorageMockRecorder) Id() *gomock.Call {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Root mocks base method.
|
// Root mocks base method.
|
||||||
func (m *MockListStorage) Root() (*consensusproto.RawRecordWithId, error) {
|
func (m *MockListStorage) Root() (*aclrecordproto.RawAclRecordWithId, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Root")
|
ret := m.ctrl.Call(m, "Root")
|
||||||
ret0, _ := ret[0].(*consensusproto.RawRecordWithId)
|
ret0, _ := ret[0].(*aclrecordproto.RawAclRecordWithId)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,120 +0,0 @@
|
|||||||
//go:generate mockgen -destination mock_syncacl/mock_syncacl.go github.com/anyproto/any-sync/commonspace/object/acl/syncacl SyncAcl,SyncClient,RequestFactory,AclSyncProtocol
|
|
||||||
package syncacl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AclSyncProtocol interface {
|
|
||||||
HeadUpdate(ctx context.Context, senderId string, update *consensusproto.LogHeadUpdate) (request *consensusproto.LogSyncMessage, err error)
|
|
||||||
FullSyncRequest(ctx context.Context, senderId string, request *consensusproto.LogFullSyncRequest) (response *consensusproto.LogSyncMessage, err error)
|
|
||||||
FullSyncResponse(ctx context.Context, senderId string, response *consensusproto.LogFullSyncResponse) (err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type aclSyncProtocol struct {
|
|
||||||
log logger.CtxLogger
|
|
||||||
spaceId string
|
|
||||||
aclList list.AclList
|
|
||||||
reqFactory RequestFactory
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclSyncProtocol) HeadUpdate(ctx context.Context, senderId string, update *consensusproto.LogHeadUpdate) (request *consensusproto.LogSyncMessage, err error) {
|
|
||||||
isEmptyUpdate := len(update.Records) == 0
|
|
||||||
log := a.log.With(
|
|
||||||
zap.String("senderId", senderId),
|
|
||||||
zap.String("update head", update.Head),
|
|
||||||
zap.Int("len(update records)", len(update.Records)))
|
|
||||||
log.DebugCtx(ctx, "received acl head update message")
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
log.ErrorCtx(ctx, "acl head update finished with error", zap.Error(err))
|
|
||||||
} else if request != nil {
|
|
||||||
cnt := request.Content.GetFullSyncRequest()
|
|
||||||
log.DebugCtx(ctx, "returning acl full sync request", zap.String("request head", cnt.Head))
|
|
||||||
} else {
|
|
||||||
if !isEmptyUpdate {
|
|
||||||
log.DebugCtx(ctx, "acl head update finished correctly")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if isEmptyUpdate {
|
|
||||||
headEquals := a.aclList.Head().Id == update.Head
|
|
||||||
log.DebugCtx(ctx, "is empty acl head update", zap.Bool("headEquals", headEquals))
|
|
||||||
if headEquals {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return a.reqFactory.CreateFullSyncRequest(a.aclList, update.Head)
|
|
||||||
}
|
|
||||||
if a.aclList.HasHead(update.Head) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = a.aclList.AddRawRecords(update.Records)
|
|
||||||
if err == list.ErrIncorrectRecordSequence {
|
|
||||||
return a.reqFactory.CreateFullSyncRequest(a.aclList, update.Head)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclSyncProtocol) FullSyncRequest(ctx context.Context, senderId string, request *consensusproto.LogFullSyncRequest) (response *consensusproto.LogSyncMessage, err error) {
|
|
||||||
log := a.log.With(
|
|
||||||
zap.String("senderId", senderId),
|
|
||||||
zap.String("request head", request.Head),
|
|
||||||
zap.Int("len(request records)", len(request.Records)))
|
|
||||||
log.DebugCtx(ctx, "received acl full sync request message")
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
log.ErrorCtx(ctx, "acl full sync request finished with error", zap.Error(err))
|
|
||||||
} else if response != nil {
|
|
||||||
cnt := response.Content.GetFullSyncResponse()
|
|
||||||
log.DebugCtx(ctx, "acl full sync response sent", zap.String("response head", cnt.Head), zap.Int("len(response records)", len(cnt.Records)))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if !a.aclList.HasHead(request.Head) {
|
|
||||||
if len(request.Records) > 0 {
|
|
||||||
// in this case we can try to add some records
|
|
||||||
err = a.aclList.AddRawRecords(request.Records)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// here it is impossible for us to do anything, we can't return records after head as defined in request, because we don't have it
|
|
||||||
return nil, list.ErrIncorrectRecordSequence
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return a.reqFactory.CreateFullSyncResponse(a.aclList, request.Head)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclSyncProtocol) FullSyncResponse(ctx context.Context, senderId string, response *consensusproto.LogFullSyncResponse) (err error) {
|
|
||||||
log := a.log.With(
|
|
||||||
zap.String("senderId", senderId),
|
|
||||||
zap.String("response head", response.Head),
|
|
||||||
zap.Int("len(response records)", len(response.Records)))
|
|
||||||
log.DebugCtx(ctx, "received acl full sync response message")
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
log.ErrorCtx(ctx, "acl full sync response failed", zap.Error(err))
|
|
||||||
} else {
|
|
||||||
log.DebugCtx(ctx, "acl full sync response succeeded")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if a.aclList.HasHead(response.Head) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return a.aclList.AddRawRecords(response.Records)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAclSyncProtocol(spaceId string, aclList list.AclList, reqFactory RequestFactory) *aclSyncProtocol {
|
|
||||||
return &aclSyncProtocol{
|
|
||||||
log: log.With(zap.String("spaceId", spaceId), zap.String("aclId", aclList.Id())),
|
|
||||||
spaceId: spaceId,
|
|
||||||
aclList: aclList,
|
|
||||||
reqFactory: reqFactory,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,213 +0,0 @@
|
|||||||
package syncacl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/list/mock_list"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl/mock_syncacl"
|
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type aclSyncProtocolFixture struct {
|
|
||||||
log logger.CtxLogger
|
|
||||||
spaceId string
|
|
||||||
senderId string
|
|
||||||
aclId string
|
|
||||||
aclMock *mock_list.MockAclList
|
|
||||||
reqFactory *mock_syncacl.MockRequestFactory
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
syncProtocol AclSyncProtocol
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSyncProtocolFixture(t *testing.T) *aclSyncProtocolFixture {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
aclList := mock_list.NewMockAclList(ctrl)
|
|
||||||
spaceId := "spaceId"
|
|
||||||
reqFactory := mock_syncacl.NewMockRequestFactory(ctrl)
|
|
||||||
aclList.EXPECT().Id().Return("aclId")
|
|
||||||
syncProtocol := newAclSyncProtocol(spaceId, aclList, reqFactory)
|
|
||||||
return &aclSyncProtocolFixture{
|
|
||||||
log: log,
|
|
||||||
spaceId: spaceId,
|
|
||||||
senderId: "senderId",
|
|
||||||
aclId: "aclId",
|
|
||||||
aclMock: aclList,
|
|
||||||
reqFactory: reqFactory,
|
|
||||||
ctrl: ctrl,
|
|
||||||
syncProtocol: syncProtocol,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fx *aclSyncProtocolFixture) stop() {
|
|
||||||
fx.ctrl.Finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHeadUpdate(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
fullRequest := &consensusproto.LogSyncMessage{
|
|
||||||
Content: &consensusproto.LogSyncContentValue{
|
|
||||||
Value: &consensusproto.LogSyncContentValue_FullSyncRequest{
|
|
||||||
FullSyncRequest: &consensusproto.LogFullSyncRequest{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
t.Run("head update non empty all heads added", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &consensusproto.RawRecordWithId{}
|
|
||||||
headUpdate := &consensusproto.LogHeadUpdate{
|
|
||||||
Head: "h1",
|
|
||||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
|
||||||
}
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
fx.aclMock.EXPECT().HasHead("h1").Return(false)
|
|
||||||
fx.aclMock.EXPECT().AddRawRecords(headUpdate.Records).Return(nil)
|
|
||||||
req, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
|
||||||
require.Nil(t, req)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
t.Run("head update results in full request", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &consensusproto.RawRecordWithId{}
|
|
||||||
headUpdate := &consensusproto.LogHeadUpdate{
|
|
||||||
Head: "h1",
|
|
||||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
|
||||||
}
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
fx.aclMock.EXPECT().HasHead("h1").Return(false)
|
|
||||||
fx.aclMock.EXPECT().AddRawRecords(headUpdate.Records).Return(list.ErrIncorrectRecordSequence)
|
|
||||||
fx.reqFactory.EXPECT().CreateFullSyncRequest(fx.aclMock, headUpdate.Head).Return(fullRequest, nil)
|
|
||||||
req, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
|
||||||
require.Equal(t, fullRequest, req)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
t.Run("head update old heads", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &consensusproto.RawRecordWithId{}
|
|
||||||
headUpdate := &consensusproto.LogHeadUpdate{
|
|
||||||
Head: "h1",
|
|
||||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
|
||||||
}
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
fx.aclMock.EXPECT().HasHead("h1").Return(true)
|
|
||||||
req, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
|
||||||
require.Nil(t, req)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
t.Run("head update empty equals", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
headUpdate := &consensusproto.LogHeadUpdate{
|
|
||||||
Head: "h1",
|
|
||||||
}
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
fx.aclMock.EXPECT().Head().Return(&list.AclRecord{Id: "h1"})
|
|
||||||
req, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
|
||||||
require.Nil(t, req)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
t.Run("head update empty results in full request", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
headUpdate := &consensusproto.LogHeadUpdate{
|
|
||||||
Head: "h1",
|
|
||||||
}
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
fx.aclMock.EXPECT().Head().Return(&list.AclRecord{Id: "h2"})
|
|
||||||
fx.reqFactory.EXPECT().CreateFullSyncRequest(fx.aclMock, headUpdate.Head).Return(fullRequest, nil)
|
|
||||||
req, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
|
||||||
require.Equal(t, fullRequest, req)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFullSyncRequest(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
fullResponse := &consensusproto.LogSyncMessage{
|
|
||||||
Content: &consensusproto.LogSyncContentValue{
|
|
||||||
Value: &consensusproto.LogSyncContentValue_FullSyncResponse{
|
|
||||||
FullSyncResponse: &consensusproto.LogFullSyncResponse{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
t.Run("full sync request non empty all heads added", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &consensusproto.RawRecordWithId{}
|
|
||||||
fullRequest := &consensusproto.LogFullSyncRequest{
|
|
||||||
Head: "h1",
|
|
||||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
|
||||||
}
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
fx.aclMock.EXPECT().HasHead("h1").Return(false)
|
|
||||||
fx.aclMock.EXPECT().AddRawRecords(fullRequest.Records).Return(nil)
|
|
||||||
fx.reqFactory.EXPECT().CreateFullSyncResponse(fx.aclMock, fullRequest.Head).Return(fullResponse, nil)
|
|
||||||
resp, err := fx.syncProtocol.FullSyncRequest(ctx, fx.senderId, fullRequest)
|
|
||||||
require.Equal(t, fullResponse, resp)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
t.Run("full sync request non empty head exists", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &consensusproto.RawRecordWithId{}
|
|
||||||
fullRequest := &consensusproto.LogFullSyncRequest{
|
|
||||||
Head: "h1",
|
|
||||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
|
||||||
}
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
fx.aclMock.EXPECT().HasHead("h1").Return(true)
|
|
||||||
fx.reqFactory.EXPECT().CreateFullSyncResponse(fx.aclMock, fullRequest.Head).Return(fullResponse, nil)
|
|
||||||
resp, err := fx.syncProtocol.FullSyncRequest(ctx, fx.senderId, fullRequest)
|
|
||||||
require.Equal(t, fullResponse, resp)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
t.Run("full sync request empty head not exists", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
fullRequest := &consensusproto.LogFullSyncRequest{
|
|
||||||
Head: "h1",
|
|
||||||
}
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
fx.aclMock.EXPECT().HasHead("h1").Return(false)
|
|
||||||
resp, err := fx.syncProtocol.FullSyncRequest(ctx, fx.senderId, fullRequest)
|
|
||||||
require.Nil(t, resp)
|
|
||||||
require.Error(t, list.ErrIncorrectRecordSequence, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFullSyncResponse(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
t.Run("full sync response no heads", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &consensusproto.RawRecordWithId{}
|
|
||||||
fullResponse := &consensusproto.LogFullSyncResponse{
|
|
||||||
Head: "h1",
|
|
||||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
|
||||||
}
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
fx.aclMock.EXPECT().HasHead("h1").Return(false)
|
|
||||||
fx.aclMock.EXPECT().AddRawRecords(fullResponse.Records).Return(nil)
|
|
||||||
err := fx.syncProtocol.FullSyncResponse(ctx, fx.senderId, fullResponse)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
t.Run("full sync response has heads", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &consensusproto.RawRecordWithId{}
|
|
||||||
fullResponse := &consensusproto.LogFullSyncResponse{
|
|
||||||
Head: "h1",
|
|
||||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
|
||||||
}
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
fx.aclMock.EXPECT().HasHead("h1").Return(true)
|
|
||||||
err := fx.syncProtocol.FullSyncResponse(ctx, fx.senderId, fullResponse)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
package headupdater
|
|
||||||
|
|
||||||
type HeadUpdater interface {
|
|
||||||
UpdateHeads(id string, heads []string)
|
|
||||||
}
|
|
||||||
@ -1,694 +0,0 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
|
||||||
// Source: github.com/anyproto/any-sync/commonspace/object/acl/syncacl (interfaces: SyncAcl,SyncClient,RequestFactory,AclSyncProtocol)
|
|
||||||
|
|
||||||
// Package mock_syncacl is a generated GoMock package.
|
|
||||||
package mock_syncacl
|
|
||||||
|
|
||||||
import (
|
|
||||||
context "context"
|
|
||||||
reflect "reflect"
|
|
||||||
|
|
||||||
app "github.com/anyproto/any-sync/app"
|
|
||||||
list "github.com/anyproto/any-sync/commonspace/object/acl/list"
|
|
||||||
headupdater "github.com/anyproto/any-sync/commonspace/object/acl/syncacl/headupdater"
|
|
||||||
spacesyncproto "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
|
||||||
consensusproto "github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
crypto "github.com/anyproto/any-sync/util/crypto"
|
|
||||||
gomock "go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockSyncAcl is a mock of SyncAcl interface.
|
|
||||||
type MockSyncAcl struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockSyncAclMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockSyncAclMockRecorder is the mock recorder for MockSyncAcl.
|
|
||||||
type MockSyncAclMockRecorder struct {
|
|
||||||
mock *MockSyncAcl
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockSyncAcl creates a new mock instance.
|
|
||||||
func NewMockSyncAcl(ctrl *gomock.Controller) *MockSyncAcl {
|
|
||||||
mock := &MockSyncAcl{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockSyncAclMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockSyncAcl) EXPECT() *MockSyncAclMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// AclState mocks base method.
|
|
||||||
func (m *MockSyncAcl) AclState() *list.AclState {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "AclState")
|
|
||||||
ret0, _ := ret[0].(*list.AclState)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// AclState indicates an expected call of AclState.
|
|
||||||
func (mr *MockSyncAclMockRecorder) AclState() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AclState", reflect.TypeOf((*MockSyncAcl)(nil).AclState))
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddRawRecord mocks base method.
|
|
||||||
func (m *MockSyncAcl) AddRawRecord(arg0 *consensusproto.RawRecordWithId) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "AddRawRecord", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddRawRecord indicates an expected call of AddRawRecord.
|
|
||||||
func (mr *MockSyncAclMockRecorder) AddRawRecord(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawRecord", reflect.TypeOf((*MockSyncAcl)(nil).AddRawRecord), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddRawRecords mocks base method.
|
|
||||||
func (m *MockSyncAcl) AddRawRecords(arg0 []*consensusproto.RawRecordWithId) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "AddRawRecords", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddRawRecords indicates an expected call of AddRawRecords.
|
|
||||||
func (mr *MockSyncAclMockRecorder) AddRawRecords(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawRecords", reflect.TypeOf((*MockSyncAcl)(nil).AddRawRecords), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close mocks base method.
|
|
||||||
func (m *MockSyncAcl) Close(arg0 context.Context) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Close", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close indicates an expected call of Close.
|
|
||||||
func (mr *MockSyncAclMockRecorder) Close(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockSyncAcl)(nil).Close), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get mocks base method.
|
|
||||||
func (m *MockSyncAcl) Get(arg0 string) (*list.AclRecord, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Get", arg0)
|
|
||||||
ret0, _ := ret[0].(*list.AclRecord)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get indicates an expected call of Get.
|
|
||||||
func (mr *MockSyncAclMockRecorder) Get(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockSyncAcl)(nil).Get), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetIndex mocks base method.
|
|
||||||
func (m *MockSyncAcl) GetIndex(arg0 int) (*list.AclRecord, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "GetIndex", arg0)
|
|
||||||
ret0, _ := ret[0].(*list.AclRecord)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetIndex indicates an expected call of GetIndex.
|
|
||||||
func (mr *MockSyncAclMockRecorder) GetIndex(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIndex", reflect.TypeOf((*MockSyncAcl)(nil).GetIndex), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleMessage mocks base method.
|
|
||||||
func (m *MockSyncAcl) HandleMessage(arg0 context.Context, arg1 string, arg2 *spacesyncproto.ObjectSyncMessage) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "HandleMessage", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleMessage indicates an expected call of HandleMessage.
|
|
||||||
func (mr *MockSyncAclMockRecorder) HandleMessage(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleMessage", reflect.TypeOf((*MockSyncAcl)(nil).HandleMessage), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleRequest mocks base method.
|
|
||||||
func (m *MockSyncAcl) HandleRequest(arg0 context.Context, arg1 string, arg2 *spacesyncproto.ObjectSyncMessage) (*spacesyncproto.ObjectSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "HandleRequest", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(*spacesyncproto.ObjectSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleRequest indicates an expected call of HandleRequest.
|
|
||||||
func (mr *MockSyncAclMockRecorder) HandleRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleRequest", reflect.TypeOf((*MockSyncAcl)(nil).HandleRequest), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasHead mocks base method.
|
|
||||||
func (m *MockSyncAcl) HasHead(arg0 string) bool {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "HasHead", arg0)
|
|
||||||
ret0, _ := ret[0].(bool)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasHead indicates an expected call of HasHead.
|
|
||||||
func (mr *MockSyncAclMockRecorder) HasHead(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasHead", reflect.TypeOf((*MockSyncAcl)(nil).HasHead), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Head mocks base method.
|
|
||||||
func (m *MockSyncAcl) Head() *list.AclRecord {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Head")
|
|
||||||
ret0, _ := ret[0].(*list.AclRecord)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Head indicates an expected call of Head.
|
|
||||||
func (mr *MockSyncAclMockRecorder) Head() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Head", reflect.TypeOf((*MockSyncAcl)(nil).Head))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Id mocks base method.
|
|
||||||
func (m *MockSyncAcl) Id() string {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Id")
|
|
||||||
ret0, _ := ret[0].(string)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Id indicates an expected call of Id.
|
|
||||||
func (mr *MockSyncAclMockRecorder) Id() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Id", reflect.TypeOf((*MockSyncAcl)(nil).Id))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init mocks base method.
|
|
||||||
func (m *MockSyncAcl) Init(arg0 *app.App) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Init", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init indicates an expected call of Init.
|
|
||||||
func (mr *MockSyncAclMockRecorder) Init(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockSyncAcl)(nil).Init), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsAfter mocks base method.
|
|
||||||
func (m *MockSyncAcl) IsAfter(arg0, arg1 string) (bool, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "IsAfter", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(bool)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsAfter indicates an expected call of IsAfter.
|
|
||||||
func (mr *MockSyncAclMockRecorder) IsAfter(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAfter", reflect.TypeOf((*MockSyncAcl)(nil).IsAfter), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate mocks base method.
|
|
||||||
func (m *MockSyncAcl) Iterate(arg0 func(*list.AclRecord) bool) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "Iterate", arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate indicates an expected call of Iterate.
|
|
||||||
func (mr *MockSyncAclMockRecorder) Iterate(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Iterate", reflect.TypeOf((*MockSyncAcl)(nil).Iterate), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IterateFrom mocks base method.
|
|
||||||
func (m *MockSyncAcl) IterateFrom(arg0 string, arg1 func(*list.AclRecord) bool) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "IterateFrom", arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IterateFrom indicates an expected call of IterateFrom.
|
|
||||||
func (mr *MockSyncAclMockRecorder) IterateFrom(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IterateFrom", reflect.TypeOf((*MockSyncAcl)(nil).IterateFrom), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyStorage mocks base method.
|
|
||||||
func (m *MockSyncAcl) KeyStorage() crypto.KeyStorage {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "KeyStorage")
|
|
||||||
ret0, _ := ret[0].(crypto.KeyStorage)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyStorage indicates an expected call of KeyStorage.
|
|
||||||
func (mr *MockSyncAclMockRecorder) KeyStorage() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeyStorage", reflect.TypeOf((*MockSyncAcl)(nil).KeyStorage))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lock mocks base method.
|
|
||||||
func (m *MockSyncAcl) Lock() {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "Lock")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lock indicates an expected call of Lock.
|
|
||||||
func (mr *MockSyncAclMockRecorder) Lock() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Lock", reflect.TypeOf((*MockSyncAcl)(nil).Lock))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name mocks base method.
|
|
||||||
func (m *MockSyncAcl) Name() string {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Name")
|
|
||||||
ret0, _ := ret[0].(string)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name indicates an expected call of Name.
|
|
||||||
func (mr *MockSyncAclMockRecorder) Name() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockSyncAcl)(nil).Name))
|
|
||||||
}
|
|
||||||
|
|
||||||
// RLock mocks base method.
|
|
||||||
func (m *MockSyncAcl) RLock() {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "RLock")
|
|
||||||
}
|
|
||||||
|
|
||||||
// RLock indicates an expected call of RLock.
|
|
||||||
func (mr *MockSyncAclMockRecorder) RLock() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RLock", reflect.TypeOf((*MockSyncAcl)(nil).RLock))
|
|
||||||
}
|
|
||||||
|
|
||||||
// RUnlock mocks base method.
|
|
||||||
func (m *MockSyncAcl) RUnlock() {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "RUnlock")
|
|
||||||
}
|
|
||||||
|
|
||||||
// RUnlock indicates an expected call of RUnlock.
|
|
||||||
func (mr *MockSyncAclMockRecorder) RUnlock() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RUnlock", reflect.TypeOf((*MockSyncAcl)(nil).RUnlock))
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordBuilder mocks base method.
|
|
||||||
func (m *MockSyncAcl) RecordBuilder() list.AclRecordBuilder {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "RecordBuilder")
|
|
||||||
ret0, _ := ret[0].(list.AclRecordBuilder)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordBuilder indicates an expected call of RecordBuilder.
|
|
||||||
func (mr *MockSyncAclMockRecorder) RecordBuilder() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordBuilder", reflect.TypeOf((*MockSyncAcl)(nil).RecordBuilder))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Records mocks base method.
|
|
||||||
func (m *MockSyncAcl) Records() []*list.AclRecord {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Records")
|
|
||||||
ret0, _ := ret[0].([]*list.AclRecord)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Records indicates an expected call of Records.
|
|
||||||
func (mr *MockSyncAclMockRecorder) Records() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Records", reflect.TypeOf((*MockSyncAcl)(nil).Records))
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordsAfter mocks base method.
|
|
||||||
func (m *MockSyncAcl) RecordsAfter(arg0 context.Context, arg1 string) ([]*consensusproto.RawRecordWithId, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "RecordsAfter", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].([]*consensusproto.RawRecordWithId)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordsAfter indicates an expected call of RecordsAfter.
|
|
||||||
func (mr *MockSyncAclMockRecorder) RecordsAfter(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordsAfter", reflect.TypeOf((*MockSyncAcl)(nil).RecordsAfter), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Root mocks base method.
|
|
||||||
func (m *MockSyncAcl) Root() *consensusproto.RawRecordWithId {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Root")
|
|
||||||
ret0, _ := ret[0].(*consensusproto.RawRecordWithId)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Root indicates an expected call of Root.
|
|
||||||
func (mr *MockSyncAclMockRecorder) Root() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Root", reflect.TypeOf((*MockSyncAcl)(nil).Root))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run mocks base method.
|
|
||||||
func (m *MockSyncAcl) Run(arg0 context.Context) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Run", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run indicates an expected call of Run.
|
|
||||||
func (mr *MockSyncAclMockRecorder) Run(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockSyncAcl)(nil).Run), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHeadUpdater mocks base method.
|
|
||||||
func (m *MockSyncAcl) SetHeadUpdater(arg0 headupdater.HeadUpdater) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "SetHeadUpdater", arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHeadUpdater indicates an expected call of SetHeadUpdater.
|
|
||||||
func (mr *MockSyncAclMockRecorder) SetHeadUpdater(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetHeadUpdater", reflect.TypeOf((*MockSyncAcl)(nil).SetHeadUpdater), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SyncWithPeer mocks base method.
|
|
||||||
func (m *MockSyncAcl) SyncWithPeer(arg0 context.Context, arg1 string) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "SyncWithPeer", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// SyncWithPeer indicates an expected call of SyncWithPeer.
|
|
||||||
func (mr *MockSyncAclMockRecorder) SyncWithPeer(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncWithPeer", reflect.TypeOf((*MockSyncAcl)(nil).SyncWithPeer), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unlock mocks base method.
|
|
||||||
func (m *MockSyncAcl) Unlock() {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "Unlock")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unlock indicates an expected call of Unlock.
|
|
||||||
func (mr *MockSyncAclMockRecorder) Unlock() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockSyncAcl)(nil).Unlock))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateRawRecord mocks base method.
|
|
||||||
func (m *MockSyncAcl) ValidateRawRecord(arg0 *consensusproto.RawRecord) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "ValidateRawRecord", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateRawRecord indicates an expected call of ValidateRawRecord.
|
|
||||||
func (mr *MockSyncAclMockRecorder) ValidateRawRecord(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateRawRecord", reflect.TypeOf((*MockSyncAcl)(nil).ValidateRawRecord), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockSyncClient is a mock of SyncClient interface.
|
|
||||||
type MockSyncClient struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockSyncClientMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockSyncClientMockRecorder is the mock recorder for MockSyncClient.
|
|
||||||
type MockSyncClientMockRecorder struct {
|
|
||||||
mock *MockSyncClient
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockSyncClient creates a new mock instance.
|
|
||||||
func NewMockSyncClient(ctrl *gomock.Controller) *MockSyncClient {
|
|
||||||
mock := &MockSyncClient{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockSyncClientMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockSyncClient) EXPECT() *MockSyncClientMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// Broadcast mocks base method.
|
|
||||||
func (m *MockSyncClient) Broadcast(arg0 *consensusproto.LogSyncMessage) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "Broadcast", arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Broadcast indicates an expected call of Broadcast.
|
|
||||||
func (mr *MockSyncClientMockRecorder) Broadcast(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Broadcast", reflect.TypeOf((*MockSyncClient)(nil).Broadcast), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncRequest mocks base method.
|
|
||||||
func (m *MockSyncClient) CreateFullSyncRequest(arg0 list.AclList, arg1 string) (*consensusproto.LogSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateFullSyncRequest", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncRequest indicates an expected call of CreateFullSyncRequest.
|
|
||||||
func (mr *MockSyncClientMockRecorder) CreateFullSyncRequest(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncRequest", reflect.TypeOf((*MockSyncClient)(nil).CreateFullSyncRequest), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncResponse mocks base method.
|
|
||||||
func (m *MockSyncClient) CreateFullSyncResponse(arg0 list.AclList, arg1 string) (*consensusproto.LogSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateFullSyncResponse", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncResponse indicates an expected call of CreateFullSyncResponse.
|
|
||||||
func (mr *MockSyncClientMockRecorder) CreateFullSyncResponse(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncResponse", reflect.TypeOf((*MockSyncClient)(nil).CreateFullSyncResponse), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateHeadUpdate mocks base method.
|
|
||||||
func (m *MockSyncClient) CreateHeadUpdate(arg0 list.AclList, arg1 []*consensusproto.RawRecordWithId) *consensusproto.LogSyncMessage {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateHeadUpdate", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateHeadUpdate indicates an expected call of CreateHeadUpdate.
|
|
||||||
func (mr *MockSyncClientMockRecorder) CreateHeadUpdate(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateHeadUpdate", reflect.TypeOf((*MockSyncClient)(nil).CreateHeadUpdate), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueueRequest mocks base method.
|
|
||||||
func (m *MockSyncClient) QueueRequest(arg0 string, arg1 *consensusproto.LogSyncMessage) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "QueueRequest", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueueRequest indicates an expected call of QueueRequest.
|
|
||||||
func (mr *MockSyncClientMockRecorder) QueueRequest(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueRequest", reflect.TypeOf((*MockSyncClient)(nil).QueueRequest), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendRequest mocks base method.
|
|
||||||
func (m *MockSyncClient) SendRequest(arg0 context.Context, arg1 string, arg2 *consensusproto.LogSyncMessage) (*spacesyncproto.ObjectSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "SendRequest", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(*spacesyncproto.ObjectSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendRequest indicates an expected call of SendRequest.
|
|
||||||
func (mr *MockSyncClientMockRecorder) SendRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendRequest", reflect.TypeOf((*MockSyncClient)(nil).SendRequest), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendUpdate mocks base method.
|
|
||||||
func (m *MockSyncClient) SendUpdate(arg0 string, arg1 *consensusproto.LogSyncMessage) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "SendUpdate", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendUpdate indicates an expected call of SendUpdate.
|
|
||||||
func (mr *MockSyncClientMockRecorder) SendUpdate(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendUpdate", reflect.TypeOf((*MockSyncClient)(nil).SendUpdate), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockRequestFactory is a mock of RequestFactory interface.
|
|
||||||
type MockRequestFactory struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockRequestFactoryMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockRequestFactoryMockRecorder is the mock recorder for MockRequestFactory.
|
|
||||||
type MockRequestFactoryMockRecorder struct {
|
|
||||||
mock *MockRequestFactory
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockRequestFactory creates a new mock instance.
|
|
||||||
func NewMockRequestFactory(ctrl *gomock.Controller) *MockRequestFactory {
|
|
||||||
mock := &MockRequestFactory{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockRequestFactoryMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockRequestFactory) EXPECT() *MockRequestFactoryMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncRequest mocks base method.
|
|
||||||
func (m *MockRequestFactory) CreateFullSyncRequest(arg0 list.AclList, arg1 string) (*consensusproto.LogSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateFullSyncRequest", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncRequest indicates an expected call of CreateFullSyncRequest.
|
|
||||||
func (mr *MockRequestFactoryMockRecorder) CreateFullSyncRequest(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncRequest", reflect.TypeOf((*MockRequestFactory)(nil).CreateFullSyncRequest), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncResponse mocks base method.
|
|
||||||
func (m *MockRequestFactory) CreateFullSyncResponse(arg0 list.AclList, arg1 string) (*consensusproto.LogSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateFullSyncResponse", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncResponse indicates an expected call of CreateFullSyncResponse.
|
|
||||||
func (mr *MockRequestFactoryMockRecorder) CreateFullSyncResponse(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncResponse", reflect.TypeOf((*MockRequestFactory)(nil).CreateFullSyncResponse), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateHeadUpdate mocks base method.
|
|
||||||
func (m *MockRequestFactory) CreateHeadUpdate(arg0 list.AclList, arg1 []*consensusproto.RawRecordWithId) *consensusproto.LogSyncMessage {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateHeadUpdate", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateHeadUpdate indicates an expected call of CreateHeadUpdate.
|
|
||||||
func (mr *MockRequestFactoryMockRecorder) CreateHeadUpdate(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateHeadUpdate", reflect.TypeOf((*MockRequestFactory)(nil).CreateHeadUpdate), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockAclSyncProtocol is a mock of AclSyncProtocol interface.
|
|
||||||
type MockAclSyncProtocol struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockAclSyncProtocolMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockAclSyncProtocolMockRecorder is the mock recorder for MockAclSyncProtocol.
|
|
||||||
type MockAclSyncProtocolMockRecorder struct {
|
|
||||||
mock *MockAclSyncProtocol
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockAclSyncProtocol creates a new mock instance.
|
|
||||||
func NewMockAclSyncProtocol(ctrl *gomock.Controller) *MockAclSyncProtocol {
|
|
||||||
mock := &MockAclSyncProtocol{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockAclSyncProtocolMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockAclSyncProtocol) EXPECT() *MockAclSyncProtocolMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// FullSyncRequest mocks base method.
|
|
||||||
func (m *MockAclSyncProtocol) FullSyncRequest(arg0 context.Context, arg1 string, arg2 *consensusproto.LogFullSyncRequest) (*consensusproto.LogSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "FullSyncRequest", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// FullSyncRequest indicates an expected call of FullSyncRequest.
|
|
||||||
func (mr *MockAclSyncProtocolMockRecorder) FullSyncRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FullSyncRequest", reflect.TypeOf((*MockAclSyncProtocol)(nil).FullSyncRequest), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FullSyncResponse mocks base method.
|
|
||||||
func (m *MockAclSyncProtocol) FullSyncResponse(arg0 context.Context, arg1 string, arg2 *consensusproto.LogFullSyncResponse) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "FullSyncResponse", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// FullSyncResponse indicates an expected call of FullSyncResponse.
|
|
||||||
func (mr *MockAclSyncProtocolMockRecorder) FullSyncResponse(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FullSyncResponse", reflect.TypeOf((*MockAclSyncProtocol)(nil).FullSyncResponse), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeadUpdate mocks base method.
|
|
||||||
func (m *MockAclSyncProtocol) HeadUpdate(arg0 context.Context, arg1 string, arg2 *consensusproto.LogHeadUpdate) (*consensusproto.LogSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "HeadUpdate", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeadUpdate indicates an expected call of HeadUpdate.
|
|
||||||
func (mr *MockAclSyncProtocolMockRecorder) HeadUpdate(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeadUpdate", reflect.TypeOf((*MockAclSyncProtocol)(nil).HeadUpdate), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
package syncacl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RequestFactory interface {
|
|
||||||
CreateHeadUpdate(l list.AclList, added []*consensusproto.RawRecordWithId) (msg *consensusproto.LogSyncMessage)
|
|
||||||
CreateFullSyncRequest(l list.AclList, theirHead string) (req *consensusproto.LogSyncMessage, err error)
|
|
||||||
CreateFullSyncResponse(l list.AclList, theirHead string) (*consensusproto.LogSyncMessage, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type requestFactory struct{}
|
|
||||||
|
|
||||||
func NewRequestFactory() RequestFactory {
|
|
||||||
return &requestFactory{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *requestFactory) CreateHeadUpdate(l list.AclList, added []*consensusproto.RawRecordWithId) (msg *consensusproto.LogSyncMessage) {
|
|
||||||
return consensusproto.WrapHeadUpdate(&consensusproto.LogHeadUpdate{
|
|
||||||
Head: l.Head().Id,
|
|
||||||
Records: added,
|
|
||||||
}, l.Root())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *requestFactory) CreateFullSyncRequest(l list.AclList, theirHead string) (req *consensusproto.LogSyncMessage, err error) {
|
|
||||||
if !l.HasHead(theirHead) {
|
|
||||||
return consensusproto.WrapFullRequest(&consensusproto.LogFullSyncRequest{
|
|
||||||
Head: l.Head().Id,
|
|
||||||
}, l.Root()), nil
|
|
||||||
}
|
|
||||||
records, err := l.RecordsAfter(context.Background(), theirHead)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return consensusproto.WrapFullRequest(&consensusproto.LogFullSyncRequest{
|
|
||||||
Head: l.Head().Id,
|
|
||||||
Records: records,
|
|
||||||
}, l.Root()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *requestFactory) CreateFullSyncResponse(l list.AclList, theirHead string) (resp *consensusproto.LogSyncMessage, err error) {
|
|
||||||
records, err := l.RecordsAfter(context.Background(), theirHead)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return consensusproto.WrapFullResponse(&consensusproto.LogFullSyncResponse{
|
|
||||||
Head: l.Head().Id,
|
|
||||||
Records: records,
|
|
||||||
}, l.Root()), nil
|
|
||||||
}
|
|
||||||
@ -1,130 +1,21 @@
|
|||||||
package syncacl
|
package syncacl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||||
"errors"
|
"github.com/anytypeio/any-sync/commonspace/objectsync"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl/headupdater"
|
"github.com/anytypeio/any-sync/commonspace/objectsync/synchandler"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/syncobjectgetter"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/accountservice"
|
|
||||||
"github.com/anyproto/any-sync/app"
|
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/requestmanager"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const CName = "common.acl.syncacl"
|
type SyncAcl struct {
|
||||||
|
|
||||||
var (
|
|
||||||
log = logger.NewNamed(CName)
|
|
||||||
|
|
||||||
ErrSyncAclClosed = errors.New("sync acl is closed")
|
|
||||||
)
|
|
||||||
|
|
||||||
type SyncAcl interface {
|
|
||||||
app.ComponentRunnable
|
|
||||||
list.AclList
|
list.AclList
|
||||||
syncobjectgetter.SyncObject
|
synchandler.SyncHandler
|
||||||
SetHeadUpdater(updater headupdater.HeadUpdater)
|
messagePool objectsync.MessagePool
|
||||||
SyncWithPeer(ctx context.Context, peerId string) (err error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() SyncAcl {
|
func NewSyncAcl(aclList list.AclList, messagePool objectsync.MessagePool) *SyncAcl {
|
||||||
return &syncAcl{}
|
return &SyncAcl{
|
||||||
}
|
AclList: aclList,
|
||||||
|
SyncHandler: nil,
|
||||||
type syncAcl struct {
|
messagePool: messagePool,
|
||||||
list.AclList
|
|
||||||
syncClient SyncClient
|
|
||||||
syncHandler synchandler.SyncHandler
|
|
||||||
headUpdater headupdater.HeadUpdater
|
|
||||||
isClosed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncAcl) Run(ctx context.Context) (err error) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncAcl) HandleRequest(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (response *spacesyncproto.ObjectSyncMessage, err error) {
|
|
||||||
return s.syncHandler.HandleRequest(ctx, senderId, request)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncAcl) SetHeadUpdater(updater headupdater.HeadUpdater) {
|
|
||||||
s.headUpdater = updater
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncAcl) HandleMessage(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (err error) {
|
|
||||||
return s.syncHandler.HandleMessage(ctx, senderId, request)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncAcl) Init(a *app.App) (err error) {
|
|
||||||
storage := a.MustComponent(spacestorage.CName).(spacestorage.SpaceStorage)
|
|
||||||
aclStorage, err := storage.AclStorage()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
acc := a.MustComponent(accountservice.CName).(accountservice.Service)
|
|
||||||
s.AclList, err = list.BuildAclListWithIdentity(acc.Account(), aclStorage, list.NoOpAcceptorVerifier{})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
spaceId := storage.Id()
|
|
||||||
requestManager := a.MustComponent(requestmanager.CName).(requestmanager.RequestManager)
|
|
||||||
peerManager := a.MustComponent(peermanager.CName).(peermanager.PeerManager)
|
|
||||||
syncStatus := a.MustComponent(syncstatus.CName).(syncstatus.StatusService)
|
|
||||||
s.syncClient = NewSyncClient(spaceId, requestManager, peerManager)
|
|
||||||
s.syncHandler = newSyncAclHandler(storage.Id(), s, s.syncClient, syncStatus)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncAcl) AddRawRecord(rawRec *consensusproto.RawRecordWithId) (err error) {
|
|
||||||
if s.isClosed {
|
|
||||||
return ErrSyncAclClosed
|
|
||||||
}
|
|
||||||
err = s.AclList.AddRawRecord(rawRec)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
headUpdate := s.syncClient.CreateHeadUpdate(s, []*consensusproto.RawRecordWithId{rawRec})
|
|
||||||
s.headUpdater.UpdateHeads(s.Id(), []string{rawRec.Id})
|
|
||||||
s.syncClient.Broadcast(headUpdate)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncAcl) AddRawRecords(rawRecords []*consensusproto.RawRecordWithId) (err error) {
|
|
||||||
if s.isClosed {
|
|
||||||
return ErrSyncAclClosed
|
|
||||||
}
|
|
||||||
err = s.AclList.AddRawRecords(rawRecords)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
headUpdate := s.syncClient.CreateHeadUpdate(s, rawRecords)
|
|
||||||
s.headUpdater.UpdateHeads(s.Id(), []string{rawRecords[len(rawRecords)-1].Id})
|
|
||||||
s.syncClient.Broadcast(headUpdate)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncAcl) SyncWithPeer(ctx context.Context, peerId string) (err error) {
|
|
||||||
s.Lock()
|
|
||||||
defer s.Unlock()
|
|
||||||
headUpdate := s.syncClient.CreateHeadUpdate(s, nil)
|
|
||||||
return s.syncClient.SendUpdate(peerId, headUpdate)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncAcl) Close(ctx context.Context) (err error) {
|
|
||||||
s.Lock()
|
|
||||||
defer s.Unlock()
|
|
||||||
s.isClosed = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncAcl) Name() (name string) {
|
|
||||||
return CName
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,81 +2,30 @@ package syncacl
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"fmt"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
"github.com/gogo/protobuf/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrMessageIsRequest = errors.New("message is request")
|
|
||||||
ErrMessageIsNotRequest = errors.New("message is not request")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type syncAclHandler struct {
|
type syncAclHandler struct {
|
||||||
aclList list.AclList
|
acl list.AclList
|
||||||
syncClient SyncClient
|
|
||||||
syncProtocol AclSyncProtocol
|
|
||||||
syncStatus syncstatus.StatusUpdater
|
|
||||||
spaceId string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSyncAclHandler(spaceId string, aclList list.AclList, syncClient SyncClient, syncStatus syncstatus.StatusUpdater) synchandler.SyncHandler {
|
func (s *syncAclHandler) HandleMessage(ctx context.Context, senderId string, req *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||||
return &syncAclHandler{
|
aclMsg := &aclrecordproto.AclSyncMessage{}
|
||||||
aclList: aclList,
|
if err = aclMsg.Unmarshal(req.Payload); err != nil {
|
||||||
syncClient: syncClient,
|
|
||||||
syncProtocol: newAclSyncProtocol(spaceId, aclList, syncClient),
|
|
||||||
syncStatus: syncStatus,
|
|
||||||
spaceId: spaceId,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncAclHandler) HandleMessage(ctx context.Context, senderId string, message *spacesyncproto.ObjectSyncMessage) (err error) {
|
|
||||||
unmarshalled := &consensusproto.LogSyncMessage{}
|
|
||||||
err = proto.Unmarshal(message.Payload, unmarshalled)
|
|
||||||
if err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
content := unmarshalled.GetContent()
|
content := aclMsg.GetContent()
|
||||||
head := consensusproto.GetHead(unmarshalled)
|
|
||||||
s.syncStatus.HeadsReceive(senderId, s.aclList.Id(), []string{head})
|
|
||||||
s.aclList.Lock()
|
|
||||||
defer s.aclList.Unlock()
|
|
||||||
switch {
|
switch {
|
||||||
case content.GetHeadUpdate() != nil:
|
case content.GetAddRecords() != nil:
|
||||||
var syncReq *consensusproto.LogSyncMessage
|
return s.handleAddRecords(ctx, senderId, content.GetAddRecords())
|
||||||
syncReq, err = s.syncProtocol.HeadUpdate(ctx, senderId, content.GetHeadUpdate())
|
default:
|
||||||
if err != nil || syncReq == nil {
|
return fmt.Errorf("unexpected aclSync message: %T", content.Value)
|
||||||
return
|
|
||||||
}
|
|
||||||
return s.syncClient.QueueRequest(senderId, syncReq)
|
|
||||||
case content.GetFullSyncRequest() != nil:
|
|
||||||
return ErrMessageIsRequest
|
|
||||||
case content.GetFullSyncResponse() != nil:
|
|
||||||
return s.syncProtocol.FullSyncResponse(ctx, senderId, content.GetFullSyncResponse())
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncAclHandler) handleAddRecords(ctx context.Context, senderId string, addRecord *aclrecordproto.AclAddRecords) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *syncAclHandler) HandleRequest(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (response *spacesyncproto.ObjectSyncMessage, err error) {
|
|
||||||
unmarshalled := &consensusproto.LogSyncMessage{}
|
|
||||||
err = proto.Unmarshal(request.Payload, unmarshalled)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fullSyncRequest := unmarshalled.GetContent().GetFullSyncRequest()
|
|
||||||
if fullSyncRequest == nil {
|
|
||||||
return nil, ErrMessageIsNotRequest
|
|
||||||
}
|
|
||||||
s.aclList.Lock()
|
|
||||||
defer s.aclList.Unlock()
|
|
||||||
aclResp, err := s.syncProtocol.FullSyncRequest(ctx, senderId, fullSyncRequest)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return spacesyncproto.MarshallSyncMessage(aclResp, s.spaceId, s.aclList.Id())
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,233 +0,0 @@
|
|||||||
package syncacl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/list/mock_list"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl/mock_syncacl"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
"github.com/gogo/protobuf/proto"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type testAclMock struct {
|
|
||||||
*mock_list.MockAclList
|
|
||||||
m sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTestAclMock(mockAcl *mock_list.MockAclList) *testAclMock {
|
|
||||||
return &testAclMock{
|
|
||||||
MockAclList: mockAcl,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testAclMock) Lock() {
|
|
||||||
t.m.Lock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testAclMock) RLock() {
|
|
||||||
t.m.RLock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testAclMock) Unlock() {
|
|
||||||
t.m.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testAclMock) RUnlock() {
|
|
||||||
t.m.RUnlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testAclMock) TryLock() bool {
|
|
||||||
return t.m.TryLock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testAclMock) TryRLock() bool {
|
|
||||||
return t.m.TryRLock()
|
|
||||||
}
|
|
||||||
|
|
||||||
type syncHandlerFixture struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
syncClientMock *mock_syncacl.MockSyncClient
|
|
||||||
aclMock *testAclMock
|
|
||||||
syncProtocolMock *mock_syncacl.MockAclSyncProtocol
|
|
||||||
spaceId string
|
|
||||||
senderId string
|
|
||||||
aclId string
|
|
||||||
|
|
||||||
syncHandler *syncAclHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSyncHandlerFixture(t *testing.T) *syncHandlerFixture {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
aclMock := newTestAclMock(mock_list.NewMockAclList(ctrl))
|
|
||||||
syncClientMock := mock_syncacl.NewMockSyncClient(ctrl)
|
|
||||||
syncProtocolMock := mock_syncacl.NewMockAclSyncProtocol(ctrl)
|
|
||||||
spaceId := "spaceId"
|
|
||||||
|
|
||||||
syncHandler := &syncAclHandler{
|
|
||||||
aclList: aclMock,
|
|
||||||
syncClient: syncClientMock,
|
|
||||||
syncProtocol: syncProtocolMock,
|
|
||||||
syncStatus: syncstatus.NewNoOpSyncStatus(),
|
|
||||||
spaceId: spaceId,
|
|
||||||
}
|
|
||||||
return &syncHandlerFixture{
|
|
||||||
ctrl: ctrl,
|
|
||||||
syncClientMock: syncClientMock,
|
|
||||||
aclMock: aclMock,
|
|
||||||
syncProtocolMock: syncProtocolMock,
|
|
||||||
spaceId: spaceId,
|
|
||||||
senderId: "senderId",
|
|
||||||
aclId: "aclId",
|
|
||||||
syncHandler: syncHandler,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fx *syncHandlerFixture) stop() {
|
|
||||||
fx.ctrl.Finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSyncAclHandler_HandleMessage(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
t.Run("handle head update, request returned", func(t *testing.T) {
|
|
||||||
fx := newSyncHandlerFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &consensusproto.RawRecordWithId{}
|
|
||||||
headUpdate := &consensusproto.LogHeadUpdate{
|
|
||||||
Head: "h1",
|
|
||||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
|
||||||
}
|
|
||||||
logMessage := consensusproto.WrapHeadUpdate(headUpdate, chWithId)
|
|
||||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
|
|
||||||
|
|
||||||
syncReq := &consensusproto.LogSyncMessage{}
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
fx.syncProtocolMock.EXPECT().HeadUpdate(ctx, fx.senderId, gomock.Any()).Return(syncReq, nil)
|
|
||||||
fx.syncClientMock.EXPECT().QueueRequest(fx.senderId, syncReq).Return(nil)
|
|
||||||
|
|
||||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
t.Run("handle head update, no request", func(t *testing.T) {
|
|
||||||
fx := newSyncHandlerFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &consensusproto.RawRecordWithId{}
|
|
||||||
headUpdate := &consensusproto.LogHeadUpdate{
|
|
||||||
Head: "h1",
|
|
||||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
|
||||||
}
|
|
||||||
logMessage := consensusproto.WrapHeadUpdate(headUpdate, chWithId)
|
|
||||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
|
|
||||||
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
fx.syncProtocolMock.EXPECT().HeadUpdate(ctx, fx.senderId, gomock.Any()).Return(nil, nil)
|
|
||||||
|
|
||||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
t.Run("handle head update, returned error", func(t *testing.T) {
|
|
||||||
fx := newSyncHandlerFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &consensusproto.RawRecordWithId{}
|
|
||||||
headUpdate := &consensusproto.LogHeadUpdate{
|
|
||||||
Head: "h1",
|
|
||||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
|
||||||
}
|
|
||||||
logMessage := consensusproto.WrapHeadUpdate(headUpdate, chWithId)
|
|
||||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
|
|
||||||
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
expectedErr := fmt.Errorf("some error")
|
|
||||||
fx.syncProtocolMock.EXPECT().HeadUpdate(ctx, fx.senderId, gomock.Any()).Return(nil, expectedErr)
|
|
||||||
|
|
||||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
|
||||||
require.Error(t, expectedErr, err)
|
|
||||||
})
|
|
||||||
t.Run("handle full sync request is forbidden", func(t *testing.T) {
|
|
||||||
fx := newSyncHandlerFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &consensusproto.RawRecordWithId{}
|
|
||||||
fullRequest := &consensusproto.LogFullSyncRequest{
|
|
||||||
Head: "h1",
|
|
||||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
|
||||||
}
|
|
||||||
logMessage := consensusproto.WrapFullRequest(fullRequest, chWithId)
|
|
||||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
|
|
||||||
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
|
||||||
require.Error(t, ErrMessageIsRequest, err)
|
|
||||||
})
|
|
||||||
t.Run("handle full sync response, no error", func(t *testing.T) {
|
|
||||||
fx := newSyncHandlerFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &consensusproto.RawRecordWithId{}
|
|
||||||
fullResponse := &consensusproto.LogFullSyncResponse{
|
|
||||||
Head: "h1",
|
|
||||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
|
||||||
}
|
|
||||||
logMessage := consensusproto.WrapFullResponse(fullResponse, chWithId)
|
|
||||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
|
|
||||||
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
fx.syncProtocolMock.EXPECT().FullSyncResponse(ctx, fx.senderId, gomock.Any()).Return(nil)
|
|
||||||
|
|
||||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSyncAclHandler_HandleRequest(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
t.Run("handle full sync request, no error", func(t *testing.T) {
|
|
||||||
fx := newSyncHandlerFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &consensusproto.RawRecordWithId{}
|
|
||||||
fullRequest := &consensusproto.LogFullSyncRequest{
|
|
||||||
Head: "h1",
|
|
||||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
|
||||||
}
|
|
||||||
logMessage := consensusproto.WrapFullRequest(fullRequest, chWithId)
|
|
||||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
|
|
||||||
fullResp := &consensusproto.LogSyncMessage{
|
|
||||||
Content: &consensusproto.LogSyncContentValue{
|
|
||||||
Value: &consensusproto.LogSyncContentValue_FullSyncResponse{
|
|
||||||
FullSyncResponse: &consensusproto.LogFullSyncResponse{
|
|
||||||
Head: "returnedHead",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
fx.syncProtocolMock.EXPECT().FullSyncRequest(ctx, fx.senderId, gomock.Any()).Return(fullResp, nil)
|
|
||||||
res, err := fx.syncHandler.HandleRequest(ctx, fx.senderId, objectMsg)
|
|
||||||
require.NoError(t, err)
|
|
||||||
unmarshalled := &consensusproto.LogSyncMessage{}
|
|
||||||
err = proto.Unmarshal(res.Payload, unmarshalled)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
require.Equal(t, "returnedHead", consensusproto.GetHead(unmarshalled))
|
|
||||||
})
|
|
||||||
t.Run("handle other message returns error", func(t *testing.T) {
|
|
||||||
fx := newSyncHandlerFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &consensusproto.RawRecordWithId{}
|
|
||||||
headUpdate := &consensusproto.LogHeadUpdate{
|
|
||||||
Head: "h1",
|
|
||||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
|
||||||
}
|
|
||||||
logMessage := consensusproto.WrapHeadUpdate(headUpdate, chWithId)
|
|
||||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
|
|
||||||
|
|
||||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
|
||||||
_, err := fx.syncHandler.HandleRequest(ctx, fx.senderId, objectMsg)
|
|
||||||
require.Error(t, ErrMessageIsNotRequest, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
package syncacl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/requestmanager"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
|
||||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SyncClient interface {
|
|
||||||
RequestFactory
|
|
||||||
Broadcast(msg *consensusproto.LogSyncMessage)
|
|
||||||
SendUpdate(peerId string, msg *consensusproto.LogSyncMessage) (err error)
|
|
||||||
QueueRequest(peerId string, msg *consensusproto.LogSyncMessage) (err error)
|
|
||||||
SendRequest(ctx context.Context, peerId string, msg *consensusproto.LogSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type syncClient struct {
|
|
||||||
RequestFactory
|
|
||||||
spaceId string
|
|
||||||
requestManager requestmanager.RequestManager
|
|
||||||
peerManager peermanager.PeerManager
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSyncClient(spaceId string, requestManager requestmanager.RequestManager, peerManager peermanager.PeerManager) SyncClient {
|
|
||||||
return &syncClient{
|
|
||||||
RequestFactory: &requestFactory{},
|
|
||||||
spaceId: spaceId,
|
|
||||||
requestManager: requestManager,
|
|
||||||
peerManager: peerManager,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncClient) Broadcast(msg *consensusproto.LogSyncMessage) {
|
|
||||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, msg.Id)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = s.peerManager.Broadcast(context.Background(), objMsg)
|
|
||||||
if err != nil {
|
|
||||||
log.Debug("broadcast error", zap.Error(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncClient) SendUpdate(peerId string, msg *consensusproto.LogSyncMessage) (err error) {
|
|
||||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, msg.Id)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return s.peerManager.SendPeer(context.Background(), peerId, objMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncClient) SendRequest(ctx context.Context, peerId string, msg *consensusproto.LogSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error) {
|
|
||||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, msg.Id)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return s.requestManager.SendRequest(ctx, peerId, objMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncClient) QueueRequest(peerId string, msg *consensusproto.LogSyncMessage) (err error) {
|
|
||||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, msg.Id)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return s.requestManager.QueueRequest(peerId, objMsg)
|
|
||||||
}
|
|
||||||
@ -2,7 +2,7 @@ package syncobjectgetter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
"github.com/anytypeio/any-sync/commonspace/objectsync/synchandler"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SyncObject interface {
|
type SyncObject interface {
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
package exporter
|
package exporter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
|
"github.com/anytypeio/any-sync/commonspace/object/acl/liststorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
"github.com/anytypeio/any-sync/util/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DataConverter interface {
|
type DataConverter interface {
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
package exporter
|
package exporter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
|
"github.com/anytypeio/any-sync/commonspace/object/acl/liststorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TreeImportParams struct {
|
type TreeImportParams struct {
|
||||||
@ -15,7 +15,7 @@ type TreeImportParams struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ImportHistoryTree(params TreeImportParams) (tree objecttree.ReadableObjectTree, err error) {
|
func ImportHistoryTree(params TreeImportParams) (tree objecttree.ReadableObjectTree, err error) {
|
||||||
aclList, err := list.BuildAclList(params.ListStorage, list.NoOpAcceptorVerifier{})
|
aclList, err := list.BuildAclList(params.ListStorage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,8 +2,8 @@ package objecttree
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
"github.com/anytypeio/any-sync/util/crypto"
|
||||||
"github.com/gogo/protobuf/proto"
|
"github.com/gogo/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -2,10 +2,11 @@ package objecttree
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/util/cidutil"
|
"github.com/anytypeio/any-sync/util/cidutil"
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
"github.com/anytypeio/any-sync/util/crypto"
|
||||||
"github.com/gogo/protobuf/proto"
|
"github.com/gogo/protobuf/proto"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrEmptyChange = errors.New("change payload should not be empty")
|
var ErrEmptyChange = errors.New("change payload should not be empty")
|
||||||
@ -19,7 +20,6 @@ type BuilderContent struct {
|
|||||||
PrivKey crypto.PrivKey
|
PrivKey crypto.PrivKey
|
||||||
ReadKey crypto.SymKey
|
ReadKey crypto.SymKey
|
||||||
Content []byte
|
Content []byte
|
||||||
Timestamp int64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type InitialContent struct {
|
type InitialContent struct {
|
||||||
@ -59,16 +59,13 @@ type ChangeBuilder interface {
|
|||||||
Marshall(ch *Change) (*treechangeproto.RawTreeChangeWithId, error)
|
Marshall(ch *Change) (*treechangeproto.RawTreeChangeWithId, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type newChangeFunc = func(id string, identity crypto.PubKey, ch *treechangeproto.TreeChange, signature []byte) *Change
|
|
||||||
|
|
||||||
type changeBuilder struct {
|
type changeBuilder struct {
|
||||||
rootChange *treechangeproto.RawTreeChangeWithId
|
rootChange *treechangeproto.RawTreeChangeWithId
|
||||||
keys crypto.KeyStorage
|
keys crypto.KeyStorage
|
||||||
newChange newChangeFunc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewChangeBuilder(keys crypto.KeyStorage, rootChange *treechangeproto.RawTreeChangeWithId) ChangeBuilder {
|
func NewChangeBuilder(keys crypto.KeyStorage, rootChange *treechangeproto.RawTreeChangeWithId) ChangeBuilder {
|
||||||
return &changeBuilder{keys: keys, rootChange: rootChange, newChange: NewChange}
|
return &changeBuilder{keys: keys, rootChange: rootChange}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *changeBuilder) Unmarshall(rawIdChange *treechangeproto.RawTreeChangeWithId, verify bool) (ch *Change, err error) {
|
func (c *changeBuilder) Unmarshall(rawIdChange *treechangeproto.RawTreeChangeWithId, verify bool) (ch *Change, err error) {
|
||||||
@ -166,7 +163,7 @@ func (c *changeBuilder) Build(payload BuilderContent) (ch *Change, rawIdChange *
|
|||||||
AclHeadId: payload.AclHeadId,
|
AclHeadId: payload.AclHeadId,
|
||||||
SnapshotBaseId: payload.SnapshotBaseId,
|
SnapshotBaseId: payload.SnapshotBaseId,
|
||||||
ReadKeyId: payload.ReadKeyId,
|
ReadKeyId: payload.ReadKeyId,
|
||||||
Timestamp: payload.Timestamp,
|
Timestamp: time.Now().Unix(),
|
||||||
Identity: identity,
|
Identity: identity,
|
||||||
IsSnapshot: payload.IsSnapshot,
|
IsSnapshot: payload.IsSnapshot,
|
||||||
}
|
}
|
||||||
@ -200,7 +197,7 @@ func (c *changeBuilder) Build(payload BuilderContent) (ch *Change, rawIdChange *
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ch = c.newChange(id, payload.PrivKey.GetPublic(), change, signature)
|
ch = NewChange(id, payload.PrivKey.GetPublic(), change, signature)
|
||||||
rawIdChange = &treechangeproto.RawTreeChangeWithId{
|
rawIdChange = &treechangeproto.RawTreeChangeWithId{
|
||||||
RawChange: marshalledRawChange,
|
RawChange: marshalledRawChange,
|
||||||
Id: id,
|
Id: id,
|
||||||
@ -271,7 +268,7 @@ func (c *changeBuilder) unmarshallRawChange(raw *treechangeproto.RawTreeChange,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ch = c.newChange(id, key, unmarshalled, raw.Signature)
|
ch = NewChange(id, key, unmarshalled, raw.Signature)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,47 +14,34 @@ type historyTree struct {
|
|||||||
*objectTree
|
*objectTree
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *historyTree) rebuildFromStorage(params HistoryTreeParams) (err error) {
|
func (h *historyTree) rebuildFromStorage(beforeId string, include bool) (err error) {
|
||||||
err = h.rebuild(params)
|
ot := h.objectTree
|
||||||
if err != nil {
|
ot.treeBuilder.Reset()
|
||||||
return
|
if beforeId == ot.Id() && !include {
|
||||||
}
|
|
||||||
h.aclList.RLock()
|
|
||||||
defer h.aclList.RUnlock()
|
|
||||||
state := h.aclList.AclState()
|
|
||||||
|
|
||||||
return h.readKeysFromAclState(state)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *historyTree) rebuild(params HistoryTreeParams) (err error) {
|
|
||||||
var (
|
|
||||||
beforeId = params.BeforeId
|
|
||||||
include = params.IncludeBeforeId
|
|
||||||
full = params.BuildFullTree
|
|
||||||
)
|
|
||||||
h.treeBuilder.Reset()
|
|
||||||
if full {
|
|
||||||
h.tree, err = h.treeBuilder.BuildFull()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if beforeId == h.Id() && !include {
|
|
||||||
return ErrLoadBeforeRoot
|
return ErrLoadBeforeRoot
|
||||||
}
|
}
|
||||||
|
|
||||||
heads := []string{beforeId}
|
heads := []string{beforeId}
|
||||||
if beforeId == "" {
|
if beforeId == "" {
|
||||||
heads, err = h.treeStorage.Heads()
|
heads, err = ot.treeStorage.Heads()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if !include {
|
} else if !include {
|
||||||
beforeChange, err := h.treeBuilder.loadChange(beforeId)
|
beforeChange, err := ot.treeBuilder.loadChange(beforeId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
heads = beforeChange.PreviousIds
|
heads = beforeChange.PreviousIds
|
||||||
}
|
}
|
||||||
|
|
||||||
h.tree, err = h.treeBuilder.build(heads, nil, nil)
|
ot.tree, err = ot.treeBuilder.build(heads, nil, nil)
|
||||||
return
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ot.aclList.RLock()
|
||||||
|
defer ot.aclList.RUnlock()
|
||||||
|
state := ot.aclList.AclState()
|
||||||
|
|
||||||
|
return ot.readKeysFromAclState(state)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: github.com/anyproto/any-sync/commonspace/object/tree/objecttree (interfaces: ObjectTree)
|
// Source: github.com/anytypeio/any-sync/commonspace/object/tree/objecttree (interfaces: ObjectTree)
|
||||||
|
|
||||||
// Package mock_objecttree is a generated GoMock package.
|
// Package mock_objecttree is a generated GoMock package.
|
||||||
package mock_objecttree
|
package mock_objecttree
|
||||||
@ -9,11 +9,11 @@ import (
|
|||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
time "time"
|
time "time"
|
||||||
|
|
||||||
list "github.com/anyproto/any-sync/commonspace/object/acl/list"
|
list "github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||||
objecttree "github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
objecttree "github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||||
treechangeproto "github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
treechangeproto "github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
treestorage "github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
treestorage "github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||||
gomock "go.uber.org/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockObjectTree is a mock of ObjectTree interface.
|
// MockObjectTree is a mock of ObjectTree interface.
|
||||||
|
|||||||
@ -1,18 +1,18 @@
|
|||||||
//go:generate mockgen -destination mock_objecttree/mock_objecttree.go github.com/anyproto/any-sync/commonspace/object/tree/objecttree ObjectTree
|
//go:generate mockgen -destination mock_objecttree/mock_objecttree.go github.com/anytypeio/any-sync/commonspace/object/tree/objecttree ObjectTree
|
||||||
package objecttree
|
package objecttree
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/anytypeio/any-sync/util/crypto"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
"github.com/anytypeio/any-sync/util/slice"
|
||||||
"github.com/anyproto/any-sync/util/slice"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type RWLocker interface {
|
type RWLocker interface {
|
||||||
@ -116,25 +116,13 @@ type objectTree struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ot *objectTree) rebuildFromStorage(theirHeads []string, newChanges []*Change) (err error) {
|
func (ot *objectTree) rebuildFromStorage(theirHeads []string, newChanges []*Change) (err error) {
|
||||||
oldTree := ot.tree
|
|
||||||
ot.treeBuilder.Reset()
|
ot.treeBuilder.Reset()
|
||||||
|
|
||||||
ot.tree, err = ot.treeBuilder.Build(theirHeads, newChanges)
|
ot.tree, err = ot.treeBuilder.Build(theirHeads, newChanges)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// in case there are new heads
|
|
||||||
if theirHeads != nil && oldTree != nil {
|
|
||||||
// checking that old root is still in tree
|
|
||||||
rootCh, rootExists := ot.tree.attached[oldTree.RootId()]
|
|
||||||
|
|
||||||
// checking the case where theirHeads were actually below prevHeads
|
|
||||||
// so if we did load some extra data in the tree, let's reduce it to old root
|
|
||||||
if slice.UnsortedEquals(oldTree.headIds, ot.tree.headIds) && rootExists && ot.tree.RootId() != oldTree.RootId() {
|
|
||||||
ot.tree.makeRootAndRemove(rootCh)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// during building the tree we may have marked some changes as possible roots,
|
// during building the tree we may have marked some changes as possible roots,
|
||||||
// but obviously they are not roots, because of the way how we construct the tree
|
// but obviously they are not roots, because of the way how we construct the tree
|
||||||
ot.tree.clearPossibleRoots()
|
ot.tree.clearPossibleRoots()
|
||||||
@ -199,7 +187,7 @@ func (ot *objectTree) AddContent(ctx context.Context, content SignableChangeCont
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ot.treeStorage.AddRawChangesSetHeads([]*treechangeproto.RawTreeChangeWithId{rawChange}, []string{objChange.Id})
|
err = ot.treeStorage.TransactionAdd([]*treechangeproto.RawTreeChangeWithId{rawChange}, []string{objChange.Id})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -248,7 +236,9 @@ func (ot *objectTree) prepareBuilderContent(content SignableChangeContent) (cnt
|
|||||||
pubKey = content.Key.GetPublic()
|
pubKey = content.Key.GetPublic()
|
||||||
readKeyId string
|
readKeyId string
|
||||||
)
|
)
|
||||||
if !state.Permissions(pubKey).CanWrite() {
|
canWrite := state.HasPermission(pubKey, aclrecordproto.AclUserPermissions_Writer) ||
|
||||||
|
state.HasPermission(pubKey, aclrecordproto.AclUserPermissions_Admin)
|
||||||
|
if !canWrite {
|
||||||
err = list.ErrInsufficientPermissions
|
err = list.ErrInsufficientPermissions
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -261,10 +251,6 @@ func (ot *objectTree) prepareBuilderContent(content SignableChangeContent) (cnt
|
|||||||
}
|
}
|
||||||
readKey = ot.currentReadKey
|
readKey = ot.currentReadKey
|
||||||
}
|
}
|
||||||
timestamp := content.Timestamp
|
|
||||||
if timestamp <= 0 {
|
|
||||||
timestamp = time.Now().Unix()
|
|
||||||
}
|
|
||||||
cnt = BuilderContent{
|
cnt = BuilderContent{
|
||||||
TreeHeadIds: ot.tree.Heads(),
|
TreeHeadIds: ot.tree.Heads(),
|
||||||
AclHeadId: ot.aclList.Head().Id,
|
AclHeadId: ot.aclList.Head().Id,
|
||||||
@ -274,7 +260,6 @@ func (ot *objectTree) prepareBuilderContent(content SignableChangeContent) (cnt
|
|||||||
PrivKey: content.Key,
|
PrivKey: content.Key,
|
||||||
ReadKey: readKey,
|
ReadKey: readKey,
|
||||||
Content: content.Data,
|
Content: content.Data,
|
||||||
Timestamp: timestamp,
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -294,11 +279,7 @@ func (ot *objectTree) AddRawChanges(ctx context.Context, changesPayload RawChang
|
|||||||
addResult.Mode = Rebuild
|
addResult.Mode = Rebuild
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ot.treeStorage.AddRawChangesSetHeads(addResult.Added, addResult.Heads)
|
err = ot.treeStorage.TransactionAdd(addResult.Added, addResult.Heads)
|
||||||
if err != nil {
|
|
||||||
// rolling back all changes made to inmemory state
|
|
||||||
ot.rebuildFromStorage(nil, nil)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -361,7 +342,7 @@ func (ot *objectTree) addRawChanges(ctx context.Context, changesPayload RawChang
|
|||||||
}
|
}
|
||||||
|
|
||||||
// checks if we need to go to database
|
// checks if we need to go to database
|
||||||
snapshotNotInTree := func(ch *Change) bool {
|
isOldSnapshot := func(ch *Change) bool {
|
||||||
if ch.SnapshotId == ot.tree.RootId() {
|
if ch.SnapshotId == ot.tree.RootId() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -376,12 +357,26 @@ func (ot *objectTree) addRawChanges(ctx context.Context, changesPayload RawChang
|
|||||||
|
|
||||||
shouldRebuildFromStorage := false
|
shouldRebuildFromStorage := false
|
||||||
// checking if we have some changes with different snapshot and then rebuilding
|
// checking if we have some changes with different snapshot and then rebuilding
|
||||||
for _, ch := range ot.newChangesBuf {
|
for idx, ch := range ot.newChangesBuf {
|
||||||
if snapshotNotInTree(ch) {
|
if isOldSnapshot(ch) {
|
||||||
|
var exists bool
|
||||||
|
// checking if it exists in the storage, if yes, then at some point it was added to the tree
|
||||||
|
// thus we don't need to look at this change
|
||||||
|
exists, err = ot.treeStorage.HasChange(ctx, ch.Id)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
// marking as nil to delete after
|
||||||
|
ot.newChangesBuf[idx] = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// we haven't seen the change, and it refers to old snapshot, so we should rebuild
|
||||||
shouldRebuildFromStorage = true
|
shouldRebuildFromStorage = true
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// discarding all previously seen changes
|
||||||
|
ot.newChangesBuf = slice.DiscardFromSlice(ot.newChangesBuf, func(ch *Change) bool { return ch == nil })
|
||||||
|
|
||||||
if shouldRebuildFromStorage {
|
if shouldRebuildFromStorage {
|
||||||
err = ot.rebuildFromStorage(changesPayload.NewHeads, ot.newChangesBuf)
|
err = ot.rebuildFromStorage(changesPayload.NewHeads, ot.newChangesBuf)
|
||||||
@ -478,11 +473,6 @@ func (ot *objectTree) createAddResult(oldHeads []string, mode Mode, treeChangesA
|
|||||||
|
|
||||||
var added []*treechangeproto.RawTreeChangeWithId
|
var added []*treechangeproto.RawTreeChangeWithId
|
||||||
added, err = getAddedChanges(treeChangesAdded)
|
added, err = getAddedChanges(treeChangesAdded)
|
||||||
if !ot.treeBuilder.keepInMemoryData {
|
|
||||||
for _, ch := range treeChangesAdded {
|
|
||||||
ch.Data = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -549,8 +539,22 @@ func (ot *objectTree) IterateFrom(id string, convert ChangeConvertFunc, iterate
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ot *objectTree) HasChanges(chs ...string) bool {
|
func (ot *objectTree) HasChanges(chs ...string) bool {
|
||||||
|
hasChange := func(s string) bool {
|
||||||
|
_, attachedExists := ot.tree.attached[s]
|
||||||
|
if attachedExists {
|
||||||
|
return attachedExists
|
||||||
|
}
|
||||||
|
|
||||||
|
has, err := ot.treeStorage.HasChange(context.Background(), s)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return has
|
||||||
|
}
|
||||||
|
|
||||||
for _, ch := range chs {
|
for _, ch := range chs {
|
||||||
if _, attachedExists := ot.tree.attached[ch]; !attachedExists {
|
if !hasChange(ch) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -617,7 +621,19 @@ func (ot *objectTree) ChangesAfterCommonSnapshot(theirPath, theirHeads []string)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ot.rawChangeLoader.Load(commonSnapshot, ot.tree, theirHeads)
|
if commonSnapshot == ot.tree.RootId() {
|
||||||
|
return ot.getChangesFromTree(theirHeads)
|
||||||
|
} else {
|
||||||
|
return ot.getChangesFromDB(commonSnapshot, theirHeads)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ot *objectTree) getChangesFromTree(theirHeads []string) (rawChanges []*treechangeproto.RawTreeChangeWithId, err error) {
|
||||||
|
return ot.rawChangeLoader.LoadFromTree(ot.tree, theirHeads)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ot *objectTree) getChangesFromDB(commonSnapshot string, theirHeads []string) (rawChanges []*treechangeproto.RawTreeChangeWithId, err error) {
|
||||||
|
return ot.rawChangeLoader.LoadFromStorage(commonSnapshot, ot.tree.headIds, theirHeads)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ot *objectTree) snapshotPathIsActual() bool {
|
func (ot *objectTree) snapshotPathIsActual() bool {
|
||||||
|
|||||||
@ -2,45 +2,105 @@ package objecttree
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"crypto/rand"
|
||||||
"testing"
|
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||||
"time"
|
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
"github.com/anytypeio/any-sync/util/crypto"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
|
||||||
"github.com/gogo/protobuf/proto"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type mockKeyStorage struct {
|
||||||
|
key crypto.PubKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func newKeyStorage() mockKeyStorage {
|
||||||
|
_, pk, _ := crypto.GenerateEd25519Key(rand.Reader)
|
||||||
|
return mockKeyStorage{pk}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockKeyStorage) PubKeyFromProto(protoBytes []byte) (crypto.PubKey, error) {
|
||||||
|
return m.key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockChangeCreator struct{}
|
||||||
|
|
||||||
|
func (c *mockChangeCreator) createRoot(id, aclId string) *treechangeproto.RawTreeChangeWithId {
|
||||||
|
aclChange := &treechangeproto.RootChange{
|
||||||
|
AclHeadId: aclId,
|
||||||
|
}
|
||||||
|
res, _ := aclChange.Marshal()
|
||||||
|
|
||||||
|
raw := &treechangeproto.RawTreeChange{
|
||||||
|
Payload: res,
|
||||||
|
Signature: nil,
|
||||||
|
}
|
||||||
|
rawMarshalled, _ := raw.Marshal()
|
||||||
|
|
||||||
|
return &treechangeproto.RawTreeChangeWithId{
|
||||||
|
RawChange: rawMarshalled,
|
||||||
|
Id: id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockChangeCreator) createRaw(id, aclId, snapshotId string, isSnapshot bool, prevIds ...string) *treechangeproto.RawTreeChangeWithId {
|
||||||
|
aclChange := &treechangeproto.TreeChange{
|
||||||
|
TreeHeadIds: prevIds,
|
||||||
|
AclHeadId: aclId,
|
||||||
|
SnapshotBaseId: snapshotId,
|
||||||
|
ChangesData: nil,
|
||||||
|
IsSnapshot: isSnapshot,
|
||||||
|
}
|
||||||
|
res, _ := aclChange.Marshal()
|
||||||
|
|
||||||
|
raw := &treechangeproto.RawTreeChange{
|
||||||
|
Payload: res,
|
||||||
|
Signature: nil,
|
||||||
|
}
|
||||||
|
rawMarshalled, _ := raw.Marshal()
|
||||||
|
|
||||||
|
return &treechangeproto.RawTreeChangeWithId{
|
||||||
|
RawChange: rawMarshalled,
|
||||||
|
Id: id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockChangeCreator) createNewTreeStorage(treeId, aclHeadId string) treestorage.TreeStorage {
|
||||||
|
root := c.createRoot(treeId, aclHeadId)
|
||||||
|
treeStorage, _ := treestorage.NewInMemoryTreeStorage(root, []string{root.Id}, []*treechangeproto.RawTreeChangeWithId{root})
|
||||||
|
return treeStorage
|
||||||
|
}
|
||||||
|
|
||||||
type testTreeContext struct {
|
type testTreeContext struct {
|
||||||
aclList list.AclList
|
aclList list.AclList
|
||||||
treeStorage treestorage.TreeStorage
|
treeStorage treestorage.TreeStorage
|
||||||
changeCreator *MockChangeCreator
|
changeBuilder ChangeBuilder
|
||||||
|
changeCreator *mockChangeCreator
|
||||||
objTree ObjectTree
|
objTree ObjectTree
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareAclList(t *testing.T) (list.AclList, *accountdata.AccountKeys) {
|
func prepareAclList(t *testing.T) list.AclList {
|
||||||
randKeys, err := accountdata.NewRandom()
|
randKeys, err := accountdata.NewRandom()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
aclList, err := list.NewTestDerivedAcl("spaceId", randKeys)
|
aclList, err := list.NewTestDerivedAcl("spaceId", randKeys)
|
||||||
require.NoError(t, err, "building acl list should be without error")
|
require.NoError(t, err, "building acl list should be without error")
|
||||||
|
|
||||||
return aclList, randKeys
|
return aclList
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareHistoryTreeDeps(aclList list.AclList) (*MockChangeCreator, objectTreeDeps) {
|
func prepareTreeDeps(aclList list.AclList) (*mockChangeCreator, objectTreeDeps) {
|
||||||
changeCreator := NewMockChangeCreator()
|
changeCreator := &mockChangeCreator{}
|
||||||
treeStorage := changeCreator.CreateNewTreeStorage("0", aclList.Head().Id)
|
treeStorage := changeCreator.createNewTreeStorage("0", aclList.Head().Id)
|
||||||
root, _ := treeStorage.Root()
|
root, _ := treeStorage.Root()
|
||||||
changeBuilder := &nonVerifiableChangeBuilder{
|
changeBuilder := &nonVerifiableChangeBuilder{
|
||||||
ChangeBuilder: NewChangeBuilder(newMockKeyStorage(), root),
|
ChangeBuilder: NewChangeBuilder(newKeyStorage(), root),
|
||||||
}
|
}
|
||||||
deps := objectTreeDeps{
|
deps := objectTreeDeps{
|
||||||
changeBuilder: changeBuilder,
|
changeBuilder: changeBuilder,
|
||||||
treeBuilder: newTreeBuilder(true, treeStorage, changeBuilder),
|
treeBuilder: newTreeBuilder(treeStorage, changeBuilder),
|
||||||
treeStorage: treeStorage,
|
treeStorage: treeStorage,
|
||||||
rawChangeLoader: newRawChangeLoader(treeStorage, changeBuilder),
|
rawChangeLoader: newRawChangeLoader(treeStorage, changeBuilder),
|
||||||
validator: &noOpTreeValidator{},
|
validator: &noOpTreeValidator{},
|
||||||
@ -50,26 +110,23 @@ func prepareHistoryTreeDeps(aclList list.AclList) (*MockChangeCreator, objectTre
|
|||||||
}
|
}
|
||||||
|
|
||||||
func prepareTreeContext(t *testing.T, aclList list.AclList) testTreeContext {
|
func prepareTreeContext(t *testing.T, aclList list.AclList) testTreeContext {
|
||||||
return prepareContext(t, aclList, BuildTestableTree, nil)
|
changeCreator := &mockChangeCreator{}
|
||||||
}
|
treeStorage := changeCreator.createNewTreeStorage("0", aclList.Head().Id)
|
||||||
|
root, _ := treeStorage.Root()
|
||||||
func prepareEmptyDataTreeContext(t *testing.T, aclList list.AclList, additionalChanges func(changeCreator *MockChangeCreator) RawChangesPayload) testTreeContext {
|
changeBuilder := &nonVerifiableChangeBuilder{
|
||||||
return prepareContext(t, aclList, BuildEmptyDataTestableTree, additionalChanges)
|
ChangeBuilder: NewChangeBuilder(newKeyStorage(), root),
|
||||||
}
|
|
||||||
|
|
||||||
func prepareContext(
|
|
||||||
t *testing.T,
|
|
||||||
aclList list.AclList,
|
|
||||||
objTreeBuilder BuildObjectTreeFunc,
|
|
||||||
additionalChanges func(changeCreator *MockChangeCreator) RawChangesPayload) testTreeContext {
|
|
||||||
changeCreator := NewMockChangeCreator()
|
|
||||||
treeStorage := changeCreator.CreateNewTreeStorage("0", aclList.Head().Id)
|
|
||||||
if additionalChanges != nil {
|
|
||||||
payload := additionalChanges(changeCreator)
|
|
||||||
err := treeStorage.AddRawChangesSetHeads(payload.RawChanges, payload.NewHeads)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
}
|
||||||
objTree, err := objTreeBuilder(treeStorage, aclList)
|
deps := objectTreeDeps{
|
||||||
|
changeBuilder: changeBuilder,
|
||||||
|
treeBuilder: newTreeBuilder(treeStorage, changeBuilder),
|
||||||
|
treeStorage: treeStorage,
|
||||||
|
rawChangeLoader: newRawChangeLoader(treeStorage, changeBuilder),
|
||||||
|
validator: &noOpTreeValidator{},
|
||||||
|
aclList: aclList,
|
||||||
|
}
|
||||||
|
|
||||||
|
// check build
|
||||||
|
objTree, err := buildObjectTree(deps)
|
||||||
require.NoError(t, err, "building tree should be without error")
|
require.NoError(t, err, "building tree should be without error")
|
||||||
|
|
||||||
// check tree iterate
|
// check tree iterate
|
||||||
@ -79,71 +136,18 @@ func prepareContext(
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
require.NoError(t, err, "iterate should be without error")
|
require.NoError(t, err, "iterate should be without error")
|
||||||
if additionalChanges == nil {
|
assert.Equal(t, []string{"0"}, iterChangesId)
|
||||||
assert.Equal(t, []string{"0"}, iterChangesId)
|
|
||||||
}
|
|
||||||
return testTreeContext{
|
return testTreeContext{
|
||||||
aclList: aclList,
|
aclList: aclList,
|
||||||
treeStorage: treeStorage,
|
treeStorage: treeStorage,
|
||||||
|
changeBuilder: changeBuilder,
|
||||||
changeCreator: changeCreator,
|
changeCreator: changeCreator,
|
||||||
objTree: objTree,
|
objTree: objTree,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestObjectTree(t *testing.T) {
|
func TestObjectTree(t *testing.T) {
|
||||||
aclList, keys := prepareAclList(t)
|
aclList := prepareAclList(t)
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
t.Run("add content", func(t *testing.T) {
|
|
||||||
root, err := CreateObjectTreeRoot(ObjectTreeCreatePayload{
|
|
||||||
PrivKey: keys.SignKey,
|
|
||||||
ChangeType: "changeType",
|
|
||||||
ChangePayload: nil,
|
|
||||||
SpaceId: "spaceId",
|
|
||||||
IsEncrypted: true,
|
|
||||||
}, aclList)
|
|
||||||
require.NoError(t, err)
|
|
||||||
store, _ := treestorage.NewInMemoryTreeStorage(root, []string{root.Id}, []*treechangeproto.RawTreeChangeWithId{root})
|
|
||||||
oTree, err := BuildObjectTree(store, aclList)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
t.Run("0 timestamp is changed to current", func(t *testing.T) {
|
|
||||||
start := time.Now()
|
|
||||||
res, err := oTree.AddContent(ctx, SignableChangeContent{
|
|
||||||
Data: []byte("some"),
|
|
||||||
Key: keys.SignKey,
|
|
||||||
IsSnapshot: false,
|
|
||||||
IsEncrypted: true,
|
|
||||||
Timestamp: 0,
|
|
||||||
})
|
|
||||||
end := time.Now()
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, oTree.Heads(), 1)
|
|
||||||
require.Equal(t, res.Added[0].Id, oTree.Heads()[0])
|
|
||||||
ch, err := oTree.(*objectTree).changeBuilder.Unmarshall(res.Added[0], true)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.GreaterOrEqual(t, start.Unix(), ch.Timestamp)
|
|
||||||
require.LessOrEqual(t, end.Unix(), ch.Timestamp)
|
|
||||||
require.Equal(t, res.Added[0].Id, oTree.(*objectTree).tree.lastIteratedHeadId)
|
|
||||||
})
|
|
||||||
t.Run("timestamp is set correctly", func(t *testing.T) {
|
|
||||||
someTs := time.Now().Add(time.Hour).Unix()
|
|
||||||
res, err := oTree.AddContent(ctx, SignableChangeContent{
|
|
||||||
Data: []byte("some"),
|
|
||||||
Key: keys.SignKey,
|
|
||||||
IsSnapshot: false,
|
|
||||||
IsEncrypted: true,
|
|
||||||
Timestamp: someTs,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, oTree.Heads(), 1)
|
|
||||||
require.Equal(t, res.Added[0].Id, oTree.Heads()[0])
|
|
||||||
ch, err := oTree.(*objectTree).changeBuilder.Unmarshall(res.Added[0], true)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, ch.Timestamp, someTs)
|
|
||||||
require.Equal(t, res.Added[0].Id, oTree.(*objectTree).tree.lastIteratedHeadId)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("add simple", func(t *testing.T) {
|
t.Run("add simple", func(t *testing.T) {
|
||||||
ctx := prepareTreeContext(t, aclList)
|
ctx := prepareTreeContext(t, aclList)
|
||||||
@ -152,8 +156,8 @@ func TestObjectTree(t *testing.T) {
|
|||||||
objTree := ctx.objTree
|
objTree := ctx.objTree
|
||||||
|
|
||||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", false, "0"),
|
changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||||
changeCreator.CreateRaw("2", aclList.Head().Id, "0", false, "1"),
|
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||||
}
|
}
|
||||||
payload := RawChangesPayload{
|
payload := RawChangesPayload{
|
||||||
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
|
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
|
||||||
@ -197,7 +201,7 @@ func TestObjectTree(t *testing.T) {
|
|||||||
objTree := ctx.objTree
|
objTree := ctx.objTree
|
||||||
|
|
||||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||||
changeCreator.CreateRaw("0", aclList.Head().Id, "", true, ""),
|
changeCreator.createRaw("0", aclList.Head().Id, "", true, ""),
|
||||||
}
|
}
|
||||||
payload := RawChangesPayload{
|
payload := RawChangesPayload{
|
||||||
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
|
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
|
||||||
@ -221,33 +225,7 @@ func TestObjectTree(t *testing.T) {
|
|||||||
objTree := ctx.objTree
|
objTree := ctx.objTree
|
||||||
|
|
||||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||||
changeCreator.CreateRaw("2", aclList.Head().Id, "0", false, "1"),
|
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||||
}
|
|
||||||
payload := RawChangesPayload{
|
|
||||||
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
|
|
||||||
RawChanges: rawChanges,
|
|
||||||
}
|
|
||||||
res, err := objTree.AddRawChanges(context.Background(), payload)
|
|
||||||
require.NoError(t, err, "adding changes should be without error")
|
|
||||||
|
|
||||||
// check result
|
|
||||||
assert.Equal(t, []string{"0"}, res.OldHeads)
|
|
||||||
assert.Equal(t, []string{"0"}, res.Heads)
|
|
||||||
assert.Equal(t, 0, len(res.Added))
|
|
||||||
assert.Equal(t, Nothing, res.Mode)
|
|
||||||
|
|
||||||
// check tree heads
|
|
||||||
assert.Equal(t, []string{"0"}, objTree.Heads())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("add not connected changes", func(t *testing.T) {
|
|
||||||
ctx := prepareTreeContext(t, aclList)
|
|
||||||
changeCreator := ctx.changeCreator
|
|
||||||
objTree := ctx.objTree
|
|
||||||
|
|
||||||
// this change could in theory replace current snapshot, we should prevent that
|
|
||||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
|
||||||
changeCreator.CreateRaw("2", aclList.Head().Id, "0", true, "1"),
|
|
||||||
}
|
}
|
||||||
payload := RawChangesPayload{
|
payload := RawChangesPayload{
|
||||||
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
|
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
|
||||||
@ -273,10 +251,10 @@ func TestObjectTree(t *testing.T) {
|
|||||||
objTree := ctx.objTree
|
objTree := ctx.objTree
|
||||||
|
|
||||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", false, "0"),
|
changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||||
changeCreator.CreateRaw("2", aclList.Head().Id, "0", false, "1"),
|
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||||
changeCreator.CreateRaw("3", aclList.Head().Id, "0", true, "2"),
|
changeCreator.createRaw("3", aclList.Head().Id, "0", true, "2"),
|
||||||
changeCreator.CreateRaw("4", aclList.Head().Id, "3", false, "3"),
|
changeCreator.createRaw("4", aclList.Head().Id, "3", false, "3"),
|
||||||
}
|
}
|
||||||
payload := RawChangesPayload{
|
payload := RawChangesPayload{
|
||||||
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
|
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
|
||||||
@ -323,9 +301,9 @@ func TestObjectTree(t *testing.T) {
|
|||||||
objTree := ctx.objTree
|
objTree := ctx.objTree
|
||||||
|
|
||||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", false, "0"),
|
changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||||
changeCreator.CreateRaw("2", aclList.Head().Id, "0", false, "1"),
|
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||||
changeCreator.CreateRaw("3", aclList.Head().Id, "0", true, "2"),
|
changeCreator.createRaw("3", aclList.Head().Id, "0", true, "2"),
|
||||||
}
|
}
|
||||||
payload := RawChangesPayload{
|
payload := RawChangesPayload{
|
||||||
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
|
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
|
||||||
@ -341,195 +319,18 @@ func TestObjectTree(t *testing.T) {
|
|||||||
assert.Equal(t, true, objTree.(*objectTree).snapshotPathIsActual())
|
assert.Equal(t, true, objTree.(*objectTree).snapshotPathIsActual())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("test empty data tree", func(t *testing.T) {
|
|
||||||
t.Run("empty tree add", func(t *testing.T) {
|
|
||||||
ctx := prepareEmptyDataTreeContext(t, aclList, nil)
|
|
||||||
changeCreator := ctx.changeCreator
|
|
||||||
objTree := ctx.objTree
|
|
||||||
|
|
||||||
rawChangesFirst := []*treechangeproto.RawTreeChangeWithId{
|
|
||||||
changeCreator.CreateRawWithData("1", aclList.Head().Id, "0", false, []byte("1"), "0"),
|
|
||||||
changeCreator.CreateRawWithData("2", aclList.Head().Id, "0", false, []byte("2"), "1"),
|
|
||||||
changeCreator.CreateRawWithData("3", aclList.Head().Id, "0", false, []byte("3"), "2"),
|
|
||||||
}
|
|
||||||
rawChangesSecond := []*treechangeproto.RawTreeChangeWithId{
|
|
||||||
changeCreator.CreateRawWithData("4", aclList.Head().Id, "0", false, []byte("4"), "2"),
|
|
||||||
changeCreator.CreateRawWithData("5", aclList.Head().Id, "0", false, []byte("5"), "1"),
|
|
||||||
changeCreator.CreateRawWithData("6", aclList.Head().Id, "0", false, []byte("6"), "3", "4", "5"),
|
|
||||||
}
|
|
||||||
|
|
||||||
// making them to be saved in unattached
|
|
||||||
_, err := objTree.AddRawChanges(context.Background(), RawChangesPayload{
|
|
||||||
NewHeads: []string{"6"},
|
|
||||||
RawChanges: rawChangesSecond,
|
|
||||||
})
|
|
||||||
require.NoError(t, err, "adding changes should be without error")
|
|
||||||
// attaching them
|
|
||||||
res, err := objTree.AddRawChanges(context.Background(), RawChangesPayload{
|
|
||||||
NewHeads: []string{"3"},
|
|
||||||
RawChanges: rawChangesFirst,
|
|
||||||
})
|
|
||||||
|
|
||||||
require.NoError(t, err, "adding changes should be without error")
|
|
||||||
require.Equal(t, "0", objTree.Root().Id)
|
|
||||||
require.Equal(t, []string{"6"}, objTree.Heads())
|
|
||||||
require.Equal(t, 6, len(res.Added))
|
|
||||||
|
|
||||||
// checking that added changes still have data
|
|
||||||
for _, ch := range res.Added {
|
|
||||||
unmarshallRaw := &treechangeproto.RawTreeChange{}
|
|
||||||
proto.Unmarshal(ch.RawChange, unmarshallRaw)
|
|
||||||
treeCh := &treechangeproto.TreeChange{}
|
|
||||||
proto.Unmarshal(unmarshallRaw.Payload, treeCh)
|
|
||||||
require.Equal(t, ch.Id, string(treeCh.ChangesData))
|
|
||||||
}
|
|
||||||
|
|
||||||
// checking that the tree doesn't have data in memory
|
|
||||||
err = objTree.IterateRoot(nil, func(change *Change) bool {
|
|
||||||
if change.Id == "0" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
require.Nil(t, change.Data)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("empty tree load", func(t *testing.T) {
|
|
||||||
ctx := prepareEmptyDataTreeContext(t, aclList, func(changeCreator *MockChangeCreator) RawChangesPayload {
|
|
||||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
|
||||||
changeCreator.CreateRawWithData("1", aclList.Head().Id, "0", false, []byte("1"), "0"),
|
|
||||||
changeCreator.CreateRawWithData("2", aclList.Head().Id, "0", false, []byte("2"), "1"),
|
|
||||||
changeCreator.CreateRawWithData("3", aclList.Head().Id, "0", false, []byte("3"), "2"),
|
|
||||||
changeCreator.CreateRawWithData("4", aclList.Head().Id, "0", false, []byte("4"), "2"),
|
|
||||||
changeCreator.CreateRawWithData("5", aclList.Head().Id, "0", false, []byte("5"), "1"),
|
|
||||||
changeCreator.CreateRawWithData("6", aclList.Head().Id, "0", false, []byte("6"), "3", "4", "5"),
|
|
||||||
}
|
|
||||||
return RawChangesPayload{NewHeads: []string{"6"}, RawChanges: rawChanges}
|
|
||||||
})
|
|
||||||
ctx.objTree.IterateRoot(nil, func(change *Change) bool {
|
|
||||||
if change.Id == "0" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
require.Nil(t, change.Data)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
rawChanges, err := ctx.objTree.ChangesAfterCommonSnapshot([]string{"0"}, []string{"6"})
|
|
||||||
require.NoError(t, err)
|
|
||||||
for _, ch := range rawChanges {
|
|
||||||
unmarshallRaw := &treechangeproto.RawTreeChange{}
|
|
||||||
proto.Unmarshal(ch.RawChange, unmarshallRaw)
|
|
||||||
treeCh := &treechangeproto.TreeChange{}
|
|
||||||
proto.Unmarshal(unmarshallRaw.Payload, treeCh)
|
|
||||||
require.Equal(t, ch.Id, string(treeCh.ChangesData))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("rollback when add to storage returns error", func(t *testing.T) {
|
|
||||||
ctx := prepareTreeContext(t, aclList)
|
|
||||||
changeCreator := ctx.changeCreator
|
|
||||||
objTree := ctx.objTree
|
|
||||||
store := ctx.treeStorage.(*treestorage.InMemoryTreeStorage)
|
|
||||||
addErr := fmt.Errorf("error saving")
|
|
||||||
store.SetReturnErrorOnAdd(addErr)
|
|
||||||
|
|
||||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
|
||||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", true, "0"),
|
|
||||||
}
|
|
||||||
payload := RawChangesPayload{
|
|
||||||
NewHeads: []string{"1"},
|
|
||||||
RawChanges: rawChanges,
|
|
||||||
}
|
|
||||||
_, err := objTree.AddRawChanges(context.Background(), payload)
|
|
||||||
require.Error(t, err, addErr)
|
|
||||||
require.Equal(t, "0", objTree.Root().Id)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("their heads before common snapshot", func(t *testing.T) {
|
|
||||||
// checking that adding old changes did not affect the tree
|
|
||||||
ctx := prepareTreeContext(t, aclList)
|
|
||||||
changeCreator := ctx.changeCreator
|
|
||||||
objTree := ctx.objTree
|
|
||||||
|
|
||||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
|
||||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", true, "0"),
|
|
||||||
changeCreator.CreateRaw("2", aclList.Head().Id, "1", false, "1"),
|
|
||||||
changeCreator.CreateRaw("3", aclList.Head().Id, "1", true, "2"),
|
|
||||||
changeCreator.CreateRaw("4", aclList.Head().Id, "1", false, "2"),
|
|
||||||
changeCreator.CreateRaw("5", aclList.Head().Id, "1", false, "1"),
|
|
||||||
changeCreator.CreateRaw("6", aclList.Head().Id, "1", true, "3", "4", "5"),
|
|
||||||
}
|
|
||||||
payload := RawChangesPayload{
|
|
||||||
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
|
|
||||||
RawChanges: rawChanges,
|
|
||||||
}
|
|
||||||
_, err := objTree.AddRawChanges(context.Background(), payload)
|
|
||||||
require.NoError(t, err, "adding changes should be without error")
|
|
||||||
require.Equal(t, "6", objTree.Root().Id)
|
|
||||||
|
|
||||||
rawChangesPrevious := []*treechangeproto.RawTreeChangeWithId{
|
|
||||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", true, "0"),
|
|
||||||
}
|
|
||||||
payload = RawChangesPayload{
|
|
||||||
NewHeads: []string{"1"},
|
|
||||||
RawChanges: rawChangesPrevious,
|
|
||||||
}
|
|
||||||
_, err = objTree.AddRawChanges(context.Background(), payload)
|
|
||||||
require.NoError(t, err, "adding changes should be without error")
|
|
||||||
require.Equal(t, "6", objTree.Root().Id)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("stored changes will not break the pipeline if heads were not updated", func(t *testing.T) {
|
|
||||||
ctx := prepareTreeContext(t, aclList)
|
|
||||||
changeCreator := ctx.changeCreator
|
|
||||||
objTree := ctx.objTree
|
|
||||||
store := ctx.treeStorage.(*treestorage.InMemoryTreeStorage)
|
|
||||||
|
|
||||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
|
||||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", true, "0"),
|
|
||||||
}
|
|
||||||
payload := RawChangesPayload{
|
|
||||||
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
|
|
||||||
RawChanges: rawChanges,
|
|
||||||
}
|
|
||||||
_, err := objTree.AddRawChanges(context.Background(), payload)
|
|
||||||
require.NoError(t, err, "adding changes should be without error")
|
|
||||||
require.Equal(t, "1", objTree.Root().Id)
|
|
||||||
|
|
||||||
// creating changes to save in the storage
|
|
||||||
// to imitate the condition where all changes are in the storage
|
|
||||||
// but the head was not updated
|
|
||||||
storageChanges := []*treechangeproto.RawTreeChangeWithId{
|
|
||||||
changeCreator.CreateRaw("2", aclList.Head().Id, "1", false, "1"),
|
|
||||||
changeCreator.CreateRaw("3", aclList.Head().Id, "1", true, "2"),
|
|
||||||
changeCreator.CreateRaw("4", aclList.Head().Id, "1", false, "2"),
|
|
||||||
changeCreator.CreateRaw("5", aclList.Head().Id, "1", false, "1"),
|
|
||||||
changeCreator.CreateRaw("6", aclList.Head().Id, "1", true, "3", "4", "5"),
|
|
||||||
}
|
|
||||||
store.AddRawChangesSetHeads(storageChanges, []string{"1"})
|
|
||||||
|
|
||||||
// updating with subset of those changes to see that everything will still work
|
|
||||||
payload = RawChangesPayload{
|
|
||||||
NewHeads: []string{"6"},
|
|
||||||
RawChanges: storageChanges,
|
|
||||||
}
|
|
||||||
_, err = objTree.AddRawChanges(context.Background(), payload)
|
|
||||||
require.NoError(t, err, "adding changes should be without error")
|
|
||||||
require.Equal(t, "6", objTree.Root().Id)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("changes from tree after common snapshot complex", func(t *testing.T) {
|
t.Run("changes from tree after common snapshot complex", func(t *testing.T) {
|
||||||
ctx := prepareTreeContext(t, aclList)
|
ctx := prepareTreeContext(t, aclList)
|
||||||
changeCreator := ctx.changeCreator
|
changeCreator := ctx.changeCreator
|
||||||
objTree := ctx.objTree
|
objTree := ctx.objTree
|
||||||
|
|
||||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", false, "0"),
|
changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||||
changeCreator.CreateRaw("2", aclList.Head().Id, "0", false, "1"),
|
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||||
changeCreator.CreateRaw("3", aclList.Head().Id, "0", true, "2"),
|
changeCreator.createRaw("3", aclList.Head().Id, "0", true, "2"),
|
||||||
changeCreator.CreateRaw("4", aclList.Head().Id, "0", false, "2"),
|
changeCreator.createRaw("4", aclList.Head().Id, "0", false, "2"),
|
||||||
changeCreator.CreateRaw("5", aclList.Head().Id, "0", false, "1"),
|
changeCreator.createRaw("5", aclList.Head().Id, "0", false, "1"),
|
||||||
changeCreator.CreateRaw("6", aclList.Head().Id, "0", false, "3", "4", "5"),
|
changeCreator.createRaw("6", aclList.Head().Id, "0", false, "3", "4", "5"),
|
||||||
}
|
}
|
||||||
|
|
||||||
payload := RawChangesPayload{
|
payload := RawChangesPayload{
|
||||||
@ -603,13 +404,13 @@ func TestObjectTree(t *testing.T) {
|
|||||||
objTree := ctx.objTree
|
objTree := ctx.objTree
|
||||||
|
|
||||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", false, "0"),
|
changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||||
changeCreator.CreateRaw("2", aclList.Head().Id, "0", false, "1"),
|
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||||
changeCreator.CreateRaw("3", aclList.Head().Id, "0", true, "2"),
|
changeCreator.createRaw("3", aclList.Head().Id, "0", true, "2"),
|
||||||
changeCreator.CreateRaw("4", aclList.Head().Id, "0", false, "2"),
|
changeCreator.createRaw("4", aclList.Head().Id, "0", false, "2"),
|
||||||
changeCreator.CreateRaw("5", aclList.Head().Id, "0", false, "1"),
|
changeCreator.createRaw("5", aclList.Head().Id, "0", false, "1"),
|
||||||
// main difference from tree example
|
// main difference from tree example
|
||||||
changeCreator.CreateRaw("6", aclList.Head().Id, "0", true, "3", "4", "5"),
|
changeCreator.createRaw("6", aclList.Head().Id, "0", true, "3", "4", "5"),
|
||||||
}
|
}
|
||||||
|
|
||||||
payload := RawChangesPayload{
|
payload := RawChangesPayload{
|
||||||
@ -684,9 +485,9 @@ func TestObjectTree(t *testing.T) {
|
|||||||
objTree := ctx.objTree
|
objTree := ctx.objTree
|
||||||
|
|
||||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", false, "0"),
|
changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||||
changeCreator.CreateRaw("2", aclList.Head().Id, "0", false, "1"),
|
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||||
changeCreator.CreateRaw("3", aclList.Head().Id, "0", true, "2"),
|
changeCreator.createRaw("3", aclList.Head().Id, "0", true, "2"),
|
||||||
}
|
}
|
||||||
payload := RawChangesPayload{
|
payload := RawChangesPayload{
|
||||||
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
|
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
|
||||||
@ -698,9 +499,9 @@ func TestObjectTree(t *testing.T) {
|
|||||||
require.Equal(t, "3", objTree.Root().Id)
|
require.Equal(t, "3", objTree.Root().Id)
|
||||||
|
|
||||||
rawChanges = []*treechangeproto.RawTreeChangeWithId{
|
rawChanges = []*treechangeproto.RawTreeChangeWithId{
|
||||||
changeCreator.CreateRaw("4", aclList.Head().Id, "0", false, "2"),
|
changeCreator.createRaw("4", aclList.Head().Id, "0", false, "2"),
|
||||||
changeCreator.CreateRaw("5", aclList.Head().Id, "0", false, "1"),
|
changeCreator.createRaw("5", aclList.Head().Id, "0", false, "1"),
|
||||||
changeCreator.CreateRaw("6", aclList.Head().Id, "0", false, "3", "4", "5"),
|
changeCreator.createRaw("6", aclList.Head().Id, "0", false, "3", "4", "5"),
|
||||||
}
|
}
|
||||||
payload = RawChangesPayload{
|
payload = RawChangesPayload{
|
||||||
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
|
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
|
||||||
@ -741,17 +542,17 @@ func TestObjectTree(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("test history tree not include", func(t *testing.T) {
|
t.Run("test history tree not include", func(t *testing.T) {
|
||||||
changeCreator, deps := prepareHistoryTreeDeps(aclList)
|
changeCreator, deps := prepareTreeDeps(aclList)
|
||||||
|
|
||||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", false, "0"),
|
changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||||
changeCreator.CreateRaw("2", aclList.Head().Id, "0", false, "1"),
|
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||||
changeCreator.CreateRaw("3", aclList.Head().Id, "0", true, "2"),
|
changeCreator.createRaw("3", aclList.Head().Id, "0", true, "2"),
|
||||||
changeCreator.CreateRaw("4", aclList.Head().Id, "0", false, "2"),
|
changeCreator.createRaw("4", aclList.Head().Id, "0", false, "2"),
|
||||||
changeCreator.CreateRaw("5", aclList.Head().Id, "0", false, "1"),
|
changeCreator.createRaw("5", aclList.Head().Id, "0", false, "1"),
|
||||||
changeCreator.CreateRaw("6", aclList.Head().Id, "0", false, "3", "4", "5"),
|
changeCreator.createRaw("6", aclList.Head().Id, "0", false, "3", "4", "5"),
|
||||||
}
|
}
|
||||||
deps.treeStorage.AddRawChangesSetHeads(rawChanges, []string{"6"})
|
deps.treeStorage.TransactionAdd(rawChanges, []string{"6"})
|
||||||
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
|
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
|
||||||
BeforeId: "6",
|
BeforeId: "6",
|
||||||
IncludeBeforeId: false,
|
IncludeBeforeId: false,
|
||||||
@ -771,49 +572,18 @@ func TestObjectTree(t *testing.T) {
|
|||||||
assert.Equal(t, "0", hTree.Root().Id)
|
assert.Equal(t, "0", hTree.Root().Id)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("test history tree build full", func(t *testing.T) {
|
|
||||||
changeCreator, deps := prepareHistoryTreeDeps(aclList)
|
|
||||||
|
|
||||||
// sequence of snapshots: 5->1->0
|
|
||||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
|
||||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", true, "0"),
|
|
||||||
changeCreator.CreateRaw("2", aclList.Head().Id, "1", false, "1"),
|
|
||||||
changeCreator.CreateRaw("3", aclList.Head().Id, "1", true, "2"),
|
|
||||||
changeCreator.CreateRaw("4", aclList.Head().Id, "1", false, "2"),
|
|
||||||
changeCreator.CreateRaw("5", aclList.Head().Id, "1", true, "3", "4"),
|
|
||||||
changeCreator.CreateRaw("6", aclList.Head().Id, "5", false, "5"),
|
|
||||||
}
|
|
||||||
deps.treeStorage.AddRawChangesSetHeads(rawChanges, []string{"6"})
|
|
||||||
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
|
|
||||||
BuildFullTree: true,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
// check tree heads
|
|
||||||
assert.Equal(t, []string{"6"}, hTree.Heads())
|
|
||||||
|
|
||||||
// check tree iterate
|
|
||||||
var iterChangesId []string
|
|
||||||
err = hTree.IterateFrom(hTree.Root().Id, nil, func(change *Change) bool {
|
|
||||||
iterChangesId = append(iterChangesId, change.Id)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
require.NoError(t, err, "iterate should be without error")
|
|
||||||
assert.Equal(t, []string{"0", "1", "2", "3", "4", "5", "6"}, iterChangesId)
|
|
||||||
assert.Equal(t, "0", hTree.Root().Id)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("test history tree include", func(t *testing.T) {
|
t.Run("test history tree include", func(t *testing.T) {
|
||||||
changeCreator, deps := prepareHistoryTreeDeps(aclList)
|
changeCreator, deps := prepareTreeDeps(aclList)
|
||||||
|
|
||||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", false, "0"),
|
changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||||
changeCreator.CreateRaw("2", aclList.Head().Id, "0", false, "1"),
|
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||||
changeCreator.CreateRaw("3", aclList.Head().Id, "0", true, "2"),
|
changeCreator.createRaw("3", aclList.Head().Id, "0", true, "2"),
|
||||||
changeCreator.CreateRaw("4", aclList.Head().Id, "0", false, "2"),
|
changeCreator.createRaw("4", aclList.Head().Id, "0", false, "2"),
|
||||||
changeCreator.CreateRaw("5", aclList.Head().Id, "0", false, "1"),
|
changeCreator.createRaw("5", aclList.Head().Id, "0", false, "1"),
|
||||||
changeCreator.CreateRaw("6", aclList.Head().Id, "0", false, "3", "4", "5"),
|
changeCreator.createRaw("6", aclList.Head().Id, "0", false, "3", "4", "5"),
|
||||||
}
|
}
|
||||||
deps.treeStorage.AddRawChangesSetHeads(rawChanges, []string{"6"})
|
deps.treeStorage.TransactionAdd(rawChanges, []string{"6"})
|
||||||
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
|
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
|
||||||
BeforeId: "6",
|
BeforeId: "6",
|
||||||
IncludeBeforeId: true,
|
IncludeBeforeId: true,
|
||||||
@ -834,7 +604,7 @@ func TestObjectTree(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("test history tree root", func(t *testing.T) {
|
t.Run("test history tree root", func(t *testing.T) {
|
||||||
_, deps := prepareHistoryTreeDeps(aclList)
|
_, deps := prepareTreeDeps(aclList)
|
||||||
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
|
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
|
||||||
BeforeId: "0",
|
BeforeId: "0",
|
||||||
IncludeBeforeId: true,
|
IncludeBeforeId: true,
|
||||||
@ -853,40 +623,4 @@ func TestObjectTree(t *testing.T) {
|
|||||||
assert.Equal(t, []string{"0"}, iterChangesId)
|
assert.Equal(t, []string{"0"}, iterChangesId)
|
||||||
assert.Equal(t, "0", hTree.Root().Id)
|
assert.Equal(t, "0", hTree.Root().Id)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("validate correct tree", func(t *testing.T) {
|
|
||||||
ctx := prepareTreeContext(t, aclList)
|
|
||||||
changeCreator := ctx.changeCreator
|
|
||||||
|
|
||||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
|
||||||
ctx.objTree.Header(),
|
|
||||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", false, "0"),
|
|
||||||
changeCreator.CreateRaw("2", aclList.Head().Id, "0", false, "1"),
|
|
||||||
changeCreator.CreateRaw("3", aclList.Head().Id, "0", true, "2"),
|
|
||||||
}
|
|
||||||
defaultObjectTreeDeps = nonVerifiableTreeDeps
|
|
||||||
err := ValidateRawTree(treestorage.TreeStorageCreatePayload{
|
|
||||||
RootRawChange: ctx.objTree.Header(),
|
|
||||||
Heads: []string{"3"},
|
|
||||||
Changes: rawChanges,
|
|
||||||
}, ctx.aclList)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("fail to validate not connected tree", func(t *testing.T) {
|
|
||||||
ctx := prepareTreeContext(t, aclList)
|
|
||||||
changeCreator := ctx.changeCreator
|
|
||||||
|
|
||||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
|
||||||
ctx.objTree.Header(),
|
|
||||||
changeCreator.CreateRaw("3", aclList.Head().Id, "0", true, "2"),
|
|
||||||
}
|
|
||||||
defaultObjectTreeDeps = nonVerifiableTreeDeps
|
|
||||||
err := ValidateRawTree(treestorage.TreeStorageCreatePayload{
|
|
||||||
RootRawChange: ctx.objTree.Header(),
|
|
||||||
Heads: []string{"3"},
|
|
||||||
Changes: rawChanges,
|
|
||||||
}, ctx.aclList)
|
|
||||||
require.Equal(t, ErrHasInvalidChanges, err)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
package objecttree
|
package objecttree
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
"github.com/anytypeio/any-sync/util/crypto"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ObjectTreeCreatePayload struct {
|
type ObjectTreeCreatePayload struct {
|
||||||
@ -13,8 +15,6 @@ type ObjectTreeCreatePayload struct {
|
|||||||
ChangePayload []byte
|
ChangePayload []byte
|
||||||
SpaceId string
|
SpaceId string
|
||||||
IsEncrypted bool
|
IsEncrypted bool
|
||||||
Seed []byte
|
|
||||||
Timestamp int64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type HistoryTreeParams struct {
|
type HistoryTreeParams struct {
|
||||||
@ -22,7 +22,6 @@ type HistoryTreeParams struct {
|
|||||||
AclList list.AclList
|
AclList list.AclList
|
||||||
BeforeId string
|
BeforeId string
|
||||||
IncludeBeforeId bool
|
IncludeBeforeId bool
|
||||||
BuildFullTree bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type objectTreeDeps struct {
|
type objectTreeDeps struct {
|
||||||
@ -34,16 +33,12 @@ type objectTreeDeps struct {
|
|||||||
aclList list.AclList
|
aclList list.AclList
|
||||||
}
|
}
|
||||||
|
|
||||||
type BuildObjectTreeFunc = func(treeStorage treestorage.TreeStorage, aclList list.AclList) (ObjectTree, error)
|
func defaultObjectTreeDeps(
|
||||||
|
|
||||||
var defaultObjectTreeDeps = verifiableTreeDeps
|
|
||||||
|
|
||||||
func verifiableTreeDeps(
|
|
||||||
rootChange *treechangeproto.RawTreeChangeWithId,
|
rootChange *treechangeproto.RawTreeChangeWithId,
|
||||||
treeStorage treestorage.TreeStorage,
|
treeStorage treestorage.TreeStorage,
|
||||||
aclList list.AclList) objectTreeDeps {
|
aclList list.AclList) objectTreeDeps {
|
||||||
changeBuilder := NewChangeBuilder(crypto.NewKeyStorage(), rootChange)
|
changeBuilder := NewChangeBuilder(crypto.NewKeyStorage(), rootChange)
|
||||||
treeBuilder := newTreeBuilder(true, treeStorage, changeBuilder)
|
treeBuilder := newTreeBuilder(treeStorage, changeBuilder)
|
||||||
return objectTreeDeps{
|
return objectTreeDeps{
|
||||||
changeBuilder: changeBuilder,
|
changeBuilder: changeBuilder,
|
||||||
treeBuilder: treeBuilder,
|
treeBuilder: treeBuilder,
|
||||||
@ -54,28 +49,12 @@ func verifiableTreeDeps(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func emptyDataTreeDeps(
|
|
||||||
rootChange *treechangeproto.RawTreeChangeWithId,
|
|
||||||
treeStorage treestorage.TreeStorage,
|
|
||||||
aclList list.AclList) objectTreeDeps {
|
|
||||||
changeBuilder := NewChangeBuilder(crypto.NewKeyStorage(), rootChange)
|
|
||||||
treeBuilder := newTreeBuilder(false, treeStorage, changeBuilder)
|
|
||||||
return objectTreeDeps{
|
|
||||||
changeBuilder: changeBuilder,
|
|
||||||
treeBuilder: treeBuilder,
|
|
||||||
treeStorage: treeStorage,
|
|
||||||
validator: newTreeValidator(),
|
|
||||||
rawChangeLoader: newStorageLoader(treeStorage, changeBuilder),
|
|
||||||
aclList: aclList,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func nonVerifiableTreeDeps(
|
func nonVerifiableTreeDeps(
|
||||||
rootChange *treechangeproto.RawTreeChangeWithId,
|
rootChange *treechangeproto.RawTreeChangeWithId,
|
||||||
treeStorage treestorage.TreeStorage,
|
treeStorage treestorage.TreeStorage,
|
||||||
aclList list.AclList) objectTreeDeps {
|
aclList list.AclList) objectTreeDeps {
|
||||||
changeBuilder := &nonVerifiableChangeBuilder{NewChangeBuilder(newMockKeyStorage(), rootChange)}
|
changeBuilder := &nonVerifiableChangeBuilder{NewChangeBuilder(nil, rootChange)}
|
||||||
treeBuilder := newTreeBuilder(true, treeStorage, changeBuilder)
|
treeBuilder := newTreeBuilder(treeStorage, changeBuilder)
|
||||||
return objectTreeDeps{
|
return objectTreeDeps{
|
||||||
changeBuilder: changeBuilder,
|
changeBuilder: changeBuilder,
|
||||||
treeBuilder: treeBuilder,
|
treeBuilder: treeBuilder,
|
||||||
@ -86,47 +65,17 @@ func nonVerifiableTreeDeps(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildEmptyDataObjectTree(treeStorage treestorage.TreeStorage, aclList list.AclList) (ObjectTree, error) {
|
func CreateObjectTreeRoot(payload ObjectTreeCreatePayload, aclList list.AclList) (root *treechangeproto.RawTreeChangeWithId, err error) {
|
||||||
rootChange, err := treeStorage.Root()
|
bytes := make([]byte, 32)
|
||||||
|
_, err = rand.Read(bytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return
|
||||||
}
|
}
|
||||||
deps := emptyDataTreeDeps(rootChange, treeStorage, aclList)
|
return createObjectTreeRoot(payload, time.Now().Unix(), bytes, aclList)
|
||||||
return buildObjectTree(deps)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildTestableTree(treeStorage treestorage.TreeStorage, aclList list.AclList) (ObjectTree, error) {
|
func DeriveObjectTreeRoot(payload ObjectTreeCreatePayload, aclList list.AclList) (root *treechangeproto.RawTreeChangeWithId, err error) {
|
||||||
root, _ := treeStorage.Root()
|
return createObjectTreeRoot(payload, 0, nil, aclList)
|
||||||
changeBuilder := &nonVerifiableChangeBuilder{
|
|
||||||
ChangeBuilder: NewChangeBuilder(newMockKeyStorage(), root),
|
|
||||||
}
|
|
||||||
deps := objectTreeDeps{
|
|
||||||
changeBuilder: changeBuilder,
|
|
||||||
treeBuilder: newTreeBuilder(true, treeStorage, changeBuilder),
|
|
||||||
treeStorage: treeStorage,
|
|
||||||
rawChangeLoader: newRawChangeLoader(treeStorage, changeBuilder),
|
|
||||||
validator: &noOpTreeValidator{},
|
|
||||||
aclList: aclList,
|
|
||||||
}
|
|
||||||
|
|
||||||
return buildObjectTree(deps)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BuildEmptyDataTestableTree(treeStorage treestorage.TreeStorage, aclList list.AclList) (ObjectTree, error) {
|
|
||||||
root, _ := treeStorage.Root()
|
|
||||||
changeBuilder := &nonVerifiableChangeBuilder{
|
|
||||||
ChangeBuilder: NewChangeBuilder(newMockKeyStorage(), root),
|
|
||||||
}
|
|
||||||
deps := objectTreeDeps{
|
|
||||||
changeBuilder: changeBuilder,
|
|
||||||
treeBuilder: newTreeBuilder(false, treeStorage, changeBuilder),
|
|
||||||
treeStorage: treeStorage,
|
|
||||||
rawChangeLoader: newStorageLoader(treeStorage, changeBuilder),
|
|
||||||
validator: &noOpTreeValidator{},
|
|
||||||
aclList: aclList,
|
|
||||||
}
|
|
||||||
|
|
||||||
return buildObjectTree(deps)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildObjectTree(treeStorage treestorage.TreeStorage, aclList list.AclList) (ObjectTree, error) {
|
func BuildObjectTree(treeStorage treestorage.TreeStorage, aclList list.AclList) (ObjectTree, error) {
|
||||||
@ -156,7 +105,54 @@ func BuildHistoryTree(params HistoryTreeParams) (HistoryTree, error) {
|
|||||||
return buildHistoryTree(deps, params)
|
return buildHistoryTree(deps, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateObjectTreeRoot(payload ObjectTreeCreatePayload, aclList list.AclList) (root *treechangeproto.RawTreeChangeWithId, err error) {
|
func CreateDerivedObjectTree(
|
||||||
|
payload ObjectTreeCreatePayload,
|
||||||
|
aclList list.AclList,
|
||||||
|
createStorage treestorage.TreeStorageCreatorFunc) (objTree ObjectTree, err error) {
|
||||||
|
return createObjectTree(payload, 0, nil, aclList, createStorage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateObjectTree(
|
||||||
|
payload ObjectTreeCreatePayload,
|
||||||
|
aclList list.AclList,
|
||||||
|
createStorage treestorage.TreeStorageCreatorFunc) (objTree ObjectTree, err error) {
|
||||||
|
bytes := make([]byte, 32)
|
||||||
|
_, err = rand.Read(bytes)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return createObjectTree(payload, time.Now().Unix(), bytes, aclList, createStorage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createObjectTree(
|
||||||
|
payload ObjectTreeCreatePayload,
|
||||||
|
timestamp int64,
|
||||||
|
seed []byte,
|
||||||
|
aclList list.AclList,
|
||||||
|
createStorage treestorage.TreeStorageCreatorFunc) (objTree ObjectTree, err error) {
|
||||||
|
raw, err := createObjectTreeRoot(payload, timestamp, seed, aclList)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// create storage
|
||||||
|
st, err := createStorage(treestorage.TreeStorageCreatePayload{
|
||||||
|
RootRawChange: raw,
|
||||||
|
Changes: []*treechangeproto.RawTreeChangeWithId{raw},
|
||||||
|
Heads: []string{raw.Id},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return BuildObjectTree(st, aclList)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createObjectTreeRoot(
|
||||||
|
payload ObjectTreeCreatePayload,
|
||||||
|
timestamp int64,
|
||||||
|
seed []byte,
|
||||||
|
aclList list.AclList) (root *treechangeproto.RawTreeChangeWithId, err error) {
|
||||||
aclList.RLock()
|
aclList.RLock()
|
||||||
aclHeadId := aclList.Head().Id
|
aclHeadId := aclList.Head().Id
|
||||||
aclList.RUnlock()
|
aclList.RUnlock()
|
||||||
@ -170,8 +166,8 @@ func CreateObjectTreeRoot(payload ObjectTreeCreatePayload, aclList list.AclList)
|
|||||||
SpaceId: payload.SpaceId,
|
SpaceId: payload.SpaceId,
|
||||||
ChangeType: payload.ChangeType,
|
ChangeType: payload.ChangeType,
|
||||||
ChangePayload: payload.ChangePayload,
|
ChangePayload: payload.ChangePayload,
|
||||||
Timestamp: payload.Timestamp,
|
Timestamp: timestamp,
|
||||||
Seed: payload.Seed,
|
Seed: seed,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, root, err = NewChangeBuilder(crypto.NewKeyStorage(), nil).BuildRoot(cnt)
|
_, root, err = NewChangeBuilder(crypto.NewKeyStorage(), nil).BuildRoot(cnt)
|
||||||
@ -231,7 +227,7 @@ func buildHistoryTree(deps objectTreeDeps, params HistoryTreeParams) (ht History
|
|||||||
}
|
}
|
||||||
|
|
||||||
hTree := &historyTree{objectTree: objTree}
|
hTree := &historyTree{objectTree: objTree}
|
||||||
err = hTree.rebuildFromStorage(params)
|
err = hTree.rebuildFromStorage(params.BeforeId, params.IncludeBeforeId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,10 @@
|
|||||||
package objecttree
|
package objecttree
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||||
"github.com/anyproto/any-sync/util/slice"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ObjectTreeValidator interface {
|
type ObjectTreeValidator interface {
|
||||||
@ -52,18 +50,20 @@ func (v *objectTreeValidator) ValidateNewChanges(tree *Tree, aclList list.AclLis
|
|||||||
|
|
||||||
func (v *objectTreeValidator) validateChange(tree *Tree, aclList list.AclList, c *Change) (err error) {
|
func (v *objectTreeValidator) validateChange(tree *Tree, aclList list.AclList, c *Change) (err error) {
|
||||||
var (
|
var (
|
||||||
userState list.AclUserState
|
perm list.AclUserState
|
||||||
state = aclList.AclState()
|
state = aclList.AclState()
|
||||||
)
|
)
|
||||||
// checking if the user could write
|
// checking if the user could write
|
||||||
userState, err = state.StateAtRecord(c.AclHeadId, c.Identity)
|
perm, err = state.StateAtRecord(c.AclHeadId, c.Identity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !userState.Permissions.CanWrite() {
|
|
||||||
|
if perm.Permissions != aclrecordproto.AclUserPermissions_Writer && perm.Permissions != aclrecordproto.AclUserPermissions_Admin {
|
||||||
err = list.ErrInsufficientPermissions
|
err = list.ErrInsufficientPermissions
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Id == tree.RootId() {
|
if c.Id == tree.RootId() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -88,23 +88,11 @@ func (v *objectTreeValidator) validateChange(tree *Tree, aclList list.AclList, c
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ValidateRawTree(payload treestorage.TreeStorageCreatePayload, aclList list.AclList) (err error) {
|
func ValidateRawTree(payload treestorage.TreeStorageCreatePayload, aclList list.AclList) (err error) {
|
||||||
treeStorage, err := treestorage.NewInMemoryTreeStorage(payload.RootRawChange, []string{payload.RootRawChange.Id}, nil)
|
treeStorage, err := treestorage.NewInMemoryTreeStorage(payload.RootRawChange, payload.Heads, payload.Changes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tree, err := BuildObjectTree(treeStorage, aclList)
|
|
||||||
if err != nil {
|
_, err = BuildObjectTree(treeStorage, aclList)
|
||||||
return
|
|
||||||
}
|
|
||||||
res, err := tree.AddRawChanges(context.Background(), RawChangesPayload{
|
|
||||||
NewHeads: payload.Heads,
|
|
||||||
RawChanges: payload.Changes,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !slice.UnsortedEquals(res.Heads, payload.Heads) {
|
|
||||||
return ErrHasInvalidChanges
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,17 +2,15 @@ package objecttree
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||||
|
"github.com/anytypeio/any-sync/util/slice"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
|
||||||
"github.com/anyproto/any-sync/util/slice"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type rawChangeLoader struct {
|
type rawChangeLoader struct {
|
||||||
treeStorage treestorage.TreeStorage
|
treeStorage treestorage.TreeStorage
|
||||||
changeBuilder ChangeBuilder
|
changeBuilder ChangeBuilder
|
||||||
alwaysFromStorage bool
|
|
||||||
|
|
||||||
// buffers
|
// buffers
|
||||||
idStack []string
|
idStack []string
|
||||||
@ -23,13 +21,6 @@ type rawCacheEntry struct {
|
|||||||
change *Change
|
change *Change
|
||||||
rawChange *treechangeproto.RawTreeChangeWithId
|
rawChange *treechangeproto.RawTreeChangeWithId
|
||||||
position int
|
position int
|
||||||
removed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newStorageLoader(treeStorage treestorage.TreeStorage, changeBuilder ChangeBuilder) *rawChangeLoader {
|
|
||||||
loader := newRawChangeLoader(treeStorage, changeBuilder)
|
|
||||||
loader.alwaysFromStorage = true
|
|
||||||
return loader
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRawChangeLoader(treeStorage treestorage.TreeStorage, changeBuilder ChangeBuilder) *rawChangeLoader {
|
func newRawChangeLoader(treeStorage treestorage.TreeStorage, changeBuilder ChangeBuilder) *rawChangeLoader {
|
||||||
@ -39,15 +30,7 @@ func newRawChangeLoader(treeStorage treestorage.TreeStorage, changeBuilder Chang
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rawChangeLoader) Load(commonSnapshot string, t *Tree, breakpoints []string) ([]*treechangeproto.RawTreeChangeWithId, error) {
|
func (r *rawChangeLoader) LoadFromTree(t *Tree, breakpoints []string) ([]*treechangeproto.RawTreeChangeWithId, error) {
|
||||||
if commonSnapshot == t.root.Id && !r.alwaysFromStorage {
|
|
||||||
return r.loadFromTree(t, breakpoints)
|
|
||||||
} else {
|
|
||||||
return r.loadFromStorage(commonSnapshot, t.Heads(), breakpoints)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *rawChangeLoader) loadFromTree(t *Tree, breakpoints []string) ([]*treechangeproto.RawTreeChangeWithId, error) {
|
|
||||||
var stack []*Change
|
var stack []*Change
|
||||||
for _, h := range t.headIds {
|
for _, h := range t.headIds {
|
||||||
stack = append(stack, t.attached[h])
|
stack = append(stack, t.attached[h])
|
||||||
@ -115,7 +98,7 @@ func (r *rawChangeLoader) loadFromTree(t *Tree, breakpoints []string) ([]*treech
|
|||||||
return convert(results)
|
return convert(results)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoints []string) ([]*treechangeproto.RawTreeChangeWithId, error) {
|
func (r *rawChangeLoader) LoadFromStorage(commonSnapshot string, heads, breakpoints []string) ([]*treechangeproto.RawTreeChangeWithId, error) {
|
||||||
// resetting cache
|
// resetting cache
|
||||||
r.cache = make(map[string]rawCacheEntry)
|
r.cache = make(map[string]rawCacheEntry)
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -128,6 +111,7 @@ func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoi
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
entry.position = -1
|
||||||
r.cache[b] = entry
|
r.cache[b] = entry
|
||||||
existingBreakpoints = append(existingBreakpoints, b)
|
existingBreakpoints = append(existingBreakpoints, b)
|
||||||
}
|
}
|
||||||
@ -136,7 +120,8 @@ func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoi
|
|||||||
dfs := func(
|
dfs := func(
|
||||||
commonSnapshot string,
|
commonSnapshot string,
|
||||||
heads []string,
|
heads []string,
|
||||||
shouldVisit func(entry rawCacheEntry, mapExists bool) bool,
|
startCounter int,
|
||||||
|
shouldVisit func(counter int, mapExists bool) bool,
|
||||||
visit func(entry rawCacheEntry) rawCacheEntry) bool {
|
visit func(entry rawCacheEntry) rawCacheEntry) bool {
|
||||||
|
|
||||||
// resetting stack
|
// resetting stack
|
||||||
@ -150,7 +135,7 @@ func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoi
|
|||||||
r.idStack = r.idStack[:len(r.idStack)-1]
|
r.idStack = r.idStack[:len(r.idStack)-1]
|
||||||
|
|
||||||
entry, exists := r.cache[id]
|
entry, exists := r.cache[id]
|
||||||
if !shouldVisit(entry, exists) {
|
if !shouldVisit(entry.position, exists) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if id == commonSnapshot {
|
if id == commonSnapshot {
|
||||||
@ -159,6 +144,7 @@ func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoi
|
|||||||
}
|
}
|
||||||
if !exists {
|
if !exists {
|
||||||
entry, err = r.loadEntry(id)
|
entry, err = r.loadEntry(id)
|
||||||
|
entry.position = -1
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -173,7 +159,7 @@ func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoi
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
prevEntry, exists := r.cache[prev]
|
prevEntry, exists := r.cache[prev]
|
||||||
if !shouldVisit(prevEntry, exists) {
|
if !shouldVisit(prevEntry.position, exists) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
r.idStack = append(r.idStack, prev)
|
r.idStack = append(r.idStack, prev)
|
||||||
@ -186,8 +172,8 @@ func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoi
|
|||||||
r.idStack = append(r.idStack, heads...)
|
r.idStack = append(r.idStack, heads...)
|
||||||
var buffer []*treechangeproto.RawTreeChangeWithId
|
var buffer []*treechangeproto.RawTreeChangeWithId
|
||||||
|
|
||||||
rootVisited := dfs(commonSnapshot, heads,
|
rootVisited := dfs(commonSnapshot, heads, 0,
|
||||||
func(_ rawCacheEntry, mapExists bool) bool {
|
func(counter int, mapExists bool) bool {
|
||||||
return !mapExists
|
return !mapExists
|
||||||
},
|
},
|
||||||
func(entry rawCacheEntry) rawCacheEntry {
|
func(entry rawCacheEntry) rawCacheEntry {
|
||||||
@ -212,13 +198,11 @@ func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// marking all visited as nil
|
// marking all visited as nil
|
||||||
dfs(commonSnapshot, existingBreakpoints,
|
dfs(commonSnapshot, existingBreakpoints, len(buffer),
|
||||||
func(entry rawCacheEntry, mapExists bool) bool {
|
func(counter int, mapExists bool) bool {
|
||||||
// only going through already loaded changes
|
return !mapExists || counter < len(buffer)
|
||||||
return mapExists && !entry.removed
|
|
||||||
},
|
},
|
||||||
func(entry rawCacheEntry) rawCacheEntry {
|
func(entry rawCacheEntry) rawCacheEntry {
|
||||||
entry.removed = true
|
|
||||||
if entry.position != -1 {
|
if entry.position != -1 {
|
||||||
buffer[entry.position] = nil
|
buffer[entry.position] = nil
|
||||||
}
|
}
|
||||||
@ -249,7 +233,6 @@ func (r *rawChangeLoader) loadEntry(id string) (entry rawCacheEntry, err error)
|
|||||||
entry = rawCacheEntry{
|
entry = rawCacheEntry{
|
||||||
change: change,
|
change: change,
|
||||||
rawChange: rawChange,
|
rawChange: rawChange,
|
||||||
position: -1,
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +1,12 @@
|
|||||||
package objecttree
|
package objecttree
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
"github.com/anytypeio/any-sync/util/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SignableChangeContent is a payload to be passed when we are creating change
|
|
||||||
type SignableChangeContent struct {
|
type SignableChangeContent struct {
|
||||||
// Data is a data provided by the client
|
Data []byte
|
||||||
Data []byte
|
Key crypto.PrivKey
|
||||||
// Key is the key which will be used to sign the change
|
IsSnapshot bool
|
||||||
Key crypto.PrivKey
|
|
||||||
// IsSnapshot tells if the change has snapshot of all previous data
|
|
||||||
IsSnapshot bool
|
|
||||||
// IsEncrypted tells if we encrypt the data with the relevant symmetric key
|
|
||||||
IsEncrypted bool
|
IsEncrypted bool
|
||||||
// Timestamp is a timestamp of change, if it is <= 0, then we use current timestamp
|
|
||||||
Timestamp int64
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,121 +0,0 @@
|
|||||||
package objecttree
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
|
||||||
libcrypto "github.com/libp2p/go-libp2p/core/crypto"
|
|
||||||
)
|
|
||||||
|
|
||||||
type mockPubKey struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
const mockKeyValue = "mockKey"
|
|
||||||
|
|
||||||
func (m mockPubKey) Equals(key crypto.Key) bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockPubKey) Raw() ([]byte, error) {
|
|
||||||
return []byte(mockKeyValue), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockPubKey) Encrypt(message []byte) ([]byte, error) {
|
|
||||||
return message, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockPubKey) Verify(data []byte, sig []byte) (bool, error) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockPubKey) Marshall() ([]byte, error) {
|
|
||||||
return []byte(mockKeyValue), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockPubKey) Storage() []byte {
|
|
||||||
return []byte(mockKeyValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockPubKey) Account() string {
|
|
||||||
return mockKeyValue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockPubKey) Network() string {
|
|
||||||
return mockKeyValue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockPubKey) PeerId() string {
|
|
||||||
return mockKeyValue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockPubKey) LibP2P() (libcrypto.PubKey, error) {
|
|
||||||
return nil, fmt.Errorf("can't be converted in libp2p")
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockKeyStorage struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMockKeyStorage() mockKeyStorage {
|
|
||||||
return mockKeyStorage{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockKeyStorage) PubKeyFromProto(protoBytes []byte) (crypto.PubKey, error) {
|
|
||||||
return mockPubKey{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type MockChangeCreator struct{}
|
|
||||||
|
|
||||||
func NewMockChangeCreator() *MockChangeCreator {
|
|
||||||
return &MockChangeCreator{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *MockChangeCreator) CreateRoot(id, aclId string) *treechangeproto.RawTreeChangeWithId {
|
|
||||||
aclChange := &treechangeproto.RootChange{
|
|
||||||
AclHeadId: aclId,
|
|
||||||
}
|
|
||||||
res, _ := aclChange.Marshal()
|
|
||||||
|
|
||||||
raw := &treechangeproto.RawTreeChange{
|
|
||||||
Payload: res,
|
|
||||||
Signature: nil,
|
|
||||||
}
|
|
||||||
rawMarshalled, _ := raw.Marshal()
|
|
||||||
|
|
||||||
return &treechangeproto.RawTreeChangeWithId{
|
|
||||||
RawChange: rawMarshalled,
|
|
||||||
Id: id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *MockChangeCreator) CreateRaw(id, aclId, snapshotId string, isSnapshot bool, prevIds ...string) *treechangeproto.RawTreeChangeWithId {
|
|
||||||
return c.CreateRawWithData(id, aclId, snapshotId, isSnapshot, nil, prevIds...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *MockChangeCreator) CreateRawWithData(id, aclId, snapshotId string, isSnapshot bool, data []byte, prevIds ...string) *treechangeproto.RawTreeChangeWithId {
|
|
||||||
aclChange := &treechangeproto.TreeChange{
|
|
||||||
TreeHeadIds: prevIds,
|
|
||||||
AclHeadId: aclId,
|
|
||||||
SnapshotBaseId: snapshotId,
|
|
||||||
ChangesData: data,
|
|
||||||
IsSnapshot: isSnapshot,
|
|
||||||
}
|
|
||||||
res, _ := aclChange.Marshal()
|
|
||||||
|
|
||||||
raw := &treechangeproto.RawTreeChange{
|
|
||||||
Payload: res,
|
|
||||||
Signature: nil,
|
|
||||||
}
|
|
||||||
rawMarshalled, _ := raw.Marshal()
|
|
||||||
|
|
||||||
return &treechangeproto.RawTreeChangeWithId{
|
|
||||||
RawChange: rawMarshalled,
|
|
||||||
Id: id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *MockChangeCreator) CreateNewTreeStorage(treeId, aclHeadId string) treestorage.TreeStorage {
|
|
||||||
root := c.CreateRoot(treeId, aclHeadId)
|
|
||||||
treeStorage, _ := treestorage.NewInMemoryTreeStorage(root, []string{root.Id}, []*treechangeproto.RawTreeChangeWithId{root})
|
|
||||||
return treeStorage
|
|
||||||
}
|
|
||||||
@ -82,7 +82,6 @@ func (t *Tree) AddMergedHead(c *Change) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.headIds = []string{c.Id}
|
t.headIds = []string{c.Id}
|
||||||
t.lastIteratedHeadId = c.Id
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,12 +2,10 @@ package objecttree
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func newChange(id string, snapshotId string, prevIds ...string) *Change {
|
func newChange(id string, snapshotId string, prevIds ...string) *Change {
|
||||||
@ -28,17 +26,6 @@ func newSnapshot(id, snapshotId string, prevIds ...string) *Change {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTree_AddMergedHead(t *testing.T) {
|
|
||||||
tr := new(Tree)
|
|
||||||
_, _ = tr.Add(
|
|
||||||
newSnapshot("root", ""),
|
|
||||||
newChange("one", "root", "root"),
|
|
||||||
)
|
|
||||||
require.Equal(t, tr.lastIteratedHeadId, "one")
|
|
||||||
tr.AddMergedHead(newChange("two", "root", "one"))
|
|
||||||
require.Equal(t, tr.lastIteratedHeadId, "two")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTree_Add(t *testing.T) {
|
func TestTree_Add(t *testing.T) {
|
||||||
t.Run("add first el", func(t *testing.T) {
|
t.Run("add first el", func(t *testing.T) {
|
||||||
tr := new(Tree)
|
tr := new(Tree)
|
||||||
|
|||||||
@ -6,9 +6,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
"github.com/anytypeio/any-sync/app/logger"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||||
"github.com/anyproto/any-sync/util/slice"
|
"github.com/anytypeio/any-sync/util/slice"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,20 +21,18 @@ type treeBuilder struct {
|
|||||||
treeStorage treestorage.TreeStorage
|
treeStorage treestorage.TreeStorage
|
||||||
builder ChangeBuilder
|
builder ChangeBuilder
|
||||||
|
|
||||||
cache map[string]*Change
|
cache map[string]*Change
|
||||||
tree *Tree
|
tree *Tree
|
||||||
keepInMemoryData bool
|
|
||||||
|
|
||||||
// buffers
|
// buffers
|
||||||
idStack []string
|
idStack []string
|
||||||
loadBuffer []*Change
|
loadBuffer []*Change
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTreeBuilder(keepData bool, storage treestorage.TreeStorage, builder ChangeBuilder) *treeBuilder {
|
func newTreeBuilder(storage treestorage.TreeStorage, builder ChangeBuilder) *treeBuilder {
|
||||||
return &treeBuilder{
|
return &treeBuilder{
|
||||||
treeStorage: storage,
|
treeStorage: storage,
|
||||||
builder: builder,
|
builder: builder,
|
||||||
keepInMemoryData: keepData,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,22 +49,6 @@ func (tb *treeBuilder) Build(theirHeads []string, newChanges []*Change) (*Tree,
|
|||||||
return tb.build(heads, theirHeads, newChanges)
|
return tb.build(heads, theirHeads, newChanges)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tb *treeBuilder) BuildFull() (*Tree, error) {
|
|
||||||
defer func() {
|
|
||||||
tb.cache = make(map[string]*Change)
|
|
||||||
}()
|
|
||||||
tb.cache = make(map[string]*Change)
|
|
||||||
heads, err := tb.treeStorage.Heads()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = tb.buildTree(heads, tb.treeStorage.Id())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return tb.tree, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tb *treeBuilder) build(heads []string, theirHeads []string, newChanges []*Change) (*Tree, error) {
|
func (tb *treeBuilder) build(heads []string, theirHeads []string, newChanges []*Change) (*Tree, error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
tb.cache = make(map[string]*Change)
|
tb.cache = make(map[string]*Change)
|
||||||
@ -121,8 +103,8 @@ func (tb *treeBuilder) buildTree(heads []string, breakpoint string) (err error)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
changes := tb.dfs(heads, breakpoint)
|
|
||||||
tb.tree.AddFast(ch)
|
tb.tree.AddFast(ch)
|
||||||
|
changes := tb.dfs(heads, breakpoint)
|
||||||
tb.tree.AddFast(changes...)
|
tb.tree.AddFast(changes...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -181,9 +163,6 @@ func (tb *treeBuilder) loadChange(id string) (ch *Change, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !tb.keepInMemoryData {
|
|
||||||
ch.Data = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
tb.cache[id] = ch
|
tb.cache[id] = ch
|
||||||
return ch, nil
|
return ch, nil
|
||||||
|
|||||||
@ -2,7 +2,7 @@ package objecttree
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/anyproto/any-sync/util/crypto"
|
"github.com/anytypeio/any-sync/util/crypto"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: github.com/anyproto/any-sync/commonspace/object/tree/synctree (interfaces: SyncTree,ReceiveQueue,HeadNotifiable,SyncClient,RequestFactory,TreeSyncProtocol)
|
// Source: github.com/anytypeio/any-sync/commonspace/object/tree/synctree (interfaces: SyncClient,SyncTree,ReceiveQueue,HeadNotifiable)
|
||||||
|
|
||||||
// Package mock_synctree is a generated GoMock package.
|
// Package mock_synctree is a generated GoMock package.
|
||||||
package mock_synctree
|
package mock_synctree
|
||||||
@ -9,15 +9,124 @@ import (
|
|||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
time "time"
|
time "time"
|
||||||
|
|
||||||
list "github.com/anyproto/any-sync/commonspace/object/acl/list"
|
list "github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||||
objecttree "github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
objecttree "github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||||
updatelistener "github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
|
updatelistener "github.com/anytypeio/any-sync/commonspace/object/tree/synctree/updatelistener"
|
||||||
treechangeproto "github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
treechangeproto "github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
treestorage "github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
treestorage "github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||||
spacesyncproto "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
spacesyncproto "github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||||
gomock "go.uber.org/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// MockSyncClient is a mock of SyncClient interface.
|
||||||
|
type MockSyncClient struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockSyncClientMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockSyncClientMockRecorder is the mock recorder for MockSyncClient.
|
||||||
|
type MockSyncClientMockRecorder struct {
|
||||||
|
mock *MockSyncClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockSyncClient creates a new mock instance.
|
||||||
|
func NewMockSyncClient(ctrl *gomock.Controller) *MockSyncClient {
|
||||||
|
mock := &MockSyncClient{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockSyncClientMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockSyncClient) EXPECT() *MockSyncClientMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Broadcast mocks base method.
|
||||||
|
func (m *MockSyncClient) Broadcast(arg0 context.Context, arg1 *treechangeproto.TreeSyncMessage) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Broadcast", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Broadcast indicates an expected call of Broadcast.
|
||||||
|
func (mr *MockSyncClientMockRecorder) Broadcast(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Broadcast", reflect.TypeOf((*MockSyncClient)(nil).Broadcast), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateFullSyncRequest mocks base method.
|
||||||
|
func (m *MockSyncClient) CreateFullSyncRequest(arg0 objecttree.ObjectTree, arg1, arg2 []string) (*treechangeproto.TreeSyncMessage, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "CreateFullSyncRequest", arg0, arg1, arg2)
|
||||||
|
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateFullSyncRequest indicates an expected call of CreateFullSyncRequest.
|
||||||
|
func (mr *MockSyncClientMockRecorder) CreateFullSyncRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncRequest", reflect.TypeOf((*MockSyncClient)(nil).CreateFullSyncRequest), arg0, arg1, arg2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateFullSyncResponse mocks base method.
|
||||||
|
func (m *MockSyncClient) CreateFullSyncResponse(arg0 objecttree.ObjectTree, arg1, arg2 []string) (*treechangeproto.TreeSyncMessage, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "CreateFullSyncResponse", arg0, arg1, arg2)
|
||||||
|
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateFullSyncResponse indicates an expected call of CreateFullSyncResponse.
|
||||||
|
func (mr *MockSyncClientMockRecorder) CreateFullSyncResponse(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncResponse", reflect.TypeOf((*MockSyncClient)(nil).CreateFullSyncResponse), arg0, arg1, arg2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateHeadUpdate mocks base method.
|
||||||
|
func (m *MockSyncClient) CreateHeadUpdate(arg0 objecttree.ObjectTree, arg1 []*treechangeproto.RawTreeChangeWithId) *treechangeproto.TreeSyncMessage {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "CreateHeadUpdate", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateHeadUpdate indicates an expected call of CreateHeadUpdate.
|
||||||
|
func (mr *MockSyncClientMockRecorder) CreateHeadUpdate(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateHeadUpdate", reflect.TypeOf((*MockSyncClient)(nil).CreateHeadUpdate), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateNewTreeRequest mocks base method.
|
||||||
|
func (m *MockSyncClient) CreateNewTreeRequest() *treechangeproto.TreeSyncMessage {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "CreateNewTreeRequest")
|
||||||
|
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateNewTreeRequest indicates an expected call of CreateNewTreeRequest.
|
||||||
|
func (mr *MockSyncClientMockRecorder) CreateNewTreeRequest() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNewTreeRequest", reflect.TypeOf((*MockSyncClient)(nil).CreateNewTreeRequest))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendWithReply mocks base method.
|
||||||
|
func (m *MockSyncClient) SendWithReply(arg0 context.Context, arg1 string, arg2 *treechangeproto.TreeSyncMessage, arg3 string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "SendWithReply", arg0, arg1, arg2, arg3)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendWithReply indicates an expected call of SendWithReply.
|
||||||
|
func (mr *MockSyncClientMockRecorder) SendWithReply(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendWithReply", reflect.TypeOf((*MockSyncClient)(nil).SendWithReply), arg0, arg1, arg2, arg3)
|
||||||
|
}
|
||||||
|
|
||||||
// MockSyncTree is a mock of SyncTree interface.
|
// MockSyncTree is a mock of SyncTree interface.
|
||||||
type MockSyncTree struct {
|
type MockSyncTree struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
@ -186,21 +295,6 @@ func (mr *MockSyncTreeMockRecorder) HandleMessage(arg0, arg1, arg2 interface{})
|
|||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleRequest mocks base method.
|
|
||||||
func (m *MockSyncTree) HandleRequest(arg0 context.Context, arg1 string, arg2 *spacesyncproto.ObjectSyncMessage) (*spacesyncproto.ObjectSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "HandleRequest", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(*spacesyncproto.ObjectSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleRequest indicates an expected call of HandleRequest.
|
|
||||||
func (mr *MockSyncTreeMockRecorder) HandleRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleRequest", reflect.TypeOf((*MockSyncTree)(nil).HandleRequest), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasChanges mocks base method.
|
// HasChanges mocks base method.
|
||||||
func (m *MockSyncTree) HasChanges(arg0 ...string) bool {
|
func (m *MockSyncTree) HasChanges(arg0 ...string) bool {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@ -605,287 +699,3 @@ func (mr *MockHeadNotifiableMockRecorder) UpdateHeads(arg0, arg1 interface{}) *g
|
|||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateHeads", reflect.TypeOf((*MockHeadNotifiable)(nil).UpdateHeads), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateHeads", reflect.TypeOf((*MockHeadNotifiable)(nil).UpdateHeads), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockSyncClient is a mock of SyncClient interface.
|
|
||||||
type MockSyncClient struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockSyncClientMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockSyncClientMockRecorder is the mock recorder for MockSyncClient.
|
|
||||||
type MockSyncClientMockRecorder struct {
|
|
||||||
mock *MockSyncClient
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockSyncClient creates a new mock instance.
|
|
||||||
func NewMockSyncClient(ctrl *gomock.Controller) *MockSyncClient {
|
|
||||||
mock := &MockSyncClient{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockSyncClientMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockSyncClient) EXPECT() *MockSyncClientMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// Broadcast mocks base method.
|
|
||||||
func (m *MockSyncClient) Broadcast(arg0 *treechangeproto.TreeSyncMessage) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "Broadcast", arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Broadcast indicates an expected call of Broadcast.
|
|
||||||
func (mr *MockSyncClientMockRecorder) Broadcast(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Broadcast", reflect.TypeOf((*MockSyncClient)(nil).Broadcast), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncRequest mocks base method.
|
|
||||||
func (m *MockSyncClient) CreateFullSyncRequest(arg0 objecttree.ObjectTree, arg1, arg2 []string) (*treechangeproto.TreeSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateFullSyncRequest", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncRequest indicates an expected call of CreateFullSyncRequest.
|
|
||||||
func (mr *MockSyncClientMockRecorder) CreateFullSyncRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncRequest", reflect.TypeOf((*MockSyncClient)(nil).CreateFullSyncRequest), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncResponse mocks base method.
|
|
||||||
func (m *MockSyncClient) CreateFullSyncResponse(arg0 objecttree.ObjectTree, arg1, arg2 []string) (*treechangeproto.TreeSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateFullSyncResponse", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncResponse indicates an expected call of CreateFullSyncResponse.
|
|
||||||
func (mr *MockSyncClientMockRecorder) CreateFullSyncResponse(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncResponse", reflect.TypeOf((*MockSyncClient)(nil).CreateFullSyncResponse), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateHeadUpdate mocks base method.
|
|
||||||
func (m *MockSyncClient) CreateHeadUpdate(arg0 objecttree.ObjectTree, arg1 []*treechangeproto.RawTreeChangeWithId) *treechangeproto.TreeSyncMessage {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateHeadUpdate", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateHeadUpdate indicates an expected call of CreateHeadUpdate.
|
|
||||||
func (mr *MockSyncClientMockRecorder) CreateHeadUpdate(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateHeadUpdate", reflect.TypeOf((*MockSyncClient)(nil).CreateHeadUpdate), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateNewTreeRequest mocks base method.
|
|
||||||
func (m *MockSyncClient) CreateNewTreeRequest() *treechangeproto.TreeSyncMessage {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateNewTreeRequest")
|
|
||||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateNewTreeRequest indicates an expected call of CreateNewTreeRequest.
|
|
||||||
func (mr *MockSyncClientMockRecorder) CreateNewTreeRequest() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNewTreeRequest", reflect.TypeOf((*MockSyncClient)(nil).CreateNewTreeRequest))
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueueRequest mocks base method.
|
|
||||||
func (m *MockSyncClient) QueueRequest(arg0, arg1 string, arg2 *treechangeproto.TreeSyncMessage) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "QueueRequest", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueueRequest indicates an expected call of QueueRequest.
|
|
||||||
func (mr *MockSyncClientMockRecorder) QueueRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueRequest", reflect.TypeOf((*MockSyncClient)(nil).QueueRequest), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendRequest mocks base method.
|
|
||||||
func (m *MockSyncClient) SendRequest(arg0 context.Context, arg1, arg2 string, arg3 *treechangeproto.TreeSyncMessage) (*spacesyncproto.ObjectSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "SendRequest", arg0, arg1, arg2, arg3)
|
|
||||||
ret0, _ := ret[0].(*spacesyncproto.ObjectSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendRequest indicates an expected call of SendRequest.
|
|
||||||
func (mr *MockSyncClientMockRecorder) SendRequest(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendRequest", reflect.TypeOf((*MockSyncClient)(nil).SendRequest), arg0, arg1, arg2, arg3)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendUpdate mocks base method.
|
|
||||||
func (m *MockSyncClient) SendUpdate(arg0, arg1 string, arg2 *treechangeproto.TreeSyncMessage) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "SendUpdate", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendUpdate indicates an expected call of SendUpdate.
|
|
||||||
func (mr *MockSyncClientMockRecorder) SendUpdate(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendUpdate", reflect.TypeOf((*MockSyncClient)(nil).SendUpdate), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockRequestFactory is a mock of RequestFactory interface.
|
|
||||||
type MockRequestFactory struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockRequestFactoryMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockRequestFactoryMockRecorder is the mock recorder for MockRequestFactory.
|
|
||||||
type MockRequestFactoryMockRecorder struct {
|
|
||||||
mock *MockRequestFactory
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockRequestFactory creates a new mock instance.
|
|
||||||
func NewMockRequestFactory(ctrl *gomock.Controller) *MockRequestFactory {
|
|
||||||
mock := &MockRequestFactory{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockRequestFactoryMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockRequestFactory) EXPECT() *MockRequestFactoryMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncRequest mocks base method.
|
|
||||||
func (m *MockRequestFactory) CreateFullSyncRequest(arg0 objecttree.ObjectTree, arg1, arg2 []string) (*treechangeproto.TreeSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateFullSyncRequest", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncRequest indicates an expected call of CreateFullSyncRequest.
|
|
||||||
func (mr *MockRequestFactoryMockRecorder) CreateFullSyncRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncRequest", reflect.TypeOf((*MockRequestFactory)(nil).CreateFullSyncRequest), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncResponse mocks base method.
|
|
||||||
func (m *MockRequestFactory) CreateFullSyncResponse(arg0 objecttree.ObjectTree, arg1, arg2 []string) (*treechangeproto.TreeSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateFullSyncResponse", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFullSyncResponse indicates an expected call of CreateFullSyncResponse.
|
|
||||||
func (mr *MockRequestFactoryMockRecorder) CreateFullSyncResponse(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncResponse", reflect.TypeOf((*MockRequestFactory)(nil).CreateFullSyncResponse), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateHeadUpdate mocks base method.
|
|
||||||
func (m *MockRequestFactory) CreateHeadUpdate(arg0 objecttree.ObjectTree, arg1 []*treechangeproto.RawTreeChangeWithId) *treechangeproto.TreeSyncMessage {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateHeadUpdate", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateHeadUpdate indicates an expected call of CreateHeadUpdate.
|
|
||||||
func (mr *MockRequestFactoryMockRecorder) CreateHeadUpdate(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateHeadUpdate", reflect.TypeOf((*MockRequestFactory)(nil).CreateHeadUpdate), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateNewTreeRequest mocks base method.
|
|
||||||
func (m *MockRequestFactory) CreateNewTreeRequest() *treechangeproto.TreeSyncMessage {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "CreateNewTreeRequest")
|
|
||||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateNewTreeRequest indicates an expected call of CreateNewTreeRequest.
|
|
||||||
func (mr *MockRequestFactoryMockRecorder) CreateNewTreeRequest() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNewTreeRequest", reflect.TypeOf((*MockRequestFactory)(nil).CreateNewTreeRequest))
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockTreeSyncProtocol is a mock of TreeSyncProtocol interface.
|
|
||||||
type MockTreeSyncProtocol struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockTreeSyncProtocolMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockTreeSyncProtocolMockRecorder is the mock recorder for MockTreeSyncProtocol.
|
|
||||||
type MockTreeSyncProtocolMockRecorder struct {
|
|
||||||
mock *MockTreeSyncProtocol
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockTreeSyncProtocol creates a new mock instance.
|
|
||||||
func NewMockTreeSyncProtocol(ctrl *gomock.Controller) *MockTreeSyncProtocol {
|
|
||||||
mock := &MockTreeSyncProtocol{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockTreeSyncProtocolMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockTreeSyncProtocol) EXPECT() *MockTreeSyncProtocolMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// FullSyncRequest mocks base method.
|
|
||||||
func (m *MockTreeSyncProtocol) FullSyncRequest(arg0 context.Context, arg1 string, arg2 *treechangeproto.TreeFullSyncRequest) (*treechangeproto.TreeSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "FullSyncRequest", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// FullSyncRequest indicates an expected call of FullSyncRequest.
|
|
||||||
func (mr *MockTreeSyncProtocolMockRecorder) FullSyncRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FullSyncRequest", reflect.TypeOf((*MockTreeSyncProtocol)(nil).FullSyncRequest), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FullSyncResponse mocks base method.
|
|
||||||
func (m *MockTreeSyncProtocol) FullSyncResponse(arg0 context.Context, arg1 string, arg2 *treechangeproto.TreeFullSyncResponse) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "FullSyncResponse", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// FullSyncResponse indicates an expected call of FullSyncResponse.
|
|
||||||
func (mr *MockTreeSyncProtocolMockRecorder) FullSyncResponse(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FullSyncResponse", reflect.TypeOf((*MockTreeSyncProtocol)(nil).FullSyncResponse), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeadUpdate mocks base method.
|
|
||||||
func (m *MockTreeSyncProtocol) HeadUpdate(arg0 context.Context, arg1 string, arg2 *treechangeproto.TreeHeadUpdate) (*treechangeproto.TreeSyncMessage, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "HeadUpdate", arg0, arg1, arg2)
|
|
||||||
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 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeadUpdate", reflect.TypeOf((*MockTreeSyncProtocol)(nil).HeadUpdate), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,362 +0,0 @@
|
|||||||
package synctree
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"math/rand"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
|
||||||
"github.com/anyproto/any-sync/util/slice"
|
|
||||||
"github.com/gogo/protobuf/proto"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEmptyClientGetsFullHistory(t *testing.T) {
|
|
||||||
treeId := "treeId"
|
|
||||||
spaceId := "spaceId"
|
|
||||||
keys, err := accountdata.NewRandom()
|
|
||||||
require.NoError(t, err)
|
|
||||||
aclList, err := list.NewTestDerivedAcl(spaceId, keys)
|
|
||||||
require.NoError(t, err)
|
|
||||||
storage := createStorage(treeId, aclList)
|
|
||||||
changeCreator := objecttree.NewMockChangeCreator()
|
|
||||||
deps := fixtureDeps{
|
|
||||||
aclList: aclList,
|
|
||||||
initStorage: storage.(*treestorage.InMemoryTreeStorage),
|
|
||||||
connectionMap: map[string][]string{
|
|
||||||
"peer1": []string{"peer2"},
|
|
||||||
"peer2": []string{"peer1"},
|
|
||||||
},
|
|
||||||
emptyTrees: []string{"peer2"},
|
|
||||||
treeBuilder: objecttree.BuildTestableTree,
|
|
||||||
}
|
|
||||||
fx := newProtocolFixture(t, spaceId, deps)
|
|
||||||
fx.run(t)
|
|
||||||
fx.handlers["peer1"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
|
|
||||||
NewHeads: nil,
|
|
||||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{
|
|
||||||
changeCreator.CreateRaw("1", aclList.Id(), treeId, true, treeId),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
fx.stop()
|
|
||||||
firstHeads := fx.handlers["peer1"].tree().Heads()
|
|
||||||
secondHeads := fx.handlers["peer2"].tree().Heads()
|
|
||||||
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
|
|
||||||
require.Equal(t, []string{"1"}, firstHeads)
|
|
||||||
logMsgs := fx.log.batcher.GetAll()
|
|
||||||
|
|
||||||
var fullResponseMsg msgDescription
|
|
||||||
for _, msg := range logMsgs {
|
|
||||||
descr := msg.description()
|
|
||||||
if descr.name == "FullSyncResponse" {
|
|
||||||
fullResponseMsg = descr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// that means that we got not only the last snapshot, but also the first one
|
|
||||||
require.Len(t, fullResponseMsg.changes, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTreeFuzzyMerge(t *testing.T) {
|
|
||||||
testTreeFuzzyMerge(t, true)
|
|
||||||
testTreeFuzzyMerge(t, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testTreeFuzzyMerge(t *testing.T, withData bool) {
|
|
||||||
var (
|
|
||||||
rnd = rand.New(rand.NewSource(time.Now().Unix()))
|
|
||||||
levels = 20
|
|
||||||
perLevel = 20
|
|
||||||
rounds = 10
|
|
||||||
)
|
|
||||||
for i := 0; i < rounds; i++ {
|
|
||||||
testTreeMerge(t, levels, perLevel, withData, func() bool {
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
testTreeMerge(t, levels, perLevel, withData, func() bool {
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
testTreeMerge(t, levels, perLevel, withData, func() bool {
|
|
||||||
return rnd.Intn(10) > 8
|
|
||||||
})
|
|
||||||
levels += 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testTreeMerge(t *testing.T, levels, perLevel int, hasData bool, isSnapshot func() bool) {
|
|
||||||
treeId := "treeId"
|
|
||||||
spaceId := "spaceId"
|
|
||||||
keys, err := accountdata.NewRandom()
|
|
||||||
require.NoError(t, err)
|
|
||||||
aclList, err := list.NewTestDerivedAcl(spaceId, keys)
|
|
||||||
storage := createStorage(treeId, aclList)
|
|
||||||
changeCreator := objecttree.NewMockChangeCreator()
|
|
||||||
builder := objecttree.BuildTestableTree
|
|
||||||
if hasData {
|
|
||||||
builder = objecttree.BuildEmptyDataTestableTree
|
|
||||||
}
|
|
||||||
params := genParams{
|
|
||||||
prefix: "peer1",
|
|
||||||
aclId: aclList.Id(),
|
|
||||||
startIdx: 0,
|
|
||||||
levels: levels,
|
|
||||||
perLevel: perLevel,
|
|
||||||
snapshotId: treeId,
|
|
||||||
prevHeads: []string{treeId},
|
|
||||||
isSnapshot: isSnapshot,
|
|
||||||
hasData: hasData,
|
|
||||||
}
|
|
||||||
// generating initial tree
|
|
||||||
initialRes := genChanges(changeCreator, params)
|
|
||||||
err = storage.AddRawChangesSetHeads(initialRes.changes, initialRes.heads)
|
|
||||||
require.NoError(t, err)
|
|
||||||
deps := fixtureDeps{
|
|
||||||
aclList: aclList,
|
|
||||||
initStorage: storage.(*treestorage.InMemoryTreeStorage),
|
|
||||||
connectionMap: map[string][]string{
|
|
||||||
"peer1": []string{"node1"},
|
|
||||||
"peer2": []string{"node1"},
|
|
||||||
"node1": []string{"peer1", "peer2"},
|
|
||||||
},
|
|
||||||
emptyTrees: []string{"peer2", "node1"},
|
|
||||||
treeBuilder: builder,
|
|
||||||
}
|
|
||||||
fx := newProtocolFixture(t, spaceId, deps)
|
|
||||||
fx.run(t)
|
|
||||||
fx.handlers["peer1"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
|
|
||||||
NewHeads: initialRes.heads,
|
|
||||||
RawChanges: initialRes.changes,
|
|
||||||
})
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
|
||||||
firstHeads := fx.handlers["peer1"].tree().Heads()
|
|
||||||
secondHeads := fx.handlers["peer2"].tree().Heads()
|
|
||||||
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
|
|
||||||
params = genParams{
|
|
||||||
prefix: "peer1",
|
|
||||||
aclId: aclList.Id(),
|
|
||||||
startIdx: levels,
|
|
||||||
levels: levels,
|
|
||||||
perLevel: perLevel,
|
|
||||||
|
|
||||||
snapshotId: initialRes.snapshotId,
|
|
||||||
prevHeads: initialRes.heads,
|
|
||||||
isSnapshot: isSnapshot,
|
|
||||||
hasData: hasData,
|
|
||||||
}
|
|
||||||
// generating different additions to the tree for different peers
|
|
||||||
peer1Res := genChanges(changeCreator, params)
|
|
||||||
params.prefix = "peer2"
|
|
||||||
peer2Res := genChanges(changeCreator, params)
|
|
||||||
fx.handlers["peer1"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
|
|
||||||
NewHeads: peer1Res.heads,
|
|
||||||
RawChanges: peer1Res.changes,
|
|
||||||
})
|
|
||||||
fx.handlers["peer2"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
|
|
||||||
NewHeads: peer2Res.heads,
|
|
||||||
RawChanges: peer2Res.changes,
|
|
||||||
})
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
|
||||||
fx.stop()
|
|
||||||
firstTree := fx.handlers["peer1"].tree()
|
|
||||||
secondTree := fx.handlers["peer2"].tree()
|
|
||||||
firstHeads = firstTree.Heads()
|
|
||||||
secondHeads = secondTree.Heads()
|
|
||||||
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
|
|
||||||
require.True(t, slice.UnsortedEquals(firstHeads, append(peer1Res.heads, peer2Res.heads...)))
|
|
||||||
firstStorage := firstTree.Storage().(*treestorage.InMemoryTreeStorage)
|
|
||||||
secondStorage := secondTree.Storage().(*treestorage.InMemoryTreeStorage)
|
|
||||||
require.True(t, firstStorage.Equal(secondStorage))
|
|
||||||
if hasData {
|
|
||||||
for _, ch := range secondStorage.Changes {
|
|
||||||
if ch.Id == treeId {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
unmarshallRaw := &treechangeproto.RawTreeChange{}
|
|
||||||
proto.Unmarshal(ch.RawChange, unmarshallRaw)
|
|
||||||
treeCh := &treechangeproto.TreeChange{}
|
|
||||||
proto.Unmarshal(unmarshallRaw.Payload, treeCh)
|
|
||||||
require.Equal(t, ch.Id, string(treeCh.ChangesData))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTreeStorageHasExtraChanges(t *testing.T) {
|
|
||||||
var (
|
|
||||||
rnd = rand.New(rand.NewSource(time.Now().Unix()))
|
|
||||||
levels = 20
|
|
||||||
perLevel = 40
|
|
||||||
)
|
|
||||||
|
|
||||||
// simulating cases where one peer has some extra changes saved in storage
|
|
||||||
// and checking that this will not break the sync
|
|
||||||
t.Run("tree storage has extra simple", func(t *testing.T) {
|
|
||||||
testTreeStorageHasExtra(t, levels, perLevel, false, func() bool {
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
testTreeStorageHasExtra(t, levels, perLevel, false, func() bool {
|
|
||||||
return rnd.Intn(10) > 5
|
|
||||||
})
|
|
||||||
testTreeStorageHasExtra(t, levels, perLevel, true, func() bool {
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
testTreeStorageHasExtra(t, levels, perLevel, true, func() bool {
|
|
||||||
return rnd.Intn(10) > 5
|
|
||||||
})
|
|
||||||
})
|
|
||||||
t.Run("tree storage has extra three parts", func(t *testing.T) {
|
|
||||||
testTreeStorageHasExtraThreeParts(t, levels, perLevel, false, func() bool {
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
testTreeStorageHasExtraThreeParts(t, levels, perLevel, false, func() bool {
|
|
||||||
return rnd.Intn(10) > 5
|
|
||||||
})
|
|
||||||
testTreeStorageHasExtraThreeParts(t, levels, perLevel, true, func() bool {
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
testTreeStorageHasExtraThreeParts(t, levels, perLevel, true, func() bool {
|
|
||||||
return rnd.Intn(10) > 5
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func testTreeStorageHasExtra(t *testing.T, levels, perLevel int, hasData bool, isSnapshot func() bool) {
|
|
||||||
treeId := "treeId"
|
|
||||||
spaceId := "spaceId"
|
|
||||||
keys, err := accountdata.NewRandom()
|
|
||||||
require.NoError(t, err)
|
|
||||||
aclList, err := list.NewTestDerivedAcl(spaceId, keys)
|
|
||||||
storage := createStorage(treeId, aclList)
|
|
||||||
changeCreator := objecttree.NewMockChangeCreator()
|
|
||||||
builder := objecttree.BuildTestableTree
|
|
||||||
if hasData {
|
|
||||||
builder = objecttree.BuildEmptyDataTestableTree
|
|
||||||
}
|
|
||||||
params := genParams{
|
|
||||||
prefix: "peer1",
|
|
||||||
aclId: aclList.Id(),
|
|
||||||
startIdx: 0,
|
|
||||||
levels: levels,
|
|
||||||
perLevel: perLevel,
|
|
||||||
snapshotId: treeId,
|
|
||||||
prevHeads: []string{treeId},
|
|
||||||
isSnapshot: isSnapshot,
|
|
||||||
hasData: hasData,
|
|
||||||
}
|
|
||||||
deps := fixtureDeps{
|
|
||||||
aclList: aclList,
|
|
||||||
initStorage: storage.(*treestorage.InMemoryTreeStorage),
|
|
||||||
connectionMap: map[string][]string{
|
|
||||||
"peer1": []string{"peer2"},
|
|
||||||
"peer2": []string{"peer1"},
|
|
||||||
},
|
|
||||||
treeBuilder: builder,
|
|
||||||
}
|
|
||||||
fx := newProtocolFixture(t, spaceId, deps)
|
|
||||||
|
|
||||||
// generating initial tree
|
|
||||||
initialRes := genChanges(changeCreator, params)
|
|
||||||
fx.run(t)
|
|
||||||
|
|
||||||
// adding some changes to store, but without updating heads
|
|
||||||
store := fx.handlers["peer1"].tree().Storage().(*treestorage.InMemoryTreeStorage)
|
|
||||||
oldHeads, _ := store.Heads()
|
|
||||||
store.AddRawChangesSetHeads(initialRes.changes, oldHeads)
|
|
||||||
|
|
||||||
// sending those changes to other peer
|
|
||||||
fx.handlers["peer2"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
|
|
||||||
NewHeads: initialRes.heads,
|
|
||||||
RawChanges: initialRes.changes,
|
|
||||||
})
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
|
||||||
|
|
||||||
// here we want that the saved changes in storage should not affect the sync protocol
|
|
||||||
firstHeads := fx.handlers["peer1"].tree().Heads()
|
|
||||||
secondHeads := fx.handlers["peer2"].tree().Heads()
|
|
||||||
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
|
|
||||||
require.True(t, slice.UnsortedEquals(initialRes.heads, firstHeads))
|
|
||||||
}
|
|
||||||
|
|
||||||
func testTreeStorageHasExtraThreeParts(t *testing.T, levels, perLevel int, hasData bool, isSnapshot func() bool) {
|
|
||||||
treeId := "treeId"
|
|
||||||
spaceId := "spaceId"
|
|
||||||
keys, err := accountdata.NewRandom()
|
|
||||||
require.NoError(t, err)
|
|
||||||
aclList, err := list.NewTestDerivedAcl(spaceId, keys)
|
|
||||||
storage := createStorage(treeId, aclList)
|
|
||||||
changeCreator := objecttree.NewMockChangeCreator()
|
|
||||||
builder := objecttree.BuildTestableTree
|
|
||||||
if hasData {
|
|
||||||
builder = objecttree.BuildEmptyDataTestableTree
|
|
||||||
}
|
|
||||||
params := genParams{
|
|
||||||
prefix: "peer1",
|
|
||||||
aclId: aclList.Id(),
|
|
||||||
startIdx: 0,
|
|
||||||
levels: levels,
|
|
||||||
perLevel: perLevel,
|
|
||||||
snapshotId: treeId,
|
|
||||||
prevHeads: []string{treeId},
|
|
||||||
isSnapshot: isSnapshot,
|
|
||||||
hasData: hasData,
|
|
||||||
}
|
|
||||||
deps := fixtureDeps{
|
|
||||||
aclList: aclList,
|
|
||||||
initStorage: storage.(*treestorage.InMemoryTreeStorage),
|
|
||||||
connectionMap: map[string][]string{
|
|
||||||
"peer1": []string{"peer2"},
|
|
||||||
"peer2": []string{"peer1"},
|
|
||||||
},
|
|
||||||
treeBuilder: builder,
|
|
||||||
}
|
|
||||||
fx := newProtocolFixture(t, spaceId, deps)
|
|
||||||
|
|
||||||
// generating parts
|
|
||||||
firstPart := genChanges(changeCreator, params)
|
|
||||||
params.startIdx = levels
|
|
||||||
params.snapshotId = firstPart.snapshotId
|
|
||||||
params.prevHeads = firstPart.heads
|
|
||||||
secondPart := genChanges(changeCreator, params)
|
|
||||||
params.startIdx = levels * 2
|
|
||||||
params.snapshotId = secondPart.snapshotId
|
|
||||||
params.prevHeads = secondPart.heads
|
|
||||||
thirdPart := genChanges(changeCreator, params)
|
|
||||||
|
|
||||||
// adding part1 to first peer and saving part2 and part3 in its storage
|
|
||||||
res, _ := fx.handlers["peer1"].tree().AddRawChanges(context.Background(), objecttree.RawChangesPayload{
|
|
||||||
NewHeads: firstPart.heads,
|
|
||||||
RawChanges: firstPart.changes,
|
|
||||||
})
|
|
||||||
require.True(t, slice.UnsortedEquals(res.Heads, firstPart.heads))
|
|
||||||
store := fx.handlers["peer1"].tree().Storage().(*treestorage.InMemoryTreeStorage)
|
|
||||||
oldHeads, _ := store.Heads()
|
|
||||||
store.AddRawChangesSetHeads(secondPart.changes, oldHeads)
|
|
||||||
store.AddRawChangesSetHeads(thirdPart.changes, oldHeads)
|
|
||||||
|
|
||||||
var peer2Initial []*treechangeproto.RawTreeChangeWithId
|
|
||||||
peer2Initial = append(peer2Initial, firstPart.changes...)
|
|
||||||
peer2Initial = append(peer2Initial, secondPart.changes...)
|
|
||||||
|
|
||||||
// adding part1 and part2 to second peer
|
|
||||||
res, _ = fx.handlers["peer2"].tree().AddRawChanges(context.Background(), objecttree.RawChangesPayload{
|
|
||||||
NewHeads: secondPart.heads,
|
|
||||||
RawChanges: peer2Initial,
|
|
||||||
})
|
|
||||||
require.True(t, slice.UnsortedEquals(res.Heads, secondPart.heads))
|
|
||||||
fx.run(t)
|
|
||||||
|
|
||||||
// sending part3 changes to other peer
|
|
||||||
fx.handlers["peer2"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
|
|
||||||
NewHeads: thirdPart.heads,
|
|
||||||
RawChanges: thirdPart.changes,
|
|
||||||
})
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
|
||||||
firstHeads := fx.handlers["peer1"].tree().Heads()
|
|
||||||
secondHeads := fx.handlers["peer2"].tree().Heads()
|
|
||||||
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
|
|
||||||
require.True(t, slice.UnsortedEquals(thirdPart.heads, firstHeads))
|
|
||||||
}
|
|
||||||
@ -2,7 +2,7 @@ package synctree
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -2,9 +2,9 @@ package synctree
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/util/slice"
|
"github.com/anytypeio/any-sync/util/slice"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RequestFactory interface {
|
type RequestFactory interface {
|
||||||
@ -14,8 +14,10 @@ type RequestFactory interface {
|
|||||||
CreateFullSyncResponse(t objecttree.ObjectTree, theirHeads, theirSnapshotPath []string) (*treechangeproto.TreeSyncMessage, error)
|
CreateFullSyncResponse(t objecttree.ObjectTree, theirHeads, theirSnapshotPath []string) (*treechangeproto.TreeSyncMessage, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRequestFactory() RequestFactory {
|
var sharedFactory = &requestFactory{}
|
||||||
return &requestFactory{}
|
|
||||||
|
func GetRequestFactory() RequestFactory {
|
||||||
|
return sharedFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
type requestFactory struct{}
|
type requestFactory struct{}
|
||||||
|
|||||||
@ -1,70 +1,66 @@
|
|||||||
|
//go:generate mockgen -destination mock_synctree/mock_synctree.go github.com/anytypeio/any-sync/commonspace/object/tree/synctree SyncClient,SyncTree,ReceiveQueue,HeadNotifiable
|
||||||
package synctree
|
package synctree
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anytypeio/any-sync/commonspace/objectsync"
|
||||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/requestmanager"
|
"github.com/anytypeio/any-sync/nodeconf"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SyncClient interface {
|
type SyncClient interface {
|
||||||
RequestFactory
|
RequestFactory
|
||||||
Broadcast(msg *treechangeproto.TreeSyncMessage)
|
Broadcast(ctx context.Context, msg *treechangeproto.TreeSyncMessage) (err error)
|
||||||
SendUpdate(peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (err error)
|
SendWithReply(ctx context.Context, peerId string, msg *treechangeproto.TreeSyncMessage, replyId string) (err error)
|
||||||
QueueRequest(peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (err error)
|
|
||||||
SendRequest(ctx context.Context, peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type syncClient struct {
|
type syncClient struct {
|
||||||
|
objectsync.MessagePool
|
||||||
RequestFactory
|
RequestFactory
|
||||||
spaceId string
|
spaceId string
|
||||||
requestManager requestmanager.RequestManager
|
configuration nodeconf.Configuration
|
||||||
peerManager peermanager.PeerManager
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSyncClient(spaceId string, requestManager requestmanager.RequestManager, peerManager peermanager.PeerManager) SyncClient {
|
func newSyncClient(
|
||||||
|
spaceId string,
|
||||||
|
pool objectsync.MessagePool,
|
||||||
|
factory RequestFactory,
|
||||||
|
configuration nodeconf.Configuration) SyncClient {
|
||||||
return &syncClient{
|
return &syncClient{
|
||||||
RequestFactory: &requestFactory{},
|
MessagePool: pool,
|
||||||
|
RequestFactory: factory,
|
||||||
|
configuration: configuration,
|
||||||
spaceId: spaceId,
|
spaceId: spaceId,
|
||||||
requestManager: requestManager,
|
|
||||||
peerManager: peerManager,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *syncClient) Broadcast(msg *treechangeproto.TreeSyncMessage) {
|
func (s *syncClient) Broadcast(ctx context.Context, msg *treechangeproto.TreeSyncMessage) (err error) {
|
||||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, msg.RootChange.Id)
|
objMsg, err := marshallTreeMessage(msg, s.spaceId, msg.RootChange.Id, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = s.peerManager.Broadcast(context.Background(), objMsg)
|
return s.MessagePool.Broadcast(ctx, objMsg)
|
||||||
if err != nil {
|
|
||||||
log.Debug("broadcast error", zap.Error(err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *syncClient) SendUpdate(peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (err error) {
|
func (s *syncClient) SendWithReply(ctx context.Context, peerId string, msg *treechangeproto.TreeSyncMessage, replyId string) (err error) {
|
||||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, objectId)
|
objMsg, err := marshallTreeMessage(msg, s.spaceId, msg.RootChange.Id, replyId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return s.peerManager.SendPeer(context.Background(), peerId, objMsg)
|
return s.MessagePool.SendPeer(ctx, peerId, objMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *syncClient) SendRequest(ctx context.Context, peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error) {
|
func marshallTreeMessage(message *treechangeproto.TreeSyncMessage, spaceId, objectId, replyId string) (objMsg *spacesyncproto.ObjectSyncMessage, err error) {
|
||||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, objectId)
|
payload, err := message.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return s.requestManager.SendRequest(ctx, peerId, objMsg)
|
objMsg = &spacesyncproto.ObjectSyncMessage{
|
||||||
}
|
ReplyId: replyId,
|
||||||
|
Payload: payload,
|
||||||
func (s *syncClient) QueueRequest(peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (err error) {
|
ObjectId: objectId,
|
||||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, objectId)
|
SpaceId: spaceId,
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
return s.requestManager.QueueRequest(peerId, objMsg)
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
//go:generate mockgen -destination mock_synctree/mock_synctree.go github.com/anyproto/any-sync/commonspace/object/tree/synctree SyncTree,ReceiveQueue,HeadNotifiable,SyncClient,RequestFactory,TreeSyncProtocol
|
|
||||||
package synctree
|
package synctree
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -6,16 +5,17 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
"github.com/anytypeio/any-sync/app/logger"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/synctree/updatelistener"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
"github.com/anytypeio/any-sync/commonspace/objectsync"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
"github.com/anytypeio/any-sync/commonspace/objectsync/synchandler"
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
"github.com/anytypeio/any-sync/commonspace/spacestorage"
|
||||||
"github.com/anyproto/any-sync/net/peer"
|
"github.com/anytypeio/any-sync/commonspace/syncstatus"
|
||||||
"github.com/anyproto/any-sync/nodeconf"
|
"github.com/anytypeio/any-sync/net/peer"
|
||||||
|
"github.com/anytypeio/any-sync/nodeconf"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -54,35 +54,35 @@ type syncTree struct {
|
|||||||
|
|
||||||
var log = logger.NewNamed("common.commonspace.synctree")
|
var log = logger.NewNamed("common.commonspace.synctree")
|
||||||
|
|
||||||
|
var buildObjectTree = objecttree.BuildObjectTree
|
||||||
|
var createSyncClient = newSyncClient
|
||||||
|
|
||||||
type ResponsiblePeersGetter interface {
|
type ResponsiblePeersGetter interface {
|
||||||
GetResponsiblePeers(ctx context.Context) (peers []peer.Peer, err error)
|
GetResponsiblePeers(ctx context.Context) (peers []peer.Peer, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type BuildDeps struct {
|
type BuildDeps struct {
|
||||||
SpaceId string
|
SpaceId string
|
||||||
SyncClient SyncClient
|
ObjectSync objectsync.ObjectSync
|
||||||
Configuration nodeconf.NodeConf
|
Configuration nodeconf.Configuration
|
||||||
HeadNotifiable HeadNotifiable
|
HeadNotifiable HeadNotifiable
|
||||||
Listener updatelistener.UpdateListener
|
Listener updatelistener.UpdateListener
|
||||||
AclList list.AclList
|
AclList list.AclList
|
||||||
SpaceStorage spacestorage.SpaceStorage
|
SpaceStorage spacestorage.SpaceStorage
|
||||||
TreeStorage treestorage.TreeStorage
|
TreeStorage treestorage.TreeStorage
|
||||||
OnClose func(id string)
|
OnClose func(id string)
|
||||||
SyncStatus syncstatus.StatusUpdater
|
SyncStatus syncstatus.StatusUpdater
|
||||||
PeerGetter ResponsiblePeersGetter
|
PeerGetter ResponsiblePeersGetter
|
||||||
BuildObjectTree objecttree.BuildObjectTreeFunc
|
WaitTreeRemoteSync bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildSyncTreeOrGetRemote(ctx context.Context, id string, deps BuildDeps) (t SyncTree, err error) {
|
func BuildSyncTreeOrGetRemote(ctx context.Context, id string, deps BuildDeps) (t SyncTree, err error) {
|
||||||
var (
|
remoteGetter := treeRemoteGetter{treeId: id, deps: deps}
|
||||||
remoteGetter = treeRemoteGetter{treeId: id, deps: deps}
|
deps.TreeStorage, err = remoteGetter.getTree(ctx)
|
||||||
isRemote bool
|
|
||||||
)
|
|
||||||
deps.TreeStorage, isRemote, err = remoteGetter.getTree(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return buildSyncTree(ctx, isRemote, deps)
|
return buildSyncTree(ctx, true, deps)
|
||||||
}
|
}
|
||||||
|
|
||||||
func PutSyncTree(ctx context.Context, payload treestorage.TreeStorageCreatePayload, deps BuildDeps) (t SyncTree, err error) {
|
func PutSyncTree(ctx context.Context, payload treestorage.TreeStorageCreatePayload, deps BuildDeps) (t SyncTree, err error) {
|
||||||
@ -93,12 +93,16 @@ func PutSyncTree(ctx context.Context, payload treestorage.TreeStorageCreatePaylo
|
|||||||
return buildSyncTree(ctx, true, deps)
|
return buildSyncTree(ctx, true, deps)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildSyncTree(ctx context.Context, sendUpdate bool, deps BuildDeps) (t SyncTree, err error) {
|
func buildSyncTree(ctx context.Context, isFirstBuild bool, deps BuildDeps) (t SyncTree, err error) {
|
||||||
objTree, err := deps.BuildObjectTree(deps.TreeStorage, deps.AclList)
|
objTree, err := buildObjectTree(deps.TreeStorage, deps.AclList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
syncClient := deps.SyncClient
|
syncClient := createSyncClient(
|
||||||
|
deps.SpaceId,
|
||||||
|
deps.ObjectSync.MessagePool(),
|
||||||
|
sharedFactory,
|
||||||
|
deps.Configuration)
|
||||||
syncTree := &syncTree{
|
syncTree := &syncTree{
|
||||||
ObjectTree: objTree,
|
ObjectTree: objTree,
|
||||||
syncClient: syncClient,
|
syncClient: syncClient,
|
||||||
@ -114,10 +118,12 @@ func buildSyncTree(ctx context.Context, sendUpdate bool, deps BuildDeps) (t Sync
|
|||||||
syncTree.afterBuild()
|
syncTree.afterBuild()
|
||||||
syncTree.Unlock()
|
syncTree.Unlock()
|
||||||
|
|
||||||
if sendUpdate {
|
if isFirstBuild {
|
||||||
headUpdate := syncTree.syncClient.CreateHeadUpdate(t, nil)
|
headUpdate := syncTree.syncClient.CreateHeadUpdate(t, nil)
|
||||||
// send to everybody, because everybody should know that the node or client got new tree
|
// send to everybody, because everybody should know that the node or client got new tree
|
||||||
syncTree.syncClient.Broadcast(headUpdate)
|
if e := syncTree.syncClient.Broadcast(ctx, headUpdate); e != nil {
|
||||||
|
log.ErrorCtx(ctx, "broadcast error", zap.Error(e))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -154,7 +160,7 @@ func (s *syncTree) AddContent(ctx context.Context, content objecttree.SignableCh
|
|||||||
}
|
}
|
||||||
s.syncStatus.HeadsChange(s.Id(), res.Heads)
|
s.syncStatus.HeadsChange(s.Id(), res.Heads)
|
||||||
headUpdate := s.syncClient.CreateHeadUpdate(s, res.Added)
|
headUpdate := s.syncClient.CreateHeadUpdate(s, res.Added)
|
||||||
s.syncClient.Broadcast(headUpdate)
|
err = s.syncClient.Broadcast(ctx, headUpdate)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,7 +187,7 @@ func (s *syncTree) AddRawChanges(ctx context.Context, changesPayload objecttree.
|
|||||||
s.notifiable.UpdateHeads(s.Id(), res.Heads)
|
s.notifiable.UpdateHeads(s.Id(), res.Heads)
|
||||||
}
|
}
|
||||||
headUpdate := s.syncClient.CreateHeadUpdate(s, res.Added)
|
headUpdate := s.syncClient.CreateHeadUpdate(s, res.Added)
|
||||||
s.syncClient.Broadcast(headUpdate)
|
err = s.syncClient.Broadcast(ctx, headUpdate)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -205,27 +211,18 @@ func (s *syncTree) Delete() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *syncTree) TryClose(objectTTL time.Duration) (bool, error) {
|
func (s *syncTree) TryClose(objectTTL time.Duration) (bool, error) {
|
||||||
if !s.TryLock() {
|
return true, s.Close()
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
log.Debug("closing sync tree", zap.String("id", s.Id()))
|
|
||||||
return true, s.close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *syncTree) Close() (err error) {
|
func (s *syncTree) Close() (err error) {
|
||||||
log.Debug("closing sync tree", zap.String("id", s.Id()))
|
log.Debug("closing sync tree", zap.String("id", s.Id()))
|
||||||
s.Lock()
|
|
||||||
return s.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncTree) close() (err error) {
|
|
||||||
defer s.Unlock()
|
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Debug("closed sync tree", zap.Error(err), zap.String("id", s.Id()))
|
log.Debug("closed sync tree", zap.Error(err), zap.String("id", s.Id()))
|
||||||
}()
|
}()
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
if s.isClosed {
|
if s.isClosed {
|
||||||
err = ErrSyncTreeClosed
|
return ErrSyncTreeClosed
|
||||||
return
|
|
||||||
}
|
}
|
||||||
s.onClose(s.Id())
|
s.onClose(s.Id())
|
||||||
s.isClosed = true
|
s.isClosed = true
|
||||||
@ -246,7 +243,7 @@ func (s *syncTree) SyncWithPeer(ctx context.Context, peerId string) (err error)
|
|||||||
s.Lock()
|
s.Lock()
|
||||||
defer s.Unlock()
|
defer s.Unlock()
|
||||||
headUpdate := s.syncClient.CreateHeadUpdate(s, nil)
|
headUpdate := s.syncClient.CreateHeadUpdate(s, nil)
|
||||||
return s.syncClient.SendUpdate(peerId, headUpdate.RootChange.Id, headUpdate)
|
return s.syncClient.SendWithReply(ctx, peerId, headUpdate, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *syncTree) afterBuild() {
|
func (s *syncTree) afterBuild() {
|
||||||
|
|||||||
@ -2,17 +2,17 @@ package synctree
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/mock_synctree"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/synctree/mock_synctree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/synctree/updatelistener"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener/mock_updatelistener"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/synctree/updatelistener/mock_updatelistener"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync"
|
"github.com/anytypeio/any-sync/commonspace/objectsync"
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
"github.com/anytypeio/any-sync/commonspace/syncstatus"
|
||||||
"github.com/anyproto/any-sync/nodeconf"
|
"github.com/anytypeio/any-sync/nodeconf"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -34,8 +34,8 @@ func (s syncTreeMatcher) String() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func syncClientFuncCreator(client SyncClient) func(spaceId string, factory RequestFactory, objectSync objectsync.ObjectSync, configuration nodeconf.NodeConf) SyncClient {
|
func syncClientFuncCreator(client SyncClient) func(spaceId string, factory RequestFactory, objectSync objectsync.ObjectSync, configuration nodeconf.Configuration) SyncClient {
|
||||||
return func(spaceId string, factory RequestFactory, objectSync objectsync.ObjectSync, configuration nodeconf.NodeConf) SyncClient {
|
return func(spaceId string, factory RequestFactory, objectSync objectsync.ObjectSync, configuration nodeconf.Configuration) SyncClient {
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,7 +73,7 @@ func Test_BuildSyncTree(t *testing.T) {
|
|||||||
updateListenerMock.EXPECT().Update(tr)
|
updateListenerMock.EXPECT().Update(tr)
|
||||||
|
|
||||||
syncClientMock.EXPECT().CreateHeadUpdate(gomock.Eq(tr), gomock.Eq(changes)).Return(headUpdate)
|
syncClientMock.EXPECT().CreateHeadUpdate(gomock.Eq(tr), gomock.Eq(changes)).Return(headUpdate)
|
||||||
syncClientMock.EXPECT().Broadcast(gomock.Eq(headUpdate))
|
syncClientMock.EXPECT().Broadcast(gomock.Any(), gomock.Eq(headUpdate)).Return(nil)
|
||||||
res, err := tr.AddRawChanges(ctx, payload)
|
res, err := tr.AddRawChanges(ctx, payload)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, expectedRes, res)
|
require.Equal(t, expectedRes, res)
|
||||||
@ -95,7 +95,7 @@ func Test_BuildSyncTree(t *testing.T) {
|
|||||||
updateListenerMock.EXPECT().Rebuild(tr)
|
updateListenerMock.EXPECT().Rebuild(tr)
|
||||||
|
|
||||||
syncClientMock.EXPECT().CreateHeadUpdate(gomock.Eq(tr), gomock.Eq(changes)).Return(headUpdate)
|
syncClientMock.EXPECT().CreateHeadUpdate(gomock.Eq(tr), gomock.Eq(changes)).Return(headUpdate)
|
||||||
syncClientMock.EXPECT().Broadcast(gomock.Eq(headUpdate))
|
syncClientMock.EXPECT().Broadcast(gomock.Any(), gomock.Eq(headUpdate)).Return(nil)
|
||||||
res, err := tr.AddRawChanges(ctx, payload)
|
res, err := tr.AddRawChanges(ctx, payload)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, expectedRes, res)
|
require.Equal(t, expectedRes, res)
|
||||||
@ -133,7 +133,7 @@ func Test_BuildSyncTree(t *testing.T) {
|
|||||||
Return(expectedRes, nil)
|
Return(expectedRes, nil)
|
||||||
|
|
||||||
syncClientMock.EXPECT().CreateHeadUpdate(gomock.Eq(tr), gomock.Eq(changes)).Return(headUpdate)
|
syncClientMock.EXPECT().CreateHeadUpdate(gomock.Eq(tr), gomock.Eq(changes)).Return(headUpdate)
|
||||||
syncClientMock.EXPECT().Broadcast(gomock.Eq(headUpdate))
|
syncClientMock.EXPECT().Broadcast(gomock.Any(), gomock.Eq(headUpdate)).Return(nil)
|
||||||
res, err := tr.AddContent(ctx, content)
|
res, err := tr.AddContent(ctx, content)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, expectedRes, res)
|
require.Equal(t, expectedRes, res)
|
||||||
|
|||||||
@ -2,142 +2,218 @@ package synctree
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||||
"sync"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/objectsync/synchandler"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anytypeio/any-sync/commonspace/syncstatus"
|
||||||
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
"github.com/anytypeio/any-sync/util/slice"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
|
||||||
"github.com/anyproto/any-sync/util/slice"
|
|
||||||
"github.com/gogo/protobuf/proto"
|
"github.com/gogo/protobuf/proto"
|
||||||
)
|
"go.uber.org/zap"
|
||||||
|
"sync"
|
||||||
var (
|
|
||||||
ErrMessageIsRequest = errors.New("message is request")
|
|
||||||
ErrMessageIsNotRequest = errors.New("message is not request")
|
|
||||||
ErrMoreThanOneRequest = errors.New("more than one request for same peer")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type syncTreeHandler struct {
|
type syncTreeHandler struct {
|
||||||
objTree objecttree.ObjectTree
|
objTree objecttree.ObjectTree
|
||||||
syncClient SyncClient
|
syncClient SyncClient
|
||||||
syncProtocol TreeSyncProtocol
|
syncStatus syncstatus.StatusUpdater
|
||||||
syncStatus syncstatus.StatusUpdater
|
handlerLock sync.Mutex
|
||||||
spaceId string
|
spaceId string
|
||||||
|
queue ReceiveQueue
|
||||||
handlerLock sync.Mutex
|
|
||||||
pendingRequests map[string]struct{}
|
|
||||||
heads []string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxQueueSize = 5
|
const maxQueueSize = 5
|
||||||
|
|
||||||
func newSyncTreeHandler(spaceId string, objTree objecttree.ObjectTree, syncClient SyncClient, syncStatus syncstatus.StatusUpdater) synchandler.SyncHandler {
|
func newSyncTreeHandler(spaceId string, objTree objecttree.ObjectTree, syncClient SyncClient, syncStatus syncstatus.StatusUpdater) synchandler.SyncHandler {
|
||||||
return &syncTreeHandler{
|
return &syncTreeHandler{
|
||||||
objTree: objTree,
|
objTree: objTree,
|
||||||
syncProtocol: newTreeSyncProtocol(spaceId, objTree, syncClient),
|
syncClient: syncClient,
|
||||||
syncClient: syncClient,
|
syncStatus: syncStatus,
|
||||||
syncStatus: syncStatus,
|
spaceId: spaceId,
|
||||||
spaceId: spaceId,
|
queue: newReceiveQueue(maxQueueSize),
|
||||||
pendingRequests: make(map[string]struct{}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *syncTreeHandler) HandleRequest(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (response *spacesyncproto.ObjectSyncMessage, err error) {
|
|
||||||
unmarshalled := &treechangeproto.TreeSyncMessage{}
|
|
||||||
err = proto.Unmarshal(request.Payload, unmarshalled)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fullSyncRequest := unmarshalled.GetContent().GetFullSyncRequest()
|
|
||||||
if fullSyncRequest == nil {
|
|
||||||
return nil, ErrMessageIsNotRequest
|
|
||||||
}
|
|
||||||
// setting pending requests
|
|
||||||
s.handlerLock.Lock()
|
|
||||||
_, exists := s.pendingRequests[senderId]
|
|
||||||
if exists {
|
|
||||||
s.handlerLock.Unlock()
|
|
||||||
return nil, ErrMoreThanOneRequest
|
|
||||||
}
|
|
||||||
s.pendingRequests[senderId] = struct{}{}
|
|
||||||
s.handlerLock.Unlock()
|
|
||||||
|
|
||||||
response, err = s.handleRequest(ctx, senderId, fullSyncRequest)
|
|
||||||
|
|
||||||
// removing pending requests
|
|
||||||
s.handlerLock.Lock()
|
|
||||||
delete(s.pendingRequests, senderId)
|
|
||||||
s.handlerLock.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncTreeHandler) handleRequest(ctx context.Context, senderId string, fullSyncRequest *treechangeproto.TreeFullSyncRequest) (response *spacesyncproto.ObjectSyncMessage, err error) {
|
|
||||||
s.objTree.Lock()
|
|
||||||
defer s.objTree.Unlock()
|
|
||||||
treeResp, err := s.syncProtocol.FullSyncRequest(ctx, senderId, fullSyncRequest)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
response, err = spacesyncproto.MarshallSyncMessage(treeResp, s.spaceId, s.objTree.Id())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *syncTreeHandler) HandleMessage(ctx context.Context, senderId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
func (s *syncTreeHandler) HandleMessage(ctx context.Context, senderId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||||
unmarshalled := &treechangeproto.TreeSyncMessage{}
|
unmarshalled := &treechangeproto.TreeSyncMessage{}
|
||||||
err = proto.Unmarshal(msg.Payload, unmarshalled)
|
err = proto.Unmarshal(msg.Payload, unmarshalled)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
heads := treechangeproto.GetHeads(unmarshalled)
|
s.syncStatus.HeadsReceive(senderId, msg.ObjectId, treechangeproto.GetHeads(unmarshalled))
|
||||||
s.syncStatus.HeadsReceive(senderId, msg.ObjectId, heads)
|
|
||||||
s.handlerLock.Lock()
|
queueFull := s.queue.AddMessage(senderId, unmarshalled, msg.RequestId)
|
||||||
// if the update has same heads then returning not to hang on a lock
|
if queueFull {
|
||||||
if unmarshalled.GetContent().GetHeadUpdate() != nil && slice.UnsortedEquals(heads, s.heads) {
|
|
||||||
s.handlerLock.Unlock()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.handlerLock.Unlock()
|
|
||||||
return s.handleMessage(ctx, unmarshalled, senderId)
|
return s.handleMessage(ctx, senderId)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *syncTreeHandler) handleMessage(ctx context.Context, msg *treechangeproto.TreeSyncMessage, senderId string) (err error) {
|
func (s *syncTreeHandler) handleMessage(ctx context.Context, senderId string) (err error) {
|
||||||
s.objTree.Lock()
|
s.objTree.Lock()
|
||||||
defer s.objTree.Unlock()
|
defer s.objTree.Unlock()
|
||||||
|
msg, replyId, err := s.queue.GetMessage(senderId)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer s.queue.ClearQueue(senderId)
|
||||||
|
|
||||||
|
content := msg.GetContent()
|
||||||
|
switch {
|
||||||
|
case content.GetHeadUpdate() != nil:
|
||||||
|
return s.handleHeadUpdate(ctx, senderId, content.GetHeadUpdate(), replyId)
|
||||||
|
case content.GetFullSyncRequest() != nil:
|
||||||
|
return s.handleFullSyncRequest(ctx, senderId, content.GetFullSyncRequest(), replyId)
|
||||||
|
case content.GetFullSyncResponse() != nil:
|
||||||
|
return s.handleFullSyncResponse(ctx, senderId, content.GetFullSyncResponse())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncTreeHandler) handleHeadUpdate(
|
||||||
|
ctx context.Context,
|
||||||
|
senderId string,
|
||||||
|
update *treechangeproto.TreeHeadUpdate,
|
||||||
|
replyId string) (err error) {
|
||||||
var (
|
var (
|
||||||
copyHeads = make([]string, 0, len(s.objTree.Heads()))
|
fullRequest *treechangeproto.TreeSyncMessage
|
||||||
treeId = s.objTree.Id()
|
isEmptyUpdate = len(update.Changes) == 0
|
||||||
content = msg.GetContent()
|
objTree = s.objTree
|
||||||
)
|
)
|
||||||
|
|
||||||
// getting old heads
|
log := log.With(zap.Strings("heads", objTree.Heads()), zap.String("treeId", objTree.Id()), zap.String("spaceId", s.spaceId))
|
||||||
copyHeads = append(copyHeads, s.objTree.Heads()...)
|
log.DebugCtx(ctx, "received head update message")
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
// checking if something changed
|
if err != nil {
|
||||||
if !slice.UnsortedEquals(copyHeads, s.objTree.Heads()) {
|
log.With(zap.Error(err)).Debug("head update finished with error")
|
||||||
s.handlerLock.Lock()
|
} else if fullRequest != nil {
|
||||||
defer s.handlerLock.Unlock()
|
log.DebugCtx(ctx, "sending full sync request")
|
||||||
s.heads = s.heads[:0]
|
} else {
|
||||||
for _, h := range s.objTree.Heads() {
|
if !isEmptyUpdate {
|
||||||
s.heads = append(s.heads, h)
|
log.DebugCtx(ctx, "head update finished correctly")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
switch {
|
// isEmptyUpdate is sent when the tree is brought up from cache
|
||||||
case content.GetHeadUpdate() != nil:
|
if isEmptyUpdate {
|
||||||
var syncReq *treechangeproto.TreeSyncMessage
|
headEquals := slice.UnsortedEquals(objTree.Heads(), update.Heads)
|
||||||
syncReq, err = s.syncProtocol.HeadUpdate(ctx, senderId, content.GetHeadUpdate())
|
log.DebugCtx(ctx, "is empty update", zap.String("treeId", objTree.Id()), zap.Bool("headEquals", headEquals))
|
||||||
if err != nil || syncReq == nil {
|
if headEquals {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return s.syncClient.QueueRequest(senderId, treeId, syncReq)
|
|
||||||
case content.GetFullSyncRequest() != nil:
|
// we need to sync in any case
|
||||||
return ErrMessageIsRequest
|
fullRequest, err = s.syncClient.CreateFullSyncRequest(objTree, update.Heads, update.SnapshotPath)
|
||||||
case content.GetFullSyncResponse() != nil:
|
if err != nil {
|
||||||
return s.syncProtocol.FullSyncResponse(ctx, senderId, content.GetFullSyncResponse())
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.syncClient.SendWithReply(ctx, senderId, fullRequest, replyId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.alreadyHasHeads(objTree, update.Heads) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = objTree.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
||||||
|
NewHeads: update.Heads,
|
||||||
|
RawChanges: update.Changes,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.alreadyHasHeads(objTree, update.Heads) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fullRequest, err = s.syncClient.CreateFullSyncRequest(objTree, update.Heads, update.SnapshotPath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.syncClient.SendWithReply(ctx, senderId, fullRequest, replyId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncTreeHandler) handleFullSyncRequest(
|
||||||
|
ctx context.Context,
|
||||||
|
senderId string,
|
||||||
|
request *treechangeproto.TreeFullSyncRequest,
|
||||||
|
replyId string) (err error) {
|
||||||
|
var (
|
||||||
|
fullResponse *treechangeproto.TreeSyncMessage
|
||||||
|
header = s.objTree.Header()
|
||||||
|
objTree = s.objTree
|
||||||
|
)
|
||||||
|
|
||||||
|
log := log.With(zap.String("senderId", senderId),
|
||||||
|
zap.Strings("heads", request.Heads),
|
||||||
|
zap.String("treeId", s.objTree.Id()),
|
||||||
|
zap.String("replyId", replyId),
|
||||||
|
zap.String("spaceId", s.spaceId))
|
||||||
|
log.DebugCtx(ctx, "received full sync request message")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
log.With(zap.Error(err)).DebugCtx(ctx, "full sync request finished with error")
|
||||||
|
|
||||||
|
s.syncClient.SendWithReply(ctx, senderId, treechangeproto.WrapError(err, header), replyId)
|
||||||
|
return
|
||||||
|
} else if fullResponse != nil {
|
||||||
|
log.DebugCtx(ctx, "full sync response sent")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if len(request.Changes) != 0 && !s.alreadyHasHeads(objTree, request.Heads) {
|
||||||
|
_, err = objTree.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
||||||
|
NewHeads: request.Heads,
|
||||||
|
RawChanges: request.Changes,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fullResponse, err = s.syncClient.CreateFullSyncResponse(objTree, request.Heads, request.SnapshotPath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.syncClient.SendWithReply(ctx, senderId, fullResponse, replyId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncTreeHandler) handleFullSyncResponse(
|
||||||
|
ctx context.Context,
|
||||||
|
senderId string,
|
||||||
|
response *treechangeproto.TreeFullSyncResponse) (err error) {
|
||||||
|
var (
|
||||||
|
objTree = s.objTree
|
||||||
|
)
|
||||||
|
log := log.With(zap.Strings("heads", response.Heads), zap.String("treeId", s.objTree.Id()), zap.String("spaceId", s.spaceId))
|
||||||
|
log.DebugCtx(ctx, "received full sync response message")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
log.With(zap.Error(err)).DebugCtx(ctx, "full sync response failed")
|
||||||
|
} else {
|
||||||
|
log.DebugCtx(ctx, "full sync response succeeded")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if s.alreadyHasHeads(objTree, response.Heads) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = objTree.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
||||||
|
NewHeads: response.Heads,
|
||||||
|
RawChanges: response.Changes,
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *syncTreeHandler) alreadyHasHeads(t objecttree.ObjectTree, heads []string) bool {
|
||||||
|
return slice.UnsortedEquals(t.Heads(), heads) || t.HasChanges(heads...)
|
||||||
|
}
|
||||||
|
|||||||
@ -2,16 +2,19 @@ package synctree
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
|
"github.com/anytypeio/any-sync/app/logger"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/mock_synctree"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/synctree/mock_synctree"
|
||||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/syncstatus"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.uber.org/mock/gomock"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testObjTreeMock struct {
|
type testObjTreeMock struct {
|
||||||
@ -53,38 +56,29 @@ type syncHandlerFixture struct {
|
|||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
syncClientMock *mock_synctree.MockSyncClient
|
syncClientMock *mock_synctree.MockSyncClient
|
||||||
objectTreeMock *testObjTreeMock
|
objectTreeMock *testObjTreeMock
|
||||||
syncProtocolMock *mock_synctree.MockTreeSyncProtocol
|
receiveQueueMock ReceiveQueue
|
||||||
spaceId string
|
|
||||||
senderId string
|
|
||||||
treeId string
|
|
||||||
|
|
||||||
syncHandler *syncTreeHandler
|
syncHandler *syncTreeHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSyncHandlerFixture(t *testing.T) *syncHandlerFixture {
|
func newSyncHandlerFixture(t *testing.T) *syncHandlerFixture {
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
objectTreeMock := newTestObjMock(mock_objecttree.NewMockObjectTree(ctrl))
|
|
||||||
syncClientMock := mock_synctree.NewMockSyncClient(ctrl)
|
syncClientMock := mock_synctree.NewMockSyncClient(ctrl)
|
||||||
syncProtocolMock := mock_synctree.NewMockTreeSyncProtocol(ctrl)
|
objectTreeMock := newTestObjMock(mock_objecttree.NewMockObjectTree(ctrl))
|
||||||
spaceId := "spaceId"
|
receiveQueue := newReceiveQueue(5)
|
||||||
|
|
||||||
syncHandler := &syncTreeHandler{
|
syncHandler := &syncTreeHandler{
|
||||||
objTree: objectTreeMock,
|
objTree: objectTreeMock,
|
||||||
syncClient: syncClientMock,
|
syncClient: syncClientMock,
|
||||||
syncProtocol: syncProtocolMock,
|
queue: receiveQueue,
|
||||||
spaceId: spaceId,
|
syncStatus: syncstatus.NewNoOpSyncStatus(),
|
||||||
syncStatus: syncstatus.NewNoOpSyncStatus(),
|
|
||||||
pendingRequests: map[string]struct{}{},
|
|
||||||
}
|
}
|
||||||
return &syncHandlerFixture{
|
return &syncHandlerFixture{
|
||||||
ctrl: ctrl,
|
ctrl: ctrl,
|
||||||
objectTreeMock: objectTreeMock,
|
|
||||||
syncProtocolMock: syncProtocolMock,
|
|
||||||
syncClientMock: syncClientMock,
|
syncClientMock: syncClientMock,
|
||||||
|
objectTreeMock: objectTreeMock,
|
||||||
|
receiveQueueMock: receiveQueue,
|
||||||
syncHandler: syncHandler,
|
syncHandler: syncHandler,
|
||||||
spaceId: spaceId,
|
|
||||||
senderId: "senderId",
|
|
||||||
treeId: "treeId",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,149 +86,333 @@ func (fx *syncHandlerFixture) stop() {
|
|||||||
fx.ctrl.Finish()
|
fx.ctrl.Finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSyncTreeHandler_HandleMessage(t *testing.T) {
|
func TestSyncHandler_HandleHeadUpdate(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
log = logger.CtxLogger{Logger: zap.NewNop()}
|
||||||
|
|
||||||
t.Run("handle head update message, heads not equal, request returned", func(t *testing.T) {
|
t.Run("head update non empty all heads added", func(t *testing.T) {
|
||||||
fx := newSyncHandlerFixture(t)
|
fx := newSyncHandlerFixture(t)
|
||||||
defer fx.stop()
|
defer fx.stop()
|
||||||
treeId := "treeId"
|
treeId := "treeId"
|
||||||
|
senderId := "senderId"
|
||||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||||
Heads: []string{"h3"},
|
Heads: []string{"h1"},
|
||||||
|
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||||
|
SnapshotPath: []string{"h1"},
|
||||||
}
|
}
|
||||||
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(treeMsg, "spaceId", treeId)
|
objectMsg, _ := marshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||||
|
|
||||||
syncReq := &treechangeproto.TreeSyncMessage{}
|
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||||
fx.syncHandler.heads = []string{"h2"}
|
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).Times(2)
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(false)
|
||||||
fx.objectTreeMock.EXPECT().Heads().Times(2).Return([]string{"h2"})
|
fx.objectTreeMock.EXPECT().
|
||||||
fx.objectTreeMock.EXPECT().Heads().Times(2).Return([]string{"h3"})
|
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
||||||
fx.syncProtocolMock.EXPECT().HeadUpdate(ctx, fx.senderId, gomock.Any()).Return(syncReq, nil)
|
NewHeads: []string{"h1"},
|
||||||
fx.syncClientMock.EXPECT().QueueRequest(fx.senderId, fx.treeId, syncReq).Return(nil)
|
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||||
|
})).
|
||||||
|
Return(objecttree.AddResult{}, nil)
|
||||||
|
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2", "h1"})
|
||||||
|
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(true)
|
||||||
|
|
||||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, []string{"h3"}, fx.syncHandler.heads)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("handle head update message, heads equal", func(t *testing.T) {
|
|
||||||
fx := newSyncHandlerFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
treeId := "treeId"
|
|
||||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
|
||||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
|
||||||
Heads: []string{"h1"},
|
|
||||||
}
|
|
||||||
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
|
||||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(treeMsg, "spaceId", treeId)
|
|
||||||
|
|
||||||
fx.syncHandler.heads = []string{"h1"}
|
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
|
||||||
|
|
||||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("handle head update message, no sync request returned", func(t *testing.T) {
|
t.Run("head update non empty heads not added", func(t *testing.T) {
|
||||||
fx := newSyncHandlerFixture(t)
|
fx := newSyncHandlerFixture(t)
|
||||||
defer fx.stop()
|
defer fx.stop()
|
||||||
treeId := "treeId"
|
treeId := "treeId"
|
||||||
|
senderId := "senderId"
|
||||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||||
Heads: []string{"h3"},
|
Heads: []string{"h1"},
|
||||||
|
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||||
|
SnapshotPath: []string{"h1"},
|
||||||
}
|
}
|
||||||
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(treeMsg, "spaceId", treeId)
|
objectMsg, _ := marshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||||
|
fullRequest := &treechangeproto.TreeSyncMessage{}
|
||||||
|
|
||||||
fx.syncHandler.heads = []string{"h2"}
|
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).AnyTimes()
|
||||||
fx.objectTreeMock.EXPECT().Heads().Times(2).Return([]string{"h2"})
|
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(false)
|
||||||
fx.objectTreeMock.EXPECT().Heads().Times(2).Return([]string{"h3"})
|
fx.objectTreeMock.EXPECT().
|
||||||
fx.syncProtocolMock.EXPECT().HeadUpdate(ctx, fx.senderId, gomock.Any()).Return(nil, nil)
|
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
||||||
|
NewHeads: []string{"h1"},
|
||||||
|
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||||
|
})).
|
||||||
|
Return(objecttree.AddResult{}, nil)
|
||||||
|
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(false)
|
||||||
|
fx.syncClientMock.EXPECT().
|
||||||
|
CreateFullSyncRequest(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
||||||
|
Return(fullRequest, nil)
|
||||||
|
fx.syncClientMock.EXPECT().SendWithReply(gomock.Any(), gomock.Eq(senderId), gomock.Eq(fullRequest), gomock.Eq(""))
|
||||||
|
|
||||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, []string{"h3"}, fx.syncHandler.heads)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("handle full sync request returns error", func(t *testing.T) {
|
t.Run("head update non empty equal heads", func(t *testing.T) {
|
||||||
fx := newSyncHandlerFixture(t)
|
fx := newSyncHandlerFixture(t)
|
||||||
defer fx.stop()
|
defer fx.stop()
|
||||||
treeId := "treeId"
|
treeId := "treeId"
|
||||||
|
senderId := "senderId"
|
||||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||||
fullRequest := &treechangeproto.TreeFullSyncRequest{
|
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||||
Heads: []string{"h3"},
|
Heads: []string{"h1"},
|
||||||
|
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||||
|
SnapshotPath: []string{"h1"},
|
||||||
}
|
}
|
||||||
treeMsg := treechangeproto.WrapFullRequest(fullRequest, chWithId)
|
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(treeMsg, "spaceId", treeId)
|
objectMsg, _ := marshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||||
|
|
||||||
fx.syncHandler.heads = []string{"h2"}
|
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h1"}).AnyTimes()
|
||||||
fx.objectTreeMock.EXPECT().Heads().Times(3).Return([]string{"h2"})
|
|
||||||
|
|
||||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||||
require.Equal(t, err, ErrMessageIsRequest)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("handle full sync response", func(t *testing.T) {
|
t.Run("head update empty", func(t *testing.T) {
|
||||||
fx := newSyncHandlerFixture(t)
|
fx := newSyncHandlerFixture(t)
|
||||||
defer fx.stop()
|
defer fx.stop()
|
||||||
treeId := "treeId"
|
treeId := "treeId"
|
||||||
|
senderId := "senderId"
|
||||||
|
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||||
|
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||||
|
Heads: []string{"h1"},
|
||||||
|
Changes: nil,
|
||||||
|
SnapshotPath: []string{"h1"},
|
||||||
|
}
|
||||||
|
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||||
|
objectMsg, _ := marshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||||
|
fullRequest := &treechangeproto.TreeSyncMessage{}
|
||||||
|
|
||||||
|
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||||
|
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).AnyTimes()
|
||||||
|
fx.syncClientMock.EXPECT().
|
||||||
|
CreateFullSyncRequest(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
||||||
|
Return(fullRequest, nil)
|
||||||
|
fx.syncClientMock.EXPECT().SendWithReply(gomock.Any(), gomock.Eq(senderId), gomock.Eq(fullRequest), gomock.Eq(""))
|
||||||
|
|
||||||
|
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("head update empty equal heads", func(t *testing.T) {
|
||||||
|
fx := newSyncHandlerFixture(t)
|
||||||
|
defer fx.stop()
|
||||||
|
treeId := "treeId"
|
||||||
|
senderId := "senderId"
|
||||||
|
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||||
|
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||||
|
Heads: []string{"h1"},
|
||||||
|
Changes: nil,
|
||||||
|
SnapshotPath: []string{"h1"},
|
||||||
|
}
|
||||||
|
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||||
|
objectMsg, _ := marshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||||
|
|
||||||
|
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||||
|
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h1"}).AnyTimes()
|
||||||
|
|
||||||
|
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSyncHandler_HandleFullSyncRequest(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
log = logger.CtxLogger{Logger: zap.NewNop()}
|
||||||
|
|
||||||
|
t.Run("full sync request with change", func(t *testing.T) {
|
||||||
|
fx := newSyncHandlerFixture(t)
|
||||||
|
defer fx.stop()
|
||||||
|
treeId := "treeId"
|
||||||
|
senderId := "senderId"
|
||||||
|
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||||
|
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
||||||
|
Heads: []string{"h1"},
|
||||||
|
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||||
|
SnapshotPath: []string{"h1"},
|
||||||
|
}
|
||||||
|
treeMsg := treechangeproto.WrapFullRequest(fullSyncRequest, chWithId)
|
||||||
|
objectMsg, _ := marshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||||
|
fullResponse := &treechangeproto.TreeSyncMessage{}
|
||||||
|
|
||||||
|
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||||
|
fx.objectTreeMock.EXPECT().Header().Return(nil)
|
||||||
|
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"},
|
||||||
|
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||||
|
})).
|
||||||
|
Return(objecttree.AddResult{}, nil)
|
||||||
|
fx.syncClientMock.EXPECT().
|
||||||
|
CreateFullSyncResponse(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
||||||
|
Return(fullResponse, nil)
|
||||||
|
fx.syncClientMock.EXPECT().SendWithReply(gomock.Any(), gomock.Eq(senderId), gomock.Eq(fullResponse), gomock.Eq(""))
|
||||||
|
|
||||||
|
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("full sync request with change same heads", func(t *testing.T) {
|
||||||
|
fx := newSyncHandlerFixture(t)
|
||||||
|
defer fx.stop()
|
||||||
|
treeId := "treeId"
|
||||||
|
senderId := "senderId"
|
||||||
|
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||||
|
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
||||||
|
Heads: []string{"h1"},
|
||||||
|
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||||
|
SnapshotPath: []string{"h1"},
|
||||||
|
}
|
||||||
|
treeMsg := treechangeproto.WrapFullRequest(fullSyncRequest, chWithId)
|
||||||
|
objectMsg, _ := marshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||||
|
fullResponse := &treechangeproto.TreeSyncMessage{}
|
||||||
|
|
||||||
|
fx.objectTreeMock.EXPECT().
|
||||||
|
Id().AnyTimes().Return(treeId)
|
||||||
|
fx.objectTreeMock.EXPECT().Header().Return(nil)
|
||||||
|
fx.objectTreeMock.EXPECT().
|
||||||
|
Heads().
|
||||||
|
Return([]string{"h1"}).AnyTimes()
|
||||||
|
fx.syncClientMock.EXPECT().
|
||||||
|
CreateFullSyncResponse(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
||||||
|
Return(fullResponse, nil)
|
||||||
|
fx.syncClientMock.EXPECT().SendWithReply(gomock.Any(), gomock.Eq(senderId), gomock.Eq(fullResponse), gomock.Eq(""))
|
||||||
|
|
||||||
|
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("full sync request without change but with reply id", func(t *testing.T) {
|
||||||
|
fx := newSyncHandlerFixture(t)
|
||||||
|
defer fx.stop()
|
||||||
|
treeId := "treeId"
|
||||||
|
senderId := "senderId"
|
||||||
|
replyId := "replyId"
|
||||||
|
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||||
|
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
||||||
|
Heads: []string{"h1"},
|
||||||
|
SnapshotPath: []string{"h1"},
|
||||||
|
}
|
||||||
|
treeMsg := treechangeproto.WrapFullRequest(fullSyncRequest, chWithId)
|
||||||
|
objectMsg, _ := marshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||||
|
objectMsg.RequestId = replyId
|
||||||
|
fullResponse := &treechangeproto.TreeSyncMessage{}
|
||||||
|
|
||||||
|
fx.objectTreeMock.EXPECT().
|
||||||
|
Id().AnyTimes().Return(treeId)
|
||||||
|
fx.objectTreeMock.EXPECT().Header().Return(nil)
|
||||||
|
fx.syncClientMock.EXPECT().
|
||||||
|
CreateFullSyncResponse(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
||||||
|
Return(fullResponse, nil)
|
||||||
|
fx.syncClientMock.EXPECT().SendWithReply(gomock.Any(), gomock.Eq(senderId), gomock.Eq(fullResponse), gomock.Eq(replyId))
|
||||||
|
|
||||||
|
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("full sync request with add raw changes error", func(t *testing.T) {
|
||||||
|
fx := newSyncHandlerFixture(t)
|
||||||
|
defer fx.stop()
|
||||||
|
treeId := "treeId"
|
||||||
|
senderId := "senderId"
|
||||||
|
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||||
|
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
||||||
|
Heads: []string{"h1"},
|
||||||
|
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||||
|
SnapshotPath: []string{"h1"},
|
||||||
|
}
|
||||||
|
treeMsg := treechangeproto.WrapFullRequest(fullSyncRequest, chWithId)
|
||||||
|
objectMsg, _ := marshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||||
|
|
||||||
|
fx.objectTreeMock.EXPECT().
|
||||||
|
Id().AnyTimes().Return(treeId)
|
||||||
|
fx.objectTreeMock.EXPECT().Header().Return(nil)
|
||||||
|
fx.objectTreeMock.EXPECT().
|
||||||
|
Heads().
|
||||||
|
Return([]string{"h2"})
|
||||||
|
fx.objectTreeMock.EXPECT().
|
||||||
|
HasChanges(gomock.Eq([]string{"h1"})).
|
||||||
|
Return(false)
|
||||||
|
fx.objectTreeMock.EXPECT().
|
||||||
|
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
||||||
|
NewHeads: []string{"h1"},
|
||||||
|
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||||
|
})).
|
||||||
|
Return(objecttree.AddResult{}, fmt.Errorf(""))
|
||||||
|
fx.syncClientMock.EXPECT().SendWithReply(gomock.Any(), gomock.Eq(senderId), gomock.Any(), gomock.Eq(""))
|
||||||
|
|
||||||
|
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSyncHandler_HandleFullSyncResponse(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
log = logger.CtxLogger{Logger: zap.NewNop()}
|
||||||
|
|
||||||
|
t.Run("full sync response with change", func(t *testing.T) {
|
||||||
|
fx := newSyncHandlerFixture(t)
|
||||||
|
defer fx.stop()
|
||||||
|
treeId := "treeId"
|
||||||
|
senderId := "senderId"
|
||||||
|
replyId := "replyId"
|
||||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||||
fullSyncResponse := &treechangeproto.TreeFullSyncResponse{
|
fullSyncResponse := &treechangeproto.TreeFullSyncResponse{
|
||||||
Heads: []string{"h3"},
|
Heads: []string{"h1"},
|
||||||
|
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||||
|
SnapshotPath: []string{"h1"},
|
||||||
}
|
}
|
||||||
treeMsg := treechangeproto.WrapFullResponse(fullSyncResponse, chWithId)
|
treeMsg := treechangeproto.WrapFullResponse(fullSyncResponse, chWithId)
|
||||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(treeMsg, "spaceId", treeId)
|
objectMsg, _ := marshallTreeMessage(treeMsg, "spaceId", treeId, replyId)
|
||||||
|
|
||||||
fx.syncHandler.heads = []string{"h2"}
|
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
fx.objectTreeMock.EXPECT().
|
||||||
fx.objectTreeMock.EXPECT().Heads().Times(2).Return([]string{"h2"})
|
Heads().
|
||||||
fx.objectTreeMock.EXPECT().Heads().Times(2).Return([]string{"h3"})
|
Return([]string{"h2"}).AnyTimes()
|
||||||
fx.syncProtocolMock.EXPECT().FullSyncResponse(ctx, fx.senderId, gomock.Any()).Return(nil)
|
fx.objectTreeMock.EXPECT().
|
||||||
|
HasChanges(gomock.Eq([]string{"h1"})).
|
||||||
|
Return(false)
|
||||||
|
fx.objectTreeMock.EXPECT().
|
||||||
|
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
||||||
|
NewHeads: []string{"h1"},
|
||||||
|
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||||
|
})).
|
||||||
|
Return(objecttree.AddResult{}, nil)
|
||||||
|
|
||||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
func TestSyncTreeHandler_HandleRequest(t *testing.T) {
|
t.Run("full sync response with same heads", func(t *testing.T) {
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
t.Run("handle request", func(t *testing.T) {
|
|
||||||
fx := newSyncHandlerFixture(t)
|
fx := newSyncHandlerFixture(t)
|
||||||
defer fx.stop()
|
defer fx.stop()
|
||||||
treeId := "treeId"
|
treeId := "treeId"
|
||||||
|
senderId := "senderId"
|
||||||
|
replyId := "replyId"
|
||||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||||
fullRequest := &treechangeproto.TreeFullSyncRequest{}
|
fullSyncResponse := &treechangeproto.TreeFullSyncResponse{
|
||||||
treeMsg := treechangeproto.WrapFullRequest(fullRequest, chWithId)
|
Heads: []string{"h1"},
|
||||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(treeMsg, "spaceId", treeId)
|
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||||
|
SnapshotPath: []string{"h1"},
|
||||||
syncResp := &treechangeproto.TreeSyncMessage{}
|
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
|
||||||
fx.syncProtocolMock.EXPECT().FullSyncRequest(ctx, fx.senderId, gomock.Any()).Return(syncResp, nil)
|
|
||||||
|
|
||||||
res, err := fx.syncHandler.HandleRequest(ctx, fx.senderId, objectMsg)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, res)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("handle other message", func(t *testing.T) {
|
|
||||||
fx := newSyncHandlerFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
treeId := "treeId"
|
|
||||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
|
||||||
fullResponse := &treechangeproto.TreeFullSyncResponse{}
|
|
||||||
responseMsg := treechangeproto.WrapFullResponse(fullResponse, chWithId)
|
|
||||||
headUpdate := &treechangeproto.TreeHeadUpdate{}
|
|
||||||
headUpdateMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
|
||||||
for _, msg := range []*treechangeproto.TreeSyncMessage{responseMsg, headUpdateMsg} {
|
|
||||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(msg, "spaceId", treeId)
|
|
||||||
|
|
||||||
_, err := fx.syncHandler.HandleRequest(ctx, fx.senderId, objectMsg)
|
|
||||||
require.Equal(t, err, ErrMessageIsNotRequest)
|
|
||||||
}
|
}
|
||||||
|
treeMsg := treechangeproto.WrapFullResponse(fullSyncResponse, chWithId)
|
||||||
|
objectMsg, _ := marshallTreeMessage(treeMsg, "spaceId", treeId, replyId)
|
||||||
|
|
||||||
|
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||||
|
fx.objectTreeMock.EXPECT().
|
||||||
|
Heads().
|
||||||
|
Return([]string{"h1"}).AnyTimes()
|
||||||
|
|
||||||
|
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||||
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,19 +2,15 @@ package synctree
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"fmt"
|
||||||
|
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
"github.com/anytypeio/any-sync/commonspace/spacestorage"
|
||||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
"github.com/anytypeio/any-sync/net/peer"
|
||||||
"github.com/anyproto/any-sync/net/peer"
|
|
||||||
"github.com/gogo/protobuf/proto"
|
"github.com/gogo/protobuf/proto"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
"time"
|
||||||
|
|
||||||
var (
|
|
||||||
ErrNoResponsiblePeers = errors.New("no responsible peers")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type treeRemoteGetter struct {
|
type treeRemoteGetter struct {
|
||||||
@ -39,7 +35,7 @@ func (t treeRemoteGetter) getPeers(ctx context.Context) (peerIds []string, err e
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(respPeers) == 0 {
|
if len(respPeers) == 0 {
|
||||||
err = ErrNoResponsiblePeers
|
err = fmt.Errorf("no responsible peers")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, p := range respPeers {
|
for _, p := range respPeers {
|
||||||
@ -49,8 +45,13 @@ func (t treeRemoteGetter) getPeers(ctx context.Context) (peerIds []string, err e
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t treeRemoteGetter) treeRequest(ctx context.Context, peerId string) (msg *treechangeproto.TreeSyncMessage, err error) {
|
func (t treeRemoteGetter) treeRequest(ctx context.Context, peerId string) (msg *treechangeproto.TreeSyncMessage, err error) {
|
||||||
newTreeRequest := t.deps.SyncClient.CreateNewTreeRequest()
|
newTreeRequest := GetRequestFactory().CreateNewTreeRequest()
|
||||||
resp, err := t.deps.SyncClient.SendRequest(ctx, peerId, t.treeId, newTreeRequest)
|
objMsg, err := marshallTreeMessage(newTreeRequest, t.deps.SpaceId, t.treeId, "")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := t.deps.ObjectSync.MessagePool().SendSync(ctx, peerId, objMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -60,20 +61,45 @@ func (t treeRemoteGetter) treeRequest(ctx context.Context, peerId string) (msg *
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t treeRemoteGetter) treeRequestLoop(ctx context.Context) (msg *treechangeproto.TreeSyncMessage, err error) {
|
func (t treeRemoteGetter) treeRequestLoop(ctx context.Context, wait bool) (msg *treechangeproto.TreeSyncMessage, err error) {
|
||||||
availablePeers, err := t.getPeers(ctx)
|
peerIdx := 0
|
||||||
if err != nil {
|
Loop:
|
||||||
return
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, fmt.Errorf("waiting for object %s interrupted, context closed", t.treeId)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
availablePeers, err := t.getPeers(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if !wait {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
// wait for peers to connect
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
continue Loop
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, fmt.Errorf("waiting for object %s interrupted, context closed", t.treeId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
peerIdx = peerIdx % len(availablePeers)
|
||||||
|
msg, err = t.treeRequest(ctx, availablePeers[peerIdx])
|
||||||
|
if err == nil || !wait {
|
||||||
|
return msg, err
|
||||||
|
}
|
||||||
|
peerIdx++
|
||||||
}
|
}
|
||||||
// in future we will try to load from different peers
|
|
||||||
return t.treeRequest(ctx, availablePeers[0])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t treeRemoteGetter) getTree(ctx context.Context) (treeStorage treestorage.TreeStorage, isRemote bool, err error) {
|
func (t treeRemoteGetter) getTree(ctx context.Context) (treeStorage treestorage.TreeStorage, err error) {
|
||||||
treeStorage, err = t.deps.SpaceStorage.TreeStorage(t.treeId)
|
treeStorage, err = t.deps.SpaceStorage.TreeStorage(t.treeId)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil && err != treestorage.ErrUnknownTreeId {
|
if err != nil && err != treestorage.ErrUnknownTreeId {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -87,16 +113,15 @@ func (t treeRemoteGetter) getTree(ctx context.Context) (treeStorage treestorage.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isRemote = true
|
resp, err := t.treeRequestLoop(ctx, t.deps.WaitTreeRemoteSync)
|
||||||
resp, err := t.treeRequestLoop(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fullSyncResp := resp.GetContent().GetFullSyncResponse()
|
if resp.GetContent().GetFullSyncResponse() == nil {
|
||||||
if fullSyncResp == nil {
|
err = fmt.Errorf("expected to get full sync response, but got something else")
|
||||||
err = treechangeproto.ErrUnexpected
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
fullSyncResp := resp.GetContent().GetFullSyncResponse()
|
||||||
|
|
||||||
payload := treestorage.TreeStorageCreatePayload{
|
payload := treestorage.TreeStorageCreatePayload{
|
||||||
RootRawChange: resp.RootChange,
|
RootRawChange: resp.RootChange,
|
||||||
@ -111,6 +136,5 @@ func (t treeRemoteGetter) getTree(ctx context.Context) (treeStorage treestorage.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// now we are sure that we can save it to the storage
|
// now we are sure that we can save it to the storage
|
||||||
treeStorage, err = t.deps.SpaceStorage.CreateTreeStorage(payload)
|
return t.deps.SpaceStorage.CreateTreeStorage(payload)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,87 +0,0 @@
|
|||||||
package synctree
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"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/peermanager/mock_peermanager"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
|
||||||
"github.com/anyproto/any-sync/net/peer"
|
|
||||||
"github.com/anyproto/any-sync/net/peer/mock_peer"
|
|
||||||
"github.com/gogo/protobuf/proto"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"go.uber.org/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
type treeRemoteGetterFixture struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
|
|
||||||
treeGetter treeRemoteGetter
|
|
||||||
syncClientMock *mock_synctree.MockSyncClient
|
|
||||||
peerGetterMock *mock_peermanager.MockPeerManager
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTreeRemoteGetterFixture(t *testing.T) *treeRemoteGetterFixture {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
syncClientMock := mock_synctree.NewMockSyncClient(ctrl)
|
|
||||||
peerGetterMock := mock_peermanager.NewMockPeerManager(ctrl)
|
|
||||||
treeGetter := treeRemoteGetter{
|
|
||||||
deps: BuildDeps{
|
|
||||||
SyncClient: syncClientMock,
|
|
||||||
PeerGetter: peerGetterMock,
|
|
||||||
},
|
|
||||||
treeId: "treeId",
|
|
||||||
}
|
|
||||||
return &treeRemoteGetterFixture{
|
|
||||||
ctrl: ctrl,
|
|
||||||
treeGetter: treeGetter,
|
|
||||||
syncClientMock: syncClientMock,
|
|
||||||
peerGetterMock: peerGetterMock,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fx *treeRemoteGetterFixture) stop() {
|
|
||||||
fx.ctrl.Finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTreeRemoteGetter(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
peerId := "peerId"
|
|
||||||
treeRequest := &treechangeproto.TreeSyncMessage{}
|
|
||||||
treeResponse := &treechangeproto.TreeSyncMessage{
|
|
||||||
RootChange: &treechangeproto.RawTreeChangeWithId{Id: "id"},
|
|
||||||
}
|
|
||||||
marshalled, _ := proto.Marshal(treeResponse)
|
|
||||||
objectResponse := &spacesyncproto.ObjectSyncMessage{
|
|
||||||
Payload: marshalled,
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("request works", func(t *testing.T) {
|
|
||||||
fx := newTreeRemoteGetterFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
mockPeer := mock_peer.NewMockPeer(fx.ctrl)
|
|
||||||
mockPeer.EXPECT().Id().AnyTimes().Return(peerId)
|
|
||||||
fx.peerGetterMock.EXPECT().GetResponsiblePeers(ctx).Return([]peer.Peer{mockPeer}, nil)
|
|
||||||
fx.syncClientMock.EXPECT().CreateNewTreeRequest().Return(treeRequest)
|
|
||||||
fx.syncClientMock.EXPECT().SendRequest(ctx, peerId, fx.treeGetter.treeId, treeRequest).Return(objectResponse, nil)
|
|
||||||
resp, err := fx.treeGetter.treeRequestLoop(ctx)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "id", resp.RootChange.Id)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("request fails", func(t *testing.T) {
|
|
||||||
fx := newTreeRemoteGetterFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
treeRequest := &treechangeproto.TreeSyncMessage{}
|
|
||||||
mockPeer := mock_peer.NewMockPeer(fx.ctrl)
|
|
||||||
mockPeer.EXPECT().Id().AnyTimes().Return(peerId)
|
|
||||||
fx.peerGetterMock.EXPECT().GetResponsiblePeers(ctx).Return([]peer.Peer{mockPeer}, nil)
|
|
||||||
fx.syncClientMock.EXPECT().CreateNewTreeRequest().Return(treeRequest)
|
|
||||||
fx.syncClientMock.EXPECT().SendRequest(ctx, peerId, fx.treeGetter.treeId, treeRequest).AnyTimes().Return(nil, fmt.Errorf("some"))
|
|
||||||
_, err := fx.treeGetter.treeRequestLoop(ctx)
|
|
||||||
require.Error(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -1,153 +0,0 @@
|
|||||||
package synctree
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/anyproto/any-sync/app/logger"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
|
||||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
|
||||||
"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)
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
type treeSyncProtocol struct {
|
|
||||||
log logger.CtxLogger
|
|
||||||
spaceId string
|
|
||||||
objTree objecttree.ObjectTree
|
|
||||||
reqFactory RequestFactory
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTreeSyncProtocol(spaceId string, objTree objecttree.ObjectTree, reqFactory RequestFactory) *treeSyncProtocol {
|
|
||||||
return &treeSyncProtocol{
|
|
||||||
log: log.With(zap.String("spaceId", spaceId), zap.String("treeId", objTree.Id())),
|
|
||||||
spaceId: spaceId,
|
|
||||||
objTree: objTree,
|
|
||||||
reqFactory: reqFactory,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *treeSyncProtocol) HeadUpdate(ctx context.Context, senderId string, update *treechangeproto.TreeHeadUpdate) (fullRequest *treechangeproto.TreeSyncMessage, err error) {
|
|
||||||
var (
|
|
||||||
isEmptyUpdate = len(update.Changes) == 0
|
|
||||||
objTree = t.objTree
|
|
||||||
)
|
|
||||||
log := t.log.With(
|
|
||||||
zap.String("senderId", senderId),
|
|
||||||
zap.Strings("update heads", update.Heads),
|
|
||||||
zap.Int("len(update changes)", len(update.Changes)))
|
|
||||||
log.DebugCtx(ctx, "received head update message")
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
log.ErrorCtx(ctx, "head update finished with error", zap.Error(err))
|
|
||||||
} else if fullRequest != nil {
|
|
||||||
cnt := fullRequest.Content.GetFullSyncRequest()
|
|
||||||
log = log.With(zap.Strings("request heads", cnt.Heads), zap.Int("len(request changes)", len(cnt.Changes)))
|
|
||||||
log.DebugCtx(ctx, "returning full sync request")
|
|
||||||
} else {
|
|
||||||
if !isEmptyUpdate {
|
|
||||||
log.DebugCtx(ctx, "head update finished correctly")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// isEmptyUpdate is sent when the tree is brought up from cache
|
|
||||||
if isEmptyUpdate {
|
|
||||||
headEquals := slice.UnsortedEquals(objTree.Heads(), update.Heads)
|
|
||||||
log.DebugCtx(ctx, "is empty update", zap.Bool("headEquals", headEquals))
|
|
||||||
if headEquals {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// we need to sync in any case
|
|
||||||
fullRequest, err = t.reqFactory.CreateFullSyncRequest(objTree, update.Heads, update.SnapshotPath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.hasHeads(objTree, update.Heads) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = objTree.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
|
||||||
NewHeads: update.Heads,
|
|
||||||
RawChanges: update.Changes,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.hasHeads(objTree, update.Heads) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fullRequest, err = t.reqFactory.CreateFullSyncRequest(objTree, update.Heads, update.SnapshotPath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *treeSyncProtocol) FullSyncRequest(ctx context.Context, senderId string, request *treechangeproto.TreeFullSyncRequest) (fullResponse *treechangeproto.TreeSyncMessage, err error) {
|
|
||||||
var (
|
|
||||||
objTree = t.objTree
|
|
||||||
)
|
|
||||||
log := t.log.With(zap.String("senderId", senderId),
|
|
||||||
zap.Strings("request heads", request.Heads),
|
|
||||||
zap.Int("len(request changes)", len(request.Changes)))
|
|
||||||
log.DebugCtx(ctx, "received full sync request message")
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
log.ErrorCtx(ctx, "full sync request finished with error", zap.Error(err))
|
|
||||||
} else if fullResponse != nil {
|
|
||||||
cnt := fullResponse.Content.GetFullSyncResponse()
|
|
||||||
log = log.With(zap.Strings("response heads", cnt.Heads), zap.Int("len(response changes)", len(cnt.Changes)))
|
|
||||||
log.DebugCtx(ctx, "full sync response sent")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if len(request.Changes) != 0 && !t.hasHeads(objTree, request.Heads) {
|
|
||||||
_, err = objTree.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
|
||||||
NewHeads: request.Heads,
|
|
||||||
RawChanges: request.Changes,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fullResponse, err = t.reqFactory.CreateFullSyncResponse(objTree, request.Heads, request.SnapshotPath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *treeSyncProtocol) FullSyncResponse(ctx context.Context, senderId string, response *treechangeproto.TreeFullSyncResponse) (err error) {
|
|
||||||
var (
|
|
||||||
objTree = t.objTree
|
|
||||||
)
|
|
||||||
log := log.With(
|
|
||||||
zap.Strings("heads", response.Heads),
|
|
||||||
zap.Int("len(changes)", len(response.Changes)))
|
|
||||||
log.DebugCtx(ctx, "received full sync response message")
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
log.ErrorCtx(ctx, "full sync response failed", zap.Error(err))
|
|
||||||
} else {
|
|
||||||
log.DebugCtx(ctx, "full sync response succeeded")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if t.hasHeads(objTree, response.Heads) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = objTree.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
|
||||||
NewHeads: response.Heads,
|
|
||||||
RawChanges: response.Changes,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *treeSyncProtocol) hasHeads(ot objecttree.ObjectTree, heads []string) bool {
|
|
||||||
return slice.UnsortedEquals(ot.Heads(), heads) || ot.HasChanges(heads...)
|
|
||||||
}
|
|
||||||
@ -1,293 +0,0 @@
|
|||||||
package synctree
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"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"
|
|
||||||
)
|
|
||||||
|
|
||||||
type treeSyncProtocolFixture struct {
|
|
||||||
log logger.CtxLogger
|
|
||||||
spaceId string
|
|
||||||
senderId string
|
|
||||||
treeId string
|
|
||||||
objectTreeMock *testObjTreeMock
|
|
||||||
reqFactory *mock_synctree.MockRequestFactory
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
syncProtocol TreeSyncProtocol
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSyncProtocolFixture(t *testing.T) *treeSyncProtocolFixture {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
objTree := &testObjTreeMock{
|
|
||||||
MockObjectTree: mock_objecttree.NewMockObjectTree(ctrl),
|
|
||||||
}
|
|
||||||
spaceId := "spaceId"
|
|
||||||
reqFactory := mock_synctree.NewMockRequestFactory(ctrl)
|
|
||||||
objTree.EXPECT().Id().Return("treeId")
|
|
||||||
syncProtocol := newTreeSyncProtocol(spaceId, objTree, reqFactory)
|
|
||||||
return &treeSyncProtocolFixture{
|
|
||||||
log: log,
|
|
||||||
spaceId: spaceId,
|
|
||||||
senderId: "senderId",
|
|
||||||
treeId: "treeId",
|
|
||||||
objectTreeMock: objTree,
|
|
||||||
reqFactory: reqFactory,
|
|
||||||
ctrl: ctrl,
|
|
||||||
syncProtocol: syncProtocol,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fx *treeSyncProtocolFixture) stop() {
|
|
||||||
fx.ctrl.Finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTreeSyncProtocol_HeadUpdate(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
fullRequest := &treechangeproto.TreeSyncMessage{
|
|
||||||
Content: &treechangeproto.TreeSyncContentValue{
|
|
||||||
Value: &treechangeproto.TreeSyncContentValue_FullSyncRequest{
|
|
||||||
FullSyncRequest: &treechangeproto.TreeFullSyncRequest{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("head update non empty all heads added", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
|
||||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
|
||||||
Heads: []string{"h1"},
|
|
||||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
|
||||||
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().
|
|
||||||
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
|
||||||
NewHeads: []string{"h1"},
|
|
||||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
|
||||||
})).
|
|
||||||
Return(objecttree.AddResult{}, nil)
|
|
||||||
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(true)
|
|
||||||
|
|
||||||
res, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Nil(t, res)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("head update non empty equal heads", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
|
||||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
|
||||||
Heads: []string{"h1"},
|
|
||||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
|
||||||
SnapshotPath: []string{"h1"},
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Nil(t, res)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("head update empty", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
|
||||||
Heads: []string{"h1"},
|
|
||||||
Changes: nil,
|
|
||||||
SnapshotPath: []string{"h1"},
|
|
||||||
}
|
|
||||||
|
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
|
||||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).AnyTimes()
|
|
||||||
fx.reqFactory.EXPECT().
|
|
||||||
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)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, fullRequest, res)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("head update empty equal heads", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
|
||||||
Heads: []string{"h1"},
|
|
||||||
Changes: nil,
|
|
||||||
SnapshotPath: []string{"h1"},
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Nil(t, res)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTreeSyncProtocol_FullSyncRequest(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
fullResponse := &treechangeproto.TreeSyncMessage{
|
|
||||||
Content: &treechangeproto.TreeSyncContentValue{
|
|
||||||
Value: &treechangeproto.TreeSyncContentValue_FullSyncResponse{
|
|
||||||
FullSyncResponse: &treechangeproto.TreeFullSyncResponse{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("full sync request with change", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
|
||||||
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
|
||||||
Heads: []string{"h1"},
|
|
||||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
|
||||||
SnapshotPath: []string{"h1"},
|
|
||||||
}
|
|
||||||
|
|
||||||
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"},
|
|
||||||
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)
|
|
||||||
|
|
||||||
res, err := fx.syncProtocol.FullSyncRequest(ctx, fx.senderId, fullSyncRequest)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, fullResponse, res)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("full sync request with change same heads", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
|
||||||
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
|
||||||
Heads: []string{"h1"},
|
|
||||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
|
||||||
SnapshotPath: []string{"h1"},
|
|
||||||
}
|
|
||||||
|
|
||||||
fx.objectTreeMock.EXPECT().
|
|
||||||
Heads().
|
|
||||||
Return([]string{"h1"}).AnyTimes()
|
|
||||||
fx.reqFactory.EXPECT().
|
|
||||||
CreateFullSyncResponse(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
|
||||||
Return(fullResponse, nil)
|
|
||||||
|
|
||||||
res, err := fx.syncProtocol.FullSyncRequest(ctx, fx.senderId, fullSyncRequest)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, fullResponse, res)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("full sync request without changes", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
|
||||||
Heads: []string{"h1"},
|
|
||||||
SnapshotPath: []string{"h1"},
|
|
||||||
}
|
|
||||||
|
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
|
||||||
fx.reqFactory.EXPECT().
|
|
||||||
CreateFullSyncResponse(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
|
||||||
Return(fullResponse, nil)
|
|
||||||
|
|
||||||
res, err := fx.syncProtocol.FullSyncRequest(ctx, fx.senderId, fullSyncRequest)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, fullResponse, res)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("full sync request with change, raw changes error", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
|
||||||
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
|
||||||
Heads: []string{"h1"},
|
|
||||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
|
||||||
SnapshotPath: []string{"h1"},
|
|
||||||
}
|
|
||||||
|
|
||||||
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"},
|
|
||||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
|
||||||
})).
|
|
||||||
Return(objecttree.AddResult{}, fmt.Errorf("addRawChanges error"))
|
|
||||||
|
|
||||||
_, err := fx.syncProtocol.FullSyncRequest(ctx, fx.senderId, fullSyncRequest)
|
|
||||||
require.Error(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTreeSyncProtocol_FullSyncResponse(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
t.Run("full sync response with change", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
|
||||||
fullSyncResponse := &treechangeproto.TreeFullSyncResponse{
|
|
||||||
Heads: []string{"h1"},
|
|
||||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
|
||||||
SnapshotPath: []string{"h1"},
|
|
||||||
}
|
|
||||||
|
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
|
||||||
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"},
|
|
||||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
|
||||||
})).
|
|
||||||
Return(objecttree.AddResult{}, nil)
|
|
||||||
|
|
||||||
err := fx.syncProtocol.FullSyncResponse(ctx, fx.senderId, fullSyncResponse)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("full sync response with same heads", func(t *testing.T) {
|
|
||||||
fx := newSyncProtocolFixture(t)
|
|
||||||
defer fx.stop()
|
|
||||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
|
||||||
fullSyncResponse := &treechangeproto.TreeFullSyncResponse{
|
|
||||||
Heads: []string{"h1"},
|
|
||||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
|
||||||
SnapshotPath: []string{"h1"},
|
|
||||||
}
|
|
||||||
|
|
||||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
|
||||||
fx.objectTreeMock.EXPECT().
|
|
||||||
Heads().
|
|
||||||
Return([]string{"h1"}).AnyTimes()
|
|
||||||
|
|
||||||
err := fx.syncProtocol.FullSyncResponse(ctx, fx.senderId, fullSyncResponse)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener (interfaces: UpdateListener)
|
// Source: github.com/anytypeio/any-sync/commonspace/object/tree/synctree/updatelistener (interfaces: UpdateListener)
|
||||||
|
|
||||||
// Package mock_updatelistener is a generated GoMock package.
|
// Package mock_updatelistener is a generated GoMock package.
|
||||||
package mock_updatelistener
|
package mock_updatelistener
|
||||||
@ -7,8 +7,8 @@ package mock_updatelistener
|
|||||||
import (
|
import (
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
objecttree "github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
objecttree "github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||||
gomock "go.uber.org/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockUpdateListener is a mock of UpdateListener interface.
|
// MockUpdateListener is a mock of UpdateListener interface.
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user