Merge pull request #8 from anytypeio/space-sync
This commit is contained in:
commit
58b4dc760e
10
.gitignore
vendored
10
.gitignore
vendored
@ -11,5 +11,15 @@
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Golang vendor folder
|
||||
vendor
|
||||
|
||||
# Intelli-J files
|
||||
.idea
|
||||
|
||||
# MacOS file that stores custom attributes of its containing folder,
|
||||
# such as folder view options, icon positions, and other visual information
|
||||
.DS_Store
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
19
Makefile
19
Makefile
@ -12,25 +12,24 @@ endif
|
||||
|
||||
export PATH=$(GOPATH)/bin:$(shell echo $$PATH)
|
||||
|
||||
# TODO: folders were changed, so we should update Makefile and protos generation
|
||||
proto:
|
||||
@echo 'Generating protobuf packages (Go)...'
|
||||
# Uncomment if needed
|
||||
@$(eval ROOT_PKG := pkg)
|
||||
@$(eval GOGO_START := GOGO_NO_UNDERSCORE=1 GOGO_EXPORT_ONEOF_INTERFACE=1)
|
||||
@$(eval P_ACL_CHANGES_PATH_PB := $(ROOT_PKG)/acl/aclchanges/aclpb)
|
||||
@$(eval P_ACL_RECORDS_PATH_PB := $(ROOT_PKG)/acl/aclrecordproto)
|
||||
@$(eval P_TREE_CHANGES_PATH_PB := $(ROOT_PKG)/acl/treechangeproto)
|
||||
@$(eval P_SYNC_CHANGES_PATH_PB := syncproto)
|
||||
@$(eval P_TEST_CHANGES_PATH_PB := $(ROOT_PKG)/acl/testutils/testchanges)
|
||||
@$(eval P_TIMESTAMP := Mgoogle/protobuf/timestamp.proto=github.com/gogo/protobuf/types)
|
||||
@$(eval P_STRUCT := Mgoogle/protobuf/struct.proto=github.com/gogo/protobuf/types)
|
||||
@$(eval P_ACL_CHANGES := M$(P_ACL_CHANGES_PATH_PB)/protos/aclchanges.proto=github.com/anytypeio/go-anytype-infrastructure-experiments/$(P_ACL_CHANGES_PATH_PB))
|
||||
@$(eval P_ACL_RECORDS := M$(P_ACL_RECORDS_PATH_PB)/protos/aclrecord.proto=github.com/anytypeio/go-anytype-infrastructure-experiments/$(P_ACL_RECORDS_PATH_PB))
|
||||
@$(eval P_TREE_CHANGES := M$(P_TREE_CHANGES_PATH_PB)/protos/treechange.proto=github.com/anytypeio/go-anytype-infrastructure-experiments/$(P_TREE_CHANGES_PATH_PB))
|
||||
|
||||
# use if needed $(eval PKGMAP := $$(P_TIMESTAMP),$$(P_STRUCT))
|
||||
$(GOGO_START) protoc --gogofaster_out=:. $(P_ACL_CHANGES_PATH_PB)/protos/*.proto; mv $(P_ACL_CHANGES_PATH_PB)/protos/*.go $(P_ACL_CHANGES_PATH_PB)
|
||||
$(GOGO_START) protoc --gogofaster_out=:. $(P_ACL_RECORDS_PATH_PB)/protos/*.proto
|
||||
$(GOGO_START) protoc --gogofaster_out=:. $(P_TREE_CHANGES_PATH_PB)/protos/*.proto
|
||||
$(GOGO_START) protoc --gogofaster_out=:. $(P_TEST_CHANGES_PATH_PB)/proto/*.proto
|
||||
$(eval PKGMAP := $$(P_ACL_CHANGES))
|
||||
$(GOGO_START) protoc --gogofaster_out=$(PKGMAP):. $(P_SYNC_CHANGES_PATH_PB)/proto/*.proto
|
||||
$(GOGO_START) protoc --gogofaster_out=$(PKGMAP):. service/space/spacesync/protos/*.proto
|
||||
$(eval PKGMAP := $$(P_TREE_CHANGES),$$(P_ACL_RECORDS))
|
||||
$(GOGO_START) protoc --gogofaster_out=$(PKGMAP):. --go-drpc_out=protolib=github.com/gogo/protobuf:. common/commonspace/spacesyncproto/protos/*.proto
|
||||
|
||||
|
||||
build:
|
||||
@$(eval FLAGS := $$(shell govvv -flags -pkg github.com/anytypeio/go-anytype-infrastructure-experiments/app))
|
||||
|
||||
@ -27,7 +27,7 @@ var (
|
||||
type Component interface {
|
||||
// Init will be called first
|
||||
// When returned error is not nil - app start will be aborted
|
||||
Init(ctx context.Context, a *App) (err error)
|
||||
Init(a *App) (err error)
|
||||
// Name must return unique service name
|
||||
Name() (name string)
|
||||
}
|
||||
@ -157,7 +157,7 @@ func (app *App) Start(ctx context.Context) (err error) {
|
||||
}
|
||||
|
||||
for i, s := range app.components {
|
||||
if err = s.Init(ctx, app); err != nil {
|
||||
if err = s.Init(app); err != nil {
|
||||
closeServices(i)
|
||||
return fmt.Errorf("can't init service '%s': %v", s.Name(), err)
|
||||
}
|
||||
|
||||
@ -131,7 +131,7 @@ type testComponent struct {
|
||||
ids testIds
|
||||
}
|
||||
|
||||
func (t *testComponent) Init(ctx context.Context, a *App) error {
|
||||
func (t *testComponent) Init(a *App) error {
|
||||
t.ids.initId = t.seq.New()
|
||||
return t.err
|
||||
}
|
||||
|
||||
@ -6,20 +6,15 @@ import (
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/dialer"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/pool"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/rpc/server"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/secure"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/nodeconf"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/config"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/api"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/configuration"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/document"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/net/dialer"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/net/pool"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/net/rpc/server"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/net/secure"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/node"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/sync/message"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/sync/requesthandler"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/treecache"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/node/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/node/nodespace"
|
||||
"go.uber.org/zap"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
@ -94,16 +89,18 @@ func main() {
|
||||
|
||||
func Bootstrap(a *app.App) {
|
||||
a.Register(account.New()).
|
||||
Register(node.New()).
|
||||
// TODO: add space storage provider from node side
|
||||
Register(nodeconf.New()).
|
||||
Register(secure.New()).
|
||||
Register(server.New()).
|
||||
Register(dialer.New()).
|
||||
Register(pool.NewPool()).
|
||||
Register(storage.New()).
|
||||
Register(configuration.New()).
|
||||
Register(document.New()).
|
||||
Register(message.New()).
|
||||
Register(requesthandler.New()).
|
||||
Register(treecache.New()).
|
||||
Register(api.New())
|
||||
Register(pool.New()).
|
||||
Register(nodespace.New()).
|
||||
Register(commonspace.New()).
|
||||
Register(server.New())
|
||||
|
||||
//Register(document.New()).
|
||||
//Register(message.New()).
|
||||
//Register(requesthandler.New()).
|
||||
//Register(treecache.New()).
|
||||
//Register(api.New())
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/config"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/peer"
|
||||
@ -75,8 +76,8 @@ func main() {
|
||||
}
|
||||
createDir()
|
||||
}
|
||||
for _, cfg := range configs {
|
||||
path := fmt.Sprintf("%s/%s.yml", configsPath, cfg.Account.PeerId)
|
||||
for idx, cfg := range configs {
|
||||
path := fmt.Sprintf("%s/config%d.yml", configsPath, idx+1)
|
||||
bytes, err := yaml.Marshal(cfg)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("could not marshal the keys: %v", err))
|
||||
@ -100,15 +101,12 @@ func genConfig(addresses []string, apiPort string) (config.Config, error) {
|
||||
return config.Config{}, err
|
||||
}
|
||||
|
||||
encKeyDecoder := encryptionkey.NewRSAPrivKeyDecoder()
|
||||
signKeyDecoder := signingkey.NewEDPrivKeyDecoder()
|
||||
|
||||
encEncKey, err := encKeyDecoder.EncodeToString(encKey)
|
||||
encEncKey, err := keys.EncodeKeyToString(encKey)
|
||||
if err != nil {
|
||||
return config.Config{}, err
|
||||
}
|
||||
|
||||
encSignKey, err := signKeyDecoder.EncodeToString(signKey)
|
||||
encSignKey, err := keys.EncodeKeyToString(signKey)
|
||||
if err != nil {
|
||||
return config.Config{}, err
|
||||
}
|
||||
@ -132,5 +130,9 @@ func genConfig(addresses []string, apiPort string) (config.Config, error) {
|
||||
APIServer: config.APIServer{
|
||||
Port: apiPort,
|
||||
},
|
||||
Space: config.Space{
|
||||
GCTTL: 60,
|
||||
SyncPeriod: 10,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
nodes:
|
||||
- grpcAddresses:
|
||||
- "127.0.0.1:4430"
|
||||
- "127.0.0.1:4431"
|
||||
apiPort: "8080"
|
||||
- grpcAddresses:
|
||||
- "127.0.0.1:4432"
|
||||
- "127.0.0.1:4433"
|
||||
- "127.0.0.1:4431"
|
||||
apiPort: "8081"
|
||||
|
||||
13
common/account/service.go
Normal file
13
common/account/service.go
Normal file
@ -0,0 +1,13 @@
|
||||
package account
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
)
|
||||
|
||||
const CName = "common.account"
|
||||
|
||||
type Service interface {
|
||||
app.Component
|
||||
Account() *account.AccountData
|
||||
}
|
||||
29
common/commonspace/cache/treecache.go
vendored
Normal file
29
common/commonspace/cache/treecache.go
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/synctree"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree"
|
||||
)
|
||||
|
||||
const CName = "commonspace.cache"
|
||||
|
||||
var ErrSpaceNotFound = errors.New("space not found")
|
||||
|
||||
type TreeContainer interface {
|
||||
Tree() tree.ObjectTree
|
||||
}
|
||||
|
||||
type TreeResult struct {
|
||||
Release func()
|
||||
TreeContainer TreeContainer
|
||||
}
|
||||
|
||||
type BuildFunc = func(ctx context.Context, id string, listener synctree.UpdateListener) (tree.ObjectTree, error)
|
||||
|
||||
type TreeCache interface {
|
||||
app.ComponentRunnable
|
||||
GetTree(ctx context.Context, spaceId, treeId string) (TreeResult, error)
|
||||
}
|
||||
183
common/commonspace/diffservice/diffservice.go
Normal file
183
common/commonspace/diffservice/diffservice.go
Normal file
@ -0,0 +1,183 @@
|
||||
package diffservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/cache"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/remotediff"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/peer"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/rpc/rpcerr"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/nodeconf"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/ldiff"
|
||||
"go.uber.org/zap"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DiffService interface {
|
||||
HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error)
|
||||
UpdateHeads(id string, heads []string)
|
||||
RemoveObject(id string)
|
||||
|
||||
Init(objectIds []string)
|
||||
Close() (err error)
|
||||
}
|
||||
|
||||
type diffService struct {
|
||||
spaceId string
|
||||
periodicSync *periodicSync
|
||||
storage storage.SpaceStorage
|
||||
nconf nodeconf.Configuration
|
||||
diff ldiff.Diff
|
||||
cache cache.TreeCache
|
||||
log *zap.Logger
|
||||
|
||||
syncPeriod int
|
||||
}
|
||||
|
||||
func NewDiffService(
|
||||
spaceId string,
|
||||
syncPeriod int,
|
||||
storage storage.SpaceStorage,
|
||||
nconf nodeconf.Configuration,
|
||||
cache cache.TreeCache,
|
||||
log *zap.Logger) DiffService {
|
||||
return &diffService{
|
||||
spaceId: spaceId,
|
||||
storage: storage,
|
||||
nconf: nconf,
|
||||
cache: cache,
|
||||
log: log,
|
||||
syncPeriod: syncPeriod,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *diffService) Init(objectIds []string) {
|
||||
d.periodicSync = newPeriodicSync(d.syncPeriod, d.sync, d.log.With(zap.String("spaceId", d.spaceId)))
|
||||
d.diff = ldiff.New(16, 16)
|
||||
d.fillDiff(objectIds)
|
||||
}
|
||||
|
||||
func (d *diffService) HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error) {
|
||||
return remotediff.HandleRangeRequest(ctx, d.diff, req)
|
||||
}
|
||||
|
||||
func (d *diffService) UpdateHeads(id string, heads []string) {
|
||||
d.diff.Set(ldiff.Element{
|
||||
Id: id,
|
||||
Head: concatStrings(heads),
|
||||
})
|
||||
}
|
||||
|
||||
func (d *diffService) RemoveObject(id string) {
|
||||
// TODO: add space document to remove ids
|
||||
d.diff.RemoveId(id)
|
||||
}
|
||||
|
||||
func (d *diffService) Close() (err error) {
|
||||
d.periodicSync.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *diffService) sync(ctx context.Context) error {
|
||||
st := time.Now()
|
||||
// diffing with responsible peers according to configuration
|
||||
peers, err := d.nconf.ResponsiblePeers(ctx, d.spaceId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, p := range peers {
|
||||
if err := d.syncWithPeer(ctx, p); err != nil {
|
||||
d.log.Error("can't sync with peer", zap.String("peer", p.Id()), zap.Error(err))
|
||||
}
|
||||
}
|
||||
d.log.Info("synced", zap.String("spaceId", d.spaceId), zap.Duration("dur", time.Since(st)))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *diffService) syncWithPeer(ctx context.Context, p peer.Peer) (err error) {
|
||||
cl := spacesyncproto.NewDRPCSpaceClient(p)
|
||||
rdiff := remotediff.NewRemoteDiff(d.spaceId, cl)
|
||||
newIds, changedIds, removedIds, err := d.diff.Diff(ctx, rdiff)
|
||||
err = rpcerr.Unwrap(err)
|
||||
if err != nil && err != spacesyncproto.ErrSpaceMissing {
|
||||
return err
|
||||
}
|
||||
if err == spacesyncproto.ErrSpaceMissing {
|
||||
return d.sendPushSpaceRequest(ctx, cl)
|
||||
}
|
||||
|
||||
d.pingTreesInCache(ctx, newIds)
|
||||
d.pingTreesInCache(ctx, changedIds)
|
||||
|
||||
d.log.Info("sync done:", zap.Int("newIds", len(newIds)),
|
||||
zap.Int("changedIds", len(changedIds)),
|
||||
zap.Int("removedIds", len(removedIds)))
|
||||
return
|
||||
}
|
||||
|
||||
func (d *diffService) pingTreesInCache(ctx context.Context, trees []string) {
|
||||
for _, tId := range trees {
|
||||
_, _ = d.cache.GetTree(ctx, d.spaceId, tId)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *diffService) fillDiff(objectIds []string) {
|
||||
var els = make([]ldiff.Element, 0, len(objectIds))
|
||||
for _, id := range objectIds {
|
||||
st, err := d.storage.TreeStorage(id)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
heads, err := st.Heads()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
els = append(els, ldiff.Element{
|
||||
Id: id,
|
||||
Head: concatStrings(heads),
|
||||
})
|
||||
}
|
||||
d.diff.Set(els...)
|
||||
}
|
||||
|
||||
func (d *diffService) sendPushSpaceRequest(ctx context.Context, cl spacesyncproto.DRPCSpaceClient) (err error) {
|
||||
aclStorage, err := d.storage.ACLStorage()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
root, err := aclStorage.Root()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
header, err := d.storage.SpaceHeader()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = cl.PushSpace(ctx, &spacesyncproto.PushSpaceRequest{
|
||||
SpaceId: d.spaceId,
|
||||
SpaceHeader: header,
|
||||
AclRoot: root,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
58
common/commonspace/diffservice/periodicsync.go
Normal file
58
common/commonspace/diffservice/periodicsync.go
Normal file
@ -0,0 +1,58 @@
|
||||
package diffservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
)
|
||||
|
||||
func newPeriodicSync(periodSeconds int, sync func(ctx context.Context) error, l *zap.Logger) *periodicSync {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ps := &periodicSync{
|
||||
log: l,
|
||||
sync: sync,
|
||||
syncCtx: ctx,
|
||||
syncCancel: cancel,
|
||||
syncLoopDone: make(chan struct{}),
|
||||
}
|
||||
go ps.syncLoop(periodSeconds)
|
||||
return ps
|
||||
}
|
||||
|
||||
type periodicSync struct {
|
||||
log *zap.Logger
|
||||
sync func(ctx context.Context) error
|
||||
syncCtx context.Context
|
||||
syncCancel context.CancelFunc
|
||||
syncLoopDone chan struct{}
|
||||
}
|
||||
|
||||
func (p *periodicSync) syncLoop(periodSeconds int) {
|
||||
period := time.Duration(periodSeconds) * time.Second
|
||||
defer close(p.syncLoopDone)
|
||||
doSync := func() {
|
||||
ctx, cancel := context.WithTimeout(p.syncCtx, time.Minute)
|
||||
defer cancel()
|
||||
if err := p.sync(ctx); err != nil {
|
||||
p.log.Warn("periodic sync error", zap.Error(err))
|
||||
}
|
||||
}
|
||||
doSync()
|
||||
if period > 0 {
|
||||
ticker := time.NewTicker(period)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-p.syncCtx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
doSync()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *periodicSync) Close() {
|
||||
p.syncCancel()
|
||||
<-p.syncLoopDone
|
||||
}
|
||||
98
common/commonspace/remotediff/remotediff.go
Normal file
98
common/commonspace/remotediff/remotediff.go
Normal file
@ -0,0 +1,98 @@
|
||||
package remotediff
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/ldiff"
|
||||
)
|
||||
|
||||
type Client interface {
|
||||
HeadSync(ctx context.Context, in *spacesyncproto.HeadSyncRequest) (*spacesyncproto.HeadSyncResponse, error)
|
||||
}
|
||||
|
||||
func NewRemoteDiff(spaceId string, client Client) ldiff.Remote {
|
||||
return remote{
|
||||
spaceId: spaceId,
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
type remote struct {
|
||||
spaceId string
|
||||
client Client
|
||||
}
|
||||
|
||||
func (r remote) Ranges(ctx context.Context, ranges []ldiff.Range, resBuf []ldiff.RangeResult) (results []ldiff.RangeResult, err error) {
|
||||
results = resBuf[:0]
|
||||
pbRanges := make([]*spacesyncproto.HeadSyncRange, 0, len(ranges))
|
||||
for _, rg := range ranges {
|
||||
pbRanges = append(pbRanges, &spacesyncproto.HeadSyncRange{
|
||||
From: rg.From,
|
||||
To: rg.To,
|
||||
Limit: uint32(rg.Limit),
|
||||
})
|
||||
}
|
||||
req := &spacesyncproto.HeadSyncRequest{
|
||||
SpaceId: r.spaceId,
|
||||
Ranges: pbRanges,
|
||||
}
|
||||
resp, err := r.client.HeadSync(ctx, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, rr := range resp.Results {
|
||||
var elms []ldiff.Element
|
||||
if len(rr.Elements) > 0 {
|
||||
elms = make([]ldiff.Element, 0, len(rr.Elements))
|
||||
}
|
||||
for _, e := range rr.Elements {
|
||||
elms = append(elms, ldiff.Element{
|
||||
Id: e.Id,
|
||||
Head: e.Head,
|
||||
})
|
||||
}
|
||||
results = append(results, ldiff.RangeResult{
|
||||
Hash: rr.Hash,
|
||||
Elements: elms,
|
||||
Count: int(rr.Count),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func HandleRangeRequest(ctx context.Context, d ldiff.Diff, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error) {
|
||||
ranges := make([]ldiff.Range, 0, len(req.Ranges))
|
||||
for _, reqRange := range req.Ranges {
|
||||
ranges = append(ranges, ldiff.Range{
|
||||
From: reqRange.From,
|
||||
To: reqRange.To,
|
||||
Limit: int(reqRange.Limit),
|
||||
})
|
||||
}
|
||||
res, err := d.Ranges(ctx, ranges, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
resp = &spacesyncproto.HeadSyncResponse{
|
||||
Results: make([]*spacesyncproto.HeadSyncResult, 0, len(res)),
|
||||
}
|
||||
for _, rangeRes := range res {
|
||||
var elements []*spacesyncproto.HeadSyncResultElement
|
||||
if len(rangeRes.Elements) > 0 {
|
||||
elements = make([]*spacesyncproto.HeadSyncResultElement, 0, len(rangeRes.Elements))
|
||||
for _, el := range rangeRes.Elements {
|
||||
elements = append(elements, &spacesyncproto.HeadSyncResultElement{
|
||||
Id: el.Id,
|
||||
Head: el.Head,
|
||||
})
|
||||
}
|
||||
}
|
||||
resp.Results = append(resp.Results, &spacesyncproto.HeadSyncResult{
|
||||
Hash: rangeRes.Hash,
|
||||
Elements: elements,
|
||||
Count: uint32(rangeRes.Count),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
41
common/commonspace/remotediff/remotediff_test.go
Normal file
41
common/commonspace/remotediff/remotediff_test.go
Normal file
@ -0,0 +1,41 @@
|
||||
package remotediff
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/ldiff"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRemote(t *testing.T) {
|
||||
ldLocal := ldiff.New(8, 8)
|
||||
ldRemote := ldiff.New(8, 8)
|
||||
for i := 0; i < 100; i++ {
|
||||
el := ldiff.Element{
|
||||
Id: fmt.Sprint(i),
|
||||
Head: fmt.Sprint(i),
|
||||
}
|
||||
ldRemote.Set(el)
|
||||
if i%10 != 0 {
|
||||
ldLocal.Set(el)
|
||||
}
|
||||
}
|
||||
|
||||
rd := NewRemoteDiff("1", &mockClient{l: ldRemote})
|
||||
newIds, changedIds, removedIds, err := ldLocal.Diff(context.Background(), rd)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, newIds, 10)
|
||||
assert.Len(t, changedIds, 0)
|
||||
assert.Len(t, removedIds, 0)
|
||||
}
|
||||
|
||||
type mockClient struct {
|
||||
l ldiff.Diff
|
||||
}
|
||||
|
||||
func (m *mockClient) HeadSync(ctx context.Context, in *spacesyncproto.HeadSyncRequest) (*spacesyncproto.HeadSyncResponse, error) {
|
||||
return HandleRangeRequest(ctx, m.l, in)
|
||||
}
|
||||
23
common/commonspace/rpchandler.go
Normal file
23
common/commonspace/rpchandler.go
Normal file
@ -0,0 +1,23 @@
|
||||
package commonspace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
|
||||
)
|
||||
|
||||
type RpcHandler interface {
|
||||
HeadSync(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (*spacesyncproto.HeadSyncResponse, error)
|
||||
Stream(stream spacesyncproto.DRPCSpace_StreamStream) error
|
||||
}
|
||||
|
||||
type rpcHandler struct {
|
||||
s *space
|
||||
}
|
||||
|
||||
func (r *rpcHandler) HeadSync(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (*spacesyncproto.HeadSyncResponse, error) {
|
||||
return r.s.DiffService().HandleRangeRequest(ctx, req)
|
||||
}
|
||||
|
||||
func (r *rpcHandler) Stream(stream spacesyncproto.DRPCSpace_StreamStream) (err error) {
|
||||
return r.s.SyncService().StreamPool().AddAndReadStreamSync(stream)
|
||||
}
|
||||
274
common/commonspace/service.go
Normal file
274
common/commonspace/service.go
Normal file
@ -0,0 +1,274 @@
|
||||
package commonspace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/cache"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/diffservice"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/syncservice"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/nodeconf"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/config"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclrecordproto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
"hash/fnv"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
const CName = "common.commonspace"
|
||||
|
||||
var log = logger.NewNamed(CName)
|
||||
|
||||
func New() Service {
|
||||
return &service{}
|
||||
}
|
||||
|
||||
type Service interface {
|
||||
CreateSpace(ctx context.Context, cache cache.TreeCache, payload SpaceCreatePayload) (Space, error)
|
||||
DeriveSpace(ctx context.Context, cache cache.TreeCache, payload SpaceDerivePayload) (Space, error)
|
||||
GetSpace(ctx context.Context, id string) (sp Space, err error)
|
||||
app.Component
|
||||
}
|
||||
|
||||
type service struct {
|
||||
config config.Space
|
||||
configurationService nodeconf.Service
|
||||
storageProvider storage.SpaceStorageProvider
|
||||
cache cache.TreeCache
|
||||
}
|
||||
|
||||
func (s *service) Init(a *app.App) (err error) {
|
||||
s.config = a.MustComponent(config.CName).(*config.Config).Space
|
||||
s.storageProvider = a.MustComponent(storage.CName).(storage.SpaceStorageProvider)
|
||||
s.configurationService = a.MustComponent(nodeconf.CName).(nodeconf.Service)
|
||||
s.cache = a.MustComponent(cache.CName).(cache.TreeCache)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Name() (name string) {
|
||||
return CName
|
||||
}
|
||||
|
||||
func (s *service) CreateSpace(
|
||||
ctx context.Context,
|
||||
cache cache.TreeCache,
|
||||
payload SpaceCreatePayload) (sp Space, err error) {
|
||||
|
||||
// unmarshalling signing and encryption keys
|
||||
identity, err := payload.SigningKey.GetPublic().Raw()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
encPubKey, err := payload.EncryptionKey.GetPublic().Raw()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// preparing header and space id
|
||||
bytes := make([]byte, 32)
|
||||
_, err = rand.Read(bytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
header := &spacesyncproto.SpaceHeader{
|
||||
Identity: identity,
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
SpaceType: payload.SpaceType,
|
||||
ReplicationKey: payload.ReplicationKey,
|
||||
Seed: bytes,
|
||||
}
|
||||
marshalled, err := header.Marshal()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
id, err := cid.NewCIDFromBytes(marshalled)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
spaceId := NewSpaceId(id, payload.ReplicationKey)
|
||||
|
||||
// encrypting read key
|
||||
hasher := fnv.New64()
|
||||
_, err = hasher.Write(payload.ReadKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
readKeyHash := hasher.Sum64()
|
||||
encReadKey, err := payload.EncryptionKey.GetPublic().Encrypt(payload.ReadKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// preparing acl
|
||||
aclRoot := &aclrecordproto.ACLRoot{
|
||||
Identity: identity,
|
||||
EncryptionKey: encPubKey,
|
||||
SpaceId: spaceId,
|
||||
EncryptedReadKey: encReadKey,
|
||||
DerivationScheme: "",
|
||||
CurrentReadKeyHash: readKeyHash,
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
}
|
||||
rawWithId, err := marshalACLRoot(aclRoot, payload.SigningKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// creating storage
|
||||
storageCreate := storage.SpaceStorageCreatePayload{
|
||||
RecWithId: rawWithId,
|
||||
SpaceHeader: header,
|
||||
Id: id,
|
||||
}
|
||||
_, err = s.storageProvider.CreateSpaceStorage(storageCreate)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return s.GetSpace(ctx, spaceId)
|
||||
}
|
||||
|
||||
func (s *service) DeriveSpace(
|
||||
ctx context.Context,
|
||||
cache cache.TreeCache,
|
||||
payload SpaceDerivePayload) (sp Space, err error) {
|
||||
|
||||
// unmarshalling signing and encryption keys
|
||||
identity, err := payload.SigningKey.GetPublic().Raw()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
signPrivKey, err := payload.SigningKey.Raw()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
encPubKey, err := payload.EncryptionKey.GetPublic().Raw()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
encPrivKey, err := payload.EncryptionKey.Raw()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// preparing replication key
|
||||
hasher := fnv.New64()
|
||||
_, err = hasher.Write(identity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
repKey := hasher.Sum64()
|
||||
|
||||
// preparing header and space id
|
||||
header := &spacesyncproto.SpaceHeader{
|
||||
Identity: identity,
|
||||
SpaceType: SpaceTypeDerived,
|
||||
ReplicationKey: repKey,
|
||||
}
|
||||
marshalled, err := header.Marshal()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
id, err := cid.NewCIDFromBytes(marshalled)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
spaceId := NewSpaceId(id, repKey)
|
||||
|
||||
// deriving and encrypting read key
|
||||
readKey, err := aclrecordproto.ACLReadKeyDerive(signPrivKey, encPrivKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
hasher = fnv.New64()
|
||||
_, err = hasher.Write(readKey.Bytes())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
readKeyHash := hasher.Sum64()
|
||||
encReadKey, err := payload.EncryptionKey.GetPublic().Encrypt(readKey.Bytes())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// preparing acl
|
||||
aclRoot := &aclrecordproto.ACLRoot{
|
||||
Identity: identity,
|
||||
EncryptionKey: encPubKey,
|
||||
SpaceId: spaceId,
|
||||
EncryptedReadKey: encReadKey,
|
||||
DerivationScheme: "",
|
||||
CurrentReadKeyHash: readKeyHash,
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
}
|
||||
rawWithId, err := marshalACLRoot(aclRoot, payload.SigningKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// creating storage
|
||||
storageCreate := storage.SpaceStorageCreatePayload{
|
||||
RecWithId: rawWithId,
|
||||
SpaceHeader: header,
|
||||
Id: id,
|
||||
}
|
||||
_, err = s.storageProvider.CreateSpaceStorage(storageCreate)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return s.GetSpace(ctx, spaceId)
|
||||
}
|
||||
|
||||
func (s *service) GetSpace(ctx context.Context, id string) (Space, error) {
|
||||
st, err := s.storageProvider.SpaceStorage(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lastConfiguration := s.configurationService.GetLast()
|
||||
diffService := diffservice.NewDiffService(id, s.config.SyncPeriod, st, lastConfiguration, s.cache, log)
|
||||
syncService := syncservice.NewSyncService(id, diffService, s.cache, lastConfiguration)
|
||||
sp := &space{
|
||||
id: id,
|
||||
syncService: syncService,
|
||||
diffService: diffService,
|
||||
cache: s.cache,
|
||||
storage: st,
|
||||
}
|
||||
if err := sp.Init(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sp, nil
|
||||
}
|
||||
|
||||
func marshalACLRoot(aclRoot *aclrecordproto.ACLRoot, key signingkey.PrivKey) (rawWithId *aclrecordproto.RawACLRecordWithId, err error) {
|
||||
marshalledRoot, err := aclRoot.Marshal()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
signature, err := key.Sign(marshalledRoot)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
raw := &aclrecordproto.RawACLRecord{
|
||||
Payload: marshalledRoot,
|
||||
Signature: signature,
|
||||
}
|
||||
marshalledRaw, err := raw.Marshal()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
aclHeadId, err := cid.NewCIDFromBytes(marshalledRaw)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rawWithId = &aclrecordproto.RawACLRecordWithId{
|
||||
Payload: marshalledRaw,
|
||||
Id: aclHeadId,
|
||||
}
|
||||
return
|
||||
}
|
||||
150
common/commonspace/space.go
Normal file
150
common/commonspace/space.go
Normal file
@ -0,0 +1,150 @@
|
||||
package commonspace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/cache"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/diffservice"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/syncservice"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/synctree"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list"
|
||||
treestorage "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type SpaceCreatePayload struct {
|
||||
SigningKey signingkey.PrivKey
|
||||
EncryptionKey encryptionkey.PrivKey
|
||||
SpaceType string
|
||||
ReadKey []byte
|
||||
ReplicationKey uint64
|
||||
}
|
||||
|
||||
const SpaceTypeDerived = "derived.space"
|
||||
|
||||
type SpaceDerivePayload struct {
|
||||
SigningKey signingkey.PrivKey
|
||||
EncryptionKey encryptionkey.PrivKey
|
||||
}
|
||||
|
||||
func NewSpaceId(id string, repKey uint64) string {
|
||||
return fmt.Sprintf("%s.%d", id, repKey)
|
||||
}
|
||||
|
||||
type Space interface {
|
||||
Id() string
|
||||
|
||||
SpaceSyncRpc() RpcHandler
|
||||
|
||||
DeriveTree(ctx context.Context, payload tree.ObjectTreeCreatePayload, listener synctree.UpdateListener) (tree.ObjectTree, error)
|
||||
CreateTree(ctx context.Context, payload tree.ObjectTreeCreatePayload, listener synctree.UpdateListener) (tree.ObjectTree, error)
|
||||
BuildTree(ctx context.Context, id string, listener synctree.UpdateListener) (tree.ObjectTree, error)
|
||||
|
||||
Close() error
|
||||
}
|
||||
|
||||
type space struct {
|
||||
id string
|
||||
mu sync.RWMutex
|
||||
|
||||
rpc *rpcHandler
|
||||
|
||||
syncService syncservice.SyncService
|
||||
diffService diffservice.DiffService
|
||||
storage storage.SpaceStorage
|
||||
cache cache.TreeCache
|
||||
aclList list.ACLList
|
||||
}
|
||||
|
||||
func (s *space) Id() string {
|
||||
return s.id
|
||||
}
|
||||
|
||||
func (s *space) Init(ctx context.Context) (err error) {
|
||||
s.rpc = &rpcHandler{s: s}
|
||||
initialIds, err := s.storage.StoredIds()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.diffService.Init(initialIds)
|
||||
s.syncService.Init()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *space) SpaceSyncRpc() RpcHandler {
|
||||
return s.rpc
|
||||
}
|
||||
|
||||
func (s *space) SyncService() syncservice.SyncService {
|
||||
return s.syncService
|
||||
}
|
||||
|
||||
func (s *space) DiffService() diffservice.DiffService {
|
||||
return s.diffService
|
||||
}
|
||||
|
||||
func (s *space) DeriveTree(ctx context.Context, payload tree.ObjectTreeCreatePayload, listener synctree.UpdateListener) (tree.ObjectTree, error) {
|
||||
return synctree.DeriveSyncTree(ctx, payload, s.syncService, listener, s.aclList, s.storage.CreateTreeStorage)
|
||||
}
|
||||
|
||||
func (s *space) CreateTree(ctx context.Context, payload tree.ObjectTreeCreatePayload, listener synctree.UpdateListener) (tree.ObjectTree, error) {
|
||||
return synctree.CreateSyncTree(ctx, payload, s.syncService, listener, s.aclList, s.storage.CreateTreeStorage)
|
||||
}
|
||||
|
||||
func (s *space) BuildTree(ctx context.Context, id string, listener synctree.UpdateListener) (t tree.ObjectTree, err error) {
|
||||
getTreeRemote := func() (*spacesyncproto.ObjectSyncMessage, error) {
|
||||
// TODO: add empty context handling (when this is not happening due to head update)
|
||||
peerId, err := syncservice.GetPeerIdFromStreamContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.syncService.StreamPool().SendSync(
|
||||
peerId,
|
||||
spacesyncproto.WrapFullRequest(&spacesyncproto.ObjectFullSyncRequest{}, nil, id, ""),
|
||||
)
|
||||
}
|
||||
|
||||
store, err := s.storage.TreeStorage(id)
|
||||
if err != nil && err != treestorage.ErrUnknownTreeId {
|
||||
return
|
||||
}
|
||||
|
||||
if err == treestorage.ErrUnknownTreeId {
|
||||
var resp *spacesyncproto.ObjectSyncMessage
|
||||
resp, err = getTreeRemote()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fullSyncResp := resp.GetContent().GetFullSyncResponse()
|
||||
|
||||
payload := treestorage.TreeStorageCreatePayload{
|
||||
TreeId: resp.TreeId,
|
||||
RootRawChange: resp.RootChange,
|
||||
Changes: fullSyncResp.Changes,
|
||||
Heads: fullSyncResp.Heads,
|
||||
}
|
||||
|
||||
// basically building tree with inmemory storage and validating that it was without errors
|
||||
err = tree.ValidateRawTree(payload, s.aclList)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// now we are sure that we can save it to the storage
|
||||
store, err = s.storage.CreateTreeStorage(payload)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return synctree.BuildSyncTree(ctx, s.syncService, store.(treestorage.TreeStorage), listener, s.aclList)
|
||||
}
|
||||
|
||||
func (s *space) Close() error {
|
||||
s.diffService.Close()
|
||||
return s.syncService.Close()
|
||||
}
|
||||
14
common/commonspace/spacesyncproto/errors.go
Normal file
14
common/commonspace/spacesyncproto/errors.go
Normal file
@ -0,0 +1,14 @@
|
||||
package spacesyncproto
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/rpc/rpcerr"
|
||||
)
|
||||
|
||||
var (
|
||||
errGroup = rpcerr.ErrGroup(ErrCodes_ErrorOffset)
|
||||
|
||||
ErrUnexpected = errGroup.Register(errors.New("unexpected error"), uint64(ErrCodes_Unexpected))
|
||||
ErrSpaceMissing = errGroup.Register(errors.New("space is missing"), uint64(ErrCodes_SpaceMissing))
|
||||
ErrSpaceExists = errGroup.Register(errors.New("space exists"), uint64(ErrCodes_SpaceMissing))
|
||||
)
|
||||
120
common/commonspace/spacesyncproto/protos/spacesync.proto
Normal file
120
common/commonspace/spacesyncproto/protos/spacesync.proto
Normal file
@ -0,0 +1,120 @@
|
||||
syntax = "proto3";
|
||||
package anySpace;
|
||||
|
||||
option go_package = "common/commonspace/spacesyncproto";
|
||||
import "pkg/acl/treechangeproto/protos/treechange.proto";
|
||||
import "pkg/acl/aclrecordproto/protos/aclrecord.proto";
|
||||
|
||||
enum ErrCodes {
|
||||
Unexpected = 0;
|
||||
SpaceMissing = 1;
|
||||
SpaceExists = 2;
|
||||
ErrorOffset = 100;
|
||||
}
|
||||
|
||||
service Space {
|
||||
// HeadSync compares all objects and their hashes in a space
|
||||
rpc HeadSync(HeadSyncRequest) returns (HeadSyncResponse);
|
||||
// PushSpace sends new space to the node
|
||||
rpc PushSpace(PushSpaceRequest) returns (PushSpaceResponse);
|
||||
// Stream opens object sync stream with node or client
|
||||
rpc Stream(stream ObjectSyncMessage) returns (stream ObjectSyncMessage);
|
||||
}
|
||||
|
||||
// HeadSyncRange presenting a request for one range
|
||||
message HeadSyncRange {
|
||||
uint64 from = 1;
|
||||
uint64 to = 2;
|
||||
uint32 limit = 3;
|
||||
}
|
||||
|
||||
// HeadSyncResult presenting a response for one range
|
||||
message HeadSyncResult {
|
||||
bytes hash = 1;
|
||||
repeated HeadSyncResultElement elements = 2;
|
||||
uint32 count = 3;
|
||||
}
|
||||
|
||||
// HeadSyncResultElement presenting state of one object
|
||||
message HeadSyncResultElement {
|
||||
string id = 1;
|
||||
string head = 2;
|
||||
}
|
||||
|
||||
// HeadSyncRequest is a request for HeadSync
|
||||
message HeadSyncRequest {
|
||||
string spaceId = 1;
|
||||
repeated HeadSyncRange ranges = 2;
|
||||
}
|
||||
|
||||
// HeadSyncResponse is a response for HeadSync
|
||||
message HeadSyncResponse {
|
||||
repeated HeadSyncResult results = 1;
|
||||
}
|
||||
|
||||
// ObjectSyncMessage is a message sent on object sync
|
||||
message ObjectSyncMessage {
|
||||
string spaceId = 1;
|
||||
ObjectSyncContentValue content = 2;
|
||||
treechange.RawTreeChangeWithId rootChange = 3;
|
||||
string treeId = 4;
|
||||
string trackingId = 5;
|
||||
|
||||
// string identity = 5;
|
||||
// string peerSignature = 6;
|
||||
}
|
||||
|
||||
// ObjectSyncContentValue provides different types for object sync
|
||||
message ObjectSyncContentValue {
|
||||
oneof value {
|
||||
ObjectHeadUpdate headUpdate = 1;
|
||||
ObjectFullSyncRequest fullSyncRequest = 2;
|
||||
ObjectFullSyncResponse fullSyncResponse = 3;
|
||||
ObjectErrorResponse errorResponse = 4;
|
||||
}
|
||||
}
|
||||
|
||||
// ObjectHeadUpdate is a message sent on document head update
|
||||
message ObjectHeadUpdate {
|
||||
repeated string heads = 1;
|
||||
repeated treechange.RawTreeChangeWithId changes = 2;
|
||||
repeated string snapshotPath = 3;
|
||||
}
|
||||
|
||||
// ObjectHeadUpdate is a message sent when document needs full sync
|
||||
message ObjectFullSyncRequest {
|
||||
repeated string heads = 1;
|
||||
repeated treechange.RawTreeChangeWithId changes = 2;
|
||||
repeated string snapshotPath = 3;
|
||||
}
|
||||
|
||||
// ObjectFullSyncResponse is a message sent as a response for a specific full sync
|
||||
message ObjectFullSyncResponse {
|
||||
repeated string heads = 1;
|
||||
repeated treechange.RawTreeChangeWithId changes = 2;
|
||||
repeated string snapshotPath = 3;
|
||||
}
|
||||
|
||||
// ObjectErrorResponse is an error sent as a response for a full sync request
|
||||
message ObjectErrorResponse {
|
||||
string error = 1;
|
||||
}
|
||||
|
||||
// PushSpaceRequest is a request to add space on a node containing only one acl record
|
||||
message PushSpaceRequest {
|
||||
string spaceId = 1;
|
||||
SpaceHeader spaceHeader = 2;
|
||||
aclrecord.RawACLRecordWithId aclRoot = 3;
|
||||
}
|
||||
|
||||
// PushSpaceResponse is an empty response
|
||||
message PushSpaceResponse {}
|
||||
|
||||
// SpaceHeader is a header for a space
|
||||
message SpaceHeader {
|
||||
bytes identity = 1;
|
||||
int64 timestamp = 2;
|
||||
string spaceType = 3;
|
||||
uint64 replicationKey = 4;
|
||||
bytes seed = 5;
|
||||
}
|
||||
45
common/commonspace/spacesyncproto/spacesync.go
Normal file
45
common/commonspace/spacesyncproto/spacesync.go
Normal file
@ -0,0 +1,45 @@
|
||||
package spacesyncproto
|
||||
|
||||
import "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treechangeproto"
|
||||
|
||||
type SpaceStream = DRPCSpace_StreamStream
|
||||
|
||||
func WrapHeadUpdate(update *ObjectHeadUpdate, rootChange *treechangeproto.RawTreeChangeWithId, treeId, trackingId string) *ObjectSyncMessage {
|
||||
return &ObjectSyncMessage{
|
||||
Content: &ObjectSyncContentValue{
|
||||
Value: &ObjectSyncContentValue_HeadUpdate{HeadUpdate: update},
|
||||
},
|
||||
RootChange: rootChange,
|
||||
TreeId: treeId,
|
||||
}
|
||||
}
|
||||
|
||||
func WrapFullRequest(request *ObjectFullSyncRequest, rootChange *treechangeproto.RawTreeChangeWithId, treeId, trackingId string) *ObjectSyncMessage {
|
||||
return &ObjectSyncMessage{
|
||||
Content: &ObjectSyncContentValue{
|
||||
Value: &ObjectSyncContentValue_FullSyncRequest{FullSyncRequest: request},
|
||||
},
|
||||
RootChange: rootChange,
|
||||
TreeId: treeId,
|
||||
}
|
||||
}
|
||||
|
||||
func WrapFullResponse(response *ObjectFullSyncResponse, rootChange *treechangeproto.RawTreeChangeWithId, treeId, trackingId string) *ObjectSyncMessage {
|
||||
return &ObjectSyncMessage{
|
||||
Content: &ObjectSyncContentValue{
|
||||
Value: &ObjectSyncContentValue_FullSyncResponse{FullSyncResponse: response},
|
||||
},
|
||||
RootChange: rootChange,
|
||||
TreeId: treeId,
|
||||
}
|
||||
}
|
||||
|
||||
func WrapError(err error, rootChange *treechangeproto.RawTreeChangeWithId, treeId, trackingId string) *ObjectSyncMessage {
|
||||
return &ObjectSyncMessage{
|
||||
Content: &ObjectSyncContentValue{
|
||||
Value: &ObjectSyncContentValue_ErrorResponse{ErrorResponse: &ObjectErrorResponse{Error: err.Error()}},
|
||||
},
|
||||
RootChange: rootChange,
|
||||
TreeId: treeId,
|
||||
}
|
||||
}
|
||||
4037
common/commonspace/spacesyncproto/spacesync.pb.go
Normal file
4037
common/commonspace/spacesyncproto/spacesync.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
228
common/commonspace/spacesyncproto/spacesync_drpc.pb.go
Normal file
228
common/commonspace/spacesyncproto/spacesync_drpc.pb.go
Normal file
@ -0,0 +1,228 @@
|
||||
// Code generated by protoc-gen-go-drpc. DO NOT EDIT.
|
||||
// protoc-gen-go-drpc version: v0.0.32
|
||||
// source: common/commonspace/spacesyncproto/protos/spacesync.proto
|
||||
|
||||
package spacesyncproto
|
||||
|
||||
import (
|
||||
bytes "bytes"
|
||||
context "context"
|
||||
errors "errors"
|
||||
jsonpb "github.com/gogo/protobuf/jsonpb"
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
drpc "storj.io/drpc"
|
||||
drpcerr "storj.io/drpc/drpcerr"
|
||||
)
|
||||
|
||||
type drpcEncoding_File_common_commonspace_spacesyncproto_protos_spacesync_proto struct{}
|
||||
|
||||
func (drpcEncoding_File_common_commonspace_spacesyncproto_protos_spacesync_proto) Marshal(msg drpc.Message) ([]byte, error) {
|
||||
return proto.Marshal(msg.(proto.Message))
|
||||
}
|
||||
|
||||
func (drpcEncoding_File_common_commonspace_spacesyncproto_protos_spacesync_proto) Unmarshal(buf []byte, msg drpc.Message) error {
|
||||
return proto.Unmarshal(buf, msg.(proto.Message))
|
||||
}
|
||||
|
||||
func (drpcEncoding_File_common_commonspace_spacesyncproto_protos_spacesync_proto) JSONMarshal(msg drpc.Message) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
err := new(jsonpb.Marshaler).Marshal(&buf, msg.(proto.Message))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (drpcEncoding_File_common_commonspace_spacesyncproto_protos_spacesync_proto) JSONUnmarshal(buf []byte, msg drpc.Message) error {
|
||||
return jsonpb.Unmarshal(bytes.NewReader(buf), msg.(proto.Message))
|
||||
}
|
||||
|
||||
type DRPCSpaceClient interface {
|
||||
DRPCConn() drpc.Conn
|
||||
|
||||
HeadSync(ctx context.Context, in *HeadSyncRequest) (*HeadSyncResponse, error)
|
||||
PushSpace(ctx context.Context, in *PushSpaceRequest) (*PushSpaceResponse, error)
|
||||
Stream(ctx context.Context) (DRPCSpace_StreamClient, error)
|
||||
}
|
||||
|
||||
type drpcSpaceClient struct {
|
||||
cc drpc.Conn
|
||||
}
|
||||
|
||||
func NewDRPCSpaceClient(cc drpc.Conn) DRPCSpaceClient {
|
||||
return &drpcSpaceClient{cc}
|
||||
}
|
||||
|
||||
func (c *drpcSpaceClient) DRPCConn() drpc.Conn { return c.cc }
|
||||
|
||||
func (c *drpcSpaceClient) HeadSync(ctx context.Context, in *HeadSyncRequest) (*HeadSyncResponse, error) {
|
||||
out := new(HeadSyncResponse)
|
||||
err := c.cc.Invoke(ctx, "/anySpace.Space/HeadSync", drpcEncoding_File_common_commonspace_spacesyncproto_protos_spacesync_proto{}, in, out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *drpcSpaceClient) PushSpace(ctx context.Context, in *PushSpaceRequest) (*PushSpaceResponse, error) {
|
||||
out := new(PushSpaceResponse)
|
||||
err := c.cc.Invoke(ctx, "/anySpace.Space/PushSpace", drpcEncoding_File_common_commonspace_spacesyncproto_protos_spacesync_proto{}, in, out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *drpcSpaceClient) Stream(ctx context.Context) (DRPCSpace_StreamClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, "/anySpace.Space/Stream", drpcEncoding_File_common_commonspace_spacesyncproto_protos_spacesync_proto{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &drpcSpace_StreamClient{stream}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type DRPCSpace_StreamClient interface {
|
||||
drpc.Stream
|
||||
Send(*ObjectSyncMessage) error
|
||||
Recv() (*ObjectSyncMessage, error)
|
||||
}
|
||||
|
||||
type drpcSpace_StreamClient struct {
|
||||
drpc.Stream
|
||||
}
|
||||
|
||||
func (x *drpcSpace_StreamClient) Send(m *ObjectSyncMessage) error {
|
||||
return x.MsgSend(m, drpcEncoding_File_common_commonspace_spacesyncproto_protos_spacesync_proto{})
|
||||
}
|
||||
|
||||
func (x *drpcSpace_StreamClient) Recv() (*ObjectSyncMessage, error) {
|
||||
m := new(ObjectSyncMessage)
|
||||
if err := x.MsgRecv(m, drpcEncoding_File_common_commonspace_spacesyncproto_protos_spacesync_proto{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (x *drpcSpace_StreamClient) RecvMsg(m *ObjectSyncMessage) error {
|
||||
return x.MsgRecv(m, drpcEncoding_File_common_commonspace_spacesyncproto_protos_spacesync_proto{})
|
||||
}
|
||||
|
||||
type DRPCSpaceServer interface {
|
||||
HeadSync(context.Context, *HeadSyncRequest) (*HeadSyncResponse, error)
|
||||
PushSpace(context.Context, *PushSpaceRequest) (*PushSpaceResponse, error)
|
||||
Stream(DRPCSpace_StreamStream) error
|
||||
}
|
||||
|
||||
type DRPCSpaceUnimplementedServer struct{}
|
||||
|
||||
func (s *DRPCSpaceUnimplementedServer) HeadSync(context.Context, *HeadSyncRequest) (*HeadSyncResponse, error) {
|
||||
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
|
||||
}
|
||||
|
||||
func (s *DRPCSpaceUnimplementedServer) PushSpace(context.Context, *PushSpaceRequest) (*PushSpaceResponse, error) {
|
||||
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
|
||||
}
|
||||
|
||||
func (s *DRPCSpaceUnimplementedServer) Stream(DRPCSpace_StreamStream) error {
|
||||
return drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
|
||||
}
|
||||
|
||||
type DRPCSpaceDescription struct{}
|
||||
|
||||
func (DRPCSpaceDescription) NumMethods() int { return 3 }
|
||||
|
||||
func (DRPCSpaceDescription) Method(n int) (string, drpc.Encoding, drpc.Receiver, interface{}, bool) {
|
||||
switch n {
|
||||
case 0:
|
||||
return "/anySpace.Space/HeadSync", drpcEncoding_File_common_commonspace_spacesyncproto_protos_spacesync_proto{},
|
||||
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
|
||||
return srv.(DRPCSpaceServer).
|
||||
HeadSync(
|
||||
ctx,
|
||||
in1.(*HeadSyncRequest),
|
||||
)
|
||||
}, DRPCSpaceServer.HeadSync, true
|
||||
case 1:
|
||||
return "/anySpace.Space/PushSpace", drpcEncoding_File_common_commonspace_spacesyncproto_protos_spacesync_proto{},
|
||||
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
|
||||
return srv.(DRPCSpaceServer).
|
||||
PushSpace(
|
||||
ctx,
|
||||
in1.(*PushSpaceRequest),
|
||||
)
|
||||
}, DRPCSpaceServer.PushSpace, true
|
||||
case 2:
|
||||
return "/anySpace.Space/Stream", drpcEncoding_File_common_commonspace_spacesyncproto_protos_spacesync_proto{},
|
||||
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
|
||||
return nil, srv.(DRPCSpaceServer).
|
||||
Stream(
|
||||
&drpcSpace_StreamStream{in1.(drpc.Stream)},
|
||||
)
|
||||
}, DRPCSpaceServer.Stream, true
|
||||
default:
|
||||
return "", nil, nil, nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func DRPCRegisterSpace(mux drpc.Mux, impl DRPCSpaceServer) error {
|
||||
return mux.Register(impl, DRPCSpaceDescription{})
|
||||
}
|
||||
|
||||
type DRPCSpace_HeadSyncStream interface {
|
||||
drpc.Stream
|
||||
SendAndClose(*HeadSyncResponse) error
|
||||
}
|
||||
|
||||
type drpcSpace_HeadSyncStream struct {
|
||||
drpc.Stream
|
||||
}
|
||||
|
||||
func (x *drpcSpace_HeadSyncStream) SendAndClose(m *HeadSyncResponse) error {
|
||||
if err := x.MsgSend(m, drpcEncoding_File_common_commonspace_spacesyncproto_protos_spacesync_proto{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return x.CloseSend()
|
||||
}
|
||||
|
||||
type DRPCSpace_PushSpaceStream interface {
|
||||
drpc.Stream
|
||||
SendAndClose(*PushSpaceResponse) error
|
||||
}
|
||||
|
||||
type drpcSpace_PushSpaceStream struct {
|
||||
drpc.Stream
|
||||
}
|
||||
|
||||
func (x *drpcSpace_PushSpaceStream) SendAndClose(m *PushSpaceResponse) error {
|
||||
if err := x.MsgSend(m, drpcEncoding_File_common_commonspace_spacesyncproto_protos_spacesync_proto{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return x.CloseSend()
|
||||
}
|
||||
|
||||
type DRPCSpace_StreamStream interface {
|
||||
drpc.Stream
|
||||
Send(*ObjectSyncMessage) error
|
||||
Recv() (*ObjectSyncMessage, error)
|
||||
}
|
||||
|
||||
type drpcSpace_StreamStream struct {
|
||||
drpc.Stream
|
||||
}
|
||||
|
||||
func (x *drpcSpace_StreamStream) Send(m *ObjectSyncMessage) error {
|
||||
return x.MsgSend(m, drpcEncoding_File_common_commonspace_spacesyncproto_protos_spacesync_proto{})
|
||||
}
|
||||
|
||||
func (x *drpcSpace_StreamStream) Recv() (*ObjectSyncMessage, error) {
|
||||
m := new(ObjectSyncMessage)
|
||||
if err := x.MsgRecv(m, drpcEncoding_File_common_commonspace_spacesyncproto_protos_spacesync_proto{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (x *drpcSpace_StreamStream) RecvMsg(m *ObjectSyncMessage) error {
|
||||
return x.MsgRecv(m, drpcEncoding_File_common_commonspace_spacesyncproto_protos_spacesync_proto{})
|
||||
}
|
||||
32
common/commonspace/storage/storage.go
Normal file
32
common/commonspace/storage/storage.go
Normal file
@ -0,0 +1,32 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclrecordproto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage"
|
||||
)
|
||||
|
||||
const CName = "commonspace.storage"
|
||||
|
||||
var ErrSpaceStorageExists = errors.New("space storage exists")
|
||||
|
||||
type SpaceStorage interface {
|
||||
storage.Provider
|
||||
ACLStorage() (storage.ListStorage, error)
|
||||
SpaceHeader() (*spacesyncproto.SpaceHeader, error)
|
||||
StoredIds() ([]string, error)
|
||||
}
|
||||
|
||||
type SpaceStorageCreatePayload struct {
|
||||
RecWithId *aclrecordproto.RawACLRecordWithId
|
||||
SpaceHeader *spacesyncproto.SpaceHeader
|
||||
Id string
|
||||
}
|
||||
|
||||
type SpaceStorageProvider interface {
|
||||
app.Component
|
||||
SpaceStorage(id string) (SpaceStorage, error)
|
||||
CreateSpaceStorage(payload SpaceStorageCreatePayload) (SpaceStorage, error)
|
||||
}
|
||||
239
common/commonspace/syncservice/streampool.go
Normal file
239
common/commonspace/syncservice/streampool.go
Normal file
@ -0,0 +1,239 @@
|
||||
package syncservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
|
||||
"github.com/libp2p/go-libp2p-core/sec"
|
||||
"storj.io/drpc/drpcctx"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
var ErrEmptyPeer = errors.New("don't have such a peer")
|
||||
var ErrStreamClosed = errors.New("stream is already closed")
|
||||
|
||||
const maxSimultaneousOperationsPerStream = 10
|
||||
|
||||
// StreamPool can be made generic to work with different streams
|
||||
type StreamPool interface {
|
||||
SyncClient
|
||||
AddAndReadStreamSync(stream spacesyncproto.SpaceStream) (err error)
|
||||
AddAndReadStreamAsync(stream spacesyncproto.SpaceStream)
|
||||
HasActiveStream(peerId string) bool
|
||||
Close() (err error)
|
||||
}
|
||||
|
||||
type SyncClient interface {
|
||||
SendSync(peerId string, message *spacesyncproto.ObjectSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error)
|
||||
SendAsync(peerId string, message *spacesyncproto.ObjectSyncMessage) (err error)
|
||||
BroadcastAsync(message *spacesyncproto.ObjectSyncMessage) (err error)
|
||||
}
|
||||
|
||||
type MessageHandler func(ctx context.Context, senderId string, message *spacesyncproto.ObjectSyncMessage) (err error)
|
||||
|
||||
type responseWaiter struct {
|
||||
ch chan *spacesyncproto.ObjectSyncMessage
|
||||
}
|
||||
|
||||
type streamPool struct {
|
||||
sync.Mutex
|
||||
peerStreams map[string]spacesyncproto.SpaceStream
|
||||
messageHandler MessageHandler
|
||||
wg *sync.WaitGroup
|
||||
waiters map[string]responseWaiter
|
||||
waitersMx sync.Mutex
|
||||
counter uint64
|
||||
}
|
||||
|
||||
func newStreamPool(messageHandler MessageHandler) StreamPool {
|
||||
return &streamPool{
|
||||
peerStreams: make(map[string]spacesyncproto.SpaceStream),
|
||||
messageHandler: messageHandler,
|
||||
wg: &sync.WaitGroup{},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *streamPool) HasActiveStream(peerId string) (res bool) {
|
||||
_, err := s.getOrDeleteStream(peerId)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (s *streamPool) SendSync(
|
||||
peerId string,
|
||||
msg *spacesyncproto.ObjectSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error) {
|
||||
newCounter := atomic.AddUint64(&s.counter, 1)
|
||||
msg.TrackingId = genStreamPoolKey(peerId, msg.TreeId, newCounter)
|
||||
|
||||
s.waitersMx.Lock()
|
||||
waiter := responseWaiter{
|
||||
ch: make(chan *spacesyncproto.ObjectSyncMessage),
|
||||
}
|
||||
s.waiters[msg.TrackingId] = waiter
|
||||
s.waitersMx.Unlock()
|
||||
|
||||
err = s.SendAsync(peerId, msg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
reply = <-waiter.ch
|
||||
return
|
||||
}
|
||||
|
||||
func (s *streamPool) SendAsync(peerId string, message *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
stream, err := s.getOrDeleteStream(peerId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return stream.Send(message)
|
||||
}
|
||||
|
||||
func (s *streamPool) getOrDeleteStream(id string) (stream spacesyncproto.SpaceStream, err error) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
stream, exists := s.peerStreams[id]
|
||||
if !exists {
|
||||
err = ErrEmptyPeer
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case <-stream.Context().Done():
|
||||
delete(s.peerStreams, id)
|
||||
err = ErrStreamClosed
|
||||
default:
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *streamPool) getAllStreams() (streams []spacesyncproto.SpaceStream) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
Loop:
|
||||
for id, stream := range s.peerStreams {
|
||||
select {
|
||||
case <-stream.Context().Done():
|
||||
delete(s.peerStreams, id)
|
||||
continue Loop
|
||||
default:
|
||||
break
|
||||
}
|
||||
streams = append(streams, stream)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *streamPool) BroadcastAsync(message *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
streams := s.getAllStreams()
|
||||
for _, stream := range streams {
|
||||
if err = stream.Send(message); err != nil {
|
||||
// TODO: add logging
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *streamPool) AddAndReadStreamAsync(stream spacesyncproto.SpaceStream) {
|
||||
go s.AddAndReadStreamSync(stream)
|
||||
}
|
||||
|
||||
func (s *streamPool) AddAndReadStreamSync(stream spacesyncproto.SpaceStream) (err error) {
|
||||
s.Lock()
|
||||
peerId, err := GetPeerIdFromStreamContext(stream.Context())
|
||||
if err != nil {
|
||||
s.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
s.peerStreams[peerId] = stream
|
||||
s.wg.Add(1)
|
||||
s.Unlock()
|
||||
|
||||
return s.readPeerLoop(peerId, stream)
|
||||
}
|
||||
|
||||
func (s *streamPool) Close() (err error) {
|
||||
s.Lock()
|
||||
wg := s.wg
|
||||
s.Unlock()
|
||||
if wg != nil {
|
||||
wg.Wait()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *streamPool) readPeerLoop(peerId string, stream spacesyncproto.SpaceStream) (err error) {
|
||||
defer s.wg.Done()
|
||||
limiter := make(chan struct{}, maxSimultaneousOperationsPerStream)
|
||||
for i := 0; i < maxSimultaneousOperationsPerStream; i++ {
|
||||
limiter <- struct{}{}
|
||||
}
|
||||
|
||||
process := func(msg *spacesyncproto.ObjectSyncMessage) {
|
||||
if msg.TrackingId == "" {
|
||||
s.messageHandler(stream.Context(), peerId, msg)
|
||||
return
|
||||
}
|
||||
|
||||
s.waitersMx.Lock()
|
||||
waiter, exists := s.waiters[msg.TrackingId]
|
||||
|
||||
if !exists {
|
||||
s.waitersMx.Unlock()
|
||||
s.messageHandler(stream.Context(), peerId, msg)
|
||||
return
|
||||
}
|
||||
|
||||
delete(s.waiters, msg.TrackingId)
|
||||
s.waitersMx.Unlock()
|
||||
waiter.ch <- msg
|
||||
}
|
||||
|
||||
Loop:
|
||||
for {
|
||||
msg, err := stream.Recv()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
select {
|
||||
case <-limiter:
|
||||
case <-stream.Context().Done():
|
||||
break Loop
|
||||
}
|
||||
go func() {
|
||||
process(msg)
|
||||
limiter <- struct{}{}
|
||||
}()
|
||||
}
|
||||
return s.removePeer(peerId)
|
||||
}
|
||||
|
||||
func (s *streamPool) removePeer(peerId string) (err error) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
_, ok := s.peerStreams[peerId]
|
||||
if !ok {
|
||||
return ErrEmptyPeer
|
||||
}
|
||||
delete(s.peerStreams, peerId)
|
||||
return
|
||||
}
|
||||
|
||||
func GetPeerIdFromStreamContext(ctx context.Context) (string, error) {
|
||||
conn, ok := ctx.Value(drpcctx.TransportKey{}).(sec.SecureConn)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("incorrect connection type in stream")
|
||||
}
|
||||
|
||||
return conn.RemotePeer().String(), nil
|
||||
}
|
||||
|
||||
func genStreamPoolKey(peerId, treeId string, counter uint64) string {
|
||||
return fmt.Sprintf("%s.%s.%d", peerId, treeId, counter)
|
||||
}
|
||||
199
common/commonspace/syncservice/synchandler.go
Normal file
199
common/commonspace/syncservice/synchandler.go
Normal file
@ -0,0 +1,199 @@
|
||||
package syncservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/cache"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treechangeproto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice"
|
||||
)
|
||||
|
||||
type syncHandler struct {
|
||||
spaceId string
|
||||
treeCache cache.TreeCache
|
||||
syncClient SyncClient
|
||||
}
|
||||
|
||||
type SyncHandler interface {
|
||||
HandleMessage(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (err error)
|
||||
}
|
||||
|
||||
func newSyncHandler(spaceId string, treeCache cache.TreeCache, syncClient SyncClient) *syncHandler {
|
||||
return &syncHandler{
|
||||
spaceId: spaceId,
|
||||
treeCache: treeCache,
|
||||
syncClient: syncClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *syncHandler) HandleMessage(ctx context.Context, senderId string, msg *spacesyncproto.ObjectSyncMessage) error {
|
||||
content := msg.GetContent()
|
||||
switch {
|
||||
case content.GetFullSyncRequest() != nil:
|
||||
return s.HandleFullSyncRequest(ctx, senderId, content.GetFullSyncRequest(), msg)
|
||||
case content.GetFullSyncResponse() != nil:
|
||||
return s.HandleFullSyncResponse(ctx, senderId, content.GetFullSyncResponse(), msg)
|
||||
case content.GetHeadUpdate() != nil:
|
||||
return s.HandleHeadUpdate(ctx, senderId, content.GetHeadUpdate(), msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *syncHandler) HandleHeadUpdate(
|
||||
ctx context.Context,
|
||||
senderId string,
|
||||
update *spacesyncproto.ObjectHeadUpdate,
|
||||
msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
|
||||
var (
|
||||
fullRequest *spacesyncproto.ObjectFullSyncRequest
|
||||
result tree.AddResult
|
||||
)
|
||||
res, err := s.treeCache.GetTree(ctx, s.spaceId, msg.TreeId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = func() error {
|
||||
objTree := res.TreeContainer.Tree()
|
||||
objTree.Lock()
|
||||
defer res.Release()
|
||||
defer objTree.Unlock()
|
||||
|
||||
if slice.UnsortedEquals(update.Heads, objTree.Heads()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
result, err = objTree.AddRawChanges(ctx, update.Changes...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if we couldn't add all the changes
|
||||
if len(update.Changes) != len(result.Added) {
|
||||
fullRequest, err = s.prepareFullSyncRequest(objTree, update)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
|
||||
if fullRequest != nil {
|
||||
return s.syncClient.SendAsync(senderId,
|
||||
spacesyncproto.WrapFullRequest(fullRequest, msg.RootChange, msg.TreeId, msg.TrackingId))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *syncHandler) HandleFullSyncRequest(
|
||||
ctx context.Context,
|
||||
senderId string,
|
||||
request *spacesyncproto.ObjectFullSyncRequest,
|
||||
msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
var (
|
||||
fullResponse *spacesyncproto.ObjectFullSyncResponse
|
||||
header = msg.RootChange
|
||||
)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
s.syncClient.SendAsync(senderId, spacesyncproto.WrapError(err, header, msg.TreeId, msg.TrackingId))
|
||||
}
|
||||
}()
|
||||
|
||||
res, err := s.treeCache.GetTree(ctx, s.spaceId, msg.TreeId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = func() error {
|
||||
objTree := res.TreeContainer.Tree()
|
||||
objTree.Lock()
|
||||
defer res.Release()
|
||||
defer objTree.Unlock()
|
||||
|
||||
if header == nil {
|
||||
header = objTree.Header()
|
||||
}
|
||||
|
||||
_, err = objTree.AddRawChanges(ctx, request.Changes...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fullResponse, err = s.prepareFullSyncResponse(request.SnapshotPath, request.Heads, objTree)
|
||||
return err
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return s.syncClient.SendAsync(senderId,
|
||||
spacesyncproto.WrapFullResponse(fullResponse, header, msg.TreeId, msg.TrackingId))
|
||||
}
|
||||
|
||||
func (s *syncHandler) HandleFullSyncResponse(
|
||||
ctx context.Context,
|
||||
senderId string,
|
||||
response *spacesyncproto.ObjectFullSyncResponse,
|
||||
msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
res, err := s.treeCache.GetTree(ctx, s.spaceId, msg.TreeId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = func() error {
|
||||
objTree := res.TreeContainer.Tree()
|
||||
objTree.Lock()
|
||||
defer res.Release()
|
||||
defer objTree.Unlock()
|
||||
|
||||
// if we already have the heads for whatever reason
|
||||
if slice.UnsortedEquals(response.Heads, objTree.Heads()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = objTree.AddRawChanges(ctx, response.Changes...)
|
||||
return err
|
||||
}()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *syncHandler) prepareFullSyncRequest(
|
||||
t tree.ObjectTree,
|
||||
update *spacesyncproto.ObjectHeadUpdate) (req *spacesyncproto.ObjectFullSyncRequest, err error) {
|
||||
req = &spacesyncproto.ObjectFullSyncRequest{
|
||||
Heads: t.Heads(),
|
||||
SnapshotPath: t.SnapshotPath(),
|
||||
}
|
||||
if len(update.Changes) != 0 {
|
||||
var changesAfterSnapshot []*treechangeproto.RawTreeChangeWithId
|
||||
changesAfterSnapshot, err = t.ChangesAfterCommonSnapshot(update.SnapshotPath, update.Heads)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req.Changes = changesAfterSnapshot
|
||||
}
|
||||
return &spacesyncproto.ObjectFullSyncRequest{
|
||||
Heads: t.Heads(),
|
||||
SnapshotPath: t.SnapshotPath(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *syncHandler) prepareFullSyncResponse(
|
||||
theirPath,
|
||||
theirHeads []string,
|
||||
t tree.ObjectTree) (*spacesyncproto.ObjectFullSyncResponse, error) {
|
||||
ourChanges, err := t.ChangesAfterCommonSnapshot(theirPath, theirHeads)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &spacesyncproto.ObjectFullSyncResponse{
|
||||
Heads: t.Heads(),
|
||||
Changes: ourChanges,
|
||||
SnapshotPath: t.SnapshotPath(),
|
||||
}, nil
|
||||
}
|
||||
136
common/commonspace/syncservice/syncservice.go
Normal file
136
common/commonspace/syncservice/syncservice.go
Normal file
@ -0,0 +1,136 @@
|
||||
package syncservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/cache"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/rpc/rpcerr"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/nodeconf"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treechangeproto"
|
||||
"time"
|
||||
)
|
||||
|
||||
var log = logger.NewNamed("syncservice").Sugar()
|
||||
|
||||
type SyncService interface {
|
||||
NotifyHeadUpdate(
|
||||
ctx context.Context,
|
||||
treeId string,
|
||||
root *treechangeproto.RawTreeChangeWithId,
|
||||
update *spacesyncproto.ObjectHeadUpdate) (err error)
|
||||
StreamPool() StreamPool
|
||||
|
||||
Init()
|
||||
Close() (err error)
|
||||
}
|
||||
|
||||
type HeadNotifiable interface {
|
||||
UpdateHeads(id string, heads []string)
|
||||
}
|
||||
|
||||
const respPeersStreamCheckInterval = time.Second * 10
|
||||
|
||||
type syncService struct {
|
||||
spaceId string
|
||||
|
||||
syncHandler SyncHandler
|
||||
streamPool StreamPool
|
||||
headNotifiable HeadNotifiable
|
||||
configuration nodeconf.Configuration
|
||||
|
||||
streamLoopCtx context.Context
|
||||
stopStreamLoop context.CancelFunc
|
||||
streamLoopDone chan struct{}
|
||||
}
|
||||
|
||||
func NewSyncService(spaceId string, headNotifiable HeadNotifiable, cache cache.TreeCache, configuration nodeconf.Configuration) SyncService {
|
||||
var syncHandler SyncHandler
|
||||
streamPool := newStreamPool(func(ctx context.Context, senderId string, message *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
return syncHandler.HandleMessage(ctx, senderId, message)
|
||||
})
|
||||
syncHandler = newSyncHandler(spaceId, cache, streamPool)
|
||||
return newSyncService(spaceId, headNotifiable, syncHandler, streamPool, configuration)
|
||||
}
|
||||
|
||||
func newSyncService(
|
||||
spaceId string,
|
||||
headNotifiable HeadNotifiable,
|
||||
syncHandler SyncHandler,
|
||||
streamPool StreamPool,
|
||||
configuration nodeconf.Configuration) *syncService {
|
||||
return &syncService{
|
||||
syncHandler: syncHandler,
|
||||
streamPool: streamPool,
|
||||
headNotifiable: headNotifiable,
|
||||
configuration: configuration,
|
||||
spaceId: spaceId,
|
||||
streamLoopDone: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *syncService) Init() {
|
||||
s.streamLoopCtx, s.stopStreamLoop = context.WithCancel(context.Background())
|
||||
go s.responsibleStreamCheckLoop(s.streamLoopCtx)
|
||||
}
|
||||
|
||||
func (s *syncService) Close() (err error) {
|
||||
s.stopStreamLoop()
|
||||
<-s.streamLoopDone
|
||||
return s.streamPool.Close()
|
||||
}
|
||||
|
||||
func (s *syncService) NotifyHeadUpdate(
|
||||
ctx context.Context,
|
||||
treeId string,
|
||||
header *treechangeproto.RawTreeChangeWithId,
|
||||
update *spacesyncproto.ObjectHeadUpdate) (err error) {
|
||||
s.headNotifiable.UpdateHeads(treeId, update.Heads)
|
||||
return s.streamPool.BroadcastAsync(spacesyncproto.WrapHeadUpdate(update, header, treeId, ""))
|
||||
}
|
||||
|
||||
func (s *syncService) responsibleStreamCheckLoop(ctx context.Context) {
|
||||
defer close(s.streamLoopDone)
|
||||
checkResponsiblePeers := func() {
|
||||
respPeers, err := s.configuration.ResponsiblePeers(ctx, s.spaceId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, peer := range respPeers {
|
||||
if s.streamPool.HasActiveStream(peer.Id()) {
|
||||
continue
|
||||
}
|
||||
cl := spacesyncproto.NewDRPCSpaceClient(peer)
|
||||
stream, err := cl.Stream(ctx)
|
||||
if err != nil {
|
||||
// so here probably the request is failed because there is no such space,
|
||||
// but diffService should handle such cases by sending pushSpace
|
||||
continue
|
||||
}
|
||||
// sending empty message for the server to understand from which space is it coming
|
||||
err = stream.Send(&spacesyncproto.ObjectSyncMessage{SpaceId: s.spaceId})
|
||||
if err != nil {
|
||||
err = rpcerr.Unwrap(err)
|
||||
log.With("spaceId", s.spaceId).Errorf("failed to open stream: %v", err)
|
||||
continue
|
||||
}
|
||||
s.streamPool.AddAndReadStreamAsync(stream)
|
||||
}
|
||||
}
|
||||
|
||||
checkResponsiblePeers()
|
||||
ticker := time.NewTicker(respPeersStreamCheckInterval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-s.streamLoopCtx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
checkResponsiblePeers()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *syncService) StreamPool() StreamPool {
|
||||
return s.streamPool
|
||||
}
|
||||
142
common/commonspace/synctree/synctree.go
Normal file
142
common/commonspace/synctree/synctree.go
Normal file
@ -0,0 +1,142 @@
|
||||
package synctree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/syncservice"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treechangeproto"
|
||||
)
|
||||
|
||||
type UpdateListener interface {
|
||||
Update(tree tree.ObjectTree)
|
||||
Rebuild(tree tree.ObjectTree)
|
||||
}
|
||||
|
||||
// SyncTree sends head updates to sync service and also sends new changes to update listener
|
||||
type SyncTree struct {
|
||||
tree.ObjectTree
|
||||
syncService syncservice.SyncService
|
||||
listener UpdateListener
|
||||
}
|
||||
|
||||
func DeriveSyncTree(
|
||||
ctx context.Context,
|
||||
payload tree.ObjectTreeCreatePayload,
|
||||
syncService syncservice.SyncService,
|
||||
listener UpdateListener,
|
||||
aclList list.ACLList,
|
||||
createStorage storage.TreeStorageCreatorFunc) (t tree.ObjectTree, err error) {
|
||||
t, err = tree.CreateDerivedObjectTree(payload, aclList, createStorage)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
t = &SyncTree{
|
||||
ObjectTree: t,
|
||||
syncService: syncService,
|
||||
listener: listener,
|
||||
}
|
||||
|
||||
err = syncService.NotifyHeadUpdate(ctx, t.ID(), t.Header(), &spacesyncproto.ObjectHeadUpdate{
|
||||
Heads: t.Heads(),
|
||||
SnapshotPath: t.SnapshotPath(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func CreateSyncTree(
|
||||
ctx context.Context,
|
||||
payload tree.ObjectTreeCreatePayload,
|
||||
syncService syncservice.SyncService,
|
||||
listener UpdateListener,
|
||||
aclList list.ACLList,
|
||||
createStorage storage.TreeStorageCreatorFunc) (t tree.ObjectTree, err error) {
|
||||
t, err = tree.CreateObjectTree(payload, aclList, createStorage)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
t = &SyncTree{
|
||||
ObjectTree: t,
|
||||
syncService: syncService,
|
||||
listener: listener,
|
||||
}
|
||||
|
||||
err = syncService.NotifyHeadUpdate(ctx, t.ID(), t.Header(), &spacesyncproto.ObjectHeadUpdate{
|
||||
Heads: t.Heads(),
|
||||
SnapshotPath: t.SnapshotPath(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func BuildSyncTree(
|
||||
ctx context.Context,
|
||||
syncService syncservice.SyncService,
|
||||
treeStorage storage.TreeStorage,
|
||||
listener UpdateListener,
|
||||
aclList list.ACLList) (t tree.ObjectTree, err error) {
|
||||
return buildSyncTree(ctx, syncService, treeStorage, listener, aclList)
|
||||
}
|
||||
|
||||
func buildSyncTree(
|
||||
ctx context.Context,
|
||||
syncService syncservice.SyncService,
|
||||
treeStorage storage.TreeStorage,
|
||||
listener UpdateListener,
|
||||
aclList list.ACLList) (t tree.ObjectTree, err error) {
|
||||
t, err = tree.BuildObjectTree(treeStorage, aclList)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
t = &SyncTree{
|
||||
ObjectTree: t,
|
||||
syncService: syncService,
|
||||
listener: listener,
|
||||
}
|
||||
|
||||
err = syncService.NotifyHeadUpdate(ctx, t.ID(), t.Header(), &spacesyncproto.ObjectHeadUpdate{
|
||||
Heads: t.Heads(),
|
||||
SnapshotPath: t.SnapshotPath(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (s *SyncTree) AddContent(ctx context.Context, content tree.SignableChangeContent) (res tree.AddResult, err error) {
|
||||
res, err = s.AddContent(ctx, content)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = s.syncService.NotifyHeadUpdate(ctx, s.ID(), s.Header(), &spacesyncproto.ObjectHeadUpdate{
|
||||
Heads: res.Heads,
|
||||
Changes: res.Added,
|
||||
SnapshotPath: s.SnapshotPath(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (s *SyncTree) AddRawChanges(ctx context.Context, changes ...*treechangeproto.RawTreeChangeWithId) (res tree.AddResult, err error) {
|
||||
res, err = s.AddRawChanges(ctx, changes...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch res.Mode {
|
||||
case tree.Nothing:
|
||||
return
|
||||
case tree.Append:
|
||||
s.listener.Update(s)
|
||||
case tree.Rebuild:
|
||||
s.listener.Rebuild(s)
|
||||
}
|
||||
|
||||
err = s.syncService.NotifyHeadUpdate(ctx, s.ID(), s.Header(), &spacesyncproto.ObjectHeadUpdate{
|
||||
Heads: res.Heads,
|
||||
Changes: res.Added,
|
||||
SnapshotPath: s.SnapshotPath(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (s *SyncTree) Tree() tree.ObjectTree {
|
||||
return s
|
||||
}
|
||||
@ -5,10 +5,9 @@ import (
|
||||
"errors"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/peer"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/secure"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/config"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/net/peer"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/net/rpc"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/net/secure"
|
||||
"github.com/libp2p/go-libp2p-core/sec"
|
||||
"go.uber.org/zap"
|
||||
"net"
|
||||
@ -40,7 +39,7 @@ type dialer struct {
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func (d *dialer) Init(ctx context.Context, a *app.App) (err error) {
|
||||
func (d *dialer) Init(a *app.App) (err error) {
|
||||
d.transport = a.MustComponent(secure.CName).(secure.Service)
|
||||
nodes := a.MustComponent(config.CName).(*config.Config).Nodes
|
||||
d.peerAddrs = map[string][]string{}
|
||||
@ -60,7 +59,7 @@ func (d *dialer) UpdateAddrs(addrs map[string][]string) {
|
||||
d.mu.Unlock()
|
||||
}
|
||||
|
||||
func (d *dialer) Dial(ctx context.Context, peerId string) (peer peer.Peer, err error) {
|
||||
func (d *dialer) Dial(ctx context.Context, peerId string) (p peer.Peer, err error) {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
addrs, ok := d.peerAddrs[peerId]
|
||||
@ -68,11 +67,11 @@ func (d *dialer) Dial(ctx context.Context, peerId string) (peer peer.Peer, err e
|
||||
return nil, ErrArrdsNotFound
|
||||
}
|
||||
var (
|
||||
stream drpc.Stream
|
||||
sc sec.SecureConn
|
||||
conn drpc.Conn
|
||||
sc sec.SecureConn
|
||||
)
|
||||
for _, addr := range addrs {
|
||||
stream, sc, err = d.makeStream(ctx, addr)
|
||||
conn, sc, err = d.handshake(ctx, addr)
|
||||
if err != nil {
|
||||
log.Info("can't connect to host", zap.String("addr", addr))
|
||||
} else {
|
||||
@ -83,10 +82,10 @@ func (d *dialer) Dial(ctx context.Context, peerId string) (peer peer.Peer, err e
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return rpc.PeerFromStream(sc, stream, false), nil
|
||||
return peer.NewPeer(sc, conn), nil
|
||||
}
|
||||
|
||||
func (d *dialer) makeStream(ctx context.Context, addr string) (stream drpc.Stream, sc sec.SecureConn, err error) {
|
||||
func (d *dialer) handshake(ctx context.Context, addr string) (conn drpc.Conn, sc sec.SecureConn, err error) {
|
||||
tcpConn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
return
|
||||
@ -96,9 +95,6 @@ func (d *dialer) makeStream(ctx context.Context, addr string) (stream drpc.Strea
|
||||
return
|
||||
}
|
||||
log.Info("connected with remote host", zap.String("serverPeer", sc.RemotePeer().String()), zap.String("per", sc.LocalPeer().String()))
|
||||
stream, err = drpcconn.New(sc).NewStream(ctx, "", rpc.Encoding)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return stream, sc, err
|
||||
conn = drpcconn.New(sc)
|
||||
return conn, sc, err
|
||||
}
|
||||
59
common/net/peer/peer.go
Normal file
59
common/net/peer/peer.go
Normal file
@ -0,0 +1,59 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/libp2p/go-libp2p-core/sec"
|
||||
"storj.io/drpc"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewPeer(sc sec.SecureConn, conn drpc.Conn) Peer {
|
||||
return &peer{
|
||||
id: sc.RemotePeer().String(),
|
||||
lastUsage: time.Now().Unix(),
|
||||
sc: sc,
|
||||
Conn: conn,
|
||||
}
|
||||
}
|
||||
|
||||
type Peer interface {
|
||||
Id() string
|
||||
LastUsage() time.Time
|
||||
UpdateLastUsage()
|
||||
drpc.Conn
|
||||
}
|
||||
|
||||
type peer struct {
|
||||
id string
|
||||
lastUsage int64
|
||||
sc sec.SecureConn
|
||||
drpc.Conn
|
||||
}
|
||||
|
||||
func (p *peer) Id() string {
|
||||
return p.id
|
||||
}
|
||||
|
||||
func (p *peer) LastUsage() time.Time {
|
||||
select {
|
||||
case <-p.Closed():
|
||||
return time.Unix(0, 0)
|
||||
default:
|
||||
}
|
||||
return time.Unix(atomic.LoadInt64(&p.lastUsage), 0)
|
||||
}
|
||||
|
||||
func (p *peer) Invoke(ctx context.Context, rpc string, enc drpc.Encoding, in, out drpc.Message) error {
|
||||
defer p.UpdateLastUsage()
|
||||
return p.Conn.Invoke(ctx, rpc, enc, in, out)
|
||||
}
|
||||
|
||||
func (p *peer) NewStream(ctx context.Context, rpc string, enc drpc.Encoding) (drpc.Stream, error) {
|
||||
defer p.UpdateLastUsage()
|
||||
return p.Conn.NewStream(ctx, rpc, enc)
|
||||
}
|
||||
|
||||
func (p *peer) UpdateLastUsage() {
|
||||
atomic.StoreInt64(&p.lastUsage, time.Now().Unix())
|
||||
}
|
||||
107
common/net/pool/pool.go
Normal file
107
common/net/pool/pool.go
Normal file
@ -0,0 +1,107 @@
|
||||
package pool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/dialer"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/peer"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/ocache"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
CName = "net.pool"
|
||||
)
|
||||
|
||||
var log = logger.NewNamed(CName)
|
||||
|
||||
var (
|
||||
ErrUnableToConnect = errors.New("unable to connect")
|
||||
)
|
||||
|
||||
func New() Pool {
|
||||
return &pool{}
|
||||
}
|
||||
|
||||
// Pool creates and caches outgoing connection
|
||||
type Pool interface {
|
||||
// Get lookups to peer in existing connections or creates and cache new one
|
||||
Get(ctx context.Context, id string) (peer.Peer, error)
|
||||
// GetOneOf searches at least one existing connection in cache or creates a new one from a randomly selected id from given list
|
||||
GetOneOf(ctx context.Context, peerIds []string) (peer.Peer, error)
|
||||
|
||||
app.ComponentRunnable
|
||||
}
|
||||
|
||||
type pool struct {
|
||||
cache ocache.OCache
|
||||
}
|
||||
|
||||
func (p *pool) Init(a *app.App) (err error) {
|
||||
dialer := a.MustComponent(dialer.CName).(dialer.Dialer)
|
||||
p.cache = ocache.New(
|
||||
func(ctx context.Context, id string) (value ocache.Object, err error) {
|
||||
return dialer.Dial(ctx, id)
|
||||
},
|
||||
ocache.WithLogger(log.Sugar()),
|
||||
ocache.WithGCPeriod(time.Minute),
|
||||
ocache.WithTTL(time.Minute*5),
|
||||
ocache.WithRefCounter(false),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *pool) Name() (name string) {
|
||||
return CName
|
||||
}
|
||||
|
||||
func (p *pool) Run(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *pool) Get(ctx context.Context, id string) (peer.Peer, error) {
|
||||
v, err := p.cache.Get(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pr := v.(peer.Peer)
|
||||
select {
|
||||
case <-pr.Closed():
|
||||
default:
|
||||
return pr, nil
|
||||
}
|
||||
p.cache.Remove(id)
|
||||
return p.Get(ctx, id)
|
||||
}
|
||||
|
||||
func (p *pool) GetOneOf(ctx context.Context, peerIds []string) (peer.Peer, error) {
|
||||
// finding existing connection
|
||||
for _, peerId := range peerIds {
|
||||
if v, err := p.cache.Pick(ctx, peerId); err == nil {
|
||||
pr := v.(peer.Peer)
|
||||
select {
|
||||
case <-pr.Closed():
|
||||
default:
|
||||
return pr, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
// shuffle ids for better consistency
|
||||
rand.Shuffle(len(peerIds), func(i, j int) {
|
||||
peerIds[i], peerIds[j] = peerIds[j], peerIds[i]
|
||||
})
|
||||
// connecting
|
||||
for _, peerId := range peerIds {
|
||||
if v, err := p.cache.Get(ctx, peerId); err == nil {
|
||||
return v.(peer.Peer), nil
|
||||
}
|
||||
}
|
||||
return nil, ErrUnableToConnect
|
||||
}
|
||||
|
||||
func (p *pool) Close(ctx context.Context) (err error) {
|
||||
return p.cache.Close()
|
||||
}
|
||||
213
common/net/pool/pool_test.go
Normal file
213
common/net/pool/pool_test.go
Normal file
@ -0,0 +1,213 @@
|
||||
package pool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/dialer"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/peer"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"storj.io/drpc"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ctx = context.Background()
|
||||
|
||||
func TestPool_Get(t *testing.T) {
|
||||
t.Run("dial error", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
defer fx.Finish()
|
||||
var expErr = errors.New("dial error")
|
||||
fx.Dialer.dial = func(ctx context.Context, peerId string) (peer peer.Peer, err error) {
|
||||
return nil, expErr
|
||||
}
|
||||
p, err := fx.Get(ctx, "1")
|
||||
assert.Nil(t, p)
|
||||
assert.EqualError(t, err, expErr.Error())
|
||||
})
|
||||
t.Run("dial and cached", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
defer fx.Finish()
|
||||
fx.Dialer.dial = func(ctx context.Context, peerId string) (peer peer.Peer, err error) {
|
||||
return newTestPeer("1"), nil
|
||||
}
|
||||
p, err := fx.Get(ctx, "1")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, p)
|
||||
fx.Dialer.dial = nil
|
||||
p, err = fx.Get(ctx, "1")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, p)
|
||||
})
|
||||
t.Run("retry for closed", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
defer fx.Finish()
|
||||
tp := newTestPeer("1")
|
||||
fx.Dialer.dial = func(ctx context.Context, peerId string) (peer peer.Peer, err error) {
|
||||
return tp, nil
|
||||
}
|
||||
p, err := fx.Get(ctx, "1")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, p)
|
||||
p.Close()
|
||||
tp2 := newTestPeer("1")
|
||||
fx.Dialer.dial = func(ctx context.Context, peerId string) (peer peer.Peer, err error) {
|
||||
return tp2, nil
|
||||
}
|
||||
p, err = fx.Get(ctx, "1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, p, tp2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPool_GetOneOf(t *testing.T) {
|
||||
addToCache := func(t *testing.T, fx *fixture, tp *testPeer) {
|
||||
fx.Dialer.dial = func(ctx context.Context, peerId string) (peer peer.Peer, err error) {
|
||||
return tp, nil
|
||||
}
|
||||
gp, err := fx.Get(ctx, tp.Id())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, gp, tp)
|
||||
}
|
||||
|
||||
t.Run("from cache", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
defer fx.Finish()
|
||||
tp1 := newTestPeer("1")
|
||||
addToCache(t, fx, tp1)
|
||||
p, err := fx.GetOneOf(ctx, []string{"3", "2", "1"})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tp1, p)
|
||||
})
|
||||
t.Run("from cache - skip closed", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
defer fx.Finish()
|
||||
tp2 := newTestPeer("2")
|
||||
addToCache(t, fx, tp2)
|
||||
tp2.Close()
|
||||
tp1 := newTestPeer("1")
|
||||
addToCache(t, fx, tp1)
|
||||
p, err := fx.GetOneOf(ctx, []string{"3", "2", "1"})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tp1, p)
|
||||
})
|
||||
t.Run("dial", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
defer fx.Finish()
|
||||
var called bool
|
||||
fx.Dialer.dial = func(ctx context.Context, peerId string) (peer peer.Peer, err error) {
|
||||
if called {
|
||||
return nil, fmt.Errorf("not expected call")
|
||||
}
|
||||
called = true
|
||||
return newTestPeer(peerId), nil
|
||||
}
|
||||
p, err := fx.GetOneOf(ctx, []string{"3", "2", "1"})
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, p)
|
||||
})
|
||||
t.Run("unable to connect", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
defer fx.Finish()
|
||||
fx.Dialer.dial = func(ctx context.Context, peerId string) (peer peer.Peer, err error) {
|
||||
return nil, fmt.Errorf("persistent error")
|
||||
}
|
||||
p, err := fx.GetOneOf(ctx, []string{"3", "2", "1"})
|
||||
assert.Equal(t, ErrUnableToConnect, err)
|
||||
assert.Nil(t, p)
|
||||
})
|
||||
}
|
||||
|
||||
func newFixture(t *testing.T) *fixture {
|
||||
fx := &fixture{
|
||||
Pool: New(),
|
||||
Dialer: &dialerMock{},
|
||||
}
|
||||
a := new(app.App)
|
||||
a.Register(fx.Pool)
|
||||
a.Register(fx.Dialer)
|
||||
require.NoError(t, a.Start(context.Background()))
|
||||
fx.a = a
|
||||
fx.t = t
|
||||
return fx
|
||||
}
|
||||
|
||||
func (fx *fixture) Finish() {
|
||||
require.NoError(fx.t, fx.a.Close(context.Background()))
|
||||
}
|
||||
|
||||
type fixture struct {
|
||||
Pool
|
||||
Dialer *dialerMock
|
||||
a *app.App
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
var _ dialer.Dialer = (*dialerMock)(nil)
|
||||
|
||||
type dialerMock struct {
|
||||
dial func(ctx context.Context, peerId string) (peer peer.Peer, err error)
|
||||
}
|
||||
|
||||
func (d *dialerMock) Dial(ctx context.Context, peerId string) (peer peer.Peer, err error) {
|
||||
return d.dial(ctx, peerId)
|
||||
}
|
||||
|
||||
func (d *dialerMock) UpdateAddrs(addrs map[string][]string) {
|
||||
return
|
||||
}
|
||||
|
||||
func (d *dialerMock) Init(a *app.App) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (d *dialerMock) Name() (name string) {
|
||||
return dialer.CName
|
||||
}
|
||||
|
||||
func newTestPeer(id string) *testPeer {
|
||||
return &testPeer{
|
||||
id: id,
|
||||
closed: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
type testPeer struct {
|
||||
id string
|
||||
closed chan struct{}
|
||||
}
|
||||
|
||||
func (t *testPeer) Id() string {
|
||||
return t.id
|
||||
}
|
||||
|
||||
func (t *testPeer) LastUsage() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
func (t *testPeer) UpdateLastUsage() {}
|
||||
|
||||
func (t *testPeer) Close() error {
|
||||
select {
|
||||
case <-t.closed:
|
||||
return fmt.Errorf("already closed")
|
||||
default:
|
||||
close(t.closed)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *testPeer) Closed() <-chan struct{} {
|
||||
return t.closed
|
||||
}
|
||||
|
||||
func (t *testPeer) Invoke(ctx context.Context, rpc string, enc drpc.Encoding, in, out drpc.Message) error {
|
||||
return fmt.Errorf("call Invoke on test peer")
|
||||
}
|
||||
|
||||
func (t *testPeer) NewStream(ctx context.Context, rpc string, enc drpc.Encoding) (drpc.Stream, error) {
|
||||
return nil, fmt.Errorf("call NewStream on test peer")
|
||||
}
|
||||
51
common/net/rpc/rpcerr/registry.go
Normal file
51
common/net/rpc/rpcerr/registry.go
Normal file
@ -0,0 +1,51 @@
|
||||
package rpcerr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"storj.io/drpc/drpcerr"
|
||||
)
|
||||
|
||||
var (
|
||||
Unexpected = RegisterErr(errors.New("unexpected"), 1)
|
||||
Closed = RegisterErr(errors.New("closed"), 2)
|
||||
)
|
||||
|
||||
var (
|
||||
errsMap = make(map[uint64]error)
|
||||
)
|
||||
|
||||
func RegisterErr(err error, code uint64) error {
|
||||
if e, ok := errsMap[code]; ok {
|
||||
panic(fmt.Errorf("attempt to register error with exiswting code: %d; registered error: %v", code, e))
|
||||
}
|
||||
errWithCode := drpcerr.WithCode(err, code)
|
||||
errsMap[code] = errWithCode
|
||||
return errWithCode
|
||||
}
|
||||
|
||||
func Err(code uint64) error {
|
||||
err, ok := errsMap[code]
|
||||
if !ok {
|
||||
return drpcerr.WithCode(fmt.Errorf("unexpected error, code: %d", code), code)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func Unwrap(e error) error {
|
||||
code := drpcerr.Code(e)
|
||||
if code == 0 {
|
||||
return e
|
||||
}
|
||||
err, ok := errsMap[code]
|
||||
if !ok {
|
||||
return drpcerr.WithCode(fmt.Errorf("unexpected error: %v; code: %d", err, code), code)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type ErrGroup int64
|
||||
|
||||
func (g ErrGroup) Register(err error, code uint64) error {
|
||||
return RegisterErr(err, uint64(g)+code)
|
||||
}
|
||||
@ -4,15 +4,16 @@ import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/pool"
|
||||
secure2 "github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/secure"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/config"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/net/pool"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/net/rpc"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/net/secure"
|
||||
"github.com/zeebo/errs"
|
||||
"go.uber.org/zap"
|
||||
"io"
|
||||
"net"
|
||||
"storj.io/drpc"
|
||||
"storj.io/drpc/drpcmux"
|
||||
"storj.io/drpc/drpcserver"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -21,25 +22,27 @@ const CName = "net/drpcserver"
|
||||
var log = logger.NewNamed(CName)
|
||||
|
||||
func New() DRPCServer {
|
||||
return &drpcServer{}
|
||||
return &drpcServer{Mux: drpcmux.New()}
|
||||
}
|
||||
|
||||
type DRPCServer interface {
|
||||
app.ComponentRunnable
|
||||
drpc.Mux
|
||||
}
|
||||
|
||||
type drpcServer struct {
|
||||
config config.GrpcServer
|
||||
drpcServer *drpcserver.Server
|
||||
transport secure.Service
|
||||
listeners []secure.ContextListener
|
||||
transport secure2.Service
|
||||
listeners []secure2.ContextListener
|
||||
pool pool.Pool
|
||||
cancel func()
|
||||
*drpcmux.Mux
|
||||
}
|
||||
|
||||
func (s *drpcServer) Init(ctx context.Context, a *app.App) (err error) {
|
||||
func (s *drpcServer) Init(a *app.App) (err error) {
|
||||
s.config = a.MustComponent(config.CName).(*config.Config).GrpcServer
|
||||
s.transport = a.MustComponent(secure.CName).(secure.Service)
|
||||
s.transport = a.MustComponent(secure2.CName).(secure2.Service)
|
||||
s.pool = a.MustComponent(pool.CName).(pool.Pool)
|
||||
return nil
|
||||
}
|
||||
@ -49,7 +52,7 @@ func (s *drpcServer) Name() (name string) {
|
||||
}
|
||||
|
||||
func (s *drpcServer) Run(ctx context.Context) (err error) {
|
||||
s.drpcServer = drpcserver.New(s)
|
||||
s.drpcServer = drpcserver.New(s.Mux)
|
||||
ctx, s.cancel = context.WithCancel(ctx)
|
||||
for _, addr := range s.config.ListenAddrs {
|
||||
tcpList, err := net.Listen("tcp", addr)
|
||||
@ -62,7 +65,7 @@ func (s *drpcServer) Run(ctx context.Context) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (s *drpcServer) serve(ctx context.Context, lis secure.ContextListener) {
|
||||
func (s *drpcServer) serve(ctx context.Context, lis secure2.ContextListener) {
|
||||
l := log.With(zap.String("localAddr", lis.Addr().String()))
|
||||
l.Info("drpc listener started")
|
||||
defer func() {
|
||||
@ -86,7 +89,7 @@ func (s *drpcServer) serve(ctx context.Context, lis secure.ContextListener) {
|
||||
}
|
||||
continue
|
||||
}
|
||||
if _, ok := err.(secure.HandshakeError); ok {
|
||||
if _, ok := err.(secure2.HandshakeError); ok {
|
||||
l.Warn("listener handshake error", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
@ -101,7 +104,7 @@ func (s *drpcServer) serveConn(ctx context.Context, conn net.Conn) {
|
||||
l := log.With(zap.String("remoteAddr", conn.RemoteAddr().String())).With(zap.String("localAddr", conn.LocalAddr().String()))
|
||||
l.Debug("connection opened")
|
||||
if err := s.drpcServer.ServeOne(ctx, conn); err != nil {
|
||||
if err == context.Canceled || strings.Contains(err.Error(), "EOF") {
|
||||
if errs.Is(err, context.Canceled) || errs.Is(err, io.EOF) {
|
||||
l.Debug("connection closed")
|
||||
} else {
|
||||
l.Warn("serve connection error", zap.Error(err))
|
||||
@ -109,16 +112,6 @@ func (s *drpcServer) serveConn(ctx context.Context, conn net.Conn) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *drpcServer) HandleRPC(stream drpc.Stream, _ string) (err error) {
|
||||
ctx := stream.Context()
|
||||
sc, err := secure.CtxSecureConn(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.With(zap.String("peer", sc.RemotePeer().String())).Debug("stream opened")
|
||||
return s.pool.AddAndReadPeer(rpc.PeerFromStream(sc, stream, true))
|
||||
}
|
||||
|
||||
func (s *drpcServer) Close(ctx context.Context) (err error) {
|
||||
if s.cancel != nil {
|
||||
s.cancel()
|
||||
@ -2,13 +2,11 @@ package secure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/config"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
|
||||
"github.com/libp2p/go-libp2p-core/crypto"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
"github.com/libp2p/go-libp2p-core/sec"
|
||||
libp2ptls "github.com/libp2p/go-libp2p/p2p/security/tls"
|
||||
"go.uber.org/zap"
|
||||
@ -35,10 +33,9 @@ type service struct {
|
||||
key crypto.PrivKey
|
||||
}
|
||||
|
||||
func (s *service) Init(ctx context.Context, a *app.App) (err error) {
|
||||
func (s *service) Init(a *app.App) (err error) {
|
||||
account := a.MustComponent(config.CName).(*config.Config).Account
|
||||
decoder := signingkey.NewEDPrivKeyDecoder()
|
||||
pkb, err := decoder.DecodeFromStringIntoBytes(account.SigningKey)
|
||||
pkb, err := keys.DecodeBytesFromString(account.SigningKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -46,28 +43,6 @@ func (s *service) Init(ctx context.Context, a *app.App) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
pid, err := peer.Decode(account.PeerId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var testData = []byte("test data")
|
||||
sign, err := s.key.Sign(testData)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pubKey, err := pid.ExtractPublicKey()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ok, err := pubKey.Verify(testData, sign)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("peerId and privateKey mismatched")
|
||||
}
|
||||
|
||||
log.Info("secure service init", zap.String("peerId", account.PeerId))
|
||||
|
||||
return nil
|
||||
@ -1,10 +1,10 @@
|
||||
package configuration
|
||||
package nodeconf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/net/peer"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/net/pool"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/peer"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/pool"
|
||||
"github.com/anytypeio/go-chash"
|
||||
)
|
||||
|
||||
@ -13,12 +13,14 @@ func New() Service {
|
||||
}
|
||||
|
||||
type Configuration interface {
|
||||
// Id returns current configuration id
|
||||
// Id returns current nodeconf id
|
||||
Id() string
|
||||
// AllPeers returns all peers by spaceId except current account
|
||||
AllPeers(ctx context.Context, spaceId string) (peers []peer.Peer, err error)
|
||||
// OnePeer returns one of peer for spaceId
|
||||
OnePeer(ctx context.Context, spaceId string) (p peer.Peer, err error)
|
||||
// ResponsiblePeers returns peers for the space id that are responsible for the space
|
||||
ResponsiblePeers(ctx context.Context, spaceId string) (peers []peer.Peer, err error)
|
||||
// NodeIds returns list of peerId for given spaceId
|
||||
NodeIds(spaceId string) []string
|
||||
// IsResponsible checks if current account responsible for given spaceId
|
||||
@ -40,7 +42,7 @@ func (c *configuration) AllPeers(ctx context.Context, spaceId string) (peers []p
|
||||
nodeIds := c.NodeIds(spaceId)
|
||||
peers = make([]peer.Peer, 0, len(nodeIds))
|
||||
for _, id := range nodeIds {
|
||||
p, e := c.pool.DialAndAddPeer(ctx, id)
|
||||
p, e := c.pool.Get(ctx, id)
|
||||
if e == nil {
|
||||
peers = append(peers, p)
|
||||
}
|
||||
@ -51,9 +53,23 @@ func (c *configuration) AllPeers(ctx context.Context, spaceId string) (peers []p
|
||||
return
|
||||
}
|
||||
|
||||
func (c *configuration) ResponsiblePeers(ctx context.Context, spaceId string) (peers []peer.Peer, err error) {
|
||||
if c.IsResponsible(spaceId) {
|
||||
return c.AllPeers(ctx, spaceId)
|
||||
} else {
|
||||
var one peer.Peer
|
||||
one, err = c.OnePeer(ctx, spaceId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
peers = []peer.Peer{one}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *configuration) OnePeer(ctx context.Context, spaceId string) (p peer.Peer, err error) {
|
||||
nodeIds := c.NodeIds(spaceId)
|
||||
return c.pool.GetOrDialOneOf(ctx, nodeIds)
|
||||
return c.pool.GetOneOf(ctx, nodeIds)
|
||||
}
|
||||
|
||||
func (c *configuration) NodeIds(spaceId string) []string {
|
||||
124
common/nodeconf/service.go
Normal file
124
common/nodeconf/service.go
Normal file
@ -0,0 +1,124 @@
|
||||
package nodeconf
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/pool"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/config"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
"github.com/anytypeio/go-chash"
|
||||
)
|
||||
|
||||
const CName = "common.nodeconf"
|
||||
|
||||
const (
|
||||
partitionCount = 3000
|
||||
replicationFactor = 3
|
||||
)
|
||||
|
||||
var log = logger.NewNamed(CName)
|
||||
|
||||
type Service interface {
|
||||
GetLast() Configuration
|
||||
GetById(id string) Configuration
|
||||
app.Component
|
||||
}
|
||||
|
||||
type service struct {
|
||||
accountId string
|
||||
pool pool.Pool
|
||||
|
||||
last Configuration
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
Address string
|
||||
PeerId string
|
||||
SigningKey signingkey.PubKey
|
||||
EncryptionKey encryptionkey.PubKey
|
||||
}
|
||||
|
||||
func (n *Node) Id() string {
|
||||
return n.PeerId
|
||||
}
|
||||
|
||||
func (n *Node) Capacity() float64 {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (s *service) Init(a *app.App) (err error) {
|
||||
conf := a.MustComponent(config.CName).(*config.Config)
|
||||
s.accountId = conf.Account.PeerId
|
||||
s.pool = a.MustComponent(pool.CName).(pool.Pool)
|
||||
|
||||
config := &configuration{
|
||||
id: "config",
|
||||
accountId: s.accountId,
|
||||
pool: s.pool,
|
||||
}
|
||||
if config.chash, err = chash.New(chash.Config{
|
||||
PartitionCount: partitionCount,
|
||||
ReplicationFactor: replicationFactor,
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
members := make([]chash.Member, 0, len(conf.Nodes)-1)
|
||||
for _, n := range conf.Nodes {
|
||||
if n.PeerId == conf.Account.PeerId {
|
||||
continue
|
||||
}
|
||||
var member *Node
|
||||
member, err = nodeFromConfigNode(n)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
members = append(members, member)
|
||||
}
|
||||
if err = config.chash.AddMembers(members...); err != nil {
|
||||
return
|
||||
}
|
||||
s.last = config
|
||||
return
|
||||
}
|
||||
|
||||
func (s *service) Name() (name string) {
|
||||
return CName
|
||||
}
|
||||
|
||||
func (s *service) GetLast() Configuration {
|
||||
return s.last
|
||||
}
|
||||
|
||||
func (s *service) GetById(id string) Configuration {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func nodeFromConfigNode(
|
||||
n config.Node) (*Node, error) {
|
||||
decodedSigningKey, err := keys.DecodeKeyFromString(
|
||||
n.SigningKey,
|
||||
signingkey.UnmarshalEd25519PrivateKey,
|
||||
nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
decodedEncryptionKey, err := keys.DecodeKeyFromString(
|
||||
n.SigningKey,
|
||||
encryptionkey.NewEncryptionRsaPrivKeyFromBytes,
|
||||
nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Node{
|
||||
Address: n.Address,
|
||||
PeerId: n.PeerId,
|
||||
SigningKey: decodedSigningKey.GetPublic(),
|
||||
EncryptionKey: decodedEncryptionKey.GetPublic(),
|
||||
}, nil
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
||||
@ -32,8 +31,8 @@ type Config struct {
|
||||
Space Space `yaml:"space"`
|
||||
}
|
||||
|
||||
func (c *Config) Init(ctx context.Context, a *app.App) (err error) {
|
||||
logger.NewNamed("config").Info(fmt.Sprint(*c))
|
||||
func (c *Config) Init(a *app.App) (err error) {
|
||||
logger.NewNamed("config").Info(fmt.Sprint(c.Space))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package config
|
||||
|
||||
type Space struct {
|
||||
GCTTL int `json:"gcTTL"`
|
||||
SyncPeriod int `json:"syncPeriod"`
|
||||
GCTTL int `yaml:"gcTTL"`
|
||||
SyncPeriod int `yaml:"syncPeriod"`
|
||||
}
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
anytype:
|
||||
swarmKey: "/key/swarm/psk/1.0.0/base16/209992e611c27d5dce8fbd2e7389f6b51da9bee980992ef60739460b536139ec"
|
||||
|
||||
grpcServer:
|
||||
listenAddrs:
|
||||
- "127.0.0.1:4431"
|
||||
|
||||
peerList:
|
||||
myId:
|
||||
peerId: "12D3KooWA4FLWvrMbCtp2MbzKcC5RRN7HqxxBxPcSADFfzrGiW3U"
|
||||
privKey: "InCGjb55V9+jj2PebUExUuwrpOIBc4hmgk2dSqyk3k4DjmgrdoNVuFe7xCFaFdUVb0RJYj6A+OTp2yXASTmq2w=="
|
||||
remote:
|
||||
- peerId: "12D3KooWHJpSEMQUZCyK8TK181LhjzntWjKfXDr7MWks9cw41R2C"
|
||||
addr: "127.0.0.1:4430"
|
||||
- peerId: "12D3KooWK6c1CPLL4Bvjim9A9SDRmehy12hYjbqX1VASHKfH7W7H"
|
||||
addr: "127.0.0.1:4432"
|
||||
@ -1,16 +0,0 @@
|
||||
anytype:
|
||||
swarmKey: "/key/swarm/psk/1.0.0/base16/209992e611c27d5dce8fbd2e7389f6b51da9bee980992ef60739460b536139ec"
|
||||
|
||||
grpcServer:
|
||||
listenAddrs:
|
||||
- "127.0.0.1:4432"
|
||||
|
||||
peerList:
|
||||
myId:
|
||||
peerId: "12D3KooWK6c1CPLL4Bvjim9A9SDRmehy12hYjbqX1VASHKfH7W7H"
|
||||
privKey: "jynYZBgtM4elT+6e7M5UERTJCZgUd3hDdmQjCqTpApyJ4h53V6TQan4Ru4OXqz+91rCLjpIVdphhaB0l+TvNsA=="
|
||||
remote:
|
||||
- peerId: "12D3KooWA4FLWvrMbCtp2MbzKcC5RRN7HqxxBxPcSADFfzrGiW3U"
|
||||
addr: "127.0.0.1:4431"
|
||||
- peerId: "12D3KooWHJpSEMQUZCyK8TK181LhjzntWjKfXDr7MWks9cw41R2C"
|
||||
addr: "127.0.0.1:4430"
|
||||
@ -3,20 +3,22 @@ anytype:
|
||||
grpcServer:
|
||||
listenAddrs:
|
||||
- 127.0.0.1:4430
|
||||
- 127.0.0.1:4431
|
||||
tls: false
|
||||
account:
|
||||
peerId: 12D3KooWMHuhZgK2skkLrvL51QQTXaXQKYy2QqfvPNBFnzR2ubA1
|
||||
signingKey: 3id6ddLcoNoe9rDgGM88ET8T6TnvHm5GFqFdN6kBzn7Q8d6VUGgjeT59CNWFiaofdeRnHBvX2A5ZacMXvfwaYEFuCbug
|
||||
encryptionKey: JgG4CcCbae1qEpe7mKpBzsHjZhXUmDSNVNX2B1gxFZsJyMX4V6kBQUott9zRWyeXaW1ZmpzuxDXnwSQpAnNurhXyGa9iQaAPqzY9A9VWBPD33Yy1eW7TRuVemzToh8jJQKQKnZNbF8ucTWV9qahusKzyvN8uyhrqoW2tAPfA9S3E3ognCuqbLSW6yjE2rBKayvyS1BVwzjSd6FZK4DDyjfU3pbEVjut3wytGEAn9af6sNMmyCnf2MX5vLovWs9rU8av61wD4z7HTsXyGFx4K75N4Go249Hpe9SKAT6HxhRc3yvj63krPLiQV5yMuH2UeMUXBDekUQyNmBEdn9wrur7mLqB67Bc6tcc2PP8XApBCdWJHvHjN4FktSpaG5vbCqoZbLD1oCbk36q2x9s6XM8pydVqD1J9P3nTbfgMb5pJCTFjNtgKeuKv6wjfJeA9jF1VhcJQisfsahgv9MvZ9M8FJpZTq1zKUhYDCRnZxUkraoMS5yNNVdDzaUckKEDthqik7BMWCWT79vq7uVgMwEvGwGi76gtoMg1159bbPMLZ4bdPVfhH2S9QjPrzQfwZSrzB2YeVPjWpaXDeLDity5H8n1NK2oniAQR6gE71n81neSptsuhV6o6QpQ89AU8y57XmEsou4VEryn8vUxBHhULLxrLNUouxyWamCeFiDjk5cSN6koQsf9BYKSNTPFTrwjTKForDokMhcPdMtFktKwjv7u9UEGcY4MKvNzZZkc77gHiP8bqVtdNNoLpTFUC5SZ9i7bKdHvK12HpSy7yzzPeMXJ9UwhLxkok1g81ngTbN1yxRhvYXyHZFtguCR9kvGojDjka91MTBtk551qDw9eCn2xZT9U8jqzBCjdpvSg3mRWKMPnYAGB7m7u1ye165wyGFvzcHAx3vtXjxAqLUeKYZCjv2m6V9D2Y4qH1TQNddWqH14T1JVMis971UCH9Ddpj6a3387oUnufD1P6HZN2ieJCvptrmbGVvxJYYSvmVf1dkwbtqurDRNWD7TJ7gf6iqSP549C9bxP4GpLt3ygjHmMtcuUzstBuztvunJUnQhfnJxqU6LjRdsFzm53wGWgXNxab7ZvQcPyLwsevn1b98FGPnVpS5iY4LjmqW4ugrC6HgrbsjrXiKzR1yZKhLQkCbLzPoaHb8iB5iBnCr7d4yf5CtfpFRqgoqMFdK5LNZYmDX4HzUKN6A7wC3gGiSRFTLcgGZeSMkB5Pa61CZBU7WCQgFxykycE9HRA7PiQa496GWDCV15teToCpFRsAa6jDmR1MGXPeLRqQgve49VXnQN5FL7c1VuEv5SWjeTuCnMB47DJKBaP7eKJNKgLwETALzSCMF3nRiRgeb15kfoS4BbrJ5yupjrvwmbmvNg1AYFFS5sYNWft7K8v87wQvBakRtGP71Kp8NX77XFtu6xdB7sR6jpfC6qJPyB9akWNXgCrWy9kE4ih42gwAZdUugNZ9YtEsgRM3pwb6qJhkAPyEJtrxrja859PCAgqPSQiPQN33PaMkgQ6HJknu8CrjKRiXAycZ16KLUkHV64TNhEjPTcX1a7rqpD131AYMWX8d7CCdc9Ys7RUb6BwguuNSh8rJK3x4AkMDSUsaE8ynKvpC7RXZpJ9Nxfhd
|
||||
peerId: 12D3KooWSUx2LXPvoZGp72Dt7b7r1kPSmQ6zAUwKkHFyX64uiXRu
|
||||
signingKey: 4QTrtkFLQe9wQcWT/cgFEwfMHB5pt4axInNmCIMCZaz3nVdyygRoO8/YH0V15X6Mnw1NQWsS1YIWiLS22hwepA==
|
||||
encryptionKey: MIIEpAIBAAKCAQEAmqAAOPfR86po3m+zwSzbAlZGgMMF188v35Ulqf9Gb4KO8DZ9ifxrqpjlLZRxPKeXj3wSSQEXsJf3A82rZlDxddZSM0i7Mx5G2G0zRHWx9dC58PpX6o/fDuvSwcyXqOgIK55N/hyEuIbWQgp5Rk9uy2Zbrhv5ZL5CvceM0b9wSKt/hRvntxSbG+HRgXWaQvAReGuJrySVvkh6fhC3G0IwqyFbGNq2zqAJej6NBzZA3thHgTn5PoWD8O4cyukBxunKGu3HLE3vJtqEMFrkNFw5SMpdEtxyTLN6T1HIeYCY9RL+BFYfxIWg6pGtIoIJKUB0XapJr9ltzvXfT9KeSCU0VwIDAQABAoIBAAp/xsQXf7gN4CUKbKg3RX+5H/xqQaFPvi5uUCpk3QGBWfdRm+CctSrWSul3ZOD7eD0T7aHrYxJonysw8ex2no6jyN0WmS91ZNYZRBvn6feI/rcwKHwS3NCEjsD+BWZAqx1bGGyivxhQf4fociemCR3ii2MdHygKCzobrKIpX5RvhanI4j01dyLlxwqTsteuc/o5RR4jfg1eN0kldFjk3UcSNyzzEv5o5UhRsHCLJBTNTvYZBN4FpyaqcLT9gKS9aVBvQH63R+E5dyxo1+24tZZricW59h2bN3CFriqkwBo1y0gTnR6VQ22MBvIUxYUm82cxXs/Vr0YQTSAaEGThxFECgYEAxKQMRnM39WMzrNx1WDwpBERRj1T0TbLf1uq6viPiLdik2Tm2aCBZyr5j82Ey7fZ7OafKGfsM0I2AuYeoBdYDuYN6A7tE9kpnECubnWuIvUeYcL+1VzzMedVtdKwQXrYbhqKtyvnSJ9gQ6CusHtsDE1bQvTMxBX4KNBeBYllCUasCgYEAyU0RPUaj56CyLHKty8jNg6wl+06IZ0pUPIWZ//b1zeZrlHGYDp/InxS8huGFapzOg1sbQBS6j3j3YE3Ts6v6FNuIa4pcPQ91YuHiWWQdgVjrCZdleanFWGTjIx12+RGj9vx4voRhNQcHW1YeTvvyj4BN/ECR6GNaoS/ZjBKo1AUCgYEAj6AyxxJJARagG9Y6b2QhoVg1Kjem6UmJbPStyUt0XIAsh+07afqXGxrM7mtEQ8MQZiBD4Y4Y4gs4xkprUzfqKIn7iNYznKDjflAbrXNpwLaWhWPBFCL4RtS4ycsTedoRaNlRjzvBYBDU6H9djHvzVyDF/itx1s0krr+sZSVE51kCgYBxGRinecna+KFCccgNp6s34H+Se2QNzGgZfOKyOjmOTniA9XV+Oe3I2yi1C34fESzCBm0ACuVqeIdcFz3rQ6OFFnbGHP2H3OiR/uFiYepl4uRjBimgOm9DI6Ot9f8DHxMlUGIygEPxPBq5CWCL9egpEeg+4rRXgYLI7w5mMZGjVQKBgQDC4qyH7FK3lLv5JomoK6nNjpyPNBmr0Rt215oM/AWQaxDhFZH5un68ueZ7MfybwXxHHFQ4ZeSwYs006f1XGPNW6qrH6pi/3SCLFuGVfNnLVwCBkm3QaQrxFm3v9LmVCidTNta0l0DrUldZdK8/P31GBxKo/MmYF/f9LO/Mfm/uDg==
|
||||
apiServer:
|
||||
port: "8080"
|
||||
nodes:
|
||||
- peerId: 12D3KooWMHuhZgK2skkLrvL51QQTXaXQKYy2QqfvPNBFnzR2ubA1
|
||||
- peerId: 12D3KooWSUx2LXPvoZGp72Dt7b7r1kPSmQ6zAUwKkHFyX64uiXRu
|
||||
address: 127.0.0.1:4430
|
||||
signingKey: 3id6ddLcoNoe9rDgGM88ET8T6TnvHm5GFqFdN6kBzn7Q8d6VUGgjeT59CNWFiaofdeRnHBvX2A5ZacMXvfwaYEFuCbug
|
||||
encryptionKey: JgG4CcCbae1qEpe7mKpBzsHjZhXUmDSNVNX2B1gxFZsJyMX4V6kBQUott9zRWyeXaW1ZmpzuxDXnwSQpAnNurhXyGa9iQaAPqzY9A9VWBPD33Yy1eW7TRuVemzToh8jJQKQKnZNbF8ucTWV9qahusKzyvN8uyhrqoW2tAPfA9S3E3ognCuqbLSW6yjE2rBKayvyS1BVwzjSd6FZK4DDyjfU3pbEVjut3wytGEAn9af6sNMmyCnf2MX5vLovWs9rU8av61wD4z7HTsXyGFx4K75N4Go249Hpe9SKAT6HxhRc3yvj63krPLiQV5yMuH2UeMUXBDekUQyNmBEdn9wrur7mLqB67Bc6tcc2PP8XApBCdWJHvHjN4FktSpaG5vbCqoZbLD1oCbk36q2x9s6XM8pydVqD1J9P3nTbfgMb5pJCTFjNtgKeuKv6wjfJeA9jF1VhcJQisfsahgv9MvZ9M8FJpZTq1zKUhYDCRnZxUkraoMS5yNNVdDzaUckKEDthqik7BMWCWT79vq7uVgMwEvGwGi76gtoMg1159bbPMLZ4bdPVfhH2S9QjPrzQfwZSrzB2YeVPjWpaXDeLDity5H8n1NK2oniAQR6gE71n81neSptsuhV6o6QpQ89AU8y57XmEsou4VEryn8vUxBHhULLxrLNUouxyWamCeFiDjk5cSN6koQsf9BYKSNTPFTrwjTKForDokMhcPdMtFktKwjv7u9UEGcY4MKvNzZZkc77gHiP8bqVtdNNoLpTFUC5SZ9i7bKdHvK12HpSy7yzzPeMXJ9UwhLxkok1g81ngTbN1yxRhvYXyHZFtguCR9kvGojDjka91MTBtk551qDw9eCn2xZT9U8jqzBCjdpvSg3mRWKMPnYAGB7m7u1ye165wyGFvzcHAx3vtXjxAqLUeKYZCjv2m6V9D2Y4qH1TQNddWqH14T1JVMis971UCH9Ddpj6a3387oUnufD1P6HZN2ieJCvptrmbGVvxJYYSvmVf1dkwbtqurDRNWD7TJ7gf6iqSP549C9bxP4GpLt3ygjHmMtcuUzstBuztvunJUnQhfnJxqU6LjRdsFzm53wGWgXNxab7ZvQcPyLwsevn1b98FGPnVpS5iY4LjmqW4ugrC6HgrbsjrXiKzR1yZKhLQkCbLzPoaHb8iB5iBnCr7d4yf5CtfpFRqgoqMFdK5LNZYmDX4HzUKN6A7wC3gGiSRFTLcgGZeSMkB5Pa61CZBU7WCQgFxykycE9HRA7PiQa496GWDCV15teToCpFRsAa6jDmR1MGXPeLRqQgve49VXnQN5FL7c1VuEv5SWjeTuCnMB47DJKBaP7eKJNKgLwETALzSCMF3nRiRgeb15kfoS4BbrJ5yupjrvwmbmvNg1AYFFS5sYNWft7K8v87wQvBakRtGP71Kp8NX77XFtu6xdB7sR6jpfC6qJPyB9akWNXgCrWy9kE4ih42gwAZdUugNZ9YtEsgRM3pwb6qJhkAPyEJtrxrja859PCAgqPSQiPQN33PaMkgQ6HJknu8CrjKRiXAycZ16KLUkHV64TNhEjPTcX1a7rqpD131AYMWX8d7CCdc9Ys7RUb6BwguuNSh8rJK3x4AkMDSUsaE8ynKvpC7RXZpJ9Nxfhd
|
||||
- peerId: 12D3KooWT3c7Y5zvWhhjSxd5Ve3GKZi6WCsG6JHxcxgXixRFdBbw
|
||||
address: 127.0.0.1:4432
|
||||
signingKey: 3iiLPj6wMUQpPwTBNZcUgkbXub1jumg4AEV9LfMyFHZVc84GLyAjVbVvH6EAGhcNrxRxL82aW4BimhDZCpLsRCqx5vwj
|
||||
encryptionKey: JgG4CcCbae1qEpe7mKXzp7m5hNc56SSyZd9DwUaEStKJrq7RToAC2Vgd3i6hKRwa58zCWeN6Wjc3o6qrdKPEPRvcyEPysamajVo5mdQiUgWAmr97pGEsyjuRjQoC2GY2LvLiEQxEgwFgJxKGMHMiaWMtDfxCDUaDEm4bu5RdMhqRZekAWho6c3WoEeruSr14iX1TrocFNfBkBY7CjEw8kcywXCTNgtvhb2Qiwgj5AxEF4wyw4bzaNA9ctXb1hoHPFVMu6C51pkFY7jUD9zwyH3ukgnAewkGAcPNbKmaTAtMosKRVaAN97mAwXh2VRt1hWmRvVk7r76EjnVKhD4vbsKZc56RVcHTVWRVdhU7FGyPsiE5rSQAz1JQGYzxnZpX7EG77CyrmUGyfueVfRHhwY2oq8A4uQCRaQxSaJHYLowjXSxh8DQ2V6MTqyzti32C27utBYdHzLVCJSGkmdzGwrFcHqsq7nLDxmvJVErPvyReixEe8kFmqopJ3e6LLm8WdYw9K6JYBjXnEfwPzm7Von9sf3dcaGDUHYfttMyeke7fAXJkvPRje69hYVyzdQGAauuojzGkkvQWCSMK1KCMNMznRaPDCNvofrQhYrub24WhmwpKhorufdfW8Cb4T6reBDCtaWVsbuinjtL6F6Sui5aYHJFLJ6e4pPewr1P4EuZYRbMBZwN5KvDLhTGLBuBnaTqUUdF6bj2U22NoRYMogiHiftqKqiexKNDXX1Zg9RQEvxgjuVo6SBW42mVEA8agrLhruRqCmiduJxVrfqLNGeYXHXrcmMEgW7uosJbPXvTcfRvdFWS1ov7oSALvj6vhDQ28Yi9D2ETNdNsfVWAFQuwvPpW7CHQGXTitprVbqH8JYxNZuGygcLmr5efbB22Vzu4ntd1HoraQpG12qeDEUA7tXYUpoYyuSdWwKPjSAMtaQcCSfVrhKQHQuKJargrVrez8vjWuwLfvSucV7ZHe7gjqvYgULdE1ubRCRSd7DuLjEN2Vd6obzV2c3MRet7ZSf4Sp88WM5AuTyW7BjArBc4S3gUQ8rYaiZ8Tu7NCxkEzbFwWRaemZkwfvcsX3XxqjyF37tFSGkEqE5kuBvpZW72675LkDffj7kH1zA8yE6dVujJjWsNYVFJWndUtz5Vy2KCdZAbBgq19q4AtsxWPodU2N3yZXzFAFAzTrxS6V4P7Scpdau1avgRvHLcBQPunA37xaYMy8YMifJwtmRY25mnAQwZAk3eANk7tXwZd58SDnciLNvARJvwKzTQBXcshkwyy52SX8XmXDJsPnRLaHmiYBJ63Yzr5XpZuuAtxb9qrWG2NHCNxfomHokWacV1hjZPPd6ZxT1FuRozB6Qt2NLcyqY7bnTcQJb1jPUaTAGXXCR8WVmmmYo2fDQe8CdBmgyPvbzNTEJUyScBz4RdycB5PZap4SurJCWtHbuMyQbQUB6jJgURDstfXS5Akfe4oruNq9rnYcNtnsDJPtrhXHBqzDizmf1BDxR5FB2RCxzCgeAfg8WQ1Ug9PVAGTzob6ZqCrGXzWXEUniZnf1vjr7QhGKBYXEX9SWDoSMUpP4FreVDTnx15ijRZTV3p8xG5fE9e36TnugRVvTyq7XzmyPBjW2r66f1bior
|
||||
signingKey: 4QTrtkFLQe9wQcWT/cgFEwfMHB5pt4axInNmCIMCZaz3nVdyygRoO8/YH0V15X6Mnw1NQWsS1YIWiLS22hwepA==
|
||||
encryptionKey: MIIEpAIBAAKCAQEAmqAAOPfR86po3m+zwSzbAlZGgMMF188v35Ulqf9Gb4KO8DZ9ifxrqpjlLZRxPKeXj3wSSQEXsJf3A82rZlDxddZSM0i7Mx5G2G0zRHWx9dC58PpX6o/fDuvSwcyXqOgIK55N/hyEuIbWQgp5Rk9uy2Zbrhv5ZL5CvceM0b9wSKt/hRvntxSbG+HRgXWaQvAReGuJrySVvkh6fhC3G0IwqyFbGNq2zqAJej6NBzZA3thHgTn5PoWD8O4cyukBxunKGu3HLE3vJtqEMFrkNFw5SMpdEtxyTLN6T1HIeYCY9RL+BFYfxIWg6pGtIoIJKUB0XapJr9ltzvXfT9KeSCU0VwIDAQABAoIBAAp/xsQXf7gN4CUKbKg3RX+5H/xqQaFPvi5uUCpk3QGBWfdRm+CctSrWSul3ZOD7eD0T7aHrYxJonysw8ex2no6jyN0WmS91ZNYZRBvn6feI/rcwKHwS3NCEjsD+BWZAqx1bGGyivxhQf4fociemCR3ii2MdHygKCzobrKIpX5RvhanI4j01dyLlxwqTsteuc/o5RR4jfg1eN0kldFjk3UcSNyzzEv5o5UhRsHCLJBTNTvYZBN4FpyaqcLT9gKS9aVBvQH63R+E5dyxo1+24tZZricW59h2bN3CFriqkwBo1y0gTnR6VQ22MBvIUxYUm82cxXs/Vr0YQTSAaEGThxFECgYEAxKQMRnM39WMzrNx1WDwpBERRj1T0TbLf1uq6viPiLdik2Tm2aCBZyr5j82Ey7fZ7OafKGfsM0I2AuYeoBdYDuYN6A7tE9kpnECubnWuIvUeYcL+1VzzMedVtdKwQXrYbhqKtyvnSJ9gQ6CusHtsDE1bQvTMxBX4KNBeBYllCUasCgYEAyU0RPUaj56CyLHKty8jNg6wl+06IZ0pUPIWZ//b1zeZrlHGYDp/InxS8huGFapzOg1sbQBS6j3j3YE3Ts6v6FNuIa4pcPQ91YuHiWWQdgVjrCZdleanFWGTjIx12+RGj9vx4voRhNQcHW1YeTvvyj4BN/ECR6GNaoS/ZjBKo1AUCgYEAj6AyxxJJARagG9Y6b2QhoVg1Kjem6UmJbPStyUt0XIAsh+07afqXGxrM7mtEQ8MQZiBD4Y4Y4gs4xkprUzfqKIn7iNYznKDjflAbrXNpwLaWhWPBFCL4RtS4ycsTedoRaNlRjzvBYBDU6H9djHvzVyDF/itx1s0krr+sZSVE51kCgYBxGRinecna+KFCccgNp6s34H+Se2QNzGgZfOKyOjmOTniA9XV+Oe3I2yi1C34fESzCBm0ACuVqeIdcFz3rQ6OFFnbGHP2H3OiR/uFiYepl4uRjBimgOm9DI6Ot9f8DHxMlUGIygEPxPBq5CWCL9egpEeg+4rRXgYLI7w5mMZGjVQKBgQDC4qyH7FK3lLv5JomoK6nNjpyPNBmr0Rt215oM/AWQaxDhFZH5un68ueZ7MfybwXxHHFQ4ZeSwYs006f1XGPNW6qrH6pi/3SCLFuGVfNnLVwCBkm3QaQrxFm3v9LmVCidTNta0l0DrUldZdK8/P31GBxKo/MmYF/f9LO/Mfm/uDg==
|
||||
- peerId: 12D3KooWFnz9fYCxHAnf2rvPQ7iPZcCprEqyN8kCtVQfN2K1TfqK
|
||||
address: 127.0.0.1:4431
|
||||
signingKey: IM0BTVQf4LKMUVRTAHxbBXmdz656+G2ssw4WdLc30pRYy6TsVVdh+n03pKXSCdg665tM/9AjQRCbzgvDf9riWg==
|
||||
encryptionKey: MIIEpAIBAAKCAQEAm0HILjO7GRYYb0AvESmxdaj6ruIcSHEQIyqhPbfXZSmJNo9wIq89SaYYL4ZTwrF+ykPDJcBA8SjNHGXBPhZY+ejwCDzDyyv42FMs5lKw+/x94Yg++W72sxawtCLVi0RVY1g4UxOlCgAxl3YC9mVYoqQveXN3EsDd0YNK9fWiWP/Xl3KaJ4ErsfW3LZS9rD36dgDsKr9GqeVQf7lGkCkDmivCwHn3uaN/uzHaWvaZ7e7QWE/36vTmMsllTvi0Q9Y+v+HB5isIX9Jve1QmCS//DbDl9IMGdmyg/jlBs63Nk86Qwlw8ft3ttTWNldTpvD4Ycbgj3l59jT4rIvFJ88+5UwIDAQABAoIBAFfUn/1bMIYhlNMi+T15W7YXUTGsIpnstFdgn3T90rGdDM272ZEVl9NZTidck3f516NvMC/kEhkbnuVovyhzlgRS/a97SLxgdNdUPntR3mO/VCtJW27akl9//5j4d9vgXXnlB4AgBeahc2yey1A+xyTDQ0QuyPbn+tSytK5uNlioCeAqH4ruWxcg4t8MnwNQEOsnchrYHfXqJG+XxGn7m60U4oclbObGfxWxYZ85I0B6M5PW71VLkj/eKTvRJcW5ShDKLG5meiUM3KtwUdFRzv9Xi4aB9eTwEQ8ZV18KVmIF7baBy5anWDfGO4O9MvFSMmbMCe3EkrGaEaCp/gXenhkCgYEAw57dj7ewVHIAQxcNZ9SPRUNAY8g9yEYQ//30yTcpUjsGlqGNzua2OvALGL2ntFY304X9Iego+7Tzxs3T0x2FQ1N33NhoxwRcMqBdksMqmCb8Bm8UvnFIuvmsfPGkkzwa/8xNH81GZiz0p9zfi4lSKdZRfTQ4lBqvogExdnalSd0CgYEAyy2Mw4eeJQ0Y6QX3nad2/06oxWiS3++CITI6dAqiepAB6V7lnP90NKfLgzJcCJwzKlMhoVv7Lu4bDCXbvQ03ba+Dl+To8Jf5/9di8j8OfllqDWPnbqyueTHu5CUk+A2Gz3RhjmMXHpVgbFkUJTkJ1RDWPImNq0KzTYQ+ZwU8lO8CgYEAo1/0zuisnXowedew3HyLw17tUeiUoMTTwdiJLduh6Qle8UKvupK4svRzcBBFFbnEGiaXSFAqmj2AMxMHzBOljpsRSiJ7L2uWzLleLQpOcpBsf7sZ6guWoIGQ6zCtMEJMkkJAT0UTfJYjJmazVEg1lLdni1enwRmggX7ZnoRsewkCgYB2SpLF1FOSpsl2Ae9kbnettRI1vOimUD+nLCM0JGzshqNWR9XPTjtN3NN0EwHaUXbIkZXm6DKZ5C8DJ5eDvgojZihrau7kBNecyL3m5CeAEHbaTOwVV5xNG3FGiwm3EckHR271A2QWfkmhS0ubUFYVIrRYko1UxIS4AOKEAFyBKQKBgQCfIsGy4wOqRsKLzbun6fIVSz8X+sS3HclD7UZ6WKanOYyWZ420qnhLnfdDcNQjF/ApbzHdfwwTKVJSaZiK23kmReUVB9Cq2YAHMJYlp0ErgPzZstrRiRidtzJHm93owWc7GZinzd1M8EOYUSJ3+t8EZXZlbsD/oCTbX/BGqolo2w==
|
||||
space:
|
||||
gcTTL: 60
|
||||
syncPeriod: 10
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
anytype:
|
||||
swarmKey: /key/swarm/psk/1.0.0/base16/209992e611c27d5dce8fbd2e7389f6b51da9bee980992ef60739460b536139ec
|
||||
grpcServer:
|
||||
listenAddrs:
|
||||
- 127.0.0.1:4430
|
||||
- 127.0.0.1:4431
|
||||
tls: false
|
||||
account:
|
||||
peerId: 12D3KooWMHuhZgK2skkLrvL51QQTXaXQKYy2QqfvPNBFnzR2ubA1
|
||||
signingKey: 3id6ddLcoNoe9rDgGM88ET8T6TnvHm5GFqFdN6kBzn7Q8d6VUGgjeT59CNWFiaofdeRnHBvX2A5ZacMXvfwaYEFuCbug
|
||||
encryptionKey: JgG4CcCbae1qEpe7mKpBzsHjZhXUmDSNVNX2B1gxFZsJyMX4V6kBQUott9zRWyeXaW1ZmpzuxDXnwSQpAnNurhXyGa9iQaAPqzY9A9VWBPD33Yy1eW7TRuVemzToh8jJQKQKnZNbF8ucTWV9qahusKzyvN8uyhrqoW2tAPfA9S3E3ognCuqbLSW6yjE2rBKayvyS1BVwzjSd6FZK4DDyjfU3pbEVjut3wytGEAn9af6sNMmyCnf2MX5vLovWs9rU8av61wD4z7HTsXyGFx4K75N4Go249Hpe9SKAT6HxhRc3yvj63krPLiQV5yMuH2UeMUXBDekUQyNmBEdn9wrur7mLqB67Bc6tcc2PP8XApBCdWJHvHjN4FktSpaG5vbCqoZbLD1oCbk36q2x9s6XM8pydVqD1J9P3nTbfgMb5pJCTFjNtgKeuKv6wjfJeA9jF1VhcJQisfsahgv9MvZ9M8FJpZTq1zKUhYDCRnZxUkraoMS5yNNVdDzaUckKEDthqik7BMWCWT79vq7uVgMwEvGwGi76gtoMg1159bbPMLZ4bdPVfhH2S9QjPrzQfwZSrzB2YeVPjWpaXDeLDity5H8n1NK2oniAQR6gE71n81neSptsuhV6o6QpQ89AU8y57XmEsou4VEryn8vUxBHhULLxrLNUouxyWamCeFiDjk5cSN6koQsf9BYKSNTPFTrwjTKForDokMhcPdMtFktKwjv7u9UEGcY4MKvNzZZkc77gHiP8bqVtdNNoLpTFUC5SZ9i7bKdHvK12HpSy7yzzPeMXJ9UwhLxkok1g81ngTbN1yxRhvYXyHZFtguCR9kvGojDjka91MTBtk551qDw9eCn2xZT9U8jqzBCjdpvSg3mRWKMPnYAGB7m7u1ye165wyGFvzcHAx3vtXjxAqLUeKYZCjv2m6V9D2Y4qH1TQNddWqH14T1JVMis971UCH9Ddpj6a3387oUnufD1P6HZN2ieJCvptrmbGVvxJYYSvmVf1dkwbtqurDRNWD7TJ7gf6iqSP549C9bxP4GpLt3ygjHmMtcuUzstBuztvunJUnQhfnJxqU6LjRdsFzm53wGWgXNxab7ZvQcPyLwsevn1b98FGPnVpS5iY4LjmqW4ugrC6HgrbsjrXiKzR1yZKhLQkCbLzPoaHb8iB5iBnCr7d4yf5CtfpFRqgoqMFdK5LNZYmDX4HzUKN6A7wC3gGiSRFTLcgGZeSMkB5Pa61CZBU7WCQgFxykycE9HRA7PiQa496GWDCV15teToCpFRsAa6jDmR1MGXPeLRqQgve49VXnQN5FL7c1VuEv5SWjeTuCnMB47DJKBaP7eKJNKgLwETALzSCMF3nRiRgeb15kfoS4BbrJ5yupjrvwmbmvNg1AYFFS5sYNWft7K8v87wQvBakRtGP71Kp8NX77XFtu6xdB7sR6jpfC6qJPyB9akWNXgCrWy9kE4ih42gwAZdUugNZ9YtEsgRM3pwb6qJhkAPyEJtrxrja859PCAgqPSQiPQN33PaMkgQ6HJknu8CrjKRiXAycZ16KLUkHV64TNhEjPTcX1a7rqpD131AYMWX8d7CCdc9Ys7RUb6BwguuNSh8rJK3x4AkMDSUsaE8ynKvpC7RXZpJ9Nxfhd
|
||||
apiServer:
|
||||
port: "8080"
|
||||
nodes:
|
||||
- peerId: 12D3KooWMHuhZgK2skkLrvL51QQTXaXQKYy2QqfvPNBFnzR2ubA1
|
||||
address: 127.0.0.1:4430
|
||||
signingKey: 3id6ddLcoNoe9rDgGM88ET8T6TnvHm5GFqFdN6kBzn7Q8d6VUGgjeT59CNWFiaofdeRnHBvX2A5ZacMXvfwaYEFuCbug
|
||||
encryptionKey: JgG4CcCbae1qEpe7mKpBzsHjZhXUmDSNVNX2B1gxFZsJyMX4V6kBQUott9zRWyeXaW1ZmpzuxDXnwSQpAnNurhXyGa9iQaAPqzY9A9VWBPD33Yy1eW7TRuVemzToh8jJQKQKnZNbF8ucTWV9qahusKzyvN8uyhrqoW2tAPfA9S3E3ognCuqbLSW6yjE2rBKayvyS1BVwzjSd6FZK4DDyjfU3pbEVjut3wytGEAn9af6sNMmyCnf2MX5vLovWs9rU8av61wD4z7HTsXyGFx4K75N4Go249Hpe9SKAT6HxhRc3yvj63krPLiQV5yMuH2UeMUXBDekUQyNmBEdn9wrur7mLqB67Bc6tcc2PP8XApBCdWJHvHjN4FktSpaG5vbCqoZbLD1oCbk36q2x9s6XM8pydVqD1J9P3nTbfgMb5pJCTFjNtgKeuKv6wjfJeA9jF1VhcJQisfsahgv9MvZ9M8FJpZTq1zKUhYDCRnZxUkraoMS5yNNVdDzaUckKEDthqik7BMWCWT79vq7uVgMwEvGwGi76gtoMg1159bbPMLZ4bdPVfhH2S9QjPrzQfwZSrzB2YeVPjWpaXDeLDity5H8n1NK2oniAQR6gE71n81neSptsuhV6o6QpQ89AU8y57XmEsou4VEryn8vUxBHhULLxrLNUouxyWamCeFiDjk5cSN6koQsf9BYKSNTPFTrwjTKForDokMhcPdMtFktKwjv7u9UEGcY4MKvNzZZkc77gHiP8bqVtdNNoLpTFUC5SZ9i7bKdHvK12HpSy7yzzPeMXJ9UwhLxkok1g81ngTbN1yxRhvYXyHZFtguCR9kvGojDjka91MTBtk551qDw9eCn2xZT9U8jqzBCjdpvSg3mRWKMPnYAGB7m7u1ye165wyGFvzcHAx3vtXjxAqLUeKYZCjv2m6V9D2Y4qH1TQNddWqH14T1JVMis971UCH9Ddpj6a3387oUnufD1P6HZN2ieJCvptrmbGVvxJYYSvmVf1dkwbtqurDRNWD7TJ7gf6iqSP549C9bxP4GpLt3ygjHmMtcuUzstBuztvunJUnQhfnJxqU6LjRdsFzm53wGWgXNxab7ZvQcPyLwsevn1b98FGPnVpS5iY4LjmqW4ugrC6HgrbsjrXiKzR1yZKhLQkCbLzPoaHb8iB5iBnCr7d4yf5CtfpFRqgoqMFdK5LNZYmDX4HzUKN6A7wC3gGiSRFTLcgGZeSMkB5Pa61CZBU7WCQgFxykycE9HRA7PiQa496GWDCV15teToCpFRsAa6jDmR1MGXPeLRqQgve49VXnQN5FL7c1VuEv5SWjeTuCnMB47DJKBaP7eKJNKgLwETALzSCMF3nRiRgeb15kfoS4BbrJ5yupjrvwmbmvNg1AYFFS5sYNWft7K8v87wQvBakRtGP71Kp8NX77XFtu6xdB7sR6jpfC6qJPyB9akWNXgCrWy9kE4ih42gwAZdUugNZ9YtEsgRM3pwb6qJhkAPyEJtrxrja859PCAgqPSQiPQN33PaMkgQ6HJknu8CrjKRiXAycZ16KLUkHV64TNhEjPTcX1a7rqpD131AYMWX8d7CCdc9Ys7RUb6BwguuNSh8rJK3x4AkMDSUsaE8ynKvpC7RXZpJ9Nxfhd
|
||||
- peerId: 12D3KooWT3c7Y5zvWhhjSxd5Ve3GKZi6WCsG6JHxcxgXixRFdBbw
|
||||
address: 127.0.0.1:4432
|
||||
signingKey: 3iiLPj6wMUQpPwTBNZcUgkbXub1jumg4AEV9LfMyFHZVc84GLyAjVbVvH6EAGhcNrxRxL82aW4BimhDZCpLsRCqx5vwj
|
||||
encryptionKey: JgG4CcCbae1qEpe7mKXzp7m5hNc56SSyZd9DwUaEStKJrq7RToAC2Vgd3i6hKRwa58zCWeN6Wjc3o6qrdKPEPRvcyEPysamajVo5mdQiUgWAmr97pGEsyjuRjQoC2GY2LvLiEQxEgwFgJxKGMHMiaWMtDfxCDUaDEm4bu5RdMhqRZekAWho6c3WoEeruSr14iX1TrocFNfBkBY7CjEw8kcywXCTNgtvhb2Qiwgj5AxEF4wyw4bzaNA9ctXb1hoHPFVMu6C51pkFY7jUD9zwyH3ukgnAewkGAcPNbKmaTAtMosKRVaAN97mAwXh2VRt1hWmRvVk7r76EjnVKhD4vbsKZc56RVcHTVWRVdhU7FGyPsiE5rSQAz1JQGYzxnZpX7EG77CyrmUGyfueVfRHhwY2oq8A4uQCRaQxSaJHYLowjXSxh8DQ2V6MTqyzti32C27utBYdHzLVCJSGkmdzGwrFcHqsq7nLDxmvJVErPvyReixEe8kFmqopJ3e6LLm8WdYw9K6JYBjXnEfwPzm7Von9sf3dcaGDUHYfttMyeke7fAXJkvPRje69hYVyzdQGAauuojzGkkvQWCSMK1KCMNMznRaPDCNvofrQhYrub24WhmwpKhorufdfW8Cb4T6reBDCtaWVsbuinjtL6F6Sui5aYHJFLJ6e4pPewr1P4EuZYRbMBZwN5KvDLhTGLBuBnaTqUUdF6bj2U22NoRYMogiHiftqKqiexKNDXX1Zg9RQEvxgjuVo6SBW42mVEA8agrLhruRqCmiduJxVrfqLNGeYXHXrcmMEgW7uosJbPXvTcfRvdFWS1ov7oSALvj6vhDQ28Yi9D2ETNdNsfVWAFQuwvPpW7CHQGXTitprVbqH8JYxNZuGygcLmr5efbB22Vzu4ntd1HoraQpG12qeDEUA7tXYUpoYyuSdWwKPjSAMtaQcCSfVrhKQHQuKJargrVrez8vjWuwLfvSucV7ZHe7gjqvYgULdE1ubRCRSd7DuLjEN2Vd6obzV2c3MRet7ZSf4Sp88WM5AuTyW7BjArBc4S3gUQ8rYaiZ8Tu7NCxkEzbFwWRaemZkwfvcsX3XxqjyF37tFSGkEqE5kuBvpZW72675LkDffj7kH1zA8yE6dVujJjWsNYVFJWndUtz5Vy2KCdZAbBgq19q4AtsxWPodU2N3yZXzFAFAzTrxS6V4P7Scpdau1avgRvHLcBQPunA37xaYMy8YMifJwtmRY25mnAQwZAk3eANk7tXwZd58SDnciLNvARJvwKzTQBXcshkwyy52SX8XmXDJsPnRLaHmiYBJ63Yzr5XpZuuAtxb9qrWG2NHCNxfomHokWacV1hjZPPd6ZxT1FuRozB6Qt2NLcyqY7bnTcQJb1jPUaTAGXXCR8WVmmmYo2fDQe8CdBmgyPvbzNTEJUyScBz4RdycB5PZap4SurJCWtHbuMyQbQUB6jJgURDstfXS5Akfe4oruNq9rnYcNtnsDJPtrhXHBqzDizmf1BDxR5FB2RCxzCgeAfg8WQ1Ug9PVAGTzob6ZqCrGXzWXEUniZnf1vjr7QhGKBYXEX9SWDoSMUpP4FreVDTnx15ijRZTV3p8xG5fE9e36TnugRVvTyq7XzmyPBjW2r66f1bior
|
||||
@ -1,22 +0,0 @@
|
||||
anytype:
|
||||
swarmKey: /key/swarm/psk/1.0.0/base16/209992e611c27d5dce8fbd2e7389f6b51da9bee980992ef60739460b536139ec
|
||||
grpcServer:
|
||||
listenAddrs:
|
||||
- 127.0.0.1:4432
|
||||
- 127.0.0.1:4433
|
||||
tls: false
|
||||
account:
|
||||
peerId: 12D3KooWT3c7Y5zvWhhjSxd5Ve3GKZi6WCsG6JHxcxgXixRFdBbw
|
||||
signingKey: 3iiLPj6wMUQpPwTBNZcUgkbXub1jumg4AEV9LfMyFHZVc84GLyAjVbVvH6EAGhcNrxRxL82aW4BimhDZCpLsRCqx5vwj
|
||||
encryptionKey: JgG4CcCbae1qEpe7mKXzp7m5hNc56SSyZd9DwUaEStKJrq7RToAC2Vgd3i6hKRwa58zCWeN6Wjc3o6qrdKPEPRvcyEPysamajVo5mdQiUgWAmr97pGEsyjuRjQoC2GY2LvLiEQxEgwFgJxKGMHMiaWMtDfxCDUaDEm4bu5RdMhqRZekAWho6c3WoEeruSr14iX1TrocFNfBkBY7CjEw8kcywXCTNgtvhb2Qiwgj5AxEF4wyw4bzaNA9ctXb1hoHPFVMu6C51pkFY7jUD9zwyH3ukgnAewkGAcPNbKmaTAtMosKRVaAN97mAwXh2VRt1hWmRvVk7r76EjnVKhD4vbsKZc56RVcHTVWRVdhU7FGyPsiE5rSQAz1JQGYzxnZpX7EG77CyrmUGyfueVfRHhwY2oq8A4uQCRaQxSaJHYLowjXSxh8DQ2V6MTqyzti32C27utBYdHzLVCJSGkmdzGwrFcHqsq7nLDxmvJVErPvyReixEe8kFmqopJ3e6LLm8WdYw9K6JYBjXnEfwPzm7Von9sf3dcaGDUHYfttMyeke7fAXJkvPRje69hYVyzdQGAauuojzGkkvQWCSMK1KCMNMznRaPDCNvofrQhYrub24WhmwpKhorufdfW8Cb4T6reBDCtaWVsbuinjtL6F6Sui5aYHJFLJ6e4pPewr1P4EuZYRbMBZwN5KvDLhTGLBuBnaTqUUdF6bj2U22NoRYMogiHiftqKqiexKNDXX1Zg9RQEvxgjuVo6SBW42mVEA8agrLhruRqCmiduJxVrfqLNGeYXHXrcmMEgW7uosJbPXvTcfRvdFWS1ov7oSALvj6vhDQ28Yi9D2ETNdNsfVWAFQuwvPpW7CHQGXTitprVbqH8JYxNZuGygcLmr5efbB22Vzu4ntd1HoraQpG12qeDEUA7tXYUpoYyuSdWwKPjSAMtaQcCSfVrhKQHQuKJargrVrez8vjWuwLfvSucV7ZHe7gjqvYgULdE1ubRCRSd7DuLjEN2Vd6obzV2c3MRet7ZSf4Sp88WM5AuTyW7BjArBc4S3gUQ8rYaiZ8Tu7NCxkEzbFwWRaemZkwfvcsX3XxqjyF37tFSGkEqE5kuBvpZW72675LkDffj7kH1zA8yE6dVujJjWsNYVFJWndUtz5Vy2KCdZAbBgq19q4AtsxWPodU2N3yZXzFAFAzTrxS6V4P7Scpdau1avgRvHLcBQPunA37xaYMy8YMifJwtmRY25mnAQwZAk3eANk7tXwZd58SDnciLNvARJvwKzTQBXcshkwyy52SX8XmXDJsPnRLaHmiYBJ63Yzr5XpZuuAtxb9qrWG2NHCNxfomHokWacV1hjZPPd6ZxT1FuRozB6Qt2NLcyqY7bnTcQJb1jPUaTAGXXCR8WVmmmYo2fDQe8CdBmgyPvbzNTEJUyScBz4RdycB5PZap4SurJCWtHbuMyQbQUB6jJgURDstfXS5Akfe4oruNq9rnYcNtnsDJPtrhXHBqzDizmf1BDxR5FB2RCxzCgeAfg8WQ1Ug9PVAGTzob6ZqCrGXzWXEUniZnf1vjr7QhGKBYXEX9SWDoSMUpP4FreVDTnx15ijRZTV3p8xG5fE9e36TnugRVvTyq7XzmyPBjW2r66f1bior
|
||||
apiServer:
|
||||
port: "8081"
|
||||
nodes:
|
||||
- peerId: 12D3KooWMHuhZgK2skkLrvL51QQTXaXQKYy2QqfvPNBFnzR2ubA1
|
||||
address: 127.0.0.1:4430
|
||||
signingKey: 3id6ddLcoNoe9rDgGM88ET8T6TnvHm5GFqFdN6kBzn7Q8d6VUGgjeT59CNWFiaofdeRnHBvX2A5ZacMXvfwaYEFuCbug
|
||||
encryptionKey: JgG4CcCbae1qEpe7mKpBzsHjZhXUmDSNVNX2B1gxFZsJyMX4V6kBQUott9zRWyeXaW1ZmpzuxDXnwSQpAnNurhXyGa9iQaAPqzY9A9VWBPD33Yy1eW7TRuVemzToh8jJQKQKnZNbF8ucTWV9qahusKzyvN8uyhrqoW2tAPfA9S3E3ognCuqbLSW6yjE2rBKayvyS1BVwzjSd6FZK4DDyjfU3pbEVjut3wytGEAn9af6sNMmyCnf2MX5vLovWs9rU8av61wD4z7HTsXyGFx4K75N4Go249Hpe9SKAT6HxhRc3yvj63krPLiQV5yMuH2UeMUXBDekUQyNmBEdn9wrur7mLqB67Bc6tcc2PP8XApBCdWJHvHjN4FktSpaG5vbCqoZbLD1oCbk36q2x9s6XM8pydVqD1J9P3nTbfgMb5pJCTFjNtgKeuKv6wjfJeA9jF1VhcJQisfsahgv9MvZ9M8FJpZTq1zKUhYDCRnZxUkraoMS5yNNVdDzaUckKEDthqik7BMWCWT79vq7uVgMwEvGwGi76gtoMg1159bbPMLZ4bdPVfhH2S9QjPrzQfwZSrzB2YeVPjWpaXDeLDity5H8n1NK2oniAQR6gE71n81neSptsuhV6o6QpQ89AU8y57XmEsou4VEryn8vUxBHhULLxrLNUouxyWamCeFiDjk5cSN6koQsf9BYKSNTPFTrwjTKForDokMhcPdMtFktKwjv7u9UEGcY4MKvNzZZkc77gHiP8bqVtdNNoLpTFUC5SZ9i7bKdHvK12HpSy7yzzPeMXJ9UwhLxkok1g81ngTbN1yxRhvYXyHZFtguCR9kvGojDjka91MTBtk551qDw9eCn2xZT9U8jqzBCjdpvSg3mRWKMPnYAGB7m7u1ye165wyGFvzcHAx3vtXjxAqLUeKYZCjv2m6V9D2Y4qH1TQNddWqH14T1JVMis971UCH9Ddpj6a3387oUnufD1P6HZN2ieJCvptrmbGVvxJYYSvmVf1dkwbtqurDRNWD7TJ7gf6iqSP549C9bxP4GpLt3ygjHmMtcuUzstBuztvunJUnQhfnJxqU6LjRdsFzm53wGWgXNxab7ZvQcPyLwsevn1b98FGPnVpS5iY4LjmqW4ugrC6HgrbsjrXiKzR1yZKhLQkCbLzPoaHb8iB5iBnCr7d4yf5CtfpFRqgoqMFdK5LNZYmDX4HzUKN6A7wC3gGiSRFTLcgGZeSMkB5Pa61CZBU7WCQgFxykycE9HRA7PiQa496GWDCV15teToCpFRsAa6jDmR1MGXPeLRqQgve49VXnQN5FL7c1VuEv5SWjeTuCnMB47DJKBaP7eKJNKgLwETALzSCMF3nRiRgeb15kfoS4BbrJ5yupjrvwmbmvNg1AYFFS5sYNWft7K8v87wQvBakRtGP71Kp8NX77XFtu6xdB7sR6jpfC6qJPyB9akWNXgCrWy9kE4ih42gwAZdUugNZ9YtEsgRM3pwb6qJhkAPyEJtrxrja859PCAgqPSQiPQN33PaMkgQ6HJknu8CrjKRiXAycZ16KLUkHV64TNhEjPTcX1a7rqpD131AYMWX8d7CCdc9Ys7RUb6BwguuNSh8rJK3x4AkMDSUsaE8ynKvpC7RXZpJ9Nxfhd
|
||||
- peerId: 12D3KooWT3c7Y5zvWhhjSxd5Ve3GKZi6WCsG6JHxcxgXixRFdBbw
|
||||
address: 127.0.0.1:4432
|
||||
signingKey: 3iiLPj6wMUQpPwTBNZcUgkbXub1jumg4AEV9LfMyFHZVc84GLyAjVbVvH6EAGhcNrxRxL82aW4BimhDZCpLsRCqx5vwj
|
||||
encryptionKey: JgG4CcCbae1qEpe7mKXzp7m5hNc56SSyZd9DwUaEStKJrq7RToAC2Vgd3i6hKRwa58zCWeN6Wjc3o6qrdKPEPRvcyEPysamajVo5mdQiUgWAmr97pGEsyjuRjQoC2GY2LvLiEQxEgwFgJxKGMHMiaWMtDfxCDUaDEm4bu5RdMhqRZekAWho6c3WoEeruSr14iX1TrocFNfBkBY7CjEw8kcywXCTNgtvhb2Qiwgj5AxEF4wyw4bzaNA9ctXb1hoHPFVMu6C51pkFY7jUD9zwyH3ukgnAewkGAcPNbKmaTAtMosKRVaAN97mAwXh2VRt1hWmRvVk7r76EjnVKhD4vbsKZc56RVcHTVWRVdhU7FGyPsiE5rSQAz1JQGYzxnZpX7EG77CyrmUGyfueVfRHhwY2oq8A4uQCRaQxSaJHYLowjXSxh8DQ2V6MTqyzti32C27utBYdHzLVCJSGkmdzGwrFcHqsq7nLDxmvJVErPvyReixEe8kFmqopJ3e6LLm8WdYw9K6JYBjXnEfwPzm7Von9sf3dcaGDUHYfttMyeke7fAXJkvPRje69hYVyzdQGAauuojzGkkvQWCSMK1KCMNMznRaPDCNvofrQhYrub24WhmwpKhorufdfW8Cb4T6reBDCtaWVsbuinjtL6F6Sui5aYHJFLJ6e4pPewr1P4EuZYRbMBZwN5KvDLhTGLBuBnaTqUUdF6bj2U22NoRYMogiHiftqKqiexKNDXX1Zg9RQEvxgjuVo6SBW42mVEA8agrLhruRqCmiduJxVrfqLNGeYXHXrcmMEgW7uosJbPXvTcfRvdFWS1ov7oSALvj6vhDQ28Yi9D2ETNdNsfVWAFQuwvPpW7CHQGXTitprVbqH8JYxNZuGygcLmr5efbB22Vzu4ntd1HoraQpG12qeDEUA7tXYUpoYyuSdWwKPjSAMtaQcCSfVrhKQHQuKJargrVrez8vjWuwLfvSucV7ZHe7gjqvYgULdE1ubRCRSd7DuLjEN2Vd6obzV2c3MRet7ZSf4Sp88WM5AuTyW7BjArBc4S3gUQ8rYaiZ8Tu7NCxkEzbFwWRaemZkwfvcsX3XxqjyF37tFSGkEqE5kuBvpZW72675LkDffj7kH1zA8yE6dVujJjWsNYVFJWndUtz5Vy2KCdZAbBgq19q4AtsxWPodU2N3yZXzFAFAzTrxS6V4P7Scpdau1avgRvHLcBQPunA37xaYMy8YMifJwtmRY25mnAQwZAk3eANk7tXwZd58SDnciLNvARJvwKzTQBXcshkwyy52SX8XmXDJsPnRLaHmiYBJ63Yzr5XpZuuAtxb9qrWG2NHCNxfomHokWacV1hjZPPd6ZxT1FuRozB6Qt2NLcyqY7bnTcQJb1jPUaTAGXXCR8WVmmmYo2fDQe8CdBmgyPvbzNTEJUyScBz4RdycB5PZap4SurJCWtHbuMyQbQUB6jJgURDstfXS5Akfe4oruNq9rnYcNtnsDJPtrhXHBqzDizmf1BDxR5FB2RCxzCgeAfg8WQ1Ug9PVAGTzob6ZqCrGXzWXEUniZnf1vjr7QhGKBYXEX9SWDoSMUpP4FreVDTnx15ijRZTV3p8xG5fE9e36TnugRVvTyq7XzmyPBjW2r66f1bior
|
||||
24
etc/configs/config1.yml
Executable file
24
etc/configs/config1.yml
Executable file
@ -0,0 +1,24 @@
|
||||
anytype:
|
||||
swarmKey: /key/swarm/psk/1.0.0/base16/209992e611c27d5dce8fbd2e7389f6b51da9bee980992ef60739460b536139ec
|
||||
grpcServer:
|
||||
listenAddrs:
|
||||
- 127.0.0.1:4430
|
||||
tls: false
|
||||
account:
|
||||
peerId: 12D3KooWSUx2LXPvoZGp72Dt7b7r1kPSmQ6zAUwKkHFyX64uiXRu
|
||||
signingKey: 4QTrtkFLQe9wQcWT/cgFEwfMHB5pt4axInNmCIMCZaz3nVdyygRoO8/YH0V15X6Mnw1NQWsS1YIWiLS22hwepA==
|
||||
encryptionKey: MIIEpAIBAAKCAQEAmqAAOPfR86po3m+zwSzbAlZGgMMF188v35Ulqf9Gb4KO8DZ9ifxrqpjlLZRxPKeXj3wSSQEXsJf3A82rZlDxddZSM0i7Mx5G2G0zRHWx9dC58PpX6o/fDuvSwcyXqOgIK55N/hyEuIbWQgp5Rk9uy2Zbrhv5ZL5CvceM0b9wSKt/hRvntxSbG+HRgXWaQvAReGuJrySVvkh6fhC3G0IwqyFbGNq2zqAJej6NBzZA3thHgTn5PoWD8O4cyukBxunKGu3HLE3vJtqEMFrkNFw5SMpdEtxyTLN6T1HIeYCY9RL+BFYfxIWg6pGtIoIJKUB0XapJr9ltzvXfT9KeSCU0VwIDAQABAoIBAAp/xsQXf7gN4CUKbKg3RX+5H/xqQaFPvi5uUCpk3QGBWfdRm+CctSrWSul3ZOD7eD0T7aHrYxJonysw8ex2no6jyN0WmS91ZNYZRBvn6feI/rcwKHwS3NCEjsD+BWZAqx1bGGyivxhQf4fociemCR3ii2MdHygKCzobrKIpX5RvhanI4j01dyLlxwqTsteuc/o5RR4jfg1eN0kldFjk3UcSNyzzEv5o5UhRsHCLJBTNTvYZBN4FpyaqcLT9gKS9aVBvQH63R+E5dyxo1+24tZZricW59h2bN3CFriqkwBo1y0gTnR6VQ22MBvIUxYUm82cxXs/Vr0YQTSAaEGThxFECgYEAxKQMRnM39WMzrNx1WDwpBERRj1T0TbLf1uq6viPiLdik2Tm2aCBZyr5j82Ey7fZ7OafKGfsM0I2AuYeoBdYDuYN6A7tE9kpnECubnWuIvUeYcL+1VzzMedVtdKwQXrYbhqKtyvnSJ9gQ6CusHtsDE1bQvTMxBX4KNBeBYllCUasCgYEAyU0RPUaj56CyLHKty8jNg6wl+06IZ0pUPIWZ//b1zeZrlHGYDp/InxS8huGFapzOg1sbQBS6j3j3YE3Ts6v6FNuIa4pcPQ91YuHiWWQdgVjrCZdleanFWGTjIx12+RGj9vx4voRhNQcHW1YeTvvyj4BN/ECR6GNaoS/ZjBKo1AUCgYEAj6AyxxJJARagG9Y6b2QhoVg1Kjem6UmJbPStyUt0XIAsh+07afqXGxrM7mtEQ8MQZiBD4Y4Y4gs4xkprUzfqKIn7iNYznKDjflAbrXNpwLaWhWPBFCL4RtS4ycsTedoRaNlRjzvBYBDU6H9djHvzVyDF/itx1s0krr+sZSVE51kCgYBxGRinecna+KFCccgNp6s34H+Se2QNzGgZfOKyOjmOTniA9XV+Oe3I2yi1C34fESzCBm0ACuVqeIdcFz3rQ6OFFnbGHP2H3OiR/uFiYepl4uRjBimgOm9DI6Ot9f8DHxMlUGIygEPxPBq5CWCL9egpEeg+4rRXgYLI7w5mMZGjVQKBgQDC4qyH7FK3lLv5JomoK6nNjpyPNBmr0Rt215oM/AWQaxDhFZH5un68ueZ7MfybwXxHHFQ4ZeSwYs006f1XGPNW6qrH6pi/3SCLFuGVfNnLVwCBkm3QaQrxFm3v9LmVCidTNta0l0DrUldZdK8/P31GBxKo/MmYF/f9LO/Mfm/uDg==
|
||||
apiServer:
|
||||
port: "8080"
|
||||
nodes:
|
||||
- peerId: 12D3KooWSUx2LXPvoZGp72Dt7b7r1kPSmQ6zAUwKkHFyX64uiXRu
|
||||
address: 127.0.0.1:4430
|
||||
signingKey: 4QTrtkFLQe9wQcWT/cgFEwfMHB5pt4axInNmCIMCZaz3nVdyygRoO8/YH0V15X6Mnw1NQWsS1YIWiLS22hwepA==
|
||||
encryptionKey: MIIEpAIBAAKCAQEAmqAAOPfR86po3m+zwSzbAlZGgMMF188v35Ulqf9Gb4KO8DZ9ifxrqpjlLZRxPKeXj3wSSQEXsJf3A82rZlDxddZSM0i7Mx5G2G0zRHWx9dC58PpX6o/fDuvSwcyXqOgIK55N/hyEuIbWQgp5Rk9uy2Zbrhv5ZL5CvceM0b9wSKt/hRvntxSbG+HRgXWaQvAReGuJrySVvkh6fhC3G0IwqyFbGNq2zqAJej6NBzZA3thHgTn5PoWD8O4cyukBxunKGu3HLE3vJtqEMFrkNFw5SMpdEtxyTLN6T1HIeYCY9RL+BFYfxIWg6pGtIoIJKUB0XapJr9ltzvXfT9KeSCU0VwIDAQABAoIBAAp/xsQXf7gN4CUKbKg3RX+5H/xqQaFPvi5uUCpk3QGBWfdRm+CctSrWSul3ZOD7eD0T7aHrYxJonysw8ex2no6jyN0WmS91ZNYZRBvn6feI/rcwKHwS3NCEjsD+BWZAqx1bGGyivxhQf4fociemCR3ii2MdHygKCzobrKIpX5RvhanI4j01dyLlxwqTsteuc/o5RR4jfg1eN0kldFjk3UcSNyzzEv5o5UhRsHCLJBTNTvYZBN4FpyaqcLT9gKS9aVBvQH63R+E5dyxo1+24tZZricW59h2bN3CFriqkwBo1y0gTnR6VQ22MBvIUxYUm82cxXs/Vr0YQTSAaEGThxFECgYEAxKQMRnM39WMzrNx1WDwpBERRj1T0TbLf1uq6viPiLdik2Tm2aCBZyr5j82Ey7fZ7OafKGfsM0I2AuYeoBdYDuYN6A7tE9kpnECubnWuIvUeYcL+1VzzMedVtdKwQXrYbhqKtyvnSJ9gQ6CusHtsDE1bQvTMxBX4KNBeBYllCUasCgYEAyU0RPUaj56CyLHKty8jNg6wl+06IZ0pUPIWZ//b1zeZrlHGYDp/InxS8huGFapzOg1sbQBS6j3j3YE3Ts6v6FNuIa4pcPQ91YuHiWWQdgVjrCZdleanFWGTjIx12+RGj9vx4voRhNQcHW1YeTvvyj4BN/ECR6GNaoS/ZjBKo1AUCgYEAj6AyxxJJARagG9Y6b2QhoVg1Kjem6UmJbPStyUt0XIAsh+07afqXGxrM7mtEQ8MQZiBD4Y4Y4gs4xkprUzfqKIn7iNYznKDjflAbrXNpwLaWhWPBFCL4RtS4ycsTedoRaNlRjzvBYBDU6H9djHvzVyDF/itx1s0krr+sZSVE51kCgYBxGRinecna+KFCccgNp6s34H+Se2QNzGgZfOKyOjmOTniA9XV+Oe3I2yi1C34fESzCBm0ACuVqeIdcFz3rQ6OFFnbGHP2H3OiR/uFiYepl4uRjBimgOm9DI6Ot9f8DHxMlUGIygEPxPBq5CWCL9egpEeg+4rRXgYLI7w5mMZGjVQKBgQDC4qyH7FK3lLv5JomoK6nNjpyPNBmr0Rt215oM/AWQaxDhFZH5un68ueZ7MfybwXxHHFQ4ZeSwYs006f1XGPNW6qrH6pi/3SCLFuGVfNnLVwCBkm3QaQrxFm3v9LmVCidTNta0l0DrUldZdK8/P31GBxKo/MmYF/f9LO/Mfm/uDg==
|
||||
- peerId: 12D3KooWFnz9fYCxHAnf2rvPQ7iPZcCprEqyN8kCtVQfN2K1TfqK
|
||||
address: 127.0.0.1:4431
|
||||
signingKey: IM0BTVQf4LKMUVRTAHxbBXmdz656+G2ssw4WdLc30pRYy6TsVVdh+n03pKXSCdg665tM/9AjQRCbzgvDf9riWg==
|
||||
encryptionKey: MIIEpAIBAAKCAQEAm0HILjO7GRYYb0AvESmxdaj6ruIcSHEQIyqhPbfXZSmJNo9wIq89SaYYL4ZTwrF+ykPDJcBA8SjNHGXBPhZY+ejwCDzDyyv42FMs5lKw+/x94Yg++W72sxawtCLVi0RVY1g4UxOlCgAxl3YC9mVYoqQveXN3EsDd0YNK9fWiWP/Xl3KaJ4ErsfW3LZS9rD36dgDsKr9GqeVQf7lGkCkDmivCwHn3uaN/uzHaWvaZ7e7QWE/36vTmMsllTvi0Q9Y+v+HB5isIX9Jve1QmCS//DbDl9IMGdmyg/jlBs63Nk86Qwlw8ft3ttTWNldTpvD4Ycbgj3l59jT4rIvFJ88+5UwIDAQABAoIBAFfUn/1bMIYhlNMi+T15W7YXUTGsIpnstFdgn3T90rGdDM272ZEVl9NZTidck3f516NvMC/kEhkbnuVovyhzlgRS/a97SLxgdNdUPntR3mO/VCtJW27akl9//5j4d9vgXXnlB4AgBeahc2yey1A+xyTDQ0QuyPbn+tSytK5uNlioCeAqH4ruWxcg4t8MnwNQEOsnchrYHfXqJG+XxGn7m60U4oclbObGfxWxYZ85I0B6M5PW71VLkj/eKTvRJcW5ShDKLG5meiUM3KtwUdFRzv9Xi4aB9eTwEQ8ZV18KVmIF7baBy5anWDfGO4O9MvFSMmbMCe3EkrGaEaCp/gXenhkCgYEAw57dj7ewVHIAQxcNZ9SPRUNAY8g9yEYQ//30yTcpUjsGlqGNzua2OvALGL2ntFY304X9Iego+7Tzxs3T0x2FQ1N33NhoxwRcMqBdksMqmCb8Bm8UvnFIuvmsfPGkkzwa/8xNH81GZiz0p9zfi4lSKdZRfTQ4lBqvogExdnalSd0CgYEAyy2Mw4eeJQ0Y6QX3nad2/06oxWiS3++CITI6dAqiepAB6V7lnP90NKfLgzJcCJwzKlMhoVv7Lu4bDCXbvQ03ba+Dl+To8Jf5/9di8j8OfllqDWPnbqyueTHu5CUk+A2Gz3RhjmMXHpVgbFkUJTkJ1RDWPImNq0KzTYQ+ZwU8lO8CgYEAo1/0zuisnXowedew3HyLw17tUeiUoMTTwdiJLduh6Qle8UKvupK4svRzcBBFFbnEGiaXSFAqmj2AMxMHzBOljpsRSiJ7L2uWzLleLQpOcpBsf7sZ6guWoIGQ6zCtMEJMkkJAT0UTfJYjJmazVEg1lLdni1enwRmggX7ZnoRsewkCgYB2SpLF1FOSpsl2Ae9kbnettRI1vOimUD+nLCM0JGzshqNWR9XPTjtN3NN0EwHaUXbIkZXm6DKZ5C8DJ5eDvgojZihrau7kBNecyL3m5CeAEHbaTOwVV5xNG3FGiwm3EckHR271A2QWfkmhS0ubUFYVIrRYko1UxIS4AOKEAFyBKQKBgQCfIsGy4wOqRsKLzbun6fIVSz8X+sS3HclD7UZ6WKanOYyWZ420qnhLnfdDcNQjF/ApbzHdfwwTKVJSaZiK23kmReUVB9Cq2YAHMJYlp0ErgPzZstrRiRidtzJHm93owWc7GZinzd1M8EOYUSJ3+t8EZXZlbsD/oCTbX/BGqolo2w==
|
||||
space:
|
||||
gcTTL: 60
|
||||
syncPeriod: 10
|
||||
24
etc/configs/config2.yml
Executable file
24
etc/configs/config2.yml
Executable file
@ -0,0 +1,24 @@
|
||||
anytype:
|
||||
swarmKey: /key/swarm/psk/1.0.0/base16/209992e611c27d5dce8fbd2e7389f6b51da9bee980992ef60739460b536139ec
|
||||
grpcServer:
|
||||
listenAddrs:
|
||||
- 127.0.0.1:4431
|
||||
tls: false
|
||||
account:
|
||||
peerId: 12D3KooWFnz9fYCxHAnf2rvPQ7iPZcCprEqyN8kCtVQfN2K1TfqK
|
||||
signingKey: IM0BTVQf4LKMUVRTAHxbBXmdz656+G2ssw4WdLc30pRYy6TsVVdh+n03pKXSCdg665tM/9AjQRCbzgvDf9riWg==
|
||||
encryptionKey: MIIEpAIBAAKCAQEAm0HILjO7GRYYb0AvESmxdaj6ruIcSHEQIyqhPbfXZSmJNo9wIq89SaYYL4ZTwrF+ykPDJcBA8SjNHGXBPhZY+ejwCDzDyyv42FMs5lKw+/x94Yg++W72sxawtCLVi0RVY1g4UxOlCgAxl3YC9mVYoqQveXN3EsDd0YNK9fWiWP/Xl3KaJ4ErsfW3LZS9rD36dgDsKr9GqeVQf7lGkCkDmivCwHn3uaN/uzHaWvaZ7e7QWE/36vTmMsllTvi0Q9Y+v+HB5isIX9Jve1QmCS//DbDl9IMGdmyg/jlBs63Nk86Qwlw8ft3ttTWNldTpvD4Ycbgj3l59jT4rIvFJ88+5UwIDAQABAoIBAFfUn/1bMIYhlNMi+T15W7YXUTGsIpnstFdgn3T90rGdDM272ZEVl9NZTidck3f516NvMC/kEhkbnuVovyhzlgRS/a97SLxgdNdUPntR3mO/VCtJW27akl9//5j4d9vgXXnlB4AgBeahc2yey1A+xyTDQ0QuyPbn+tSytK5uNlioCeAqH4ruWxcg4t8MnwNQEOsnchrYHfXqJG+XxGn7m60U4oclbObGfxWxYZ85I0B6M5PW71VLkj/eKTvRJcW5ShDKLG5meiUM3KtwUdFRzv9Xi4aB9eTwEQ8ZV18KVmIF7baBy5anWDfGO4O9MvFSMmbMCe3EkrGaEaCp/gXenhkCgYEAw57dj7ewVHIAQxcNZ9SPRUNAY8g9yEYQ//30yTcpUjsGlqGNzua2OvALGL2ntFY304X9Iego+7Tzxs3T0x2FQ1N33NhoxwRcMqBdksMqmCb8Bm8UvnFIuvmsfPGkkzwa/8xNH81GZiz0p9zfi4lSKdZRfTQ4lBqvogExdnalSd0CgYEAyy2Mw4eeJQ0Y6QX3nad2/06oxWiS3++CITI6dAqiepAB6V7lnP90NKfLgzJcCJwzKlMhoVv7Lu4bDCXbvQ03ba+Dl+To8Jf5/9di8j8OfllqDWPnbqyueTHu5CUk+A2Gz3RhjmMXHpVgbFkUJTkJ1RDWPImNq0KzTYQ+ZwU8lO8CgYEAo1/0zuisnXowedew3HyLw17tUeiUoMTTwdiJLduh6Qle8UKvupK4svRzcBBFFbnEGiaXSFAqmj2AMxMHzBOljpsRSiJ7L2uWzLleLQpOcpBsf7sZ6guWoIGQ6zCtMEJMkkJAT0UTfJYjJmazVEg1lLdni1enwRmggX7ZnoRsewkCgYB2SpLF1FOSpsl2Ae9kbnettRI1vOimUD+nLCM0JGzshqNWR9XPTjtN3NN0EwHaUXbIkZXm6DKZ5C8DJ5eDvgojZihrau7kBNecyL3m5CeAEHbaTOwVV5xNG3FGiwm3EckHR271A2QWfkmhS0ubUFYVIrRYko1UxIS4AOKEAFyBKQKBgQCfIsGy4wOqRsKLzbun6fIVSz8X+sS3HclD7UZ6WKanOYyWZ420qnhLnfdDcNQjF/ApbzHdfwwTKVJSaZiK23kmReUVB9Cq2YAHMJYlp0ErgPzZstrRiRidtzJHm93owWc7GZinzd1M8EOYUSJ3+t8EZXZlbsD/oCTbX/BGqolo2w==
|
||||
apiServer:
|
||||
port: "8081"
|
||||
nodes:
|
||||
- peerId: 12D3KooWSUx2LXPvoZGp72Dt7b7r1kPSmQ6zAUwKkHFyX64uiXRu
|
||||
address: 127.0.0.1:4430
|
||||
signingKey: 4QTrtkFLQe9wQcWT/cgFEwfMHB5pt4axInNmCIMCZaz3nVdyygRoO8/YH0V15X6Mnw1NQWsS1YIWiLS22hwepA==
|
||||
encryptionKey: MIIEpAIBAAKCAQEAmqAAOPfR86po3m+zwSzbAlZGgMMF188v35Ulqf9Gb4KO8DZ9ifxrqpjlLZRxPKeXj3wSSQEXsJf3A82rZlDxddZSM0i7Mx5G2G0zRHWx9dC58PpX6o/fDuvSwcyXqOgIK55N/hyEuIbWQgp5Rk9uy2Zbrhv5ZL5CvceM0b9wSKt/hRvntxSbG+HRgXWaQvAReGuJrySVvkh6fhC3G0IwqyFbGNq2zqAJej6NBzZA3thHgTn5PoWD8O4cyukBxunKGu3HLE3vJtqEMFrkNFw5SMpdEtxyTLN6T1HIeYCY9RL+BFYfxIWg6pGtIoIJKUB0XapJr9ltzvXfT9KeSCU0VwIDAQABAoIBAAp/xsQXf7gN4CUKbKg3RX+5H/xqQaFPvi5uUCpk3QGBWfdRm+CctSrWSul3ZOD7eD0T7aHrYxJonysw8ex2no6jyN0WmS91ZNYZRBvn6feI/rcwKHwS3NCEjsD+BWZAqx1bGGyivxhQf4fociemCR3ii2MdHygKCzobrKIpX5RvhanI4j01dyLlxwqTsteuc/o5RR4jfg1eN0kldFjk3UcSNyzzEv5o5UhRsHCLJBTNTvYZBN4FpyaqcLT9gKS9aVBvQH63R+E5dyxo1+24tZZricW59h2bN3CFriqkwBo1y0gTnR6VQ22MBvIUxYUm82cxXs/Vr0YQTSAaEGThxFECgYEAxKQMRnM39WMzrNx1WDwpBERRj1T0TbLf1uq6viPiLdik2Tm2aCBZyr5j82Ey7fZ7OafKGfsM0I2AuYeoBdYDuYN6A7tE9kpnECubnWuIvUeYcL+1VzzMedVtdKwQXrYbhqKtyvnSJ9gQ6CusHtsDE1bQvTMxBX4KNBeBYllCUasCgYEAyU0RPUaj56CyLHKty8jNg6wl+06IZ0pUPIWZ//b1zeZrlHGYDp/InxS8huGFapzOg1sbQBS6j3j3YE3Ts6v6FNuIa4pcPQ91YuHiWWQdgVjrCZdleanFWGTjIx12+RGj9vx4voRhNQcHW1YeTvvyj4BN/ECR6GNaoS/ZjBKo1AUCgYEAj6AyxxJJARagG9Y6b2QhoVg1Kjem6UmJbPStyUt0XIAsh+07afqXGxrM7mtEQ8MQZiBD4Y4Y4gs4xkprUzfqKIn7iNYznKDjflAbrXNpwLaWhWPBFCL4RtS4ycsTedoRaNlRjzvBYBDU6H9djHvzVyDF/itx1s0krr+sZSVE51kCgYBxGRinecna+KFCccgNp6s34H+Se2QNzGgZfOKyOjmOTniA9XV+Oe3I2yi1C34fESzCBm0ACuVqeIdcFz3rQ6OFFnbGHP2H3OiR/uFiYepl4uRjBimgOm9DI6Ot9f8DHxMlUGIygEPxPBq5CWCL9egpEeg+4rRXgYLI7w5mMZGjVQKBgQDC4qyH7FK3lLv5JomoK6nNjpyPNBmr0Rt215oM/AWQaxDhFZH5un68ueZ7MfybwXxHHFQ4ZeSwYs006f1XGPNW6qrH6pi/3SCLFuGVfNnLVwCBkm3QaQrxFm3v9LmVCidTNta0l0DrUldZdK8/P31GBxKo/MmYF/f9LO/Mfm/uDg==
|
||||
- peerId: 12D3KooWFnz9fYCxHAnf2rvPQ7iPZcCprEqyN8kCtVQfN2K1TfqK
|
||||
address: 127.0.0.1:4431
|
||||
signingKey: IM0BTVQf4LKMUVRTAHxbBXmdz656+G2ssw4WdLc30pRYy6TsVVdh+n03pKXSCdg665tM/9AjQRCbzgvDf9riWg==
|
||||
encryptionKey: MIIEpAIBAAKCAQEAm0HILjO7GRYYb0AvESmxdaj6ruIcSHEQIyqhPbfXZSmJNo9wIq89SaYYL4ZTwrF+ykPDJcBA8SjNHGXBPhZY+ejwCDzDyyv42FMs5lKw+/x94Yg++W72sxawtCLVi0RVY1g4UxOlCgAxl3YC9mVYoqQveXN3EsDd0YNK9fWiWP/Xl3KaJ4ErsfW3LZS9rD36dgDsKr9GqeVQf7lGkCkDmivCwHn3uaN/uzHaWvaZ7e7QWE/36vTmMsllTvi0Q9Y+v+HB5isIX9Jve1QmCS//DbDl9IMGdmyg/jlBs63Nk86Qwlw8ft3ttTWNldTpvD4Ycbgj3l59jT4rIvFJ88+5UwIDAQABAoIBAFfUn/1bMIYhlNMi+T15W7YXUTGsIpnstFdgn3T90rGdDM272ZEVl9NZTidck3f516NvMC/kEhkbnuVovyhzlgRS/a97SLxgdNdUPntR3mO/VCtJW27akl9//5j4d9vgXXnlB4AgBeahc2yey1A+xyTDQ0QuyPbn+tSytK5uNlioCeAqH4ruWxcg4t8MnwNQEOsnchrYHfXqJG+XxGn7m60U4oclbObGfxWxYZ85I0B6M5PW71VLkj/eKTvRJcW5ShDKLG5meiUM3KtwUdFRzv9Xi4aB9eTwEQ8ZV18KVmIF7baBy5anWDfGO4O9MvFSMmbMCe3EkrGaEaCp/gXenhkCgYEAw57dj7ewVHIAQxcNZ9SPRUNAY8g9yEYQ//30yTcpUjsGlqGNzua2OvALGL2ntFY304X9Iego+7Tzxs3T0x2FQ1N33NhoxwRcMqBdksMqmCb8Bm8UvnFIuvmsfPGkkzwa/8xNH81GZiz0p9zfi4lSKdZRfTQ4lBqvogExdnalSd0CgYEAyy2Mw4eeJQ0Y6QX3nad2/06oxWiS3++CITI6dAqiepAB6V7lnP90NKfLgzJcCJwzKlMhoVv7Lu4bDCXbvQ03ba+Dl+To8Jf5/9di8j8OfllqDWPnbqyueTHu5CUk+A2Gz3RhjmMXHpVgbFkUJTkJ1RDWPImNq0KzTYQ+ZwU8lO8CgYEAo1/0zuisnXowedew3HyLw17tUeiUoMTTwdiJLduh6Qle8UKvupK4svRzcBBFFbnEGiaXSFAqmj2AMxMHzBOljpsRSiJ7L2uWzLleLQpOcpBsf7sZ6guWoIGQ6zCtMEJMkkJAT0UTfJYjJmazVEg1lLdni1enwRmggX7ZnoRsewkCgYB2SpLF1FOSpsl2Ae9kbnettRI1vOimUD+nLCM0JGzshqNWR9XPTjtN3NN0EwHaUXbIkZXm6DKZ5C8DJ5eDvgojZihrau7kBNecyL3m5CeAEHbaTOwVV5xNG3FGiwm3EckHR271A2QWfkmhS0ubUFYVIrRYko1UxIS4AOKEAFyBKQKBgQCfIsGy4wOqRsKLzbun6fIVSz8X+sS3HclD7UZ6WKanOYyWZ420qnhLnfdDcNQjF/ApbzHdfwwTKVJSaZiK23kmReUVB9Cq2YAHMJYlp0ErgPzZstrRiRidtzJHm93owWc7GZinzd1M8EOYUSJ3+t8EZXZlbsD/oCTbX/BGqolo2w==
|
||||
space:
|
||||
gcTTL: 60
|
||||
syncPeriod: 10
|
||||
4
go.mod
4
go.mod
@ -3,6 +3,7 @@ module github.com/anytypeio/go-anytype-infrastructure-experiments
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/anytypeio/go-chash v0.0.0-20220629194632-4ad1154fe232
|
||||
github.com/awalterschulze/gographviz v0.0.0-20190522210029-fa59802746ab
|
||||
github.com/cespare/xxhash v1.1.0
|
||||
github.com/goccy/go-graphviz v0.0.9
|
||||
@ -16,6 +17,7 @@ require (
|
||||
github.com/multiformats/go-multihash v0.1.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/zeebo/blake3 v0.2.3
|
||||
github.com/zeebo/errs v1.3.0
|
||||
go.uber.org/zap v1.21.0
|
||||
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
@ -24,7 +26,6 @@ require (
|
||||
|
||||
require (
|
||||
github.com/OneOfOne/xxhash v1.2.8 // indirect
|
||||
github.com/anytypeio/go-chash v0.0.0-20220629194632-4ad1154fe232 // indirect
|
||||
github.com/btcsuite/btcd v0.22.1 // indirect
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.1.3 // indirect
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect
|
||||
@ -46,7 +47,6 @@ require (
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/zeebo/errs v1.2.2 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
|
||||
|
||||
4
go.sum
4
go.sum
@ -113,8 +113,8 @@ github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN
|
||||
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
||||
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
|
||||
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
|
||||
github.com/zeebo/errs v1.2.2 h1:5NFypMTuSdoySVTqlNs1dEoU21QVamMQJxW/Fii5O7g=
|
||||
github.com/zeebo/errs v1.2.2/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
|
||||
github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs=
|
||||
github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
|
||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
|
||||
63
node/account/service.go
Normal file
63
node/account/service.go
Normal file
@ -0,0 +1,63 @@
|
||||
package account
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
commonaccount "github.com/anytypeio/go-anytype-infrastructure-experiments/common/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/config"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
)
|
||||
|
||||
type service struct {
|
||||
accountData *account.AccountData
|
||||
peerId string
|
||||
}
|
||||
|
||||
func (s *service) Account() *account.AccountData {
|
||||
return s.accountData
|
||||
}
|
||||
|
||||
func New() app.Component {
|
||||
return &service{}
|
||||
}
|
||||
|
||||
func (s *service) Init(a *app.App) (err error) {
|
||||
cfg := a.MustComponent(config.CName).(*config.Config)
|
||||
acc := cfg.Account
|
||||
|
||||
decodedEncryptionKey, err := keys.DecodeKeyFromString(
|
||||
acc.EncryptionKey,
|
||||
encryptionkey.NewEncryptionRsaPrivKeyFromBytes,
|
||||
nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
decodedSigningKey, err := keys.DecodeKeyFromString(
|
||||
acc.SigningKey,
|
||||
signingkey.NewSigningEd25519PrivKeyFromBytes,
|
||||
nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
identity, err := decodedSigningKey.GetPublic().Raw()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.accountData = &account.AccountData{
|
||||
Identity: identity,
|
||||
SignKey: decodedSigningKey,
|
||||
EncKey: decodedEncryptionKey,
|
||||
}
|
||||
s.peerId = acc.PeerId
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Name() (name string) {
|
||||
return commonaccount.CName
|
||||
}
|
||||
85
node/nodespace/nodecache/treecache.go
Normal file
85
node/nodespace/nodecache/treecache.go
Normal file
@ -0,0 +1,85 @@
|
||||
package nodecache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/cache"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/node/nodespace"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/ocache"
|
||||
"time"
|
||||
)
|
||||
|
||||
var log = logger.NewNamed("treecache")
|
||||
var ErrCacheObjectWithoutTree = errors.New("cache object contains no tree")
|
||||
|
||||
type ctxKey int
|
||||
|
||||
const spaceKey ctxKey = 0
|
||||
|
||||
type treeCache struct {
|
||||
gcttl int
|
||||
cache ocache.OCache
|
||||
nodeService nodespace.Service
|
||||
}
|
||||
|
||||
func (c *treeCache) Run(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *treeCache) Close(ctx context.Context) (err error) {
|
||||
return c.cache.Close()
|
||||
}
|
||||
|
||||
func (c *treeCache) Init(a *app.App) (err error) {
|
||||
c.nodeService = a.MustComponent(nodespace.CName).(nodespace.Service)
|
||||
c.cache = ocache.New(
|
||||
func(ctx context.Context, id string) (value ocache.Object, err error) {
|
||||
spaceId := ctx.Value(spaceKey).(string)
|
||||
space, err := c.nodeService.GetSpace(ctx, spaceId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return space.BuildTree(ctx, id, nil)
|
||||
},
|
||||
ocache.WithLogger(log.Sugar()),
|
||||
ocache.WithGCPeriod(time.Minute),
|
||||
ocache.WithTTL(time.Duration(c.gcttl)*time.Second),
|
||||
ocache.WithRefCounter(false),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *treeCache) Name() (name string) {
|
||||
return cache.CName
|
||||
}
|
||||
|
||||
func NewNodeCache(ttl int) cache.TreeCache {
|
||||
return &treeCache{
|
||||
gcttl: ttl,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *treeCache) GetTree(ctx context.Context, spaceId, id string) (res cache.TreeResult, err error) {
|
||||
var cacheRes ocache.Object
|
||||
ctx = context.WithValue(ctx, spaceKey, spaceId)
|
||||
cacheRes, err = c.cache.Get(ctx, id)
|
||||
if err != nil {
|
||||
return cache.TreeResult{}, err
|
||||
}
|
||||
|
||||
treeContainer, ok := cacheRes.(cache.TreeContainer)
|
||||
if !ok {
|
||||
err = ErrCacheObjectWithoutTree
|
||||
return
|
||||
}
|
||||
|
||||
res = cache.TreeResult{
|
||||
Release: func() {
|
||||
c.cache.Release(id)
|
||||
},
|
||||
TreeContainer: treeContainer,
|
||||
}
|
||||
return
|
||||
}
|
||||
59
node/nodespace/rpchandler.go
Normal file
59
node/nodespace/rpchandler.go
Normal file
@ -0,0 +1,59 @@
|
||||
package nodespace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/cache"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage"
|
||||
)
|
||||
|
||||
type rpcHandler struct {
|
||||
s *service
|
||||
}
|
||||
|
||||
func (r *rpcHandler) PushSpace(ctx context.Context, req *spacesyncproto.PushSpaceRequest) (resp *spacesyncproto.PushSpaceResponse, err error) {
|
||||
_, err = r.s.GetSpace(ctx, req.SpaceId)
|
||||
if err == nil {
|
||||
err = spacesyncproto.ErrSpaceExists
|
||||
return
|
||||
}
|
||||
if err != cache.ErrSpaceNotFound {
|
||||
err = spacesyncproto.ErrUnexpected
|
||||
return
|
||||
}
|
||||
|
||||
payload := storage.SpaceStorageCreatePayload{
|
||||
RecWithId: req.AclRoot,
|
||||
SpaceHeader: req.SpaceHeader,
|
||||
Id: req.SpaceId,
|
||||
}
|
||||
_, err = r.s.spaceStorageProvider.CreateSpaceStorage(payload)
|
||||
if err != nil {
|
||||
err = spacesyncproto.ErrUnexpected
|
||||
if err == storage.ErrSpaceStorageExists {
|
||||
err = spacesyncproto.ErrSpaceExists
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *rpcHandler) HeadSync(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (*spacesyncproto.HeadSyncResponse, error) {
|
||||
sp, err := r.s.GetSpace(ctx, req.SpaceId)
|
||||
if err != nil {
|
||||
return nil, spacesyncproto.ErrSpaceMissing
|
||||
}
|
||||
return sp.SpaceSyncRpc().HeadSync(ctx, req)
|
||||
}
|
||||
|
||||
func (r *rpcHandler) Stream(stream spacesyncproto.DRPCSpace_StreamStream) error {
|
||||
msg, err := stream.Recv()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sp, err := r.s.GetSpace(stream.Context(), msg.SpaceId)
|
||||
if err != nil {
|
||||
return spacesyncproto.ErrSpaceMissing
|
||||
}
|
||||
return sp.SpaceSyncRpc().Stream(stream)
|
||||
}
|
||||
74
node/nodespace/service.go
Normal file
74
node/nodespace/service.go
Normal file
@ -0,0 +1,74 @@
|
||||
package nodespace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/rpc/server"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/config"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/ocache"
|
||||
"time"
|
||||
)
|
||||
|
||||
const CName = "node.nodespace"
|
||||
|
||||
var log = logger.NewNamed(CName)
|
||||
|
||||
func New() Service {
|
||||
return &service{}
|
||||
}
|
||||
|
||||
type Service interface {
|
||||
GetSpace(ctx context.Context, id string) (commonspace.Space, error)
|
||||
app.ComponentRunnable
|
||||
}
|
||||
|
||||
type service struct {
|
||||
conf config.Space
|
||||
spaceCache ocache.OCache
|
||||
commonSpace commonspace.Service
|
||||
spaceStorageProvider storage.SpaceStorageProvider
|
||||
}
|
||||
|
||||
func (s *service) Init(a *app.App) (err error) {
|
||||
s.conf = a.MustComponent(config.CName).(*config.Config).Space
|
||||
s.commonSpace = a.MustComponent(commonspace.CName).(commonspace.Service)
|
||||
s.spaceStorageProvider = a.MustComponent(storage.CName).(storage.SpaceStorageProvider)
|
||||
s.spaceCache = ocache.New(
|
||||
func(ctx context.Context, id string) (value ocache.Object, err error) {
|
||||
return s.commonSpace.GetSpace(ctx, id)
|
||||
},
|
||||
ocache.WithLogger(log.Sugar()),
|
||||
ocache.WithGCPeriod(time.Minute),
|
||||
ocache.WithTTL(time.Duration(s.conf.GCTTL)*time.Second),
|
||||
ocache.WithRefCounter(false),
|
||||
)
|
||||
return spacesyncproto.DRPCRegisterSpace(a.MustComponent(server.CName).(server.DRPCServer), &rpcHandler{s})
|
||||
}
|
||||
|
||||
func (s *service) Name() (name string) {
|
||||
return CName
|
||||
}
|
||||
|
||||
func (s *service) Run(ctx context.Context) (err error) {
|
||||
go func() {
|
||||
time.Sleep(time.Second * 5)
|
||||
_, _ = s.GetSpace(ctx, "testDSpace")
|
||||
}()
|
||||
return
|
||||
}
|
||||
|
||||
func (s *service) GetSpace(ctx context.Context, id string) (commonspace.Space, error) {
|
||||
v, err := s.spaceCache.Get(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return v.(commonspace.Space), nil
|
||||
}
|
||||
|
||||
func (s *service) Close(ctx context.Context) (err error) {
|
||||
return s.spaceCache.Close()
|
||||
}
|
||||
@ -1,14 +1,12 @@
|
||||
package account
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
)
|
||||
|
||||
type AccountData struct { // TODO: create a convenient constructor for this
|
||||
Identity string // TODO: this is essentially the same as sign key
|
||||
Identity []byte // public key
|
||||
SignKey signingkey.PrivKey
|
||||
EncKey encryptionkey.PrivKey
|
||||
Decoder keys.Decoder
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,148 +0,0 @@
|
||||
syntax = "proto3";
|
||||
package acl;
|
||||
option go_package = "aclpb";
|
||||
|
||||
message RawChange {
|
||||
bytes payload = 1;
|
||||
bytes signature = 2;
|
||||
string id = 3;
|
||||
}
|
||||
|
||||
message RawRecord {
|
||||
bytes payload = 1;
|
||||
bytes signature = 2;
|
||||
string id = 3;
|
||||
}
|
||||
|
||||
// the element of change tree used to store and internal apply smartBlock history
|
||||
message ACLChange {
|
||||
repeated string treeHeadIds = 1;
|
||||
repeated string aclHeadIds = 2;
|
||||
string snapshotBaseId = 3; // we will only have one base snapshot for both
|
||||
ACLData aclData = 4;
|
||||
// the data is encoded with read key and should be read in ChangesData format
|
||||
bytes changesData = 5;
|
||||
uint64 currentReadKeyHash = 6;
|
||||
int64 timestamp = 7;
|
||||
string identity = 8;
|
||||
|
||||
message ACLContentValue {
|
||||
oneof value {
|
||||
UserAdd userAdd = 1;
|
||||
UserRemove userRemove = 2;
|
||||
UserPermissionChange userPermissionChange = 3;
|
||||
UserInvite userInvite = 4;
|
||||
UserJoin userJoin = 5;
|
||||
UserConfirm userConfirm = 6;
|
||||
}
|
||||
}
|
||||
|
||||
message ACLData {
|
||||
ACLSnapshot aclSnapshot = 1;
|
||||
repeated ACLContentValue aclContent = 2;
|
||||
}
|
||||
|
||||
message ACLSnapshot {
|
||||
// We don't need ACLState as a separate message now, because we simplified the snapshot model
|
||||
ACLState aclState = 1;
|
||||
}
|
||||
|
||||
message ACLState {
|
||||
repeated uint64 readKeyHashes = 1;
|
||||
repeated UserState userStates = 2;
|
||||
map<string, UserInvite> invites = 3; // TODO: later
|
||||
// repeated string unconfirmedUsers = 4; // TODO: later
|
||||
}
|
||||
|
||||
message UserState {
|
||||
string identity = 1;
|
||||
bytes encryptionKey = 2;
|
||||
repeated bytes encryptedReadKeys = 3; // all read keys that we know
|
||||
UserPermissions permissions = 4;
|
||||
bool IsConfirmed = 5;
|
||||
}
|
||||
|
||||
// we already know identity and encryptionKey
|
||||
message UserAdd {
|
||||
string identity = 1; // public signing key
|
||||
bytes encryptionKey = 2; // public encryption key
|
||||
repeated bytes encryptedReadKeys = 3; // all read keys that we know for the user
|
||||
UserPermissions permissions = 4;
|
||||
}
|
||||
|
||||
// TODO: this is not used as of now
|
||||
message UserConfirm { // not needed for read permissions
|
||||
string identity = 1; // not needed
|
||||
string userAddId = 2;
|
||||
}
|
||||
|
||||
message UserInvite {
|
||||
bytes acceptPublicKey = 1;
|
||||
bytes encryptPublicKey = 2;
|
||||
repeated bytes encryptedReadKeys = 3; // all read keys that we know for the user
|
||||
UserPermissions permissions = 4;
|
||||
string InviteId = 5;
|
||||
}
|
||||
|
||||
message UserJoin {
|
||||
string identity = 1;
|
||||
bytes encryptionKey = 2;
|
||||
bytes acceptSignature = 3; // sign acceptPublicKey
|
||||
string userInviteId = 4;
|
||||
repeated bytes encryptedReadKeys = 5; // the idea is that user should itself reencrypt the keys with the pub key
|
||||
}
|
||||
|
||||
message UserRemove {
|
||||
string identity = 1;
|
||||
repeated ReadKeyReplace readKeyReplaces = 3; // new read key encrypted for all users
|
||||
}
|
||||
|
||||
message ReadKeyReplace {
|
||||
string identity = 1;
|
||||
bytes encryptionKey = 2;
|
||||
bytes encryptedReadKey = 3;
|
||||
}
|
||||
|
||||
message UserPermissionChange {
|
||||
string identity = 1;
|
||||
UserPermissions permissions = 2;
|
||||
}
|
||||
|
||||
enum UserPermissions {
|
||||
Admin = 0;
|
||||
Writer = 1;
|
||||
Reader = 2;
|
||||
Removed = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message Change {
|
||||
repeated string treeHeadIds = 1;
|
||||
string aclHeadId = 2;
|
||||
string snapshotBaseId = 3; // we will only have one base snapshot for both
|
||||
bytes changesData = 4;
|
||||
uint64 currentReadKeyHash = 5;
|
||||
int64 timestamp = 6;
|
||||
string identity = 7;
|
||||
bool isSnapshot = 8;
|
||||
}
|
||||
|
||||
message Record {
|
||||
string prevId = 1;
|
||||
string identity = 2;
|
||||
bytes data = 3;
|
||||
uint64 currentReadKeyHash = 4;
|
||||
int64 timestamp = 5;
|
||||
}
|
||||
|
||||
message Header {
|
||||
string firstId = 1;
|
||||
string aclListId = 2;
|
||||
string workspaceId = 3;
|
||||
DocType docType = 4;
|
||||
|
||||
enum DocType {
|
||||
ACL = 0;
|
||||
DocTree = 1;
|
||||
}
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
package aclchanges
|
||||
|
||||
import (
|
||||
"github.com/gogo/protobuf/proto"
|
||||
)
|
||||
|
||||
type Change interface {
|
||||
ProtoChange() proto.Marshaler
|
||||
DecryptedChangeContent() []byte
|
||||
Signature() []byte
|
||||
CID() string
|
||||
}
|
||||
10
pkg/acl/aclrecordproto/aclreadkeyderive.go
Normal file
10
pkg/acl/aclrecordproto/aclreadkeyderive.go
Normal file
@ -0,0 +1,10 @@
|
||||
package aclrecordproto
|
||||
|
||||
import "github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/symmetric"
|
||||
|
||||
func ACLReadKeyDerive(signKey []byte, encKey []byte) (*symmetric.Key, error) {
|
||||
concBuf := make([]byte, 0, len(signKey)+len(encKey))
|
||||
concBuf = append(concBuf, signKey...)
|
||||
concBuf = append(concBuf, encKey...)
|
||||
return symmetric.DeriveFromBytes(concBuf)
|
||||
}
|
||||
4714
pkg/acl/aclrecordproto/aclrecord.pb.go
Normal file
4714
pkg/acl/aclrecordproto/aclrecord.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
102
pkg/acl/aclrecordproto/protos/aclrecord.proto
Normal file
102
pkg/acl/aclrecordproto/protos/aclrecord.proto
Normal file
@ -0,0 +1,102 @@
|
||||
syntax = "proto3";
|
||||
package aclrecord;
|
||||
option go_package = "pkg/acl/aclrecordproto";
|
||||
|
||||
message RawACLRecord {
|
||||
bytes payload = 1;
|
||||
bytes signature = 2;
|
||||
}
|
||||
|
||||
message RawACLRecordWithId {
|
||||
bytes payload = 1;
|
||||
string id = 2;
|
||||
}
|
||||
|
||||
message ACLRecord {
|
||||
string prevId = 1;
|
||||
bytes identity = 2;
|
||||
bytes data = 3;
|
||||
uint64 currentReadKeyHash = 4;
|
||||
int64 timestamp = 5;
|
||||
}
|
||||
|
||||
message ACLRoot {
|
||||
bytes identity = 1;
|
||||
bytes encryptionKey = 2;
|
||||
string spaceId = 3;
|
||||
bytes encryptedReadKey = 4;
|
||||
string derivationScheme = 5;
|
||||
uint64 currentReadKeyHash = 6;
|
||||
int64 timestamp = 7;
|
||||
}
|
||||
|
||||
message ACLContentValue {
|
||||
oneof value {
|
||||
ACLUserAdd userAdd = 1;
|
||||
ACLUserRemove userRemove = 2;
|
||||
ACLUserPermissionChange userPermissionChange = 3;
|
||||
ACLUserInvite userInvite = 4;
|
||||
ACLUserJoin userJoin = 5;
|
||||
}
|
||||
}
|
||||
|
||||
message ACLData {
|
||||
repeated ACLContentValue aclContent = 1;
|
||||
}
|
||||
|
||||
message ACLState {
|
||||
repeated uint64 readKeyHashes = 1;
|
||||
repeated ACLUserState userStates = 2;
|
||||
map<string, ACLUserInvite> invites = 3;
|
||||
}
|
||||
|
||||
message ACLUserState {
|
||||
bytes identity = 1;
|
||||
bytes encryptionKey = 2;
|
||||
ACLUserPermissions permissions = 3;
|
||||
}
|
||||
|
||||
message ACLUserAdd {
|
||||
bytes identity = 1;
|
||||
bytes encryptionKey = 2;
|
||||
repeated bytes encryptedReadKeys = 3;
|
||||
ACLUserPermissions permissions = 4;
|
||||
}
|
||||
|
||||
message ACLUserInvite {
|
||||
bytes acceptPublicKey = 1;
|
||||
bytes encryptPublicKey = 2;
|
||||
repeated bytes encryptedReadKeys = 3;
|
||||
ACLUserPermissions permissions = 4;
|
||||
string inviteId = 5;
|
||||
}
|
||||
|
||||
message ACLUserJoin {
|
||||
bytes identity = 1;
|
||||
bytes encryptionKey = 2;
|
||||
bytes acceptSignature = 3;
|
||||
string inviteId = 4;
|
||||
repeated bytes encryptedReadKeys = 5;
|
||||
}
|
||||
|
||||
message ACLUserRemove {
|
||||
bytes identity = 1;
|
||||
repeated ACLReadKeyReplace readKeyReplaces = 3;
|
||||
}
|
||||
|
||||
message ACLReadKeyReplace {
|
||||
bytes identity = 1;
|
||||
bytes encryptionKey = 2;
|
||||
bytes encryptedReadKey = 3;
|
||||
}
|
||||
|
||||
message ACLUserPermissionChange {
|
||||
bytes identity = 1;
|
||||
ACLUserPermissions permissions = 2;
|
||||
}
|
||||
|
||||
enum ACLUserPermissions {
|
||||
Admin = 0;
|
||||
Writer = 1;
|
||||
Reader = 2;
|
||||
}
|
||||
@ -1,19 +1,16 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
)
|
||||
|
||||
type Keychain struct {
|
||||
decoder keys.Decoder
|
||||
keys map[string]signingkey.PubKey
|
||||
keys map[string]signingkey.PubKey
|
||||
}
|
||||
|
||||
func NewKeychain() *Keychain {
|
||||
return &Keychain{
|
||||
decoder: signingkey.NewEDPubKeyDecoder(),
|
||||
keys: make(map[string]signingkey.PubKey),
|
||||
keys: make(map[string]signingkey.PubKey),
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,7 +18,7 @@ func (k *Keychain) GetOrAdd(identity string) (signingkey.PubKey, error) {
|
||||
if key, exists := k.keys[identity]; exists {
|
||||
return key, nil
|
||||
}
|
||||
res, err := k.decoder.DecodeFromString(identity)
|
||||
res, err := signingkey.NewSigningEd25519PubKeyFromBytes([]byte(identity))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
95
pkg/acl/list/aclrecordbuilder.go
Normal file
95
pkg/acl/list/aclrecordbuilder.go
Normal file
@ -0,0 +1,95 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclrecordproto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/common"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
)
|
||||
|
||||
type ACLRecordBuilder interface {
|
||||
ConvertFromRaw(rawIdRecord *aclrecordproto.RawACLRecordWithId) (rec *ACLRecord, err error)
|
||||
}
|
||||
|
||||
type aclRecordBuilder struct {
|
||||
id string
|
||||
keychain *common.Keychain
|
||||
}
|
||||
|
||||
func newACLRecordBuilder(id string, keychain *common.Keychain) ACLRecordBuilder {
|
||||
return &aclRecordBuilder{
|
||||
id: id,
|
||||
keychain: keychain,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) ConvertFromRaw(rawIdRecord *aclrecordproto.RawACLRecordWithId) (rec *ACLRecord, err error) {
|
||||
rawRec := &aclrecordproto.RawACLRecord{}
|
||||
err = proto.Unmarshal(rawIdRecord.Payload, rawRec)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if rawIdRecord.Id == a.id {
|
||||
aclRoot := &aclrecordproto.ACLRoot{}
|
||||
err = proto.Unmarshal(rawRec.Payload, aclRoot)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rec = &ACLRecord{
|
||||
Id: rawIdRecord.Id,
|
||||
CurrentReadKeyHash: aclRoot.CurrentReadKeyHash,
|
||||
Timestamp: aclRoot.Timestamp,
|
||||
Signature: rawRec.Signature,
|
||||
Identity: aclRoot.Identity,
|
||||
Model: aclRoot,
|
||||
}
|
||||
} else {
|
||||
aclRecord := &aclrecordproto.ACLRecord{}
|
||||
err = proto.Unmarshal(rawRec.Payload, aclRecord)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rec = &ACLRecord{
|
||||
Id: rawIdRecord.Id,
|
||||
PrevId: aclRecord.PrevId,
|
||||
CurrentReadKeyHash: aclRecord.CurrentReadKeyHash,
|
||||
Timestamp: aclRecord.Timestamp,
|
||||
Data: aclRecord.Data,
|
||||
Signature: rawRec.Signature,
|
||||
Identity: aclRecord.Identity,
|
||||
}
|
||||
}
|
||||
|
||||
err = verifyRaw(a.keychain, rawRec, rawIdRecord, rec.Identity)
|
||||
return
|
||||
}
|
||||
|
||||
func verifyRaw(
|
||||
keychain *common.Keychain,
|
||||
rawRec *aclrecordproto.RawACLRecord,
|
||||
recWithId *aclrecordproto.RawACLRecordWithId,
|
||||
identity []byte) (err error) {
|
||||
identityKey, err := keychain.GetOrAdd(string(identity))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// verifying signature
|
||||
res, err := identityKey.Verify(rawRec.Payload, rawRec.Signature)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !res {
|
||||
err = ErrInvalidSignature
|
||||
return
|
||||
}
|
||||
|
||||
// verifying ID
|
||||
if !cid.VerifyCID(recWithId.Payload, recWithId.Id) {
|
||||
err = ErrIncorrectCID
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -1,12 +1,12 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclrecordproto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/common"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/symmetric"
|
||||
@ -26,20 +26,21 @@ var ErrNoSuchRecord = errors.New("no such record")
|
||||
var ErrInsufficientPermissions = errors.New("insufficient permissions")
|
||||
var ErrNoReadKey = errors.New("acl state doesn't have a read key")
|
||||
var ErrInvalidSignature = errors.New("signature is invalid")
|
||||
var ErrIncorrectRoot = errors.New("incorrect root")
|
||||
|
||||
type UserPermissionPair struct {
|
||||
Identity string
|
||||
Permission aclpb.ACLChangeUserPermissions
|
||||
Permission aclrecordproto.ACLUserPermissions
|
||||
}
|
||||
|
||||
type ACLState struct {
|
||||
id string
|
||||
currentReadKeyHash uint64
|
||||
userReadKeys map[uint64]*symmetric.Key
|
||||
userStates map[string]*aclpb.ACLChangeUserState
|
||||
userInvites map[string]*aclpb.ACLChangeUserInvite
|
||||
|
||||
signingPubKeyDecoder keys.Decoder
|
||||
encryptionKey encryptionkey.PrivKey
|
||||
userStates map[string]*aclrecordproto.ACLUserState
|
||||
userInvites map[string]*aclrecordproto.ACLUserInvite
|
||||
encryptionKey encryptionkey.PrivKey
|
||||
signingKey signingkey.PrivKey
|
||||
|
||||
identity string
|
||||
permissionsAtRecord map[string][]UserPermissionPair
|
||||
@ -47,30 +48,33 @@ type ACLState struct {
|
||||
keychain *common.Keychain
|
||||
}
|
||||
|
||||
func newACLStateWithIdentity(
|
||||
identity string,
|
||||
encryptionKey encryptionkey.PrivKey,
|
||||
decoder keys.Decoder) *ACLState {
|
||||
return &ACLState{
|
||||
identity: identity,
|
||||
encryptionKey: encryptionKey,
|
||||
userReadKeys: make(map[uint64]*symmetric.Key),
|
||||
userStates: make(map[string]*aclpb.ACLChangeUserState),
|
||||
userInvites: make(map[string]*aclpb.ACLChangeUserInvite),
|
||||
signingPubKeyDecoder: decoder,
|
||||
permissionsAtRecord: make(map[string][]UserPermissionPair),
|
||||
keychain: common.NewKeychain(),
|
||||
func newACLStateWithKeys(
|
||||
id string,
|
||||
signingKey signingkey.PrivKey,
|
||||
encryptionKey encryptionkey.PrivKey) (*ACLState, error) {
|
||||
identity, err := signingKey.Raw()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ACLState{
|
||||
id: id,
|
||||
identity: string(identity),
|
||||
signingKey: signingKey,
|
||||
encryptionKey: encryptionKey,
|
||||
userReadKeys: make(map[uint64]*symmetric.Key),
|
||||
userStates: make(map[string]*aclrecordproto.ACLUserState),
|
||||
userInvites: make(map[string]*aclrecordproto.ACLUserInvite),
|
||||
permissionsAtRecord: make(map[string][]UserPermissionPair),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newACLState(decoder keys.Decoder) *ACLState {
|
||||
func newACLState(id string) *ACLState {
|
||||
return &ACLState{
|
||||
signingPubKeyDecoder: decoder,
|
||||
userReadKeys: make(map[uint64]*symmetric.Key),
|
||||
userStates: make(map[string]*aclpb.ACLChangeUserState),
|
||||
userInvites: make(map[string]*aclpb.ACLChangeUserInvite),
|
||||
permissionsAtRecord: make(map[string][]UserPermissionPair),
|
||||
keychain: common.NewKeychain(),
|
||||
id: id,
|
||||
userReadKeys: make(map[uint64]*symmetric.Key),
|
||||
userStates: make(map[string]*aclrecordproto.ACLUserState),
|
||||
userInvites: make(map[string]*aclrecordproto.ACLUserInvite),
|
||||
permissionsAtRecord: make(map[string][]UserPermissionPair),
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,40 +109,27 @@ func (st *ACLState) PermissionsAtRecord(id string, identity string) (UserPermiss
|
||||
return UserPermissionPair{}, ErrNoSuchUser
|
||||
}
|
||||
|
||||
func (st *ACLState) applyRecord(record *aclpb.Record) (err error) {
|
||||
aclData := &aclpb.ACLChangeACLData{}
|
||||
|
||||
err = proto.Unmarshal(record.Data, aclData)
|
||||
if err != nil {
|
||||
return
|
||||
func (st *ACLState) applyRecord(record *ACLRecord) (err error) {
|
||||
if record.Id == st.id {
|
||||
root, ok := record.Model.(*aclrecordproto.ACLRoot)
|
||||
if !ok {
|
||||
return ErrIncorrectRoot
|
||||
}
|
||||
return st.applyRoot(root)
|
||||
}
|
||||
aclData := &aclrecordproto.ACLData{}
|
||||
|
||||
err = st.applyChangeData(aclData, record.CurrentReadKeyHash, record.Identity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
st.currentReadKeyHash = record.CurrentReadKeyHash
|
||||
return
|
||||
}
|
||||
|
||||
func (st *ACLState) applyChangeAndUpdate(recordWrapper *Record) (err error) {
|
||||
var (
|
||||
change = recordWrapper.Content
|
||||
aclData = &aclpb.ACLChangeACLData{}
|
||||
)
|
||||
|
||||
if recordWrapper.Model != nil {
|
||||
aclData = recordWrapper.Model.(*aclpb.ACLChangeACLData)
|
||||
if record.Model != nil {
|
||||
aclData = record.Model.(*aclrecordproto.ACLData)
|
||||
} else {
|
||||
err = proto.Unmarshal(change.Data, aclData)
|
||||
err = proto.Unmarshal(record.Data, aclData)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
recordWrapper.Model = aclData
|
||||
record.Model = aclData
|
||||
}
|
||||
|
||||
err = st.applyChangeData(aclData, recordWrapper.Content.CurrentReadKeyHash, recordWrapper.Content.Identity)
|
||||
err = st.applyChangeData(aclData, record.CurrentReadKeyHash, record.Identity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -147,17 +138,68 @@ func (st *ACLState) applyChangeAndUpdate(recordWrapper *Record) (err error) {
|
||||
var permissions []UserPermissionPair
|
||||
for _, state := range st.userStates {
|
||||
permission := UserPermissionPair{
|
||||
Identity: state.Identity,
|
||||
Identity: string(state.Identity),
|
||||
Permission: state.Permissions,
|
||||
}
|
||||
permissions = append(permissions, permission)
|
||||
}
|
||||
|
||||
st.permissionsAtRecord[recordWrapper.Id] = permissions
|
||||
st.permissionsAtRecord[record.Id] = permissions
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *ACLState) applyChangeData(changeData *aclpb.ACLChangeACLData, hash uint64, identity string) (err error) {
|
||||
func (st *ACLState) applyRoot(root *aclrecordproto.ACLRoot) (err error) {
|
||||
if st.signingKey != nil && st.encryptionKey != nil {
|
||||
err = st.saveReadKeyFromRoot(root)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// adding user to the list
|
||||
userState := &aclrecordproto.ACLUserState{
|
||||
Identity: root.Identity,
|
||||
EncryptionKey: root.EncryptionKey,
|
||||
Permissions: aclrecordproto.ACLUserPermissions_Admin,
|
||||
}
|
||||
st.userStates[string(root.Identity)] = userState
|
||||
return
|
||||
}
|
||||
|
||||
func (st *ACLState) saveReadKeyFromRoot(root *aclrecordproto.ACLRoot) (err error) {
|
||||
var readKey *symmetric.Key
|
||||
if len(root.GetDerivationScheme()) != 0 {
|
||||
var encPubKey []byte
|
||||
encPubKey, err = st.encryptionKey.GetPublic().Raw()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
readKey, err = aclrecordproto.ACLReadKeyDerive([]byte(st.identity), encPubKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
readKey, _, err = st.decryptReadKeyAndHash(root.EncryptedReadKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
hasher := fnv.New64()
|
||||
_, err = hasher.Write(readKey.Bytes())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if hasher.Sum64() != root.CurrentReadKeyHash {
|
||||
return ErrIncorrectRoot
|
||||
}
|
||||
st.currentReadKeyHash = root.CurrentReadKeyHash
|
||||
st.userReadKeys[root.CurrentReadKeyHash] = readKey
|
||||
return
|
||||
}
|
||||
|
||||
func (st *ACLState) applyChangeData(changeData *aclrecordproto.ACLData, hash uint64, identity []byte) (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
return
|
||||
@ -165,17 +207,14 @@ func (st *ACLState) applyChangeData(changeData *aclpb.ACLChangeACLData, hash uin
|
||||
st.currentReadKeyHash = hash
|
||||
}()
|
||||
|
||||
// we can't check this for the user which is joining, because it will not be in our list
|
||||
// the same is for the first change to be added
|
||||
skipIdentityCheck := st.isUserJoin(changeData) || (st.currentReadKeyHash == 0 && st.isUserAdd(changeData, identity))
|
||||
if !skipIdentityCheck {
|
||||
// we check signature when we add this to the Tree, so no need to do it here
|
||||
if _, exists := st.userStates[identity]; !exists {
|
||||
if !st.isUserJoin(changeData) {
|
||||
// we check signature when we add this to the List, so no need to do it here
|
||||
if _, exists := st.userStates[string(identity)]; !exists {
|
||||
err = ErrNoSuchUser
|
||||
return
|
||||
}
|
||||
|
||||
if !st.hasPermission(identity, aclpb.ACLChange_Admin) {
|
||||
if !st.hasPermission(identity, aclrecordproto.ACLUserPermissions_Admin) {
|
||||
err = fmt.Errorf("user %s must have admin permissions", identity)
|
||||
return
|
||||
}
|
||||
@ -191,7 +230,7 @@ func (st *ACLState) applyChangeData(changeData *aclpb.ACLChangeACLData, hash uin
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *ACLState) applyChangeContent(ch *aclpb.ACLChangeACLContentValue) error {
|
||||
func (st *ACLState) applyChangeContent(ch *aclrecordproto.ACLContentValue) error {
|
||||
switch {
|
||||
case ch.GetUserPermissionChange() != nil:
|
||||
return st.applyUserPermissionChange(ch.GetUserPermissionChange())
|
||||
@ -203,50 +242,46 @@ func (st *ACLState) applyChangeContent(ch *aclpb.ACLChangeACLContentValue) error
|
||||
return st.applyUserInvite(ch.GetUserInvite())
|
||||
case ch.GetUserJoin() != nil:
|
||||
return st.applyUserJoin(ch.GetUserJoin())
|
||||
case ch.GetUserConfirm() != nil:
|
||||
return st.applyUserConfirm(ch.GetUserConfirm())
|
||||
default:
|
||||
return fmt.Errorf("unexpected change type: %v", ch)
|
||||
}
|
||||
}
|
||||
|
||||
func (st *ACLState) applyUserPermissionChange(ch *aclpb.ACLChangeUserPermissionChange) error {
|
||||
if _, exists := st.userStates[ch.Identity]; !exists {
|
||||
func (st *ACLState) applyUserPermissionChange(ch *aclrecordproto.ACLUserPermissionChange) error {
|
||||
chIdentity := string(ch.Identity)
|
||||
state, exists := st.userStates[chIdentity]
|
||||
if !exists {
|
||||
return ErrNoSuchUser
|
||||
}
|
||||
|
||||
st.userStates[ch.Identity].Permissions = ch.Permissions
|
||||
state.Permissions = ch.Permissions
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *ACLState) applyUserInvite(ch *aclpb.ACLChangeUserInvite) error {
|
||||
func (st *ACLState) applyUserInvite(ch *aclrecordproto.ACLUserInvite) error {
|
||||
st.userInvites[ch.InviteId] = ch
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *ACLState) applyUserJoin(ch *aclpb.ACLChangeUserJoin) error {
|
||||
invite, exists := st.userInvites[ch.UserInviteId]
|
||||
func (st *ACLState) applyUserJoin(ch *aclrecordproto.ACLUserJoin) error {
|
||||
invite, exists := st.userInvites[ch.InviteId]
|
||||
if !exists {
|
||||
return fmt.Errorf("no such invite with id %s", ch.UserInviteId)
|
||||
return fmt.Errorf("no such invite with id %s", ch.InviteId)
|
||||
}
|
||||
chIdentity := string(ch.Identity)
|
||||
|
||||
if _, exists = st.userStates[ch.Identity]; exists {
|
||||
if _, exists = st.userStates[chIdentity]; exists {
|
||||
return ErrUserAlreadyExists
|
||||
}
|
||||
|
||||
// validating signature
|
||||
signature := ch.GetAcceptSignature()
|
||||
verificationKey, err := st.signingPubKeyDecoder.DecodeFromBytes(invite.AcceptPublicKey)
|
||||
verificationKey, err := signingkey.NewSigningEd25519PubKeyFromBytes(invite.AcceptPublicKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("public key verifying invite accepts is given in incorrect format: %v", err)
|
||||
}
|
||||
|
||||
rawSignedId, err := st.signingPubKeyDecoder.DecodeFromStringIntoBytes(ch.Identity)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode signing identity as bytes")
|
||||
}
|
||||
|
||||
res, err := verificationKey.(signingkey.PubKey).Verify(rawSignedId, signature)
|
||||
res, err := verificationKey.(signingkey.PubKey).Verify(ch.Identity, signature)
|
||||
if err != nil {
|
||||
return fmt.Errorf("verification returned error: %w", err)
|
||||
}
|
||||
@ -255,7 +290,7 @@ func (st *ACLState) applyUserJoin(ch *aclpb.ACLChangeUserJoin) error {
|
||||
}
|
||||
|
||||
// if ourselves -> we need to decrypt the read keys
|
||||
if st.identity == ch.Identity {
|
||||
if st.identity == chIdentity {
|
||||
for _, key := range ch.EncryptedReadKeys {
|
||||
key, hash, err := st.decryptReadKeyAndHash(key)
|
||||
if err != nil {
|
||||
@ -267,30 +302,28 @@ func (st *ACLState) applyUserJoin(ch *aclpb.ACLChangeUserJoin) error {
|
||||
}
|
||||
|
||||
// adding user to the list
|
||||
userState := &aclpb.ACLChangeUserState{
|
||||
Identity: ch.Identity,
|
||||
EncryptionKey: ch.EncryptionKey,
|
||||
EncryptedReadKeys: ch.EncryptedReadKeys,
|
||||
Permissions: invite.Permissions,
|
||||
IsConfirmed: true,
|
||||
userState := &aclrecordproto.ACLUserState{
|
||||
Identity: ch.Identity,
|
||||
EncryptionKey: ch.EncryptionKey,
|
||||
Permissions: invite.Permissions,
|
||||
}
|
||||
st.userStates[ch.Identity] = userState
|
||||
st.userStates[chIdentity] = userState
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *ACLState) applyUserAdd(ch *aclpb.ACLChangeUserAdd) error {
|
||||
if _, exists := st.userStates[ch.Identity]; exists {
|
||||
func (st *ACLState) applyUserAdd(ch *aclrecordproto.ACLUserAdd) error {
|
||||
chIdentity := string(ch.Identity)
|
||||
if _, exists := st.userStates[chIdentity]; exists {
|
||||
return ErrUserAlreadyExists
|
||||
}
|
||||
|
||||
st.userStates[ch.Identity] = &aclpb.ACLChangeUserState{
|
||||
Identity: ch.Identity,
|
||||
EncryptionKey: ch.EncryptionKey,
|
||||
Permissions: ch.Permissions,
|
||||
EncryptedReadKeys: ch.EncryptedReadKeys,
|
||||
st.userStates[chIdentity] = &aclrecordproto.ACLUserState{
|
||||
Identity: ch.Identity,
|
||||
EncryptionKey: ch.EncryptionKey,
|
||||
Permissions: ch.Permissions,
|
||||
}
|
||||
|
||||
if ch.Identity == st.identity {
|
||||
if chIdentity == st.identity {
|
||||
for _, key := range ch.EncryptedReadKeys {
|
||||
key, hash, err := st.decryptReadKeyAndHash(key)
|
||||
if err != nil {
|
||||
@ -304,26 +337,22 @@ func (st *ACLState) applyUserAdd(ch *aclpb.ACLChangeUserAdd) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *ACLState) applyUserRemove(ch *aclpb.ACLChangeUserRemove) error {
|
||||
if ch.Identity == st.identity {
|
||||
func (st *ACLState) applyUserRemove(ch *aclrecordproto.ACLUserRemove) error {
|
||||
chIdentity := string(ch.Identity)
|
||||
if chIdentity == st.identity {
|
||||
return ErrDocumentForbidden
|
||||
}
|
||||
|
||||
if _, exists := st.userStates[ch.Identity]; !exists {
|
||||
if _, exists := st.userStates[chIdentity]; !exists {
|
||||
return ErrNoSuchUser
|
||||
}
|
||||
|
||||
delete(st.userStates, ch.Identity)
|
||||
delete(st.userStates, chIdentity)
|
||||
|
||||
for _, replace := range ch.ReadKeyReplaces {
|
||||
userState, exists := st.userStates[replace.Identity]
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
|
||||
userState.EncryptedReadKeys = append(userState.EncryptedReadKeys, replace.EncryptedReadKey)
|
||||
repIdentity := string(replace.Identity)
|
||||
// if this is our identity then we have to decrypt the key
|
||||
if replace.Identity == st.identity {
|
||||
if repIdentity == st.identity {
|
||||
key, hash, err := st.decryptReadKeyAndHash(replace.EncryptedReadKey)
|
||||
if err != nil {
|
||||
return ErrFailedToDecrypt
|
||||
@ -336,16 +365,6 @@ func (st *ACLState) applyUserRemove(ch *aclpb.ACLChangeUserRemove) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *ACLState) applyUserConfirm(ch *aclpb.ACLChangeUserConfirm) error {
|
||||
if _, exists := st.userStates[ch.Identity]; !exists {
|
||||
return ErrNoSuchUser
|
||||
}
|
||||
|
||||
userState := st.userStates[ch.Identity]
|
||||
userState.IsConfirmed = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *ACLState) decryptReadKeyAndHash(msg []byte) (*symmetric.Key, uint64, error) {
|
||||
decrypted, err := st.encryptionKey.Decrypt(msg)
|
||||
if err != nil {
|
||||
@ -362,8 +381,8 @@ func (st *ACLState) decryptReadKeyAndHash(msg []byte) (*symmetric.Key, uint64, e
|
||||
return key, hasher.Sum64(), nil
|
||||
}
|
||||
|
||||
func (st *ACLState) hasPermission(identity string, permission aclpb.ACLChangeUserPermissions) bool {
|
||||
state, exists := st.userStates[identity]
|
||||
func (st *ACLState) hasPermission(identity []byte, permission aclrecordproto.ACLUserPermissions) bool {
|
||||
state, exists := st.userStates[string(identity)]
|
||||
if !exists {
|
||||
return false
|
||||
}
|
||||
@ -371,17 +390,17 @@ func (st *ACLState) hasPermission(identity string, permission aclpb.ACLChangeUse
|
||||
return state.Permissions == permission
|
||||
}
|
||||
|
||||
func (st *ACLState) isUserJoin(data *aclpb.ACLChangeACLData) bool {
|
||||
func (st *ACLState) isUserJoin(data *aclrecordproto.ACLData) bool {
|
||||
// if we have a UserJoin, then it should always be the first one applied
|
||||
return data.GetAclContent() != nil && data.GetAclContent()[0].GetUserJoin() != nil
|
||||
}
|
||||
|
||||
func (st *ACLState) isUserAdd(data *aclpb.ACLChangeACLData, identity string) bool {
|
||||
func (st *ACLState) isUserAdd(data *aclrecordproto.ACLData, identity []byte) bool {
|
||||
// if we have a UserAdd, then it should always be the first one applied
|
||||
userAdd := data.GetAclContent()[0].GetUserAdd()
|
||||
return data.GetAclContent() != nil && userAdd != nil && userAdd.GetIdentity() == identity
|
||||
return data.GetAclContent() != nil && userAdd != nil && bytes.Compare(userAdd.GetIdentity(), identity) == 0
|
||||
}
|
||||
|
||||
func (st *ACLState) GetUserStates() map[string]*aclpb.ACLChangeUserState {
|
||||
func (st *ACLState) GetUserStates() map[string]*aclrecordproto.ACLUserState {
|
||||
return st.userStates
|
||||
}
|
||||
|
||||
@ -2,43 +2,42 @@ package list
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
)
|
||||
|
||||
type aclStateBuilder struct {
|
||||
identity string
|
||||
key encryptionkey.PrivKey
|
||||
decoder keys.Decoder
|
||||
signPrivKey signingkey.PrivKey
|
||||
encPrivKey encryptionkey.PrivKey
|
||||
id string
|
||||
}
|
||||
|
||||
func newACLStateBuilderWithIdentity(decoder keys.Decoder, accountData *account.AccountData) *aclStateBuilder {
|
||||
func newACLStateBuilderWithIdentity(accountData *account.AccountData) *aclStateBuilder {
|
||||
return &aclStateBuilder{
|
||||
decoder: decoder,
|
||||
identity: accountData.Identity,
|
||||
key: accountData.EncKey,
|
||||
signPrivKey: accountData.SignKey,
|
||||
encPrivKey: accountData.EncKey,
|
||||
}
|
||||
}
|
||||
|
||||
func newACLStateBuilder(decoder keys.Decoder) *aclStateBuilder {
|
||||
return &aclStateBuilder{
|
||||
decoder: decoder,
|
||||
}
|
||||
func newACLStateBuilder() *aclStateBuilder {
|
||||
return &aclStateBuilder{}
|
||||
}
|
||||
|
||||
func (sb *aclStateBuilder) Build(records []*Record) (*ACLState, error) {
|
||||
var (
|
||||
err error
|
||||
state *ACLState
|
||||
)
|
||||
func (sb *aclStateBuilder) Init(id string) {
|
||||
sb.id = id
|
||||
}
|
||||
|
||||
if sb.key != nil {
|
||||
state = newACLStateWithIdentity(sb.identity, sb.key, sb.decoder)
|
||||
func (sb *aclStateBuilder) Build(records []*ACLRecord) (state *ACLState, err error) {
|
||||
if sb.encPrivKey != nil && sb.signPrivKey != nil {
|
||||
state, err = newACLStateWithKeys(sb.id, sb.signPrivKey, sb.encPrivKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
state = newACLState(sb.decoder)
|
||||
state = newACLState(sb.id)
|
||||
}
|
||||
for _, rec := range records {
|
||||
err = state.applyChangeAndUpdate(rec)
|
||||
err = state.applyRecord(rec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -1,131 +0,0 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/symmetric"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"hash/fnv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MarshalledChange = []byte
|
||||
|
||||
type ACLChangeBuilder interface {
|
||||
UserAdd(identity string, encryptionKey encryptionkey.PubKey, permissions aclpb.ACLChangeUserPermissions) error
|
||||
AddId(id string) // TODO: this is only for testing
|
||||
}
|
||||
|
||||
type aclChangeBuilder struct {
|
||||
aclState *ACLState
|
||||
list ACLList
|
||||
acc *account.AccountData
|
||||
|
||||
aclData *aclpb.ACLChangeACLData
|
||||
id string
|
||||
readKey *symmetric.Key
|
||||
readKeyHash uint64
|
||||
}
|
||||
|
||||
func newACLChangeBuilder() *aclChangeBuilder {
|
||||
return &aclChangeBuilder{}
|
||||
}
|
||||
|
||||
func (c *aclChangeBuilder) Init(state *ACLState, list ACLList, acc *account.AccountData) {
|
||||
c.aclState = state
|
||||
c.list = list
|
||||
c.acc = acc
|
||||
|
||||
c.aclData = &aclpb.ACLChangeACLData{}
|
||||
// setting read key for further encryption etc
|
||||
if state.currentReadKeyHash == 0 {
|
||||
c.readKey, _ = symmetric.NewRandom()
|
||||
|
||||
hasher := fnv.New64()
|
||||
hasher.Write(c.readKey.Bytes())
|
||||
c.readKeyHash = hasher.Sum64()
|
||||
} else {
|
||||
c.readKey = c.aclState.userReadKeys[c.aclState.currentReadKeyHash]
|
||||
c.readKeyHash = c.aclState.currentReadKeyHash
|
||||
}
|
||||
}
|
||||
|
||||
func (c *aclChangeBuilder) AddId(id string) {
|
||||
c.id = id
|
||||
}
|
||||
|
||||
func (c *aclChangeBuilder) UserAdd(identity string, encryptionKey encryptionkey.PubKey, permissions aclpb.ACLChangeUserPermissions) error {
|
||||
var allKeys []*symmetric.Key
|
||||
if c.aclState.currentReadKeyHash != 0 {
|
||||
for _, key := range c.aclState.userReadKeys {
|
||||
allKeys = append(allKeys, key)
|
||||
}
|
||||
} else {
|
||||
allKeys = append(allKeys, c.readKey)
|
||||
}
|
||||
|
||||
var encryptedKeys [][]byte
|
||||
for _, k := range allKeys {
|
||||
res, err := encryptionKey.Encrypt(k.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encryptedKeys = append(encryptedKeys, res)
|
||||
}
|
||||
rawKey, err := encryptionKey.Raw()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ch := &aclpb.ACLChangeACLContentValue{
|
||||
Value: &aclpb.ACLChangeACLContentValueValueOfUserAdd{
|
||||
UserAdd: &aclpb.ACLChangeUserAdd{
|
||||
Identity: identity,
|
||||
EncryptionKey: rawKey,
|
||||
EncryptedReadKeys: encryptedKeys,
|
||||
Permissions: permissions,
|
||||
},
|
||||
},
|
||||
}
|
||||
c.aclData.AclContent = append(c.aclData.AclContent, ch)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *aclChangeBuilder) BuildAndApply() (*Record, []byte, error) {
|
||||
aclRecord := &aclpb.Record{
|
||||
PrevId: c.list.Head().Id,
|
||||
CurrentReadKeyHash: c.readKeyHash,
|
||||
Timestamp: int64(time.Now().Nanosecond()),
|
||||
Identity: c.acc.Identity,
|
||||
}
|
||||
|
||||
marshalledData, err := proto.Marshal(c.aclData)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
aclRecord.Data = marshalledData
|
||||
err = c.aclState.applyRecord(aclRecord)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
fullMarshalledChange, err := proto.Marshal(aclRecord)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
signature, err := c.acc.SignKey.Sign(fullMarshalledChange)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
id, err := cid.NewCIDFromBytes(fullMarshalledChange)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
ch := NewRecord(id, aclRecord)
|
||||
ch.Model = c.aclData
|
||||
ch.Sign = signature
|
||||
|
||||
return ch, fullMarshalledChange, nil
|
||||
}
|
||||
@ -5,15 +5,13 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclrecordproto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/common"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type IterFunc = func(record *Record) (IsContinue bool)
|
||||
type IterFunc = func(record *ACLRecord) (IsContinue bool)
|
||||
|
||||
var ErrIncorrectCID = errors.New("incorrect CID")
|
||||
|
||||
@ -26,20 +24,20 @@ type RWLocker interface {
|
||||
type ACLList interface {
|
||||
RWLocker
|
||||
ID() string
|
||||
Header() *aclpb.Header
|
||||
Records() []*Record
|
||||
Root() *aclrecordproto.ACLRoot
|
||||
Records() []*ACLRecord
|
||||
ACLState() *ACLState
|
||||
IsAfter(first string, second string) (bool, error)
|
||||
Head() *Record
|
||||
Get(id string) (*Record, error)
|
||||
Head() *ACLRecord
|
||||
Get(id string) (*ACLRecord, error)
|
||||
Iterate(iterFunc IterFunc)
|
||||
IterateFrom(startId string, iterFunc IterFunc)
|
||||
Close() (err error)
|
||||
}
|
||||
|
||||
type aclList struct {
|
||||
header *aclpb.Header
|
||||
records []*Record
|
||||
root *aclrecordproto.ACLRoot
|
||||
records []*ACLRecord
|
||||
indexes map[string]int
|
||||
id string
|
||||
|
||||
@ -51,57 +49,57 @@ type aclList struct {
|
||||
}
|
||||
|
||||
func BuildACLListWithIdentity(acc *account.AccountData, storage storage.ListStorage) (ACLList, error) {
|
||||
builder := newACLStateBuilderWithIdentity(acc.Decoder, acc)
|
||||
return buildWithACLStateBuilder(builder, storage)
|
||||
}
|
||||
|
||||
func BuildACLList(decoder keys.Decoder, storage storage.ListStorage) (ACLList, error) {
|
||||
return buildWithACLStateBuilder(newACLStateBuilder(decoder), storage)
|
||||
}
|
||||
|
||||
func buildWithACLStateBuilder(builder *aclStateBuilder, storage storage.ListStorage) (list ACLList, err error) {
|
||||
header, err := storage.Header()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
id, err := storage.ID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
builder := newACLStateBuilderWithIdentity(acc)
|
||||
return build(id, builder, newACLRecordBuilder(id, common.NewKeychain()), storage)
|
||||
}
|
||||
|
||||
func BuildACLList(storage storage.ListStorage) (ACLList, error) {
|
||||
id, err := storage.ID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return build(id, newACLStateBuilder(), newACLRecordBuilder(id, common.NewKeychain()), storage)
|
||||
}
|
||||
|
||||
func build(id string, stateBuilder *aclStateBuilder, recBuilder ACLRecordBuilder, storage storage.ListStorage) (list ACLList, err error) {
|
||||
rootWithId, err := storage.Root()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rawRecord, err := storage.Head()
|
||||
aclRecRoot, err := recBuilder.ConvertFromRaw(rootWithId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
keychain := common.NewKeychain()
|
||||
record, err := NewFromRawRecord(rawRecord)
|
||||
rawRecordWithId, err := storage.Head()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = verifyRecord(keychain, rawRecord, record)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
records := []*Record{record}
|
||||
|
||||
for record.Content.PrevId != "" {
|
||||
rawRecord, err = storage.GetRawRecord(context.Background(), record.Content.PrevId)
|
||||
record, err := recBuilder.ConvertFromRaw(rawRecordWithId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
records := []*ACLRecord{record}
|
||||
|
||||
for record.PrevId != "" && record.PrevId != id {
|
||||
rawRecordWithId, err = storage.GetRawRecord(context.Background(), record.PrevId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
record, err = NewFromRawRecord(rawRecord)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = verifyRecord(keychain, rawRecord, record)
|
||||
record, err = recBuilder.ConvertFromRaw(rawRecordWithId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
records = append(records, record)
|
||||
}
|
||||
// adding root in the end, because we already parsed it
|
||||
records = append(records, aclRecRoot)
|
||||
|
||||
indexes := make(map[string]int)
|
||||
for i, j := 0, len(records)-1; i < j; i, j = i+1, j-1 {
|
||||
@ -114,16 +112,17 @@ func buildWithACLStateBuilder(builder *aclStateBuilder, storage storage.ListStor
|
||||
indexes[records[len(records)/2].Id] = len(records) / 2
|
||||
}
|
||||
|
||||
state, err := builder.Build(records)
|
||||
stateBuilder.Init(id)
|
||||
state, err := stateBuilder.Build(records)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
list = &aclList{
|
||||
header: header,
|
||||
root: aclRecRoot.Model.(*aclrecordproto.ACLRoot),
|
||||
records: records,
|
||||
indexes: indexes,
|
||||
builder: builder,
|
||||
builder: stateBuilder,
|
||||
aclState: state,
|
||||
id: id,
|
||||
RWMutex: sync.RWMutex{},
|
||||
@ -131,7 +130,7 @@ func buildWithACLStateBuilder(builder *aclStateBuilder, storage storage.ListStor
|
||||
return
|
||||
}
|
||||
|
||||
func (a *aclList) Records() []*Record {
|
||||
func (a *aclList) Records() []*ACLRecord {
|
||||
return a.records
|
||||
}
|
||||
|
||||
@ -139,8 +138,8 @@ func (a *aclList) ID() string {
|
||||
return a.id
|
||||
}
|
||||
|
||||
func (a *aclList) Header() *aclpb.Header {
|
||||
return a.header
|
||||
func (a *aclList) Root() *aclrecordproto.ACLRoot {
|
||||
return a.root
|
||||
}
|
||||
|
||||
func (a *aclList) ACLState() *ACLState {
|
||||
@ -156,11 +155,11 @@ func (a *aclList) IsAfter(first string, second string) (bool, error) {
|
||||
return firstRec >= secondRec, nil
|
||||
}
|
||||
|
||||
func (a *aclList) Head() *Record {
|
||||
func (a *aclList) Head() *ACLRecord {
|
||||
return a.records[len(a.records)-1]
|
||||
}
|
||||
|
||||
func (a *aclList) Get(id string) (*Record, error) {
|
||||
func (a *aclList) Get(id string) (*ACLRecord, error) {
|
||||
recIdx, ok := a.indexes[id]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no such record")
|
||||
@ -191,26 +190,3 @@ func (a *aclList) IterateFrom(startId string, iterFunc IterFunc) {
|
||||
func (a *aclList) Close() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifyRecord(keychain *common.Keychain, rawRecord *aclpb.RawRecord, record *Record) (err error) {
|
||||
identityKey, err := keychain.GetOrAdd(record.Content.Identity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// verifying signature
|
||||
res, err := identityKey.Verify(rawRecord.Payload, rawRecord.Signature)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !res {
|
||||
err = ErrInvalidSignature
|
||||
return
|
||||
}
|
||||
|
||||
// verifying ID
|
||||
if !cid.VerifyCID(rawRecord.Payload, rawRecord.Id) {
|
||||
err = ErrIncorrectCID
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclrecordproto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/acllistbuilder"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
@ -15,7 +14,7 @@ func TestAclList_ACLState_UserInviteAndJoin(t *testing.T) {
|
||||
|
||||
keychain := st.(*acllistbuilder.ACLListStorageBuilder).GetKeychain()
|
||||
|
||||
aclList, err := BuildACLList(signingkey.NewEDPubKeyDecoder(), st)
|
||||
aclList, err := BuildACLList(st)
|
||||
require.NoError(t, err, "building acl list should be without error")
|
||||
|
||||
idA := keychain.GetIdentity("A")
|
||||
@ -23,13 +22,13 @@ func TestAclList_ACLState_UserInviteAndJoin(t *testing.T) {
|
||||
idC := keychain.GetIdentity("C")
|
||||
|
||||
// checking final state
|
||||
assert.Equal(t, aclpb.ACLChange_Admin, aclList.ACLState().GetUserStates()[idA].Permissions)
|
||||
assert.Equal(t, aclpb.ACLChange_Writer, aclList.ACLState().GetUserStates()[idB].Permissions)
|
||||
assert.Equal(t, aclpb.ACLChange_Reader, aclList.ACLState().GetUserStates()[idC].Permissions)
|
||||
assert.Equal(t, aclList.Head().Content.CurrentReadKeyHash, aclList.ACLState().CurrentReadKeyHash())
|
||||
assert.Equal(t, aclrecordproto.ACLUserPermissions_Admin, aclList.ACLState().GetUserStates()[idA].Permissions)
|
||||
assert.Equal(t, aclrecordproto.ACLUserPermissions_Writer, aclList.ACLState().GetUserStates()[idB].Permissions)
|
||||
assert.Equal(t, aclrecordproto.ACLUserPermissions_Reader, aclList.ACLState().GetUserStates()[idC].Permissions)
|
||||
assert.Equal(t, aclList.Head().CurrentReadKeyHash, aclList.ACLState().CurrentReadKeyHash())
|
||||
|
||||
var records []*Record
|
||||
aclList.Iterate(func(record *Record) (IsContinue bool) {
|
||||
var records []*ACLRecord
|
||||
aclList.Iterate(func(record *ACLRecord) (IsContinue bool) {
|
||||
records = append(records, record)
|
||||
return true
|
||||
})
|
||||
@ -44,7 +43,7 @@ func TestAclList_ACLState_UserInviteAndJoin(t *testing.T) {
|
||||
assert.NoError(t, err, "should have no error with permissions of B in the record 2")
|
||||
assert.Equal(t, UserPermissionPair{
|
||||
Identity: idB,
|
||||
Permission: aclpb.ACLChange_Writer,
|
||||
Permission: aclrecordproto.ACLUserPermissions_Writer,
|
||||
}, perm)
|
||||
}
|
||||
|
||||
@ -54,7 +53,7 @@ func TestAclList_ACLState_UserJoinAndRemove(t *testing.T) {
|
||||
|
||||
keychain := st.(*acllistbuilder.ACLListStorageBuilder).GetKeychain()
|
||||
|
||||
aclList, err := BuildACLList(signingkey.NewEDPubKeyDecoder(), st)
|
||||
aclList, err := BuildACLList(st)
|
||||
require.NoError(t, err, "building acl list should be without error")
|
||||
|
||||
idA := keychain.GetIdentity("A")
|
||||
@ -62,15 +61,15 @@ func TestAclList_ACLState_UserJoinAndRemove(t *testing.T) {
|
||||
idC := keychain.GetIdentity("C")
|
||||
|
||||
// checking final state
|
||||
assert.Equal(t, aclpb.ACLChange_Admin, aclList.ACLState().GetUserStates()[idA].Permissions)
|
||||
assert.Equal(t, aclpb.ACLChange_Reader, aclList.ACLState().GetUserStates()[idC].Permissions)
|
||||
assert.Equal(t, aclList.Head().Content.CurrentReadKeyHash, aclList.ACLState().CurrentReadKeyHash())
|
||||
assert.Equal(t, aclrecordproto.ACLUserPermissions_Admin, aclList.ACLState().GetUserStates()[idA].Permissions)
|
||||
assert.Equal(t, aclrecordproto.ACLUserPermissions_Reader, aclList.ACLState().GetUserStates()[idC].Permissions)
|
||||
assert.Equal(t, aclList.Head().CurrentReadKeyHash, aclList.ACLState().CurrentReadKeyHash())
|
||||
|
||||
_, exists := aclList.ACLState().GetUserStates()[idB]
|
||||
assert.Equal(t, false, exists)
|
||||
|
||||
var records []*Record
|
||||
aclList.Iterate(func(record *Record) (IsContinue bool) {
|
||||
var records []*ACLRecord
|
||||
aclList.Iterate(func(record *ACLRecord) (IsContinue bool) {
|
||||
records = append(records, record)
|
||||
return true
|
||||
})
|
||||
@ -78,13 +77,13 @@ func TestAclList_ACLState_UserJoinAndRemove(t *testing.T) {
|
||||
// checking permissions at specific records
|
||||
assert.Equal(t, 4, len(records))
|
||||
|
||||
assert.NotEqual(t, records[2].Content.CurrentReadKeyHash, aclList.ACLState().CurrentReadKeyHash())
|
||||
assert.NotEqual(t, records[2].CurrentReadKeyHash, aclList.ACLState().CurrentReadKeyHash())
|
||||
|
||||
perm, err := aclList.ACLState().PermissionsAtRecord(records[2].Id, idB)
|
||||
assert.NoError(t, err, "should have no error with permissions of B in the record 2")
|
||||
assert.Equal(t, UserPermissionPair{
|
||||
Identity: idB,
|
||||
Permission: aclpb.ACLChange_Writer,
|
||||
Permission: aclrecordproto.ACLUserPermissions_Writer,
|
||||
}, perm)
|
||||
|
||||
_, err = aclList.ACLState().PermissionsAtRecord(records[3].Id, idB)
|
||||
|
||||
@ -1,34 +1,12 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
)
|
||||
|
||||
type Record struct {
|
||||
Id string
|
||||
Content *aclpb.Record
|
||||
Model interface{}
|
||||
Sign []byte
|
||||
}
|
||||
|
||||
func NewRecord(id string, aclRecord *aclpb.Record) *Record {
|
||||
return &Record{
|
||||
Id: id,
|
||||
Content: aclRecord,
|
||||
}
|
||||
}
|
||||
|
||||
func NewFromRawRecord(rawRec *aclpb.RawRecord) (*Record, error) {
|
||||
aclRec := &aclpb.Record{}
|
||||
err := proto.Unmarshal(rawRec.Payload, aclRec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Record{
|
||||
Id: rawRec.Id,
|
||||
Content: aclRec,
|
||||
Sign: rawRec.Signature,
|
||||
}, nil
|
||||
type ACLRecord struct {
|
||||
Id string
|
||||
PrevId string
|
||||
CurrentReadKeyHash uint64
|
||||
Timestamp int64
|
||||
Data []byte
|
||||
Identity []byte
|
||||
Model interface{}
|
||||
Signature []byte
|
||||
}
|
||||
|
||||
@ -3,44 +3,41 @@ package storage
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclrecordproto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treechangeproto"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type inMemoryACLListStorage struct {
|
||||
header *aclpb.Header
|
||||
records []*aclpb.RawRecord
|
||||
|
||||
id string
|
||||
records []*aclrecordproto.RawACLRecordWithId
|
||||
id string
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func NewInMemoryACLListStorage(
|
||||
id string,
|
||||
header *aclpb.Header,
|
||||
records []*aclpb.RawRecord) (ListStorage, error) {
|
||||
records []*aclrecordproto.RawACLRecordWithId) (ListStorage, error) {
|
||||
return &inMemoryACLListStorage{
|
||||
id: id,
|
||||
header: header,
|
||||
records: records,
|
||||
RWMutex: sync.RWMutex{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (i *inMemoryACLListStorage) Header() (*aclpb.Header, error) {
|
||||
func (i *inMemoryACLListStorage) Root() (*aclrecordproto.RawACLRecordWithId, error) {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return i.header, nil
|
||||
return i.records[0], nil
|
||||
}
|
||||
|
||||
func (i *inMemoryACLListStorage) Head() (*aclpb.RawRecord, error) {
|
||||
func (i *inMemoryACLListStorage) Head() (*aclrecordproto.RawACLRecordWithId, error) {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return i.records[len(i.records)-1], nil
|
||||
}
|
||||
|
||||
func (i *inMemoryACLListStorage) GetRawRecord(ctx context.Context, id string) (*aclpb.RawRecord, error) {
|
||||
func (i *inMemoryACLListStorage) GetRawRecord(ctx context.Context, id string) (*aclrecordproto.RawACLRecordWithId, error) {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
for _, rec := range i.records {
|
||||
@ -51,7 +48,7 @@ func (i *inMemoryACLListStorage) GetRawRecord(ctx context.Context, id string) (*
|
||||
return nil, fmt.Errorf("no such record")
|
||||
}
|
||||
|
||||
func (i *inMemoryACLListStorage) AddRawRecord(ctx context.Context, rec *aclpb.RawRecord) error {
|
||||
func (i *inMemoryACLListStorage) AddRawRecord(ctx context.Context, rec *aclrecordproto.RawACLRecordWithId) error {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
@ -63,26 +60,27 @@ func (i *inMemoryACLListStorage) ID() (string, error) {
|
||||
|
||||
type inMemoryTreeStorage struct {
|
||||
id string
|
||||
header *aclpb.Header
|
||||
root *treechangeproto.RawTreeChangeWithId
|
||||
heads []string
|
||||
changes map[string]*aclpb.RawChange
|
||||
changes map[string]*treechangeproto.RawTreeChangeWithId
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func NewInMemoryTreeStorage(
|
||||
treeId string,
|
||||
header *aclpb.Header,
|
||||
root *treechangeproto.RawTreeChangeWithId,
|
||||
heads []string,
|
||||
changes []*aclpb.RawChange) (TreeStorage, error) {
|
||||
allChanges := make(map[string]*aclpb.RawChange)
|
||||
changes []*treechangeproto.RawTreeChangeWithId) (TreeStorage, error) {
|
||||
allChanges := make(map[string]*treechangeproto.RawTreeChangeWithId)
|
||||
for _, ch := range changes {
|
||||
allChanges[ch.Id] = ch
|
||||
}
|
||||
allChanges[treeId] = root
|
||||
|
||||
return &inMemoryTreeStorage{
|
||||
id: treeId,
|
||||
header: header,
|
||||
root: root,
|
||||
heads: heads,
|
||||
changes: allChanges,
|
||||
RWMutex: sync.RWMutex{},
|
||||
@ -95,10 +93,10 @@ func (t *inMemoryTreeStorage) ID() (string, error) {
|
||||
return t.id, nil
|
||||
}
|
||||
|
||||
func (t *inMemoryTreeStorage) Header() (*aclpb.Header, error) {
|
||||
func (t *inMemoryTreeStorage) Root() (*treechangeproto.RawTreeChangeWithId, error) {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
return t.header, nil
|
||||
return t.root, nil
|
||||
}
|
||||
|
||||
func (t *inMemoryTreeStorage) Heads() ([]string, error) {
|
||||
@ -118,7 +116,7 @@ func (t *inMemoryTreeStorage) SetHeads(heads []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *inMemoryTreeStorage) AddRawChange(change *aclpb.RawChange) error {
|
||||
func (t *inMemoryTreeStorage) AddRawChange(change *treechangeproto.RawTreeChangeWithId) error {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
// TODO: better to do deep copy
|
||||
@ -126,7 +124,7 @@ func (t *inMemoryTreeStorage) AddRawChange(change *aclpb.RawChange) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *inMemoryTreeStorage) GetRawChange(ctx context.Context, changeId string) (*aclpb.RawChange, error) {
|
||||
func (t *inMemoryTreeStorage) GetRawChange(ctx context.Context, changeId string) (*treechangeproto.RawTreeChangeWithId, error) {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
if res, exists := t.changes[changeId]; exists {
|
||||
@ -136,22 +134,11 @@ func (t *inMemoryTreeStorage) GetRawChange(ctx context.Context, changeId string)
|
||||
}
|
||||
|
||||
type inMemoryStorageProvider struct {
|
||||
objects map[string]Storage
|
||||
objects map[string]TreeStorage
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func (i *inMemoryStorageProvider) AddStorage(id string, st Storage) error {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
if _, exists := i.objects[id]; exists {
|
||||
return fmt.Errorf("storage already exists")
|
||||
}
|
||||
|
||||
i.objects[id] = st
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *inMemoryStorageProvider) Storage(id string) (Storage, error) {
|
||||
func (i *inMemoryStorageProvider) TreeStorage(id string) (TreeStorage, error) {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
if tree, exists := i.objects[id]; exists {
|
||||
@ -163,7 +150,7 @@ func (i *inMemoryStorageProvider) Storage(id string) (Storage, error) {
|
||||
func (i *inMemoryStorageProvider) CreateTreeStorage(payload TreeStorageCreatePayload) (TreeStorage, error) {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
res, err := NewInMemoryTreeStorage(payload.TreeId, payload.Header, payload.Heads, payload.Changes)
|
||||
res, err := NewInMemoryTreeStorage(payload.TreeId, payload.RootRawChange, payload.Heads, payload.Changes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -172,20 +159,8 @@ func (i *inMemoryStorageProvider) CreateTreeStorage(payload TreeStorageCreatePay
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (i *inMemoryStorageProvider) CreateACLListStorage(payload ACLListStorageCreatePayload) (ListStorage, error) {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
res, err := NewInMemoryACLListStorage(payload.ListId, payload.Header, payload.Records)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
i.objects[payload.ListId] = res
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func NewInMemoryTreeStorageProvider() Provider {
|
||||
return &inMemoryStorageProvider{
|
||||
objects: make(map[string]Storage),
|
||||
objects: make(map[string]TreeStorage),
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,13 +2,14 @@ package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclrecordproto"
|
||||
)
|
||||
|
||||
type ListStorage interface {
|
||||
Storage
|
||||
Head() (*aclpb.RawRecord, error)
|
||||
Root() (*aclrecordproto.RawACLRecordWithId, error)
|
||||
Head() (*aclrecordproto.RawACLRecordWithId, error)
|
||||
|
||||
GetRawRecord(ctx context.Context, id string) (*aclpb.RawRecord, error)
|
||||
AddRawRecord(ctx context.Context, rec *aclpb.RawRecord) error
|
||||
GetRawRecord(ctx context.Context, id string) (*aclrecordproto.RawACLRecordWithId, error)
|
||||
AddRawRecord(ctx context.Context, rec *aclrecordproto.RawACLRecordWithId) error
|
||||
}
|
||||
|
||||
@ -2,27 +2,19 @@ package storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treechangeproto"
|
||||
)
|
||||
|
||||
var ErrUnknownTreeId = errors.New("tree does not exist")
|
||||
|
||||
type TreeStorageCreatePayload struct {
|
||||
TreeId string
|
||||
Header *aclpb.Header
|
||||
Changes []*aclpb.RawChange
|
||||
Heads []string
|
||||
}
|
||||
|
||||
type ACLListStorageCreatePayload struct {
|
||||
ListId string
|
||||
Header *aclpb.Header
|
||||
Records []*aclpb.RawRecord
|
||||
TreeId string
|
||||
RootRawChange *treechangeproto.RawTreeChangeWithId
|
||||
Changes []*treechangeproto.RawTreeChangeWithId
|
||||
Heads []string
|
||||
}
|
||||
|
||||
type Provider interface {
|
||||
Storage(id string) (Storage, error)
|
||||
AddStorage(id string, st Storage) error
|
||||
TreeStorage(id string) (TreeStorage, error)
|
||||
CreateTreeStorage(payload TreeStorageCreatePayload) (TreeStorage, error)
|
||||
CreateACLListStorage(payload ACLListStorageCreatePayload) (ListStorage, error)
|
||||
}
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
package storage
|
||||
|
||||
import "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
|
||||
type Storage interface {
|
||||
ID() (string, error)
|
||||
Header() (*aclpb.Header, error)
|
||||
}
|
||||
|
||||
@ -2,16 +2,17 @@ package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treechangeproto"
|
||||
)
|
||||
|
||||
type TreeStorage interface {
|
||||
Storage
|
||||
Root() (*treechangeproto.RawTreeChangeWithId, error)
|
||||
Heads() ([]string, error)
|
||||
SetHeads(heads []string) error
|
||||
|
||||
AddRawChange(change *aclpb.RawChange) error
|
||||
GetRawChange(ctx context.Context, recordID string) (*aclpb.RawChange, error)
|
||||
AddRawChange(change *treechangeproto.RawTreeChangeWithId) error
|
||||
GetRawChange(ctx context.Context, id string) (*treechangeproto.RawTreeChangeWithId, error)
|
||||
}
|
||||
|
||||
type TreeStorageCreatorFunc = func(payload TreeStorageCreatePayload) (TreeStorage, error)
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package acllistbuilder
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclrecordproto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
"hash/fnv"
|
||||
@ -14,29 +16,29 @@ type SymKey struct {
|
||||
Key *symmetric.Key
|
||||
}
|
||||
|
||||
type Keychain struct {
|
||||
SigningKeys map[string]signingkey.PrivKey
|
||||
SigningKeysByIdentity map[string]signingkey.PrivKey
|
||||
EncryptionKeys map[string]encryptionkey.PrivKey
|
||||
ReadKeys map[string]*SymKey
|
||||
ReadKeysByHash map[uint64]*SymKey
|
||||
GeneratedIdentities map[string]string
|
||||
coder signingkey.PubKeyDecoder
|
||||
type YAMLKeychain struct {
|
||||
SigningKeysByYAMLIdentity map[string]signingkey.PrivKey
|
||||
SigningKeysByRealIdentity map[string]signingkey.PrivKey
|
||||
EncryptionKeysByYAMLIdentity map[string]encryptionkey.PrivKey
|
||||
ReadKeysByYAMLIdentity map[string]*SymKey
|
||||
ReadKeysByHash map[uint64]*SymKey
|
||||
GeneratedIdentities map[string]string
|
||||
DerivedIdentity string
|
||||
}
|
||||
|
||||
func NewKeychain() *Keychain {
|
||||
return &Keychain{
|
||||
SigningKeys: map[string]signingkey.PrivKey{},
|
||||
SigningKeysByIdentity: map[string]signingkey.PrivKey{},
|
||||
EncryptionKeys: map[string]encryptionkey.PrivKey{},
|
||||
GeneratedIdentities: map[string]string{},
|
||||
ReadKeys: map[string]*SymKey{},
|
||||
ReadKeysByHash: map[uint64]*SymKey{},
|
||||
coder: signingkey.NewEd25519PubKeyDecoder(),
|
||||
func NewKeychain() *YAMLKeychain {
|
||||
return &YAMLKeychain{
|
||||
SigningKeysByYAMLIdentity: map[string]signingkey.PrivKey{},
|
||||
SigningKeysByRealIdentity: map[string]signingkey.PrivKey{},
|
||||
EncryptionKeysByYAMLIdentity: map[string]encryptionkey.PrivKey{},
|
||||
GeneratedIdentities: map[string]string{},
|
||||
ReadKeysByYAMLIdentity: map[string]*SymKey{},
|
||||
ReadKeysByHash: map[uint64]*SymKey{},
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Keychain) ParseKeys(keys *Keys) {
|
||||
func (k *YAMLKeychain) ParseKeys(keys *Keys) {
|
||||
k.DerivedIdentity = keys.Derived
|
||||
for _, encKey := range keys.Enc {
|
||||
k.AddEncryptionKey(encKey)
|
||||
}
|
||||
@ -50,8 +52,8 @@ func (k *Keychain) ParseKeys(keys *Keys) {
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Keychain) AddEncryptionKey(key *Key) {
|
||||
if _, exists := k.EncryptionKeys[key.Name]; exists {
|
||||
func (k *YAMLKeychain) AddEncryptionKey(key *Key) {
|
||||
if _, exists := k.EncryptionKeysByYAMLIdentity[key.Name]; exists {
|
||||
return
|
||||
}
|
||||
var (
|
||||
@ -64,18 +66,16 @@ func (k *Keychain) AddEncryptionKey(key *Key) {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
decoder := encryptionkey.NewRSAPrivKeyDecoder()
|
||||
privKey, err := decoder.DecodeFromString(key.Value)
|
||||
newPrivKey, err = keys.DecodeKeyFromString(key.Value, encryptionkey.NewEncryptionRsaPrivKeyFromBytes, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
newPrivKey = privKey.(encryptionkey.PrivKey)
|
||||
}
|
||||
k.EncryptionKeys[key.Name] = newPrivKey
|
||||
k.EncryptionKeysByYAMLIdentity[key.Name] = newPrivKey
|
||||
}
|
||||
|
||||
func (k *Keychain) AddSigningKey(key *Key) {
|
||||
if _, exists := k.SigningKeys[key.Name]; exists {
|
||||
func (k *YAMLKeychain) AddSigningKey(key *Key) {
|
||||
if _, exists := k.SigningKeysByYAMLIdentity[key.Name]; exists {
|
||||
return
|
||||
}
|
||||
var (
|
||||
@ -89,26 +89,26 @@ func (k *Keychain) AddSigningKey(key *Key) {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
decoder := signingkey.NewEDPrivKeyDecoder()
|
||||
privKey, err := decoder.DecodeFromString(key.Value)
|
||||
newPrivKey, err = keys.DecodeKeyFromString(key.Value, signingkey.NewSigningEd25519PrivKeyFromBytes, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
newPrivKey = privKey.(signingkey.PrivKey)
|
||||
pubKey = newPrivKey.GetPublic()
|
||||
}
|
||||
|
||||
k.SigningKeys[key.Name] = newPrivKey
|
||||
res, err := k.coder.EncodeToString(pubKey)
|
||||
k.SigningKeysByYAMLIdentity[key.Name] = newPrivKey
|
||||
rawPubKey, err := pubKey.Raw()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
k.SigningKeysByIdentity[res] = newPrivKey
|
||||
k.GeneratedIdentities[key.Name] = res
|
||||
encoded := string(rawPubKey)
|
||||
|
||||
k.SigningKeysByRealIdentity[encoded] = newPrivKey
|
||||
k.GeneratedIdentities[key.Name] = encoded
|
||||
}
|
||||
|
||||
func (k *Keychain) AddReadKey(key *Key) {
|
||||
if _, exists := k.ReadKeys[key.Name]; exists {
|
||||
func (k *YAMLKeychain) AddReadKey(key *Key) {
|
||||
if _, exists := k.ReadKeysByYAMLIdentity[key.Name]; exists {
|
||||
return
|
||||
}
|
||||
|
||||
@ -121,6 +121,13 @@ func (k *Keychain) AddReadKey(key *Key) {
|
||||
if err != nil {
|
||||
panic("should be able to generate symmetric key")
|
||||
}
|
||||
} else if key.Value == "derived" {
|
||||
signKey, _ := k.SigningKeysByYAMLIdentity[k.DerivedIdentity].Raw()
|
||||
encKey, _ := k.EncryptionKeysByYAMLIdentity[k.DerivedIdentity].Raw()
|
||||
rkey, err = aclrecordproto.ACLReadKeyDerive(signKey, encKey)
|
||||
if err != nil {
|
||||
panic("should be able to derive symmetric key")
|
||||
}
|
||||
} else {
|
||||
rkey, err = symmetric.FromString(key.Value)
|
||||
if err != nil {
|
||||
@ -131,7 +138,7 @@ func (k *Keychain) AddReadKey(key *Key) {
|
||||
hasher := fnv.New64()
|
||||
hasher.Write(rkey.Bytes())
|
||||
|
||||
k.ReadKeys[key.Name] = &SymKey{
|
||||
k.ReadKeysByYAMLIdentity[key.Name] = &SymKey{
|
||||
Hash: hasher.Sum64(),
|
||||
Key: rkey,
|
||||
}
|
||||
@ -141,14 +148,14 @@ func (k *Keychain) AddReadKey(key *Key) {
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Keychain) AddKey(key *Key) {
|
||||
func (k *YAMLKeychain) AddKey(key *Key) {
|
||||
parts := strings.Split(key.Name, ".")
|
||||
if len(parts) != 3 {
|
||||
panic("cannot parse a key")
|
||||
}
|
||||
|
||||
switch parts[1] {
|
||||
case "Sign":
|
||||
case "Signature":
|
||||
k.AddSigningKey(key)
|
||||
case "Enc":
|
||||
k.AddEncryptionKey(key)
|
||||
@ -159,7 +166,7 @@ func (k *Keychain) AddKey(key *Key) {
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Keychain) GetKey(key string) interface{} {
|
||||
func (k *YAMLKeychain) GetKey(key string) interface{} {
|
||||
parts := strings.Split(key, ".")
|
||||
if len(parts) != 3 {
|
||||
panic("cannot parse a key")
|
||||
@ -168,15 +175,15 @@ func (k *Keychain) GetKey(key string) interface{} {
|
||||
|
||||
switch parts[1] {
|
||||
case "Sign":
|
||||
if key, exists := k.SigningKeys[name]; exists {
|
||||
if key, exists := k.SigningKeysByYAMLIdentity[name]; exists {
|
||||
return key
|
||||
}
|
||||
case "Enc":
|
||||
if key, exists := k.EncryptionKeys[name]; exists {
|
||||
if key, exists := k.EncryptionKeysByYAMLIdentity[name]; exists {
|
||||
return key
|
||||
}
|
||||
case "Read":
|
||||
if key, exists := k.ReadKeys[name]; exists {
|
||||
if key, exists := k.ReadKeysByYAMLIdentity[name]; exists {
|
||||
return key
|
||||
}
|
||||
default:
|
||||
@ -185,6 +192,6 @@ func (k *Keychain) GetKey(key string) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *Keychain) GetIdentity(name string) string {
|
||||
func (k *YAMLKeychain) GetIdentity(name string) string {
|
||||
return k.GeneratedIdentities[name]
|
||||
}
|
||||
|
||||
@ -3,12 +3,13 @@ package acllistbuilder
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclrecordproto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/yamltests"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
"hash/fnv"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"time"
|
||||
@ -19,17 +20,18 @@ import (
|
||||
|
||||
type ACLListStorageBuilder struct {
|
||||
aclList string
|
||||
records []*aclpb.Record
|
||||
rawRecords []*aclpb.RawRecord
|
||||
records []*aclrecordproto.ACLRecord
|
||||
rawRecords []*aclrecordproto.RawACLRecordWithId
|
||||
indexes map[string]int
|
||||
keychain *Keychain
|
||||
header *aclpb.Header
|
||||
keychain *YAMLKeychain
|
||||
rawRoot *aclrecordproto.RawACLRecordWithId
|
||||
root *aclrecordproto.ACLRoot
|
||||
id string
|
||||
}
|
||||
|
||||
func NewACLListStorageBuilder(keychain *Keychain) *ACLListStorageBuilder {
|
||||
func NewACLListStorageBuilder(keychain *YAMLKeychain) *ACLListStorageBuilder {
|
||||
return &ACLListStorageBuilder{
|
||||
records: make([]*aclpb.Record, 0),
|
||||
records: make([]*aclrecordproto.ACLRecord, 0),
|
||||
indexes: make(map[string]int),
|
||||
keychain: keychain,
|
||||
}
|
||||
@ -58,47 +60,59 @@ func NewACLListStorageBuilderFromFile(file string) (*ACLListStorageBuilder, erro
|
||||
return tb, nil
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) createRaw(rec *aclpb.Record) *aclpb.RawRecord {
|
||||
aclMarshaled, err := proto.Marshal(rec)
|
||||
func (t *ACLListStorageBuilder) createRaw(rec proto.Marshaler, identity []byte) *aclrecordproto.RawACLRecordWithId {
|
||||
protoMarshalled, err := rec.Marshal()
|
||||
if err != nil {
|
||||
panic("should be able to marshal final acl message!")
|
||||
}
|
||||
|
||||
signature, err := t.keychain.SigningKeysByIdentity[rec.Identity].Sign(aclMarshaled)
|
||||
signature, err := t.keychain.SigningKeysByRealIdentity[string(identity)].Sign(protoMarshalled)
|
||||
if err != nil {
|
||||
panic("should be able to sign final acl message!")
|
||||
}
|
||||
|
||||
id, _ := cid.NewCIDFromBytes(aclMarshaled)
|
||||
|
||||
return &aclpb.RawRecord{
|
||||
Payload: aclMarshaled,
|
||||
rawRec := &aclrecordproto.RawACLRecord{
|
||||
Payload: protoMarshalled,
|
||||
Signature: signature,
|
||||
Id: id,
|
||||
}
|
||||
|
||||
rawMarshalled, err := proto.Marshal(rawRec)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
id, _ := cid.NewCIDFromBytes(rawMarshalled)
|
||||
|
||||
return &aclrecordproto.RawACLRecordWithId{
|
||||
Payload: rawMarshalled,
|
||||
Id: id,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) getRecord(idx int) *aclpb.RawRecord {
|
||||
return t.rawRecords[idx]
|
||||
func (t *ACLListStorageBuilder) Head() (*aclrecordproto.RawACLRecordWithId, error) {
|
||||
l := len(t.records)
|
||||
if l > 0 {
|
||||
return t.rawRecords[l-1], nil
|
||||
}
|
||||
return t.rawRoot, nil
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) Head() (*aclpb.RawRecord, error) {
|
||||
return t.getRecord(len(t.records) - 1), nil
|
||||
func (t *ACLListStorageBuilder) Root() (*aclrecordproto.RawACLRecordWithId, error) {
|
||||
return t.rawRoot, nil
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) Header() (*aclpb.Header, error) {
|
||||
return t.header, nil
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) GetRawRecord(ctx context.Context, id string) (*aclpb.RawRecord, error) {
|
||||
func (t *ACLListStorageBuilder) GetRawRecord(ctx context.Context, id string) (*aclrecordproto.RawACLRecordWithId, error) {
|
||||
recIdx, ok := t.indexes[id]
|
||||
if !ok {
|
||||
if id == t.rawRoot.Id {
|
||||
return t.rawRoot, nil
|
||||
}
|
||||
return nil, fmt.Errorf("no such record")
|
||||
}
|
||||
return t.getRecord(recIdx), nil
|
||||
return t.rawRecords[recIdx], nil
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) AddRawRecord(ctx context.Context, rec *aclpb.RawRecord) error {
|
||||
func (t *ACLListStorageBuilder) AddRawRecord(ctx context.Context, rec *aclrecordproto.RawACLRecordWithId) error {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
@ -106,11 +120,11 @@ func (t *ACLListStorageBuilder) ID() (string, error) {
|
||||
return t.id, nil
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) GetRawRecords() []*aclpb.RawRecord {
|
||||
func (t *ACLListStorageBuilder) GetRawRecords() []*aclrecordproto.RawACLRecordWithId {
|
||||
return t.rawRecords
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) GetKeychain() *Keychain {
|
||||
func (t *ACLListStorageBuilder) GetKeychain() *YAMLKeychain {
|
||||
return t.keychain
|
||||
}
|
||||
|
||||
@ -119,41 +133,40 @@ func (t *ACLListStorageBuilder) Parse(tree *YMLList) {
|
||||
// are specified in the yml file, because our identities should be Ed25519
|
||||
// the same thing is happening for the encryption keys
|
||||
t.keychain.ParseKeys(&tree.Keys)
|
||||
prevId := ""
|
||||
t.parseRoot(tree.Root)
|
||||
prevId := t.id
|
||||
for idx, rec := range tree.Records {
|
||||
newRecord := t.parseRecord(rec, prevId)
|
||||
rawRecord := t.createRaw(newRecord)
|
||||
rawRecord := t.createRaw(newRecord, newRecord.Identity)
|
||||
t.records = append(t.records, newRecord)
|
||||
t.rawRecords = append(t.rawRecords, t.createRaw(newRecord))
|
||||
t.rawRecords = append(t.rawRecords, rawRecord)
|
||||
t.indexes[rawRecord.Id] = idx
|
||||
prevId = rawRecord.Id
|
||||
}
|
||||
|
||||
t.createHeaderAndId()
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) parseRecord(rec *Record, prevId string) *aclpb.Record {
|
||||
func (t *ACLListStorageBuilder) parseRecord(rec *Record, prevId string) *aclrecordproto.ACLRecord {
|
||||
k := t.keychain.GetKey(rec.ReadKey).(*SymKey)
|
||||
var aclChangeContents []*aclpb.ACLChangeACLContentValue
|
||||
var aclChangeContents []*aclrecordproto.ACLContentValue
|
||||
for _, ch := range rec.AclChanges {
|
||||
aclChangeContent := t.parseACLChange(ch)
|
||||
aclChangeContents = append(aclChangeContents, aclChangeContent)
|
||||
}
|
||||
data := &aclpb.ACLChangeACLData{
|
||||
data := &aclrecordproto.ACLData{
|
||||
AclContent: aclChangeContents,
|
||||
}
|
||||
bytes, _ := data.Marshal()
|
||||
|
||||
return &aclpb.Record{
|
||||
return &aclrecordproto.ACLRecord{
|
||||
PrevId: prevId,
|
||||
Identity: t.keychain.GetIdentity(rec.Identity),
|
||||
Identity: []byte(t.keychain.GetIdentity(rec.Identity)),
|
||||
Data: bytes,
|
||||
CurrentReadKeyHash: k.Hash,
|
||||
Timestamp: time.Now().Unix(),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) parseACLChange(ch *ACLChange) (convCh *aclpb.ACLChangeACLContentValue) {
|
||||
func (t *ACLListStorageBuilder) parseACLChange(ch *ACLChange) (convCh *aclrecordproto.ACLContentValue) {
|
||||
switch {
|
||||
case ch.UserAdd != nil:
|
||||
add := ch.UserAdd
|
||||
@ -161,10 +174,10 @@ func (t *ACLListStorageBuilder) parseACLChange(ch *ACLChange) (convCh *aclpb.ACL
|
||||
encKey := t.keychain.GetKey(add.EncryptionKey).(encryptionkey.PrivKey)
|
||||
rawKey, _ := encKey.GetPublic().Raw()
|
||||
|
||||
convCh = &aclpb.ACLChangeACLContentValue{
|
||||
Value: &aclpb.ACLChangeACLContentValueValueOfUserAdd{
|
||||
UserAdd: &aclpb.ACLChangeUserAdd{
|
||||
Identity: t.keychain.GetIdentity(add.Identity),
|
||||
convCh = &aclrecordproto.ACLContentValue{
|
||||
Value: &aclrecordproto.ACLContentValue_UserAdd{
|
||||
UserAdd: &aclrecordproto.ACLUserAdd{
|
||||
Identity: []byte(t.keychain.GetIdentity(add.Identity)),
|
||||
EncryptionKey: rawKey,
|
||||
EncryptedReadKeys: t.encryptReadKeys(add.EncryptedReadKeys, encKey),
|
||||
Permissions: t.convertPermission(add.Permission),
|
||||
@ -178,20 +191,20 @@ func (t *ACLListStorageBuilder) parseACLChange(ch *ACLChange) (convCh *aclpb.ACL
|
||||
GetKey(join.EncryptionKey).(encryptionkey.PrivKey)
|
||||
rawKey, _ := encKey.GetPublic().Raw()
|
||||
|
||||
idKey, _ := t.keychain.SigningKeys[join.Identity].GetPublic().Raw()
|
||||
idKey, _ := t.keychain.SigningKeysByYAMLIdentity[join.Identity].GetPublic().Raw()
|
||||
signKey := t.keychain.GetKey(join.AcceptSignature).(signingkey.PrivKey)
|
||||
signature, err := signKey.Sign(idKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
convCh = &aclpb.ACLChangeACLContentValue{
|
||||
Value: &aclpb.ACLChangeACLContentValueValueOfUserJoin{
|
||||
UserJoin: &aclpb.ACLChangeUserJoin{
|
||||
Identity: t.keychain.GetIdentity(join.Identity),
|
||||
convCh = &aclrecordproto.ACLContentValue{
|
||||
Value: &aclrecordproto.ACLContentValue_UserJoin{
|
||||
UserJoin: &aclrecordproto.ACLUserJoin{
|
||||
Identity: []byte(t.keychain.GetIdentity(join.Identity)),
|
||||
EncryptionKey: rawKey,
|
||||
AcceptSignature: signature,
|
||||
UserInviteId: join.InviteId,
|
||||
InviteId: join.InviteId,
|
||||
EncryptedReadKeys: t.encryptReadKeys(join.EncryptedReadKeys, encKey),
|
||||
},
|
||||
},
|
||||
@ -203,9 +216,9 @@ func (t *ACLListStorageBuilder) parseACLChange(ch *ACLChange) (convCh *aclpb.ACL
|
||||
GetKey(invite.EncryptionKey).(encryptionkey.PrivKey)
|
||||
rawEncKey, _ := encKey.GetPublic().Raw()
|
||||
|
||||
convCh = &aclpb.ACLChangeACLContentValue{
|
||||
Value: &aclpb.ACLChangeACLContentValueValueOfUserInvite{
|
||||
UserInvite: &aclpb.ACLChangeUserInvite{
|
||||
convCh = &aclrecordproto.ACLContentValue{
|
||||
Value: &aclrecordproto.ACLContentValue_UserInvite{
|
||||
UserInvite: &aclrecordproto.ACLUserInvite{
|
||||
AcceptPublicKey: rawAcceptKey,
|
||||
EncryptPublicKey: rawEncKey,
|
||||
EncryptedReadKeys: t.encryptReadKeys(invite.EncryptedReadKeys, encKey),
|
||||
@ -214,24 +227,13 @@ func (t *ACLListStorageBuilder) parseACLChange(ch *ACLChange) (convCh *aclpb.ACL
|
||||
},
|
||||
},
|
||||
}
|
||||
case ch.UserConfirm != nil:
|
||||
confirm := ch.UserConfirm
|
||||
|
||||
convCh = &aclpb.ACLChangeACLContentValue{
|
||||
Value: &aclpb.ACLChangeACLContentValueValueOfUserConfirm{
|
||||
UserConfirm: &aclpb.ACLChangeUserConfirm{
|
||||
Identity: t.keychain.GetIdentity(confirm.Identity),
|
||||
UserAddId: confirm.UserAddId,
|
||||
},
|
||||
},
|
||||
}
|
||||
case ch.UserPermissionChange != nil:
|
||||
permissionChange := ch.UserPermissionChange
|
||||
|
||||
convCh = &aclpb.ACLChangeACLContentValue{
|
||||
Value: &aclpb.ACLChangeACLContentValueValueOfUserPermissionChange{
|
||||
UserPermissionChange: &aclpb.ACLChangeUserPermissionChange{
|
||||
Identity: t.keychain.GetIdentity(permissionChange.Identity),
|
||||
convCh = &aclrecordproto.ACLContentValue{
|
||||
Value: &aclrecordproto.ACLContentValue_UserPermissionChange{
|
||||
UserPermissionChange: &aclrecordproto.ACLUserPermissionChange{
|
||||
Identity: []byte(t.keychain.GetIdentity(permissionChange.Identity)),
|
||||
Permissions: t.convertPermission(permissionChange.Permission),
|
||||
},
|
||||
},
|
||||
@ -241,26 +243,25 @@ func (t *ACLListStorageBuilder) parseACLChange(ch *ACLChange) (convCh *aclpb.ACL
|
||||
|
||||
newReadKey := t.keychain.GetKey(remove.NewReadKey).(*SymKey)
|
||||
|
||||
var replaces []*aclpb.ACLChangeReadKeyReplace
|
||||
var replaces []*aclrecordproto.ACLReadKeyReplace
|
||||
for _, id := range remove.IdentitiesLeft {
|
||||
identity := t.keychain.GetIdentity(id)
|
||||
encKey := t.keychain.EncryptionKeys[id]
|
||||
encKey := t.keychain.EncryptionKeysByYAMLIdentity[id]
|
||||
rawEncKey, _ := encKey.GetPublic().Raw()
|
||||
encReadKey, err := encKey.GetPublic().Encrypt(newReadKey.Key.Bytes())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
replaces = append(replaces, &aclpb.ACLChangeReadKeyReplace{
|
||||
Identity: identity,
|
||||
replaces = append(replaces, &aclrecordproto.ACLReadKeyReplace{
|
||||
Identity: []byte(t.keychain.GetIdentity(id)),
|
||||
EncryptionKey: rawEncKey,
|
||||
EncryptedReadKey: encReadKey,
|
||||
})
|
||||
}
|
||||
|
||||
convCh = &aclpb.ACLChangeACLContentValue{
|
||||
Value: &aclpb.ACLChangeACLContentValueValueOfUserRemove{
|
||||
UserRemove: &aclpb.ACLChangeUserRemove{
|
||||
Identity: t.keychain.GetIdentity(remove.RemovedIdentity),
|
||||
convCh = &aclrecordproto.ACLContentValue{
|
||||
Value: &aclrecordproto.ACLContentValue_UserRemove{
|
||||
UserRemove: &aclrecordproto.ACLUserRemove{
|
||||
Identity: []byte(t.keychain.GetIdentity(remove.RemovedIdentity)),
|
||||
ReadKeyReplaces: replaces,
|
||||
},
|
||||
},
|
||||
@ -286,20 +287,20 @@ func (t *ACLListStorageBuilder) encryptReadKeys(keys []string, encKey encryption
|
||||
return
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) convertPermission(perm string) aclpb.ACLChangeUserPermissions {
|
||||
func (t *ACLListStorageBuilder) convertPermission(perm string) aclrecordproto.ACLUserPermissions {
|
||||
switch perm {
|
||||
case "admin":
|
||||
return aclpb.ACLChange_Admin
|
||||
return aclrecordproto.ACLUserPermissions_Admin
|
||||
case "writer":
|
||||
return aclpb.ACLChange_Writer
|
||||
return aclrecordproto.ACLUserPermissions_Writer
|
||||
case "reader":
|
||||
return aclpb.ACLChange_Reader
|
||||
return aclrecordproto.ACLUserPermissions_Reader
|
||||
default:
|
||||
panic(fmt.Sprintf("incorrect permission: %s", perm))
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) traverseFromHead(f func(rec *aclpb.Record, id string) error) (err error) {
|
||||
func (t *ACLListStorageBuilder) traverseFromHead(f func(rec *aclrecordproto.ACLRecord, id string) error) (err error) {
|
||||
for i := len(t.records) - 1; i >= 0; i-- {
|
||||
err = f(t.records[i], t.rawRecords[i].Id)
|
||||
if err != nil {
|
||||
@ -309,14 +310,20 @@ func (t *ACLListStorageBuilder) traverseFromHead(f func(rec *aclpb.Record, id st
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *ACLListStorageBuilder) createHeaderAndId() {
|
||||
t.header = &aclpb.Header{
|
||||
FirstId: t.rawRecords[0].Id,
|
||||
AclListId: "",
|
||||
WorkspaceId: "",
|
||||
DocType: aclpb.Header_ACL,
|
||||
func (t *ACLListStorageBuilder) parseRoot(root *Root) {
|
||||
rawSignKey, _ := t.keychain.SigningKeysByYAMLIdentity[root.Identity].GetPublic().Raw()
|
||||
rawEncKey, _ := t.keychain.EncryptionKeysByYAMLIdentity[root.Identity].GetPublic().Raw()
|
||||
readKey, _ := aclrecordproto.ACLReadKeyDerive(rawSignKey, rawEncKey)
|
||||
hasher := fnv.New64()
|
||||
hasher.Write(readKey.Bytes())
|
||||
t.root = &aclrecordproto.ACLRoot{
|
||||
Identity: rawSignKey,
|
||||
EncryptionKey: rawEncKey,
|
||||
SpaceId: root.SpaceId,
|
||||
EncryptedReadKey: nil,
|
||||
DerivationScheme: "scheme",
|
||||
CurrentReadKeyHash: hasher.Sum64(),
|
||||
}
|
||||
bytes, _ := t.header.Marshal()
|
||||
id, _ := cid.NewCIDFromBytes(bytes)
|
||||
t.id = id
|
||||
t.rawRoot = t.createRaw(t.root, rawSignKey)
|
||||
t.id = t.rawRoot.Id
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ package acllistbuilder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclrecordproto"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"strings"
|
||||
@ -33,11 +33,11 @@ func (t *ACLListStorageBuilder) Graph() (string, error) {
|
||||
graph.SetDir(true)
|
||||
var nodes = make(map[string]struct{})
|
||||
|
||||
var addNodes = func(r *aclpb.Record, id string) error {
|
||||
var addNodes = func(r *aclrecordproto.ACLRecord, id string) error {
|
||||
style := "solid"
|
||||
|
||||
var chSymbs []string
|
||||
aclData := &aclpb.ACLChangeACLData{}
|
||||
aclData := &aclrecordproto.ACLData{}
|
||||
err := proto.Unmarshal(r.GetData(), aclData)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -92,7 +92,7 @@ func (t *ACLListStorageBuilder) Graph() (string, error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
var addLinks = func(r *aclpb.Record, id string) error {
|
||||
var addLinks = func(r *aclrecordproto.ACLRecord, id string) error {
|
||||
if r.PrevId == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -6,9 +6,10 @@ type Key struct {
|
||||
}
|
||||
|
||||
type Keys struct {
|
||||
Enc []*Key `yaml:"Enc"`
|
||||
Sign []*Key `yaml:"Sign"`
|
||||
Read []*Key `yaml:"Read"`
|
||||
Derived string `yaml:"Derived"`
|
||||
Enc []*Key `yaml:"Enc"`
|
||||
Sign []*Key `yaml:"Sign"`
|
||||
Read []*Key `yaml:"Read"`
|
||||
}
|
||||
|
||||
type ACLChange struct {
|
||||
@ -35,11 +36,6 @@ type ACLChange struct {
|
||||
InviteId string `yaml:"inviteId"`
|
||||
} `yaml:"userInvite"`
|
||||
|
||||
UserConfirm *struct {
|
||||
Identity string `yaml:"identity"`
|
||||
UserAddId string `yaml:"UserAddId"`
|
||||
} `yaml:"userConfirm"`
|
||||
|
||||
UserRemove *struct {
|
||||
RemovedIdentity string `yaml:"removedIdentity"`
|
||||
NewReadKey string `yaml:"newReadKey"`
|
||||
@ -63,7 +59,13 @@ type Header struct {
|
||||
IsWorkspace bool `yaml:"isWorkspace"`
|
||||
}
|
||||
|
||||
type Root struct {
|
||||
Identity string `yaml:"identity"`
|
||||
SpaceId string `yaml:"spaceId"`
|
||||
}
|
||||
|
||||
type YMLList struct {
|
||||
Root *Root
|
||||
Records []*Record `yaml:"records"`
|
||||
|
||||
Keys Keys `yaml:"keys"`
|
||||
|
||||
@ -58,24 +58,24 @@ func (m *PlainTextChange) XXX_DiscardUnknown() {
|
||||
|
||||
var xxx_messageInfo_PlainTextChange proto.InternalMessageInfo
|
||||
|
||||
type PlainTextChangeContent struct {
|
||||
type PlainTextChange_Content struct {
|
||||
// Types that are valid to be assigned to Value:
|
||||
// *PlainTextChangeContentValueOfTextAppend
|
||||
Value IsPlainTextChangeContentValue `protobuf_oneof:"value"`
|
||||
// *PlainTextChange_Content_TextAppend
|
||||
Value isPlainTextChange_Content_Value `protobuf_oneof:"value"`
|
||||
}
|
||||
|
||||
func (m *PlainTextChangeContent) Reset() { *m = PlainTextChangeContent{} }
|
||||
func (m *PlainTextChangeContent) String() string { return proto.CompactTextString(m) }
|
||||
func (*PlainTextChangeContent) ProtoMessage() {}
|
||||
func (*PlainTextChangeContent) Descriptor() ([]byte, []int) {
|
||||
func (m *PlainTextChange_Content) Reset() { *m = PlainTextChange_Content{} }
|
||||
func (m *PlainTextChange_Content) String() string { return proto.CompactTextString(m) }
|
||||
func (*PlainTextChange_Content) ProtoMessage() {}
|
||||
func (*PlainTextChange_Content) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_37f33c266ada4318, []int{0, 0}
|
||||
}
|
||||
func (m *PlainTextChangeContent) XXX_Unmarshal(b []byte) error {
|
||||
func (m *PlainTextChange_Content) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *PlainTextChangeContent) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
func (m *PlainTextChange_Content) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
if deterministic {
|
||||
return xxx_messageInfo_PlainTextChangeContent.Marshal(b, m, deterministic)
|
||||
return xxx_messageInfo_PlainTextChange_Content.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
@ -85,67 +85,67 @@ func (m *PlainTextChangeContent) XXX_Marshal(b []byte, deterministic bool) ([]by
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (m *PlainTextChangeContent) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_PlainTextChangeContent.Merge(m, src)
|
||||
func (m *PlainTextChange_Content) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_PlainTextChange_Content.Merge(m, src)
|
||||
}
|
||||
func (m *PlainTextChangeContent) XXX_Size() int {
|
||||
func (m *PlainTextChange_Content) XXX_Size() int {
|
||||
return m.Size()
|
||||
}
|
||||
func (m *PlainTextChangeContent) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_PlainTextChangeContent.DiscardUnknown(m)
|
||||
func (m *PlainTextChange_Content) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_PlainTextChange_Content.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_PlainTextChangeContent proto.InternalMessageInfo
|
||||
var xxx_messageInfo_PlainTextChange_Content proto.InternalMessageInfo
|
||||
|
||||
type IsPlainTextChangeContentValue interface {
|
||||
IsPlainTextChangeContentValue()
|
||||
type isPlainTextChange_Content_Value interface {
|
||||
isPlainTextChange_Content_Value()
|
||||
MarshalTo([]byte) (int, error)
|
||||
Size() int
|
||||
}
|
||||
|
||||
type PlainTextChangeContentValueOfTextAppend struct {
|
||||
TextAppend *PlainTextChangeTextAppend `protobuf:"bytes,1,opt,name=textAppend,proto3,oneof" json:"textAppend,omitempty"`
|
||||
type PlainTextChange_Content_TextAppend struct {
|
||||
TextAppend *PlainTextChange_TextAppend `protobuf:"bytes,1,opt,name=textAppend,proto3,oneof" json:"textAppend,omitempty"`
|
||||
}
|
||||
|
||||
func (*PlainTextChangeContentValueOfTextAppend) IsPlainTextChangeContentValue() {}
|
||||
func (*PlainTextChange_Content_TextAppend) isPlainTextChange_Content_Value() {}
|
||||
|
||||
func (m *PlainTextChangeContent) GetValue() IsPlainTextChangeContentValue {
|
||||
func (m *PlainTextChange_Content) GetValue() isPlainTextChange_Content_Value {
|
||||
if m != nil {
|
||||
return m.Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *PlainTextChangeContent) GetTextAppend() *PlainTextChangeTextAppend {
|
||||
if x, ok := m.GetValue().(*PlainTextChangeContentValueOfTextAppend); ok {
|
||||
func (m *PlainTextChange_Content) GetTextAppend() *PlainTextChange_TextAppend {
|
||||
if x, ok := m.GetValue().(*PlainTextChange_Content_TextAppend); ok {
|
||||
return x.TextAppend
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// XXX_OneofWrappers is for the internal use of the proto package.
|
||||
func (*PlainTextChangeContent) XXX_OneofWrappers() []interface{} {
|
||||
func (*PlainTextChange_Content) XXX_OneofWrappers() []interface{} {
|
||||
return []interface{}{
|
||||
(*PlainTextChangeContentValueOfTextAppend)(nil),
|
||||
(*PlainTextChange_Content_TextAppend)(nil),
|
||||
}
|
||||
}
|
||||
|
||||
type PlainTextChangeTextAppend struct {
|
||||
type PlainTextChange_TextAppend struct {
|
||||
Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"`
|
||||
}
|
||||
|
||||
func (m *PlainTextChangeTextAppend) Reset() { *m = PlainTextChangeTextAppend{} }
|
||||
func (m *PlainTextChangeTextAppend) String() string { return proto.CompactTextString(m) }
|
||||
func (*PlainTextChangeTextAppend) ProtoMessage() {}
|
||||
func (*PlainTextChangeTextAppend) Descriptor() ([]byte, []int) {
|
||||
func (m *PlainTextChange_TextAppend) Reset() { *m = PlainTextChange_TextAppend{} }
|
||||
func (m *PlainTextChange_TextAppend) String() string { return proto.CompactTextString(m) }
|
||||
func (*PlainTextChange_TextAppend) ProtoMessage() {}
|
||||
func (*PlainTextChange_TextAppend) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_37f33c266ada4318, []int{0, 1}
|
||||
}
|
||||
func (m *PlainTextChangeTextAppend) XXX_Unmarshal(b []byte) error {
|
||||
func (m *PlainTextChange_TextAppend) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *PlainTextChangeTextAppend) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
func (m *PlainTextChange_TextAppend) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
if deterministic {
|
||||
return xxx_messageInfo_PlainTextChangeTextAppend.Marshal(b, m, deterministic)
|
||||
return xxx_messageInfo_PlainTextChange_TextAppend.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
@ -155,41 +155,41 @@ func (m *PlainTextChangeTextAppend) XXX_Marshal(b []byte, deterministic bool) ([
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (m *PlainTextChangeTextAppend) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_PlainTextChangeTextAppend.Merge(m, src)
|
||||
func (m *PlainTextChange_TextAppend) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_PlainTextChange_TextAppend.Merge(m, src)
|
||||
}
|
||||
func (m *PlainTextChangeTextAppend) XXX_Size() int {
|
||||
func (m *PlainTextChange_TextAppend) XXX_Size() int {
|
||||
return m.Size()
|
||||
}
|
||||
func (m *PlainTextChangeTextAppend) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_PlainTextChangeTextAppend.DiscardUnknown(m)
|
||||
func (m *PlainTextChange_TextAppend) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_PlainTextChange_TextAppend.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_PlainTextChangeTextAppend proto.InternalMessageInfo
|
||||
var xxx_messageInfo_PlainTextChange_TextAppend proto.InternalMessageInfo
|
||||
|
||||
func (m *PlainTextChangeTextAppend) GetText() string {
|
||||
func (m *PlainTextChange_TextAppend) GetText() string {
|
||||
if m != nil {
|
||||
return m.Text
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type PlainTextChangeSnapshot struct {
|
||||
type PlainTextChange_Snapshot struct {
|
||||
Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"`
|
||||
}
|
||||
|
||||
func (m *PlainTextChangeSnapshot) Reset() { *m = PlainTextChangeSnapshot{} }
|
||||
func (m *PlainTextChangeSnapshot) String() string { return proto.CompactTextString(m) }
|
||||
func (*PlainTextChangeSnapshot) ProtoMessage() {}
|
||||
func (*PlainTextChangeSnapshot) Descriptor() ([]byte, []int) {
|
||||
func (m *PlainTextChange_Snapshot) Reset() { *m = PlainTextChange_Snapshot{} }
|
||||
func (m *PlainTextChange_Snapshot) String() string { return proto.CompactTextString(m) }
|
||||
func (*PlainTextChange_Snapshot) ProtoMessage() {}
|
||||
func (*PlainTextChange_Snapshot) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_37f33c266ada4318, []int{0, 2}
|
||||
}
|
||||
func (m *PlainTextChangeSnapshot) XXX_Unmarshal(b []byte) error {
|
||||
func (m *PlainTextChange_Snapshot) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *PlainTextChangeSnapshot) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
func (m *PlainTextChange_Snapshot) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
if deterministic {
|
||||
return xxx_messageInfo_PlainTextChangeSnapshot.Marshal(b, m, deterministic)
|
||||
return xxx_messageInfo_PlainTextChange_Snapshot.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
@ -199,42 +199,42 @@ func (m *PlainTextChangeSnapshot) XXX_Marshal(b []byte, deterministic bool) ([]b
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (m *PlainTextChangeSnapshot) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_PlainTextChangeSnapshot.Merge(m, src)
|
||||
func (m *PlainTextChange_Snapshot) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_PlainTextChange_Snapshot.Merge(m, src)
|
||||
}
|
||||
func (m *PlainTextChangeSnapshot) XXX_Size() int {
|
||||
func (m *PlainTextChange_Snapshot) XXX_Size() int {
|
||||
return m.Size()
|
||||
}
|
||||
func (m *PlainTextChangeSnapshot) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_PlainTextChangeSnapshot.DiscardUnknown(m)
|
||||
func (m *PlainTextChange_Snapshot) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_PlainTextChange_Snapshot.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_PlainTextChangeSnapshot proto.InternalMessageInfo
|
||||
var xxx_messageInfo_PlainTextChange_Snapshot proto.InternalMessageInfo
|
||||
|
||||
func (m *PlainTextChangeSnapshot) GetText() string {
|
||||
func (m *PlainTextChange_Snapshot) GetText() string {
|
||||
if m != nil {
|
||||
return m.Text
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type PlainTextChangeData struct {
|
||||
Content []*PlainTextChangeContent `protobuf:"bytes,1,rep,name=content,proto3" json:"content,omitempty"`
|
||||
Snapshot *PlainTextChangeSnapshot `protobuf:"bytes,2,opt,name=snapshot,proto3" json:"snapshot,omitempty"`
|
||||
type PlainTextChange_Data struct {
|
||||
Content []*PlainTextChange_Content `protobuf:"bytes,1,rep,name=content,proto3" json:"content,omitempty"`
|
||||
Snapshot *PlainTextChange_Snapshot `protobuf:"bytes,2,opt,name=snapshot,proto3" json:"snapshot,omitempty"`
|
||||
}
|
||||
|
||||
func (m *PlainTextChangeData) Reset() { *m = PlainTextChangeData{} }
|
||||
func (m *PlainTextChangeData) String() string { return proto.CompactTextString(m) }
|
||||
func (*PlainTextChangeData) ProtoMessage() {}
|
||||
func (*PlainTextChangeData) Descriptor() ([]byte, []int) {
|
||||
func (m *PlainTextChange_Data) Reset() { *m = PlainTextChange_Data{} }
|
||||
func (m *PlainTextChange_Data) String() string { return proto.CompactTextString(m) }
|
||||
func (*PlainTextChange_Data) ProtoMessage() {}
|
||||
func (*PlainTextChange_Data) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_37f33c266ada4318, []int{0, 3}
|
||||
}
|
||||
func (m *PlainTextChangeData) XXX_Unmarshal(b []byte) error {
|
||||
func (m *PlainTextChange_Data) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *PlainTextChangeData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
func (m *PlainTextChange_Data) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
if deterministic {
|
||||
return xxx_messageInfo_PlainTextChangeData.Marshal(b, m, deterministic)
|
||||
return xxx_messageInfo_PlainTextChange_Data.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
@ -244,26 +244,26 @@ func (m *PlainTextChangeData) XXX_Marshal(b []byte, deterministic bool) ([]byte,
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (m *PlainTextChangeData) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_PlainTextChangeData.Merge(m, src)
|
||||
func (m *PlainTextChange_Data) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_PlainTextChange_Data.Merge(m, src)
|
||||
}
|
||||
func (m *PlainTextChangeData) XXX_Size() int {
|
||||
func (m *PlainTextChange_Data) XXX_Size() int {
|
||||
return m.Size()
|
||||
}
|
||||
func (m *PlainTextChangeData) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_PlainTextChangeData.DiscardUnknown(m)
|
||||
func (m *PlainTextChange_Data) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_PlainTextChange_Data.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_PlainTextChangeData proto.InternalMessageInfo
|
||||
var xxx_messageInfo_PlainTextChange_Data proto.InternalMessageInfo
|
||||
|
||||
func (m *PlainTextChangeData) GetContent() []*PlainTextChangeContent {
|
||||
func (m *PlainTextChange_Data) GetContent() []*PlainTextChange_Content {
|
||||
if m != nil {
|
||||
return m.Content
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *PlainTextChangeData) GetSnapshot() *PlainTextChangeSnapshot {
|
||||
func (m *PlainTextChange_Data) GetSnapshot() *PlainTextChange_Snapshot {
|
||||
if m != nil {
|
||||
return m.Snapshot
|
||||
}
|
||||
@ -272,10 +272,10 @@ func (m *PlainTextChangeData) GetSnapshot() *PlainTextChangeSnapshot {
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*PlainTextChange)(nil), "anytype.PlainTextChange")
|
||||
proto.RegisterType((*PlainTextChangeContent)(nil), "anytype.PlainTextChange.Content")
|
||||
proto.RegisterType((*PlainTextChangeTextAppend)(nil), "anytype.PlainTextChange.TextAppend")
|
||||
proto.RegisterType((*PlainTextChangeSnapshot)(nil), "anytype.PlainTextChange.Snapshot")
|
||||
proto.RegisterType((*PlainTextChangeData)(nil), "anytype.PlainTextChange.Data")
|
||||
proto.RegisterType((*PlainTextChange_Content)(nil), "anytype.PlainTextChange.Content")
|
||||
proto.RegisterType((*PlainTextChange_TextAppend)(nil), "anytype.PlainTextChange.TextAppend")
|
||||
proto.RegisterType((*PlainTextChange_Snapshot)(nil), "anytype.PlainTextChange.Snapshot")
|
||||
proto.RegisterType((*PlainTextChange_Data)(nil), "anytype.PlainTextChange.Data")
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -326,7 +326,7 @@ func (m *PlainTextChange) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func (m *PlainTextChangeContent) Marshal() (dAtA []byte, err error) {
|
||||
func (m *PlainTextChange_Content) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
@ -336,12 +336,12 @@ func (m *PlainTextChangeContent) Marshal() (dAtA []byte, err error) {
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *PlainTextChangeContent) MarshalTo(dAtA []byte) (int, error) {
|
||||
func (m *PlainTextChange_Content) MarshalTo(dAtA []byte) (int, error) {
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *PlainTextChangeContent) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
func (m *PlainTextChange_Content) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
@ -358,12 +358,12 @@ func (m *PlainTextChangeContent) MarshalToSizedBuffer(dAtA []byte) (int, error)
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func (m *PlainTextChangeContentValueOfTextAppend) MarshalTo(dAtA []byte) (int, error) {
|
||||
func (m *PlainTextChange_Content_TextAppend) MarshalTo(dAtA []byte) (int, error) {
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *PlainTextChangeContentValueOfTextAppend) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
func (m *PlainTextChange_Content_TextAppend) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
if m.TextAppend != nil {
|
||||
{
|
||||
@ -379,7 +379,7 @@ func (m *PlainTextChangeContentValueOfTextAppend) MarshalToSizedBuffer(dAtA []by
|
||||
}
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
func (m *PlainTextChangeTextAppend) Marshal() (dAtA []byte, err error) {
|
||||
func (m *PlainTextChange_TextAppend) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
@ -389,12 +389,12 @@ func (m *PlainTextChangeTextAppend) Marshal() (dAtA []byte, err error) {
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *PlainTextChangeTextAppend) MarshalTo(dAtA []byte) (int, error) {
|
||||
func (m *PlainTextChange_TextAppend) MarshalTo(dAtA []byte) (int, error) {
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *PlainTextChangeTextAppend) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
func (m *PlainTextChange_TextAppend) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
@ -409,7 +409,7 @@ func (m *PlainTextChangeTextAppend) MarshalToSizedBuffer(dAtA []byte) (int, erro
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func (m *PlainTextChangeSnapshot) Marshal() (dAtA []byte, err error) {
|
||||
func (m *PlainTextChange_Snapshot) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
@ -419,12 +419,12 @@ func (m *PlainTextChangeSnapshot) Marshal() (dAtA []byte, err error) {
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *PlainTextChangeSnapshot) MarshalTo(dAtA []byte) (int, error) {
|
||||
func (m *PlainTextChange_Snapshot) MarshalTo(dAtA []byte) (int, error) {
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *PlainTextChangeSnapshot) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
func (m *PlainTextChange_Snapshot) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
@ -439,7 +439,7 @@ func (m *PlainTextChangeSnapshot) MarshalToSizedBuffer(dAtA []byte) (int, error)
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func (m *PlainTextChangeData) Marshal() (dAtA []byte, err error) {
|
||||
func (m *PlainTextChange_Data) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
@ -449,12 +449,12 @@ func (m *PlainTextChangeData) Marshal() (dAtA []byte, err error) {
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *PlainTextChangeData) MarshalTo(dAtA []byte) (int, error) {
|
||||
func (m *PlainTextChange_Data) MarshalTo(dAtA []byte) (int, error) {
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *PlainTextChangeData) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
func (m *PlainTextChange_Data) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
@ -508,7 +508,7 @@ func (m *PlainTextChange) Size() (n int) {
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *PlainTextChangeContent) Size() (n int) {
|
||||
func (m *PlainTextChange_Content) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
@ -520,7 +520,7 @@ func (m *PlainTextChangeContent) Size() (n int) {
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *PlainTextChangeContentValueOfTextAppend) Size() (n int) {
|
||||
func (m *PlainTextChange_Content_TextAppend) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
@ -532,7 +532,7 @@ func (m *PlainTextChangeContentValueOfTextAppend) Size() (n int) {
|
||||
}
|
||||
return n
|
||||
}
|
||||
func (m *PlainTextChangeTextAppend) Size() (n int) {
|
||||
func (m *PlainTextChange_TextAppend) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
@ -545,7 +545,7 @@ func (m *PlainTextChangeTextAppend) Size() (n int) {
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *PlainTextChangeSnapshot) Size() (n int) {
|
||||
func (m *PlainTextChange_Snapshot) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
@ -558,7 +558,7 @@ func (m *PlainTextChangeSnapshot) Size() (n int) {
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *PlainTextChangeData) Size() (n int) {
|
||||
func (m *PlainTextChange_Data) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
@ -633,7 +633,7 @@ func (m *PlainTextChange) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *PlainTextChangeContent) Unmarshal(dAtA []byte) error {
|
||||
func (m *PlainTextChange_Content) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
@ -691,11 +691,11 @@ func (m *PlainTextChangeContent) Unmarshal(dAtA []byte) error {
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
v := &PlainTextChangeTextAppend{}
|
||||
v := &PlainTextChange_TextAppend{}
|
||||
if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
m.Value = &PlainTextChangeContentValueOfTextAppend{v}
|
||||
m.Value = &PlainTextChange_Content_TextAppend{v}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
@ -718,7 +718,7 @@ func (m *PlainTextChangeContent) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *PlainTextChangeTextAppend) Unmarshal(dAtA []byte) error {
|
||||
func (m *PlainTextChange_TextAppend) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
@ -800,7 +800,7 @@ func (m *PlainTextChangeTextAppend) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *PlainTextChangeSnapshot) Unmarshal(dAtA []byte) error {
|
||||
func (m *PlainTextChange_Snapshot) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
@ -882,7 +882,7 @@ func (m *PlainTextChangeSnapshot) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *PlainTextChangeData) Unmarshal(dAtA []byte) error {
|
||||
func (m *PlainTextChange_Data) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
@ -940,7 +940,7 @@ func (m *PlainTextChangeData) Unmarshal(dAtA []byte) error {
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Content = append(m.Content, &PlainTextChangeContent{})
|
||||
m.Content = append(m.Content, &PlainTextChange_Content{})
|
||||
if err := m.Content[len(m.Content)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -975,7 +975,7 @@ func (m *PlainTextChangeData) Unmarshal(dAtA []byte) error {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if m.Snapshot == nil {
|
||||
m.Snapshot = &PlainTextChangeSnapshot{}
|
||||
m.Snapshot = &PlainTextChange_Snapshot{}
|
||||
}
|
||||
if err := m.Snapshot.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
|
||||
@ -1,12 +1,7 @@
|
||||
root:
|
||||
identity: A
|
||||
spaceId: space
|
||||
records:
|
||||
- identity: A
|
||||
aclChanges:
|
||||
- userAdd:
|
||||
identity: A
|
||||
permission: admin
|
||||
encryptionKey: key.Enc.A
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
readKey: key.Read.1
|
||||
- identity: A
|
||||
aclChanges:
|
||||
- userInvite:
|
||||
@ -31,6 +26,7 @@ records:
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
readKey: key.Read.1
|
||||
keys:
|
||||
Derived: A
|
||||
Enc:
|
||||
- name: A
|
||||
value: generated
|
||||
@ -51,4 +47,4 @@ keys:
|
||||
value: generated
|
||||
Read:
|
||||
- name: 1
|
||||
value: generated
|
||||
value: derived
|
||||
|
||||
@ -1,12 +1,7 @@
|
||||
root:
|
||||
identity: A
|
||||
spaceId: space
|
||||
records:
|
||||
- identity: A
|
||||
aclChanges:
|
||||
- userAdd:
|
||||
identity: A
|
||||
permission: admin
|
||||
encryptionKey: key.Enc.A
|
||||
encryptedReadKeys: [key.Read.1]
|
||||
readKey: key.Read.1
|
||||
- identity: A
|
||||
aclChanges:
|
||||
- userInvite:
|
||||
@ -38,6 +33,7 @@ records:
|
||||
identitiesLeft: [A, C]
|
||||
readKey: key.Read.2
|
||||
keys:
|
||||
Derived: A
|
||||
Enc:
|
||||
- name: A
|
||||
value: generated
|
||||
@ -58,6 +54,6 @@ keys:
|
||||
value: generated
|
||||
Read:
|
||||
- name: 1
|
||||
value: generated
|
||||
value: derived
|
||||
- name: 2
|
||||
value: generated
|
||||
|
||||
@ -2,11 +2,7 @@ package tree
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/symmetric"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treechangeproto"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -14,66 +10,52 @@ var (
|
||||
ErrIncorrectCID = errors.New("change has incorrect CID")
|
||||
)
|
||||
|
||||
type ChangeContent struct {
|
||||
ChangesData proto.Marshaler
|
||||
ACLData *aclpb.ACLChangeACLData
|
||||
Id string // TODO: this is just for testing, because id should be created automatically from content
|
||||
}
|
||||
|
||||
// Change is an abstract type for all types of changes
|
||||
type Change struct {
|
||||
Next []*Change
|
||||
PreviousIds []string
|
||||
Id string
|
||||
SnapshotId string
|
||||
IsSnapshot bool
|
||||
DecryptedChange []byte // TODO: check if we need it
|
||||
ParsedModel interface{} // TODO: check if we need it
|
||||
Next []*Change
|
||||
PreviousIds []string
|
||||
AclHeadId string
|
||||
Id string
|
||||
SnapshotId string
|
||||
IsSnapshot bool
|
||||
Timestamp int64
|
||||
ReadKeyHash uint64
|
||||
Identity string
|
||||
Data []byte
|
||||
Model interface{}
|
||||
|
||||
// iterator helpers
|
||||
visited bool
|
||||
branchesFinished bool
|
||||
|
||||
Content *aclpb.Change
|
||||
Sign []byte
|
||||
Signature []byte
|
||||
}
|
||||
|
||||
func (ch *Change) ProtoChange() proto.Marshaler {
|
||||
return ch.Content
|
||||
}
|
||||
|
||||
func (ch *Change) DecryptContents(key *symmetric.Key) error {
|
||||
// if the document is already decrypted
|
||||
if ch.Content.CurrentReadKeyHash == 0 {
|
||||
return nil
|
||||
}
|
||||
decrypted, err := key.Decrypt(ch.Content.ChangesData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decrypt changes data: %w", err)
|
||||
}
|
||||
|
||||
ch.DecryptedChange = decrypted
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewChange(id string, ch *aclpb.Change, signature []byte) *Change {
|
||||
func NewChange(id string, ch *treechangeproto.TreeChange, signature []byte) *Change {
|
||||
return &Change{
|
||||
Next: nil,
|
||||
PreviousIds: ch.TreeHeadIds,
|
||||
AclHeadId: ch.AclHeadId,
|
||||
Timestamp: ch.Timestamp,
|
||||
ReadKeyHash: ch.CurrentReadKeyHash,
|
||||
Id: id,
|
||||
Content: ch,
|
||||
Data: ch.ChangesData,
|
||||
SnapshotId: ch.SnapshotBaseId,
|
||||
IsSnapshot: ch.IsSnapshot,
|
||||
Sign: signature,
|
||||
Identity: string(ch.Identity),
|
||||
Signature: signature,
|
||||
}
|
||||
}
|
||||
|
||||
func (ch *Change) DecryptedChangeContent() []byte {
|
||||
return ch.DecryptedChange
|
||||
}
|
||||
|
||||
func (ch *Change) Signature() []byte {
|
||||
return ch.Sign
|
||||
func NewChangeFromRoot(id string, ch *treechangeproto.RootChange, signature []byte) *Change {
|
||||
return &Change{
|
||||
Next: nil,
|
||||
AclHeadId: ch.AclHeadId,
|
||||
Id: id,
|
||||
IsSnapshot: true,
|
||||
Identity: string(ch.Identity),
|
||||
Signature: signature,
|
||||
}
|
||||
}
|
||||
|
||||
func (ch *Change) CID() string {
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
package tree
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"errors"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/common"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treechangeproto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/symmetric"
|
||||
@ -10,134 +11,241 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const componentBuilder = "tree.changebuilder"
|
||||
var ErrEmptyChange = errors.New("change payload should not be empty")
|
||||
|
||||
type BuilderContent struct {
|
||||
treeHeadIds []string
|
||||
aclHeadId string
|
||||
snapshotBaseId string
|
||||
currentReadKeyHash uint64
|
||||
identity string
|
||||
isSnapshot bool
|
||||
signingKey signingkey.PrivKey
|
||||
readKey *symmetric.Key
|
||||
content proto.Marshaler
|
||||
TreeHeadIds []string
|
||||
AclHeadId string
|
||||
SnapshotBaseId string
|
||||
CurrentReadKeyHash uint64
|
||||
Identity []byte
|
||||
IsSnapshot bool
|
||||
SigningKey signingkey.PrivKey
|
||||
ReadKey *symmetric.Key
|
||||
Content []byte
|
||||
}
|
||||
|
||||
type InitialContent struct {
|
||||
AclHeadId string
|
||||
Identity []byte
|
||||
SigningKey signingkey.PrivKey
|
||||
SpaceId string
|
||||
Seed []byte
|
||||
ChangeType string
|
||||
Timestamp int64
|
||||
}
|
||||
|
||||
type ChangeBuilder interface {
|
||||
ConvertFromRaw(rawChange *aclpb.RawChange) (ch *Change, err error)
|
||||
ConvertFromRawAndVerify(rawChange *aclpb.RawChange) (ch *Change, err error)
|
||||
BuildContent(payload BuilderContent) (ch *Change, raw *aclpb.RawChange, err error)
|
||||
BuildRaw(ch *Change) (*aclpb.RawChange, error)
|
||||
ConvertFromRaw(rawIdChange *treechangeproto.RawTreeChangeWithId, verify bool) (ch *Change, err error)
|
||||
BuildContent(payload BuilderContent) (ch *Change, raw *treechangeproto.RawTreeChangeWithId, err error)
|
||||
BuildInitialContent(payload InitialContent) (ch *Change, raw *treechangeproto.RawTreeChangeWithId, err error)
|
||||
BuildRaw(ch *Change) (*treechangeproto.RawTreeChangeWithId, error)
|
||||
SetRootRawChange(rawIdChange *treechangeproto.RawTreeChangeWithId)
|
||||
}
|
||||
|
||||
type changeBuilder struct {
|
||||
keys *common.Keychain
|
||||
rootChange *treechangeproto.RawTreeChangeWithId
|
||||
keys *common.Keychain
|
||||
}
|
||||
|
||||
func newChangeBuilder(keys *common.Keychain) ChangeBuilder {
|
||||
return &changeBuilder{keys: keys}
|
||||
func newChangeBuilder(keys *common.Keychain, rootChange *treechangeproto.RawTreeChangeWithId) ChangeBuilder {
|
||||
return &changeBuilder{keys: keys, rootChange: rootChange}
|
||||
}
|
||||
|
||||
func (c *changeBuilder) ConvertFromRaw(rawChange *aclpb.RawChange) (ch *Change, err error) {
|
||||
unmarshalled := &aclpb.Change{}
|
||||
err = proto.Unmarshal(rawChange.Payload, unmarshalled)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (c *changeBuilder) ConvertFromRaw(rawIdChange *treechangeproto.RawTreeChangeWithId, verify bool) (ch *Change, err error) {
|
||||
if rawIdChange.GetRawChange() == nil {
|
||||
err = ErrEmptyChange
|
||||
return
|
||||
}
|
||||
|
||||
ch = NewChange(rawChange.Id, unmarshalled, rawChange.Signature)
|
||||
return
|
||||
if verify {
|
||||
// verifying ID
|
||||
if !cid.VerifyCID(rawIdChange.RawChange, rawIdChange.Id) {
|
||||
err = ErrIncorrectCID
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
raw := &treechangeproto.RawTreeChange{} // TODO: sync pool
|
||||
err = proto.Unmarshal(rawIdChange.GetRawChange(), raw)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if verify {
|
||||
var identityKey signingkey.PubKey
|
||||
identityKey, err = c.keys.GetOrAdd(ch.Identity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// verifying signature
|
||||
var res bool
|
||||
res, err = identityKey.Verify(raw.Payload, raw.Signature)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !res {
|
||||
err = ErrIncorrectSignature
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return c.unmarshallRawChange(raw, rawIdChange.Id)
|
||||
}
|
||||
|
||||
func (c *changeBuilder) ConvertFromRawAndVerify(rawChange *aclpb.RawChange) (ch *Change, err error) {
|
||||
unmarshalled := &aclpb.Change{}
|
||||
ch, err = c.ConvertFromRaw(rawChange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
identityKey, err := c.keys.GetOrAdd(unmarshalled.Identity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// verifying signature
|
||||
res, err := identityKey.Verify(rawChange.Payload, rawChange.Signature)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !res {
|
||||
err = ErrIncorrectSignature
|
||||
return
|
||||
}
|
||||
|
||||
// verifying ID
|
||||
if !cid.VerifyCID(rawChange.Payload, rawChange.Id) {
|
||||
err = ErrIncorrectCID
|
||||
}
|
||||
|
||||
return
|
||||
func (c *changeBuilder) SetRootRawChange(rawIdChange *treechangeproto.RawTreeChangeWithId) {
|
||||
c.rootChange = rawIdChange
|
||||
}
|
||||
|
||||
func (c *changeBuilder) BuildContent(payload BuilderContent) (ch *Change, raw *aclpb.RawChange, err error) {
|
||||
aclChange := &aclpb.Change{
|
||||
TreeHeadIds: payload.treeHeadIds,
|
||||
AclHeadId: payload.aclHeadId,
|
||||
SnapshotBaseId: payload.snapshotBaseId,
|
||||
CurrentReadKeyHash: payload.currentReadKeyHash,
|
||||
Timestamp: int64(time.Now().Nanosecond()),
|
||||
Identity: payload.identity,
|
||||
IsSnapshot: payload.isSnapshot,
|
||||
func (c *changeBuilder) BuildInitialContent(payload InitialContent) (ch *Change, rawIdChange *treechangeproto.RawTreeChangeWithId, err error) {
|
||||
change := &treechangeproto.RootChange{
|
||||
AclHeadId: payload.AclHeadId,
|
||||
Timestamp: payload.Timestamp,
|
||||
Identity: payload.Identity,
|
||||
ChangeType: payload.ChangeType,
|
||||
SpaceId: payload.SpaceId,
|
||||
Seed: payload.Seed,
|
||||
}
|
||||
marshalledData, err := payload.content.Marshal()
|
||||
|
||||
marshalledChange, err := proto.Marshal(change)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
encrypted, err := payload.readKey.Encrypt(marshalledData)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
aclChange.ChangesData = encrypted
|
||||
|
||||
fullMarshalledChange, err := proto.Marshal(aclChange)
|
||||
signature, err := payload.SigningKey.Sign(marshalledChange)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
signature, err := payload.signingKey.Sign(fullMarshalledChange)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
id, err := cid.NewCIDFromBytes(fullMarshalledChange)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ch = NewChange(id, aclChange, signature)
|
||||
ch.ParsedModel = payload.content
|
||||
|
||||
raw = &aclpb.RawChange{
|
||||
Payload: fullMarshalledChange,
|
||||
raw := &treechangeproto.RawTreeChange{
|
||||
Payload: marshalledChange,
|
||||
Signature: signature,
|
||||
}
|
||||
|
||||
marshalledRawChange, err := proto.Marshal(raw)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
id, err := cid.NewCIDFromBytes(marshalledRawChange)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ch = NewChangeFromRoot(id, change, signature)
|
||||
|
||||
rawIdChange = &treechangeproto.RawTreeChangeWithId{
|
||||
RawChange: marshalledRawChange,
|
||||
Id: id,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *changeBuilder) BuildRaw(ch *Change) (raw *aclpb.RawChange, err error) {
|
||||
var marshalled []byte
|
||||
marshalled, err = ch.Content.Marshal()
|
||||
func (c *changeBuilder) BuildContent(payload BuilderContent) (ch *Change, rawIdChange *treechangeproto.RawTreeChangeWithId, err error) {
|
||||
change := &treechangeproto.TreeChange{
|
||||
TreeHeadIds: payload.TreeHeadIds,
|
||||
AclHeadId: payload.AclHeadId,
|
||||
SnapshotBaseId: payload.SnapshotBaseId,
|
||||
CurrentReadKeyHash: payload.CurrentReadKeyHash,
|
||||
Timestamp: int64(time.Now().Nanosecond()),
|
||||
Identity: payload.Identity,
|
||||
IsSnapshot: payload.IsSnapshot,
|
||||
}
|
||||
|
||||
encrypted, err := payload.ReadKey.Encrypt(payload.Content)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
change.ChangesData = encrypted
|
||||
|
||||
marshalledChange, err := proto.Marshal(change)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
raw = &aclpb.RawChange{
|
||||
signature, err := payload.SigningKey.Sign(marshalledChange)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
raw := &treechangeproto.RawTreeChange{
|
||||
Payload: marshalledChange,
|
||||
Signature: signature,
|
||||
}
|
||||
|
||||
marshalledRawChange, err := proto.Marshal(raw)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
id, err := cid.NewCIDFromBytes(marshalledRawChange)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ch = NewChange(id, change, signature)
|
||||
ch.Model = payload.Content
|
||||
|
||||
rawIdChange = &treechangeproto.RawTreeChangeWithId{
|
||||
RawChange: marshalledRawChange,
|
||||
Id: id,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *changeBuilder) BuildRaw(ch *Change) (raw *treechangeproto.RawTreeChangeWithId, err error) {
|
||||
if ch.Id == c.rootChange.Id {
|
||||
return c.rootChange, nil
|
||||
}
|
||||
treeChange := &treechangeproto.TreeChange{
|
||||
TreeHeadIds: ch.PreviousIds,
|
||||
AclHeadId: ch.AclHeadId,
|
||||
SnapshotBaseId: ch.SnapshotId,
|
||||
ChangesData: ch.Data,
|
||||
CurrentReadKeyHash: ch.ReadKeyHash,
|
||||
Timestamp: ch.Timestamp,
|
||||
Identity: []byte(ch.Identity),
|
||||
IsSnapshot: ch.IsSnapshot,
|
||||
}
|
||||
var marshalled []byte
|
||||
marshalled, err = treeChange.Marshal()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
marshalledRawChange, err := proto.Marshal(&treechangeproto.RawTreeChange{
|
||||
Payload: marshalled,
|
||||
Signature: ch.Signature(),
|
||||
Signature: ch.Signature,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
raw = &treechangeproto.RawTreeChangeWithId{
|
||||
RawChange: marshalledRawChange,
|
||||
Id: ch.Id,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *changeBuilder) unmarshallRawChange(raw *treechangeproto.RawTreeChange, id string) (ch *Change, err error) {
|
||||
if c.rootChange.Id == id {
|
||||
unmarshalled := &treechangeproto.RootChange{}
|
||||
err = proto.Unmarshal(raw.Payload, unmarshalled)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ch = NewChangeFromRoot(id, unmarshalled, raw.Signature)
|
||||
return
|
||||
}
|
||||
|
||||
unmarshalled := &treechangeproto.TreeChange{}
|
||||
err = proto.Unmarshal(raw.Payload, unmarshalled)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ch = NewChange(id, unmarshalled, raw.Signature)
|
||||
return
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ package tree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclrecordproto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list"
|
||||
)
|
||||
|
||||
@ -43,12 +43,12 @@ func (v *objectTreeValidator) validateChange(tree *Tree, aclList list.ACLList, c
|
||||
state = aclList.ACLState()
|
||||
)
|
||||
// checking if the user could write
|
||||
perm, err = state.PermissionsAtRecord(c.Content.AclHeadId, c.Content.Identity)
|
||||
perm, err = state.PermissionsAtRecord(c.AclHeadId, c.Identity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if perm.Permission != aclpb.ACLChange_Writer && perm.Permission != aclpb.ACLChange_Admin {
|
||||
if perm.Permission != aclrecordproto.ACLUserPermissions_Writer && perm.Permission != aclrecordproto.ACLUserPermissions_Admin {
|
||||
err = list.ErrInsufficientPermissions
|
||||
return
|
||||
}
|
||||
@ -56,16 +56,16 @@ func (v *objectTreeValidator) validateChange(tree *Tree, aclList list.ACLList, c
|
||||
// checking if the change refers to later acl heads than its previous ids
|
||||
for _, id := range c.PreviousIds {
|
||||
prevChange := tree.attached[id]
|
||||
if prevChange.Content.AclHeadId == c.Content.AclHeadId {
|
||||
if prevChange.AclHeadId == c.AclHeadId {
|
||||
continue
|
||||
}
|
||||
var after bool
|
||||
after, err = aclList.IsAfter(c.Content.AclHeadId, prevChange.Content.AclHeadId)
|
||||
after, err = aclList.IsAfter(c.AclHeadId, prevChange.AclHeadId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !after {
|
||||
err = fmt.Errorf("current acl head id (%s) should be after each of the previous ones (%s)", c.Content.AclHeadId, prevChange.Content.AclHeadId)
|
||||
err = fmt.Errorf("current acl head id (%s) should be after each of the previous ones (%s)", c.AclHeadId, prevChange.AclHeadId)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,21 +3,14 @@ package tree
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/common"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treechangeproto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/symmetric"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice"
|
||||
"go.uber.org/zap"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type ObjectTreeUpdateListener interface {
|
||||
Update(tree ObjectTree)
|
||||
Rebuild(tree ObjectTree)
|
||||
}
|
||||
|
||||
type RWLocker interface {
|
||||
sync.Locker
|
||||
RLock()
|
||||
@ -34,7 +27,7 @@ type AddResultSummary int
|
||||
type AddResult struct {
|
||||
OldHeads []string
|
||||
Heads []string
|
||||
Added []*aclpb.RawChange
|
||||
Added []*treechangeproto.RawTreeChangeWithId
|
||||
|
||||
Mode Mode
|
||||
}
|
||||
@ -46,22 +39,22 @@ type ObjectTree interface {
|
||||
RWLocker
|
||||
|
||||
ID() string
|
||||
Header() *aclpb.Header
|
||||
Header() *treechangeproto.RawTreeChangeWithId
|
||||
Heads() []string
|
||||
Root() *Change
|
||||
HasChange(string) bool
|
||||
DebugDump() (string, error)
|
||||
|
||||
Iterate(convert ChangeConvertFunc, iterate ChangeIterateFunc) error
|
||||
IterateFrom(id string, convert ChangeConvertFunc, iterate ChangeIterateFunc) error
|
||||
|
||||
SnapshotPath() []string
|
||||
ChangesAfterCommonSnapshot(snapshotPath, heads []string) ([]*aclpb.RawChange, error)
|
||||
ChangesAfterCommonSnapshot(snapshotPath, heads []string) ([]*treechangeproto.RawTreeChangeWithId, error)
|
||||
|
||||
Storage() storage.TreeStorage
|
||||
DebugDump() (string, error)
|
||||
|
||||
AddContent(ctx context.Context, content SignableChangeContent) (*aclpb.RawChange, error)
|
||||
AddRawChanges(ctx context.Context, changes ...*aclpb.RawChange) (AddResult, error)
|
||||
AddContent(ctx context.Context, content SignableChangeContent) (AddResult, error)
|
||||
AddRawChanges(ctx context.Context, changes ...*treechangeproto.RawTreeChangeWithId) (AddResult, error)
|
||||
|
||||
Close() error
|
||||
}
|
||||
@ -69,20 +62,19 @@ type ObjectTree interface {
|
||||
type objectTree struct {
|
||||
treeStorage storage.TreeStorage
|
||||
changeBuilder ChangeBuilder
|
||||
updateListener ObjectTreeUpdateListener
|
||||
validator ObjectTreeValidator
|
||||
rawChangeLoader *rawChangeLoader
|
||||
treeBuilder *treeBuilder
|
||||
aclList list.ACLList
|
||||
|
||||
id string
|
||||
header *aclpb.Header
|
||||
tree *Tree
|
||||
id string
|
||||
root *treechangeproto.RawTreeChangeWithId
|
||||
tree *Tree
|
||||
|
||||
keys map[uint64]*symmetric.Key
|
||||
|
||||
// buffers
|
||||
difSnapshotBuf []*aclpb.RawChange
|
||||
difSnapshotBuf []*treechangeproto.RawTreeChangeWithId
|
||||
tmpChangesBuf []*Change
|
||||
newSnapshotsBuf []*Change
|
||||
notSeenIdxBuf []int
|
||||
@ -96,90 +88,29 @@ type objectTreeDeps struct {
|
||||
changeBuilder ChangeBuilder
|
||||
treeBuilder *treeBuilder
|
||||
treeStorage storage.TreeStorage
|
||||
updateListener ObjectTreeUpdateListener
|
||||
validator ObjectTreeValidator
|
||||
rawChangeLoader *rawChangeLoader
|
||||
aclList list.ACLList
|
||||
}
|
||||
|
||||
func defaultObjectTreeDeps(
|
||||
rootChange *treechangeproto.RawTreeChangeWithId,
|
||||
treeStorage storage.TreeStorage,
|
||||
listener ObjectTreeUpdateListener,
|
||||
aclList list.ACLList) objectTreeDeps {
|
||||
|
||||
keychain := common.NewKeychain()
|
||||
changeBuilder := newChangeBuilder(keychain)
|
||||
changeBuilder := newChangeBuilder(keychain, rootChange)
|
||||
treeBuilder := newTreeBuilder(treeStorage, changeBuilder)
|
||||
return objectTreeDeps{
|
||||
changeBuilder: changeBuilder,
|
||||
treeBuilder: treeBuilder,
|
||||
treeStorage: treeStorage,
|
||||
updateListener: listener,
|
||||
validator: newTreeValidator(),
|
||||
rawChangeLoader: newRawChangeLoader(treeStorage, changeBuilder),
|
||||
aclList: aclList,
|
||||
}
|
||||
}
|
||||
|
||||
func buildObjectTree(deps objectTreeDeps) (ObjectTree, error) {
|
||||
objTree := &objectTree{
|
||||
treeStorage: deps.treeStorage,
|
||||
updateListener: deps.updateListener,
|
||||
treeBuilder: deps.treeBuilder,
|
||||
validator: deps.validator,
|
||||
aclList: deps.aclList,
|
||||
changeBuilder: deps.changeBuilder,
|
||||
rawChangeLoader: deps.rawChangeLoader,
|
||||
tree: nil,
|
||||
keys: make(map[uint64]*symmetric.Key),
|
||||
tmpChangesBuf: make([]*Change, 0, 10),
|
||||
difSnapshotBuf: make([]*aclpb.RawChange, 0, 10),
|
||||
notSeenIdxBuf: make([]int, 0, 10),
|
||||
newSnapshotsBuf: make([]*Change, 0, 10),
|
||||
}
|
||||
|
||||
err := objTree.rebuildFromStorage(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
storageHeads, err := objTree.treeStorage.Heads()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// comparing rebuilt heads with heads in storage
|
||||
// in theory it can happen that we didn't set heads because the process has crashed
|
||||
// therefore we want to set them later
|
||||
if !slice.UnsortedEquals(storageHeads, objTree.tree.Heads()) {
|
||||
log.With(zap.Strings("storage", storageHeads), zap.Strings("rebuilt", objTree.tree.Heads())).
|
||||
Errorf("the heads in storage and objTree are different")
|
||||
err = objTree.treeStorage.SetHeads(objTree.tree.Heads())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
objTree.id, err = objTree.treeStorage.ID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
objTree.header, err = objTree.treeStorage.Header()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if objTree.updateListener != nil {
|
||||
objTree.updateListener.Rebuild(objTree)
|
||||
}
|
||||
|
||||
return objTree, nil
|
||||
}
|
||||
|
||||
func BuildObjectTree(treeStorage storage.TreeStorage, listener ObjectTreeUpdateListener, aclList list.ACLList) (ObjectTree, error) {
|
||||
deps := defaultObjectTreeDeps(treeStorage, listener, aclList)
|
||||
return buildObjectTree(deps)
|
||||
}
|
||||
|
||||
func (ot *objectTree) rebuildFromStorage(newChanges []*Change) (err error) {
|
||||
ot.treeBuilder.Reset()
|
||||
|
||||
@ -201,29 +132,27 @@ func (ot *objectTree) ID() string {
|
||||
return ot.id
|
||||
}
|
||||
|
||||
func (ot *objectTree) Header() *aclpb.Header {
|
||||
return ot.header
|
||||
func (ot *objectTree) Header() *treechangeproto.RawTreeChangeWithId {
|
||||
return ot.root
|
||||
}
|
||||
|
||||
func (ot *objectTree) Storage() storage.TreeStorage {
|
||||
return ot.treeStorage
|
||||
}
|
||||
|
||||
func (ot *objectTree) AddContent(ctx context.Context, content SignableChangeContent) (rawChange *aclpb.RawChange, err error) {
|
||||
defer func() {
|
||||
if err == nil && ot.updateListener != nil {
|
||||
ot.updateListener.Update(ot)
|
||||
}
|
||||
}()
|
||||
|
||||
func (ot *objectTree) AddContent(ctx context.Context, content SignableChangeContent) (res AddResult, err error) {
|
||||
payload, err := ot.prepareBuilderContent(content)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// saving old heads
|
||||
oldHeads := make([]string, 0, len(ot.tree.Heads()))
|
||||
oldHeads = append(oldHeads, ot.tree.Heads()...)
|
||||
|
||||
objChange, rawChange, err := ot.changeBuilder.BuildContent(payload)
|
||||
if content.IsSnapshot {
|
||||
// clearing tree, because we already fixed everything in the last snapshot
|
||||
// clearing tree, because we already saved everything in the last snapshot
|
||||
ot.tree = &Tree{}
|
||||
}
|
||||
err = ot.tree.AddMergedHead(objChange)
|
||||
@ -237,6 +166,16 @@ func (ot *objectTree) AddContent(ctx context.Context, content SignableChangeCont
|
||||
}
|
||||
|
||||
err = ot.treeStorage.SetHeads([]string{objChange.Id})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res = AddResult{
|
||||
OldHeads: oldHeads,
|
||||
Heads: []string{objChange.Id},
|
||||
Added: []*treechangeproto.RawTreeChangeWithId{rawChange},
|
||||
Mode: Append,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -250,22 +189,21 @@ func (ot *objectTree) prepareBuilderContent(content SignableChangeContent) (cnt
|
||||
return
|
||||
}
|
||||
cnt = BuilderContent{
|
||||
treeHeadIds: ot.tree.Heads(),
|
||||
aclHeadId: ot.aclList.Head().Id,
|
||||
snapshotBaseId: ot.tree.RootId(),
|
||||
currentReadKeyHash: state.CurrentReadKeyHash(),
|
||||
identity: content.Identity,
|
||||
isSnapshot: content.IsSnapshot,
|
||||
signingKey: content.Key,
|
||||
readKey: readKey,
|
||||
content: content.Proto,
|
||||
TreeHeadIds: ot.tree.Heads(),
|
||||
AclHeadId: ot.aclList.Head().Id,
|
||||
SnapshotBaseId: ot.tree.RootId(),
|
||||
CurrentReadKeyHash: state.CurrentReadKeyHash(),
|
||||
Identity: content.Identity,
|
||||
IsSnapshot: content.IsSnapshot,
|
||||
SigningKey: content.Key,
|
||||
ReadKey: readKey,
|
||||
Content: content.Data,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ot *objectTree) AddRawChanges(ctx context.Context, rawChanges ...*aclpb.RawChange) (addResult AddResult, err error) {
|
||||
var mode Mode
|
||||
mode, addResult, err = ot.addRawChanges(ctx, rawChanges...)
|
||||
func (ot *objectTree) AddRawChanges(ctx context.Context, rawChanges ...*treechangeproto.RawTreeChangeWithId) (addResult AddResult, err error) {
|
||||
addResult, err = ot.addRawChanges(ctx, rawChanges...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -283,26 +221,10 @@ func (ot *objectTree) AddRawChanges(ctx context.Context, rawChanges ...*aclpb.Ra
|
||||
|
||||
// setting heads
|
||||
err = ot.treeStorage.SetHeads(ot.tree.Heads())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if ot.updateListener == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case Append:
|
||||
ot.updateListener.Update(ot)
|
||||
case Rebuild:
|
||||
ot.updateListener.Rebuild(ot)
|
||||
default:
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ot *objectTree) addRawChanges(ctx context.Context, rawChanges ...*aclpb.RawChange) (mode Mode, addResult AddResult, err error) {
|
||||
func (ot *objectTree) addRawChanges(ctx context.Context, rawChanges ...*treechangeproto.RawTreeChangeWithId) (addResult AddResult, err error) {
|
||||
// resetting buffers
|
||||
ot.tmpChangesBuf = ot.tmpChangesBuf[:0]
|
||||
ot.notSeenIdxBuf = ot.notSeenIdxBuf[:0]
|
||||
@ -329,7 +251,7 @@ func (ot *objectTree) addRawChanges(ctx context.Context, rawChanges ...*aclpb.Ra
|
||||
}
|
||||
|
||||
var change *Change
|
||||
change, err = ot.changeBuilder.ConvertFromRawAndVerify(ch)
|
||||
change, err = ot.changeBuilder.ConvertFromRaw(ch, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -354,7 +276,7 @@ func (ot *objectTree) addRawChanges(ctx context.Context, rawChanges ...*aclpb.Ra
|
||||
// returns changes that we added to the tree as attached this round
|
||||
// they can include not only the changes that were added now,
|
||||
// but also the changes that were previously in the tree
|
||||
getAddedChanges := func(toConvert []*Change) (added []*aclpb.RawChange, err error) {
|
||||
getAddedChanges := func(toConvert []*Change) (added []*treechangeproto.RawTreeChangeWithId, err error) {
|
||||
alreadyConverted := make(map[*Change]struct{})
|
||||
|
||||
// first we see if we have already unmarshalled those changes
|
||||
@ -379,7 +301,7 @@ func (ot *objectTree) addRawChanges(ctx context.Context, rawChanges ...*aclpb.Ra
|
||||
for _, ch := range toConvert {
|
||||
// if we got some changes that we need to convert to raw
|
||||
if _, exists := alreadyConverted[ch]; !exists {
|
||||
var raw *aclpb.RawChange
|
||||
var raw *treechangeproto.RawTreeChangeWithId
|
||||
raw, err = ot.changeBuilder.BuildRaw(ch)
|
||||
if err != nil {
|
||||
return
|
||||
@ -421,7 +343,7 @@ func (ot *objectTree) addRawChanges(ctx context.Context, rawChanges ...*aclpb.Ra
|
||||
ot.rebuildFromStorage(nil)
|
||||
return
|
||||
}
|
||||
var added []*aclpb.RawChange
|
||||
var added []*treechangeproto.RawTreeChangeWithId
|
||||
added, err = getAddedChanges(nil)
|
||||
// we shouldn't get any error in this case
|
||||
if err != nil {
|
||||
@ -457,7 +379,7 @@ func (ot *objectTree) addRawChanges(ctx context.Context, rawChanges ...*aclpb.Ra
|
||||
err = ErrHasInvalidChanges
|
||||
return
|
||||
}
|
||||
var added []*aclpb.RawChange
|
||||
var added []*treechangeproto.RawTreeChangeWithId
|
||||
added, err = getAddedChanges(treeChangesAdded)
|
||||
if err != nil {
|
||||
// that means that some unattached changes were somehow corrupted in memory
|
||||
@ -488,17 +410,21 @@ func (ot *objectTree) IterateFrom(id string, convert ChangeConvertFunc, iterate
|
||||
|
||||
ot.tree.Iterate(ot.tree.RootId(), func(c *Change) (isContinue bool) {
|
||||
var model any
|
||||
if c.ParsedModel != nil {
|
||||
if c.Model != nil {
|
||||
return iterate(c)
|
||||
}
|
||||
readKey, exists := ot.keys[c.Content.CurrentReadKeyHash]
|
||||
// if this is a root change
|
||||
if c.Id == ot.id {
|
||||
return iterate(c)
|
||||
}
|
||||
readKey, exists := ot.keys[c.ReadKeyHash]
|
||||
if !exists {
|
||||
err = list.ErrNoReadKey
|
||||
return false
|
||||
}
|
||||
|
||||
var decrypted []byte
|
||||
decrypted, err = readKey.Decrypt(c.Content.GetChangesData())
|
||||
decrypted, err = readKey.Decrypt(c.Data)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@ -508,7 +434,7 @@ func (ot *objectTree) IterateFrom(id string, convert ChangeConvertFunc, iterate
|
||||
return false
|
||||
}
|
||||
|
||||
c.ParsedModel = model
|
||||
c.Model = model
|
||||
return iterate(c)
|
||||
})
|
||||
return
|
||||
@ -553,7 +479,7 @@ func (ot *objectTree) SnapshotPath() []string {
|
||||
return path
|
||||
}
|
||||
|
||||
func (ot *objectTree) ChangesAfterCommonSnapshot(theirPath, theirHeads []string) ([]*aclpb.RawChange, error) {
|
||||
func (ot *objectTree) ChangesAfterCommonSnapshot(theirPath, theirHeads []string) ([]*treechangeproto.RawTreeChangeWithId, error) {
|
||||
var (
|
||||
needFullDocument = len(theirPath) == 0
|
||||
ourPath = ot.SnapshotPath()
|
||||
@ -577,11 +503,11 @@ func (ot *objectTree) ChangesAfterCommonSnapshot(theirPath, theirHeads []string)
|
||||
}
|
||||
}
|
||||
|
||||
func (ot *objectTree) getChangesFromTree(theirHeads []string) (rawChanges []*aclpb.RawChange, err error) {
|
||||
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 []*aclpb.RawChange, err error) {
|
||||
func (ot *objectTree) getChangesFromDB(commonSnapshot string, theirHeads []string) (rawChanges []*treechangeproto.RawTreeChangeWithId, err error) {
|
||||
return ot.rawChangeLoader.LoadFromStorage(commonSnapshot, ot.tree.headIds, theirHeads)
|
||||
}
|
||||
|
||||
|
||||
@ -2,11 +2,10 @@ package tree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/acllistbuilder"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treechangeproto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
@ -14,8 +13,26 @@ import (
|
||||
|
||||
type mockChangeCreator struct{}
|
||||
|
||||
func (c *mockChangeCreator) createRaw(id, aclId, snapshotId string, isSnapshot bool, prevIds ...string) *aclpb.RawChange {
|
||||
aclChange := &aclpb.Change{
|
||||
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,
|
||||
@ -23,22 +40,22 @@ func (c *mockChangeCreator) createRaw(id, aclId, snapshotId string, isSnapshot b
|
||||
IsSnapshot: isSnapshot,
|
||||
}
|
||||
res, _ := aclChange.Marshal()
|
||||
return &aclpb.RawChange{
|
||||
|
||||
raw := &treechangeproto.RawTreeChange{
|
||||
Payload: res,
|
||||
Signature: nil,
|
||||
}
|
||||
rawMarshalled, _ := raw.Marshal()
|
||||
|
||||
return &treechangeproto.RawTreeChangeWithId{
|
||||
RawChange: rawMarshalled,
|
||||
Id: id,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *mockChangeCreator) createNewTreeStorage(treeId, aclListId, aclHeadId, firstChangeId string) storage.TreeStorage {
|
||||
firstChange := c.createRaw(firstChangeId, aclHeadId, "", true)
|
||||
header := &aclpb.Header{
|
||||
FirstId: firstChangeId,
|
||||
AclListId: aclListId,
|
||||
WorkspaceId: "",
|
||||
DocType: aclpb.Header_DocTree,
|
||||
}
|
||||
treeStorage, _ := storage.NewInMemoryTreeStorage(treeId, header, []string{firstChangeId}, []*aclpb.RawChange{firstChange})
|
||||
func (c *mockChangeCreator) createNewTreeStorage(treeId, aclHeadId string) storage.TreeStorage {
|
||||
root := c.createRoot(treeId, aclHeadId)
|
||||
treeStorage, _ := storage.NewInMemoryTreeStorage(treeId, root, []string{root.Id}, []*treechangeproto.RawTreeChangeWithId{root})
|
||||
return treeStorage
|
||||
}
|
||||
|
||||
@ -46,19 +63,23 @@ type mockChangeBuilder struct {
|
||||
originalBuilder ChangeBuilder
|
||||
}
|
||||
|
||||
func (c *mockChangeBuilder) ConvertFromRaw(rawChange *aclpb.RawChange) (ch *Change, err error) {
|
||||
return c.originalBuilder.ConvertFromRaw(rawChange)
|
||||
}
|
||||
|
||||
func (c *mockChangeBuilder) ConvertFromRawAndVerify(rawChange *aclpb.RawChange) (ch *Change, err error) {
|
||||
return c.originalBuilder.ConvertFromRaw(rawChange)
|
||||
}
|
||||
|
||||
func (c *mockChangeBuilder) BuildContent(payload BuilderContent) (ch *Change, raw *aclpb.RawChange, err error) {
|
||||
func (c *mockChangeBuilder) BuildInitialContent(payload InitialContent) (ch *Change, raw *treechangeproto.RawTreeChangeWithId, err error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *mockChangeBuilder) BuildRaw(ch *Change) (raw *aclpb.RawChange, err error) {
|
||||
func (c *mockChangeBuilder) SetRootRawChange(rawIdChange *treechangeproto.RawTreeChangeWithId) {
|
||||
c.originalBuilder.SetRootRawChange(rawIdChange)
|
||||
}
|
||||
|
||||
func (c *mockChangeBuilder) ConvertFromRaw(rawChange *treechangeproto.RawTreeChangeWithId, verify bool) (ch *Change, err error) {
|
||||
return c.originalBuilder.ConvertFromRaw(rawChange, false)
|
||||
}
|
||||
|
||||
func (c *mockChangeBuilder) BuildContent(payload BuilderContent) (ch *Change, raw *treechangeproto.RawTreeChangeWithId, err error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *mockChangeBuilder) BuildRaw(ch *Change) (raw *treechangeproto.RawTreeChangeWithId, err error) {
|
||||
return c.originalBuilder.BuildRaw(ch)
|
||||
}
|
||||
|
||||
@ -84,7 +105,7 @@ func prepareACLList(t *testing.T) list.ACLList {
|
||||
st, err := acllistbuilder.NewListStorageWithTestName("userjoinexample.yml")
|
||||
require.NoError(t, err, "building storage should not result in error")
|
||||
|
||||
aclList, err := list.BuildACLList(signingkey.NewEDPubKeyDecoder(), st)
|
||||
aclList, err := list.BuildACLList(st)
|
||||
require.NoError(t, err, "building acl list should be without error")
|
||||
|
||||
return aclList
|
||||
@ -92,15 +113,15 @@ func prepareACLList(t *testing.T) list.ACLList {
|
||||
|
||||
func prepareTreeContext(t *testing.T, aclList list.ACLList) testTreeContext {
|
||||
changeCreator := &mockChangeCreator{}
|
||||
treeStorage := changeCreator.createNewTreeStorage("treeId", aclList.ID(), aclList.Head().Id, "0")
|
||||
treeStorage := changeCreator.createNewTreeStorage("0", aclList.Head().Id)
|
||||
root, _ := treeStorage.Root()
|
||||
changeBuilder := &mockChangeBuilder{
|
||||
originalBuilder: newChangeBuilder(nil),
|
||||
originalBuilder: newChangeBuilder(nil, root),
|
||||
}
|
||||
deps := objectTreeDeps{
|
||||
changeBuilder: changeBuilder,
|
||||
treeBuilder: newTreeBuilder(treeStorage, changeBuilder),
|
||||
treeStorage: treeStorage,
|
||||
updateListener: nil,
|
||||
rawChangeLoader: newRawChangeLoader(treeStorage, changeBuilder),
|
||||
validator: &mockChangeValidator{},
|
||||
aclList: aclList,
|
||||
@ -136,7 +157,7 @@ func TestObjectTree(t *testing.T) {
|
||||
changeCreator := ctx.changeCreator
|
||||
objTree := ctx.objTree
|
||||
|
||||
rawChanges := []*aclpb.RawChange{
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||
}
|
||||
@ -177,7 +198,7 @@ func TestObjectTree(t *testing.T) {
|
||||
changeCreator := ctx.changeCreator
|
||||
objTree := ctx.objTree
|
||||
|
||||
rawChanges := []*aclpb.RawChange{
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.createRaw("0", aclList.Head().Id, "", true, ""),
|
||||
}
|
||||
res, err := objTree.AddRawChanges(context.Background(), rawChanges...)
|
||||
@ -197,7 +218,7 @@ func TestObjectTree(t *testing.T) {
|
||||
changeCreator := ctx.changeCreator
|
||||
objTree := ctx.objTree
|
||||
|
||||
rawChanges := []*aclpb.RawChange{
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||
}
|
||||
res, err := objTree.AddRawChanges(context.Background(), rawChanges...)
|
||||
@ -219,7 +240,7 @@ func TestObjectTree(t *testing.T) {
|
||||
changeCreator := ctx.changeCreator
|
||||
objTree := ctx.objTree
|
||||
|
||||
rawChanges := []*aclpb.RawChange{
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
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"),
|
||||
@ -263,7 +284,7 @@ func TestObjectTree(t *testing.T) {
|
||||
changeCreator := ctx.changeCreator
|
||||
objTree := ctx.objTree
|
||||
|
||||
rawChanges := []*aclpb.RawChange{
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
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"),
|
||||
@ -282,7 +303,7 @@ func TestObjectTree(t *testing.T) {
|
||||
changeCreator := ctx.changeCreator
|
||||
objTree := ctx.objTree
|
||||
|
||||
rawChanges := []*aclpb.RawChange{
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
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"),
|
||||
@ -356,7 +377,7 @@ func TestObjectTree(t *testing.T) {
|
||||
changeCreator := ctx.changeCreator
|
||||
objTree := ctx.objTree
|
||||
|
||||
rawChanges := []*aclpb.RawChange{
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
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"),
|
||||
@ -432,7 +453,7 @@ func TestObjectTree(t *testing.T) {
|
||||
changeCreator := ctx.changeCreator
|
||||
objTree := ctx.objTree
|
||||
|
||||
rawChanges := []*aclpb.RawChange{
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
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"),
|
||||
@ -441,7 +462,7 @@ func TestObjectTree(t *testing.T) {
|
||||
require.NoError(t, err, "adding changes should be without error")
|
||||
require.Equal(t, "3", objTree.Root().Id)
|
||||
|
||||
rawChanges = []*aclpb.RawChange{
|
||||
rawChanges = []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.createRaw("4", aclList.Head().Id, "0", false, "2"),
|
||||
changeCreator.createRaw("5", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.createRaw("6", aclList.Head().Id, "0", false, "3", "4", "5"),
|
||||
|
||||
151
pkg/acl/tree/objecttreefactory.go
Normal file
151
pkg/acl/tree/objecttreefactory.go
Normal file
@ -0,0 +1,151 @@
|
||||
package tree
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treechangeproto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/symmetric"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice"
|
||||
"go.uber.org/zap"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ObjectTreeCreatePayload struct {
|
||||
SignKey signingkey.PrivKey
|
||||
ChangeType string
|
||||
SpaceId string
|
||||
Identity []byte
|
||||
}
|
||||
|
||||
func BuildObjectTree(treeStorage storage.TreeStorage, aclList list.ACLList) (ObjectTree, error) {
|
||||
rootChange, err := treeStorage.Root()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deps := defaultObjectTreeDeps(rootChange, treeStorage, aclList)
|
||||
return buildObjectTree(deps)
|
||||
}
|
||||
|
||||
func CreateDerivedObjectTree(
|
||||
payload ObjectTreeCreatePayload,
|
||||
aclList list.ACLList,
|
||||
createStorage storage.TreeStorageCreatorFunc) (objTree ObjectTree, err error) {
|
||||
return createObjectTree(payload, 0, nil, aclList, createStorage)
|
||||
}
|
||||
|
||||
func CreateObjectTree(
|
||||
payload ObjectTreeCreatePayload,
|
||||
aclList list.ACLList,
|
||||
createStorage storage.TreeStorageCreatorFunc) (objTree ObjectTree, err error) {
|
||||
bytes := make([]byte, 32)
|
||||
_, err = rand.Read(bytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return createObjectTree(payload, time.Now().UnixNano(), bytes, aclList, createStorage)
|
||||
}
|
||||
|
||||
func createObjectTree(
|
||||
payload ObjectTreeCreatePayload,
|
||||
timestamp int64,
|
||||
seed []byte,
|
||||
aclList list.ACLList,
|
||||
createStorage storage.TreeStorageCreatorFunc) (objTree ObjectTree, err error) {
|
||||
aclList.RLock()
|
||||
var (
|
||||
deps = defaultObjectTreeDeps(nil, nil, aclList)
|
||||
aclHeadId = aclList.Head().Id
|
||||
)
|
||||
aclList.RUnlock()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cnt := InitialContent{
|
||||
AclHeadId: aclHeadId,
|
||||
Identity: payload.Identity,
|
||||
SigningKey: payload.SignKey,
|
||||
SpaceId: payload.SpaceId,
|
||||
ChangeType: payload.ChangeType,
|
||||
Timestamp: timestamp,
|
||||
Seed: seed,
|
||||
}
|
||||
|
||||
_, raw, err := deps.changeBuilder.BuildInitialContent(cnt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
deps.changeBuilder.SetRootRawChange(raw)
|
||||
|
||||
// create storage
|
||||
st, err := createStorage(storage.TreeStorageCreatePayload{
|
||||
TreeId: raw.Id,
|
||||
RootRawChange: raw,
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{raw},
|
||||
Heads: []string{raw.Id},
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
deps.treeStorage = st
|
||||
return buildObjectTree(deps)
|
||||
}
|
||||
|
||||
func buildObjectTree(deps objectTreeDeps) (ObjectTree, error) {
|
||||
objTree := &objectTree{
|
||||
treeStorage: deps.treeStorage,
|
||||
treeBuilder: deps.treeBuilder,
|
||||
validator: deps.validator,
|
||||
aclList: deps.aclList,
|
||||
changeBuilder: deps.changeBuilder,
|
||||
rawChangeLoader: deps.rawChangeLoader,
|
||||
tree: nil,
|
||||
keys: make(map[uint64]*symmetric.Key),
|
||||
tmpChangesBuf: make([]*Change, 0, 10),
|
||||
difSnapshotBuf: make([]*treechangeproto.RawTreeChangeWithId, 0, 10),
|
||||
notSeenIdxBuf: make([]int, 0, 10),
|
||||
newSnapshotsBuf: make([]*Change, 0, 10),
|
||||
}
|
||||
|
||||
err := objTree.rebuildFromStorage(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
storageHeads, err := objTree.treeStorage.Heads()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// comparing rebuilt heads with heads in storage
|
||||
// in theory it can happen that we didn't set heads because the process has crashed
|
||||
// therefore we want to set them later
|
||||
if !slice.UnsortedEquals(storageHeads, objTree.tree.Heads()) {
|
||||
log.With(zap.Strings("storage", storageHeads), zap.Strings("rebuilt", objTree.tree.Heads())).
|
||||
Errorf("the heads in storage and objTree are different")
|
||||
err = objTree.treeStorage.SetHeads(objTree.tree.Heads())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
objTree.id, err = objTree.treeStorage.ID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
objTree.root, err = objTree.treeStorage.Root()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// verifying root
|
||||
_, err = objTree.changeBuilder.ConvertFromRaw(objTree.root, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return objTree, nil
|
||||
}
|
||||
@ -2,8 +2,8 @@ package tree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treechangeproto"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -18,7 +18,7 @@ type rawChangeLoader struct {
|
||||
|
||||
type rawCacheEntry struct {
|
||||
change *Change
|
||||
rawChange *aclpb.RawChange
|
||||
rawChange *treechangeproto.RawTreeChangeWithId
|
||||
position int
|
||||
}
|
||||
|
||||
@ -29,15 +29,15 @@ func newRawChangeLoader(treeStorage storage.TreeStorage, changeBuilder ChangeBui
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rawChangeLoader) LoadFromTree(t *Tree, breakpoints []string) ([]*aclpb.RawChange, error) {
|
||||
func (r *rawChangeLoader) LoadFromTree(t *Tree, breakpoints []string) ([]*treechangeproto.RawTreeChangeWithId, error) {
|
||||
var stack []*Change
|
||||
for _, h := range t.headIds {
|
||||
stack = append(stack, t.attached[h])
|
||||
}
|
||||
|
||||
convert := func(chs []*Change) (rawChanges []*aclpb.RawChange, err error) {
|
||||
convert := func(chs []*Change) (rawChanges []*treechangeproto.RawTreeChangeWithId, err error) {
|
||||
for _, ch := range chs {
|
||||
var raw *aclpb.RawChange
|
||||
var raw *treechangeproto.RawTreeChangeWithId
|
||||
raw, err = r.changeBuilder.BuildRaw(ch)
|
||||
if err != nil {
|
||||
return
|
||||
@ -95,7 +95,7 @@ func (r *rawChangeLoader) LoadFromTree(t *Tree, breakpoints []string) ([]*aclpb.
|
||||
return convert(results)
|
||||
}
|
||||
|
||||
func (r *rawChangeLoader) LoadFromStorage(commonSnapshot string, heads, breakpoints []string) ([]*aclpb.RawChange, error) {
|
||||
func (r *rawChangeLoader) LoadFromStorage(commonSnapshot string, heads, breakpoints []string) ([]*treechangeproto.RawTreeChangeWithId, error) {
|
||||
// resetting cache
|
||||
r.cache = make(map[string]rawCacheEntry)
|
||||
defer func() {
|
||||
@ -162,7 +162,7 @@ func (r *rawChangeLoader) LoadFromStorage(commonSnapshot string, heads, breakpoi
|
||||
|
||||
// preparing first pass
|
||||
r.idStack = append(r.idStack, heads...)
|
||||
var buffer []*aclpb.RawChange
|
||||
var buffer []*treechangeproto.RawTreeChangeWithId
|
||||
|
||||
rootVisited := dfs(commonSnapshot, heads, 0,
|
||||
func(counter int, mapExists bool) bool {
|
||||
@ -203,7 +203,7 @@ func (r *rawChangeLoader) LoadFromStorage(commonSnapshot string, heads, breakpoi
|
||||
})
|
||||
|
||||
// discarding visited
|
||||
buffer = discardFromSlice(buffer, func(change *aclpb.RawChange) bool {
|
||||
buffer = discardFromSlice(buffer, func(change *treechangeproto.RawTreeChangeWithId) bool {
|
||||
return change == nil
|
||||
})
|
||||
|
||||
@ -219,7 +219,7 @@ func (r *rawChangeLoader) loadEntry(id string) (entry rawCacheEntry, err error)
|
||||
return
|
||||
}
|
||||
|
||||
change, err := r.changeBuilder.ConvertFromRaw(rawChange)
|
||||
change, err := r.changeBuilder.ConvertFromRaw(rawChange, false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
17
pkg/acl/tree/rawtreevalidator.go
Normal file
17
pkg/acl/tree/rawtreevalidator.go
Normal file
@ -0,0 +1,17 @@
|
||||
package tree
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage"
|
||||
)
|
||||
|
||||
func ValidateRawTree(payload storage.TreeStorageCreatePayload, aclList list.ACLList) (err error) {
|
||||
provider := storage.NewInMemoryTreeStorageProvider()
|
||||
treeStorage, err := provider.CreateTreeStorage(payload)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = BuildObjectTree(treeStorage, aclList)
|
||||
return
|
||||
}
|
||||
@ -2,12 +2,11 @@ package tree
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
)
|
||||
|
||||
type SignableChangeContent struct {
|
||||
Proto proto.Marshaler
|
||||
Data []byte
|
||||
Key signingkey.PrivKey
|
||||
Identity string
|
||||
Identity []byte
|
||||
IsSnapshot bool
|
||||
}
|
||||
|
||||
@ -129,7 +129,7 @@ func (tb *treeBuilder) loadChange(id string) (ch *Change, err error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ch, err = tb.builder.ConvertFromRawAndVerify(change)
|
||||
ch, err = tb.builder.ConvertFromRaw(change, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -59,7 +59,7 @@ func (t *Tree) Graph(parser DescriptionParser) (data string, err error) {
|
||||
label := fmt.Sprintf("Id: %s\nOrd: %s\nTime: %s\nChanges: %s\n",
|
||||
shortId,
|
||||
ord,
|
||||
time.Unix(c.Content.Timestamp, 0).Format("02.01.06 15:04:05"),
|
||||
time.Unix(c.Timestamp, 0).Format("02.01.06 15:04:05"),
|
||||
strings.Join(chSymbs, ","),
|
||||
)
|
||||
n.SetLabel(label)
|
||||
|
||||
@ -1,91 +0,0 @@
|
||||
package tree
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"time"
|
||||
)
|
||||
|
||||
func CreateNewTreeStorage(
|
||||
acc *account.AccountData,
|
||||
aclList list.ACLList,
|
||||
content proto.Marshaler,
|
||||
create storage.TreeStorageCreatorFunc) (thr storage.TreeStorage, err error) {
|
||||
|
||||
state := aclList.ACLState()
|
||||
change := &aclpb.Change{
|
||||
AclHeadId: aclList.Head().Id,
|
||||
CurrentReadKeyHash: state.CurrentReadKeyHash(),
|
||||
Timestamp: int64(time.Now().Nanosecond()),
|
||||
Identity: acc.Identity,
|
||||
IsSnapshot: true,
|
||||
}
|
||||
|
||||
marshalledData, err := content.Marshal()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
readKey, err := state.CurrentReadKey()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
encrypted, err := readKey.Encrypt(marshalledData)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
change.ChangesData = encrypted
|
||||
|
||||
fullMarshalledChange, err := proto.Marshal(change)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
signature, err := acc.SignKey.Sign(fullMarshalledChange)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
changeId, err := cid.NewCIDFromBytes(fullMarshalledChange)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rawChange := &aclpb.RawChange{
|
||||
Payload: fullMarshalledChange,
|
||||
Signature: signature,
|
||||
Id: changeId,
|
||||
}
|
||||
header, treeId, err := createTreeHeaderAndId(rawChange, aclpb.Header_DocTree, aclList.ID())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return create(storage.TreeStorageCreatePayload{
|
||||
TreeId: treeId,
|
||||
Header: header,
|
||||
Changes: []*aclpb.RawChange{rawChange},
|
||||
Heads: []string{rawChange.Id},
|
||||
})
|
||||
}
|
||||
|
||||
func createTreeHeaderAndId(change *aclpb.RawChange, treeType aclpb.HeaderDocType, aclListId string) (header *aclpb.Header, treeId string, err error) {
|
||||
header = &aclpb.Header{
|
||||
FirstId: change.Id,
|
||||
DocType: treeType,
|
||||
AclListId: aclListId,
|
||||
}
|
||||
marshalledHeader, err := proto.Marshal(header)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
treeId, err = cid.NewCIDFromBytes(marshalledHeader)
|
||||
return
|
||||
}
|
||||
55
pkg/acl/treechangeproto/protos/treechange.proto
Normal file
55
pkg/acl/treechangeproto/protos/treechange.proto
Normal file
@ -0,0 +1,55 @@
|
||||
syntax = "proto3";
|
||||
package treechange;
|
||||
option go_package = "pkg/acl/treechangeproto";
|
||||
|
||||
// RootChange is a root of a tree
|
||||
message RootChange {
|
||||
// AclHeadId is a cid of latest acl record at the time of tree creation
|
||||
string aclHeadId = 1;
|
||||
// SpaceId is an id of space where the document is placed
|
||||
string spaceId = 2;
|
||||
// ChangeType is a type of tree which this RootChange is a root of
|
||||
string changeType = 3;
|
||||
// Timestamp is this change creation timestamp
|
||||
int64 timestamp = 4;
|
||||
// Seed is a random bytes to make root change unique
|
||||
bytes seed = 5;
|
||||
// Identity is a public key of the tree's creator
|
||||
bytes identity = 6;
|
||||
}
|
||||
|
||||
// TreeChange is a change of a tree
|
||||
message TreeChange {
|
||||
// TreeHeadIds are previous ids for this TreeChange
|
||||
repeated string treeHeadIds = 1;
|
||||
// AclHeadId is a cid of latest acl record at the time of this change
|
||||
string aclHeadId = 2;
|
||||
// SnapshotBaseId is a snapshot (root) of the tree where this change is added
|
||||
string snapshotBaseId = 3;
|
||||
// ChangesData is an arbitrary payload to be read by the client
|
||||
bytes changesData = 4;
|
||||
// CurrentReadKeyHash is the hash of the read key which is used to encrypt this change
|
||||
uint64 currentReadKeyHash = 5;
|
||||
// Timestamp is this change creation timestamp
|
||||
int64 timestamp = 6;
|
||||
// Identity is a public key with which the raw payload of this change is signed
|
||||
bytes identity = 7;
|
||||
// IsSnapshot indicates whether this change contains a snapshot of state
|
||||
bool isSnapshot = 8;
|
||||
}
|
||||
|
||||
// RawTreeChange is a marshalled TreeChange (or RootChange) payload and a signature of this payload
|
||||
message RawTreeChange {
|
||||
// Payload is a byte payload containing TreeChange
|
||||
bytes payload = 1;
|
||||
// Signature is a signature made by identity indicated in the TreeChange payload
|
||||
bytes signature = 2;
|
||||
}
|
||||
|
||||
// RawTreeChangeWithId is a marshalled RawTreeChange with CID
|
||||
message RawTreeChangeWithId {
|
||||
// RawChange is a byte payload of RawTreeChange
|
||||
bytes rawChange = 1;
|
||||
// Id is a cid made from rawChange payload
|
||||
string id = 2;
|
||||
}
|
||||
1537
pkg/acl/treechangeproto/treechange.pb.go
Normal file
1537
pkg/acl/treechangeproto/treechange.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -27,9 +27,9 @@ type LoadFunc func(ctx context.Context, id string) (value Object, err error)
|
||||
|
||||
type Option func(*oCache)
|
||||
|
||||
var WithLogServiceName = func(name string) Option {
|
||||
var WithLogger = func(l *zap.SugaredLogger) Option {
|
||||
return func(cache *oCache) {
|
||||
cache.log = cache.log.With("service_name", name)
|
||||
cache.log = l
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,6 +45,12 @@ var WithGCPeriod = func(gcPeriod time.Duration) Option {
|
||||
}
|
||||
}
|
||||
|
||||
var WithRefCounter = func(enable bool) Option {
|
||||
return func(cache *oCache) {
|
||||
cache.noRefCounter = !enable
|
||||
}
|
||||
}
|
||||
|
||||
func New(loadFunc LoadFunc, opts ...Option) OCache {
|
||||
c := &oCache{
|
||||
data: make(map[string]*entry),
|
||||
@ -69,10 +75,13 @@ type Object interface {
|
||||
}
|
||||
|
||||
type ObjectLocker interface {
|
||||
Object
|
||||
Locked() bool
|
||||
}
|
||||
|
||||
type ObjectLastUsage interface {
|
||||
LastUsage() time.Time
|
||||
}
|
||||
|
||||
type entry struct {
|
||||
id string
|
||||
lastUsage time.Time
|
||||
@ -99,7 +108,7 @@ type OCache interface {
|
||||
// When 'loadFunc' returns a non-nil error, an object will not be stored to cache
|
||||
Get(ctx context.Context, id string) (value Object, err error)
|
||||
// Pick returns value if it's presents in cache (will not call loadFunc)
|
||||
Pick(id string) (value Object, err error)
|
||||
Pick(ctx context.Context, id string) (value Object, err error)
|
||||
// Add adds new object to cache
|
||||
// Returns error when object exists
|
||||
Add(id string, value Object) (err error)
|
||||
@ -121,15 +130,16 @@ type OCache interface {
|
||||
}
|
||||
|
||||
type oCache struct {
|
||||
mu sync.Mutex
|
||||
data map[string]*entry
|
||||
loadFunc LoadFunc
|
||||
timeNow func() time.Time
|
||||
ttl time.Duration
|
||||
gc time.Duration
|
||||
closed bool
|
||||
closeCh chan struct{}
|
||||
log *zap.SugaredLogger
|
||||
mu sync.Mutex
|
||||
data map[string]*entry
|
||||
loadFunc LoadFunc
|
||||
timeNow func() time.Time
|
||||
ttl time.Duration
|
||||
gc time.Duration
|
||||
closed bool
|
||||
closeCh chan struct{}
|
||||
log *zap.SugaredLogger
|
||||
noRefCounter bool
|
||||
}
|
||||
|
||||
func (c *oCache) Get(ctx context.Context, id string) (value Object, err error) {
|
||||
@ -152,7 +162,9 @@ func (c *oCache) Get(ctx context.Context, id string) (value Object, err error) {
|
||||
c.data[id] = e
|
||||
}
|
||||
e.lastUsage = c.timeNow()
|
||||
e.refCount++
|
||||
if !c.noRefCounter {
|
||||
e.refCount++
|
||||
}
|
||||
c.mu.Unlock()
|
||||
|
||||
if load {
|
||||
@ -166,13 +178,18 @@ func (c *oCache) Get(ctx context.Context, id string) (value Object, err error) {
|
||||
return e.value, e.loadErr
|
||||
}
|
||||
|
||||
func (c *oCache) Pick(id string) (value Object, err error) {
|
||||
func (c *oCache) Pick(ctx context.Context, id string) (value Object, err error) {
|
||||
c.mu.Lock()
|
||||
val, ok := c.data[id]
|
||||
c.mu.Unlock()
|
||||
if !ok {
|
||||
return nil, ErrNotExists
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-val.load:
|
||||
}
|
||||
<-val.load
|
||||
return val.value, val.loadErr
|
||||
}
|
||||
@ -198,7 +215,7 @@ func (c *oCache) Release(id string) bool {
|
||||
return false
|
||||
}
|
||||
if e, ok := c.data[id]; ok {
|
||||
if e.refCount > 0 {
|
||||
if !c.noRefCounter && e.refCount > 0 {
|
||||
e.refCount--
|
||||
return true
|
||||
}
|
||||
@ -307,7 +324,11 @@ func (c *oCache) GC() {
|
||||
deadline := c.timeNow().Add(-c.ttl)
|
||||
var toClose []*entry
|
||||
for k, e := range c.data {
|
||||
if !e.locked() && e.refCount <= 0 && e.lastUsage.Before(deadline) {
|
||||
lu := e.lastUsage
|
||||
if lug, ok := e.value.(ObjectLastUsage); ok {
|
||||
lu = lug.LastUsage()
|
||||
}
|
||||
if !e.locked() && e.refCount <= 0 && lu.Before(deadline) {
|
||||
delete(c.data, k)
|
||||
toClose = append(toClose, e)
|
||||
}
|
||||
|
||||
@ -1,73 +0,0 @@
|
||||
package account
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/config"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||
)
|
||||
|
||||
const CName = "account"
|
||||
|
||||
type Service interface {
|
||||
Account() *account.AccountData
|
||||
}
|
||||
|
||||
type service struct {
|
||||
accountData *account.AccountData
|
||||
peerId string
|
||||
}
|
||||
|
||||
func (s *service) Account() *account.AccountData {
|
||||
return s.accountData
|
||||
}
|
||||
|
||||
type StaticAccount struct {
|
||||
SigningKey string `yaml:"signingKey"`
|
||||
EncryptionKey string `yaml:"encryptionKey"`
|
||||
}
|
||||
|
||||
func New() app.Component {
|
||||
return &service{}
|
||||
}
|
||||
|
||||
func (s *service) Init(ctx context.Context, a *app.App) (err error) {
|
||||
cfg := a.MustComponent(config.CName).(*config.Config)
|
||||
|
||||
// decoding our keys
|
||||
privateEncryptionDecoder := encryptionkey.NewRSAPrivKeyDecoder()
|
||||
privateSigningDecoder := signingkey.NewEDPrivKeyDecoder()
|
||||
publicSigningDecoder := signingkey.NewEDPubKeyDecoder()
|
||||
acc := cfg.Account
|
||||
|
||||
decodedEncryptionKey, err := privateEncryptionDecoder.DecodeFromString(acc.EncryptionKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
decodedSigningKey, err := privateSigningDecoder.DecodeFromString(acc.SigningKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signKey := decodedSigningKey.(signingkey.PrivKey)
|
||||
identity, err := publicSigningDecoder.EncodeToString(signKey.GetPublic())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: using acl lib format basically, but we should simplify this
|
||||
s.accountData = &account.AccountData{
|
||||
Identity: identity,
|
||||
SignKey: signKey,
|
||||
EncKey: decodedEncryptionKey.(encryptionkey.PrivKey),
|
||||
Decoder: signingkey.NewEDPubKeyDecoder(),
|
||||
}
|
||||
s.peerId = acc.PeerId
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Name() (name string) {
|
||||
return CName
|
||||
}
|
||||
@ -1,137 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/config"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/document"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/treecache"
|
||||
"go.uber.org/zap"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const CName = "APIService"
|
||||
|
||||
var log = logger.NewNamed("api")
|
||||
|
||||
func New() app.Component {
|
||||
return &service{}
|
||||
}
|
||||
|
||||
type service struct {
|
||||
treeCache treecache.Service
|
||||
documentService document.Service
|
||||
srv *http.Server
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
func (s *service) Init(ctx context.Context, a *app.App) (err error) {
|
||||
s.treeCache = a.MustComponent(treecache.CName).(treecache.Service)
|
||||
s.documentService = a.MustComponent(document.CName).(document.Service)
|
||||
s.cfg = a.MustComponent(config.CName).(*config.Config)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Name() (name string) {
|
||||
return CName
|
||||
}
|
||||
|
||||
func (s *service) Run(ctx context.Context) (err error) {
|
||||
defer func() {
|
||||
if err == nil {
|
||||
log.With(zap.String("port", s.cfg.APIServer.Port)).Info("api server started running")
|
||||
}
|
||||
}()
|
||||
|
||||
s.srv = &http.Server{
|
||||
Addr: fmt.Sprintf(":%s", s.cfg.APIServer.Port),
|
||||
}
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/treeDump", s.treeDump)
|
||||
mux.HandleFunc("/createDocumentTree", s.createDocumentTree)
|
||||
mux.HandleFunc("/appendDocument", s.appendDocument)
|
||||
s.srv.Handler = mux
|
||||
|
||||
go s.runServer()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) runServer() {
|
||||
err := s.srv.ListenAndServe()
|
||||
if err != nil {
|
||||
log.With(zap.Error(err)).Error("could not run api server")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) Close(ctx context.Context) (err error) {
|
||||
return s.srv.Shutdown(ctx)
|
||||
}
|
||||
|
||||
func (s *service) treeDump(w http.ResponseWriter, req *http.Request) {
|
||||
var (
|
||||
query = req.URL.Query()
|
||||
treeId = query.Get("treeId")
|
||||
dump string
|
||||
err error
|
||||
)
|
||||
err = s.treeCache.Do(context.Background(), treeId, func(obj interface{}) error {
|
||||
t := obj.(tree.ObjectTree)
|
||||
dump, err = t.DebugDump()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
sendText(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
sendText(w, http.StatusOK, dump)
|
||||
}
|
||||
|
||||
func (s *service) createDocumentTree(w http.ResponseWriter, req *http.Request) {
|
||||
var (
|
||||
query = req.URL.Query()
|
||||
text = query.Get("text")
|
||||
aclListId = query.Get("aclListId")
|
||||
)
|
||||
timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
treeId, err := s.documentService.CreateDocumentTree(timeoutCtx, aclListId, text)
|
||||
cancel()
|
||||
if err != nil {
|
||||
sendText(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
sendText(w, http.StatusOK, treeId)
|
||||
}
|
||||
|
||||
func (s *service) appendDocument(w http.ResponseWriter, req *http.Request) {
|
||||
var (
|
||||
query = req.URL.Query()
|
||||
text = query.Get("text")
|
||||
treeId = query.Get("treeId")
|
||||
)
|
||||
timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
err := s.documentService.UpdateDocumentTree(timeoutCtx, treeId, text)
|
||||
cancel()
|
||||
if err != nil {
|
||||
sendText(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
sendText(w, http.StatusOK, fmt.Sprintf("updated document with id: %s with text: %s", treeId, text))
|
||||
}
|
||||
|
||||
func sendText(r http.ResponseWriter, code int, body string) {
|
||||
r.Header().Set("Content-Type", "text/plain")
|
||||
r.WriteHeader(code)
|
||||
|
||||
_, err := io.WriteString(r, fmt.Sprintf("%s\n", body))
|
||||
if err != nil {
|
||||
log.Error("writing response failed", zap.Error(err))
|
||||
}
|
||||
}
|
||||
@ -1,73 +0,0 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/config"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/net/pool"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/node"
|
||||
"github.com/anytypeio/go-chash"
|
||||
)
|
||||
|
||||
const CName = "configuration"
|
||||
|
||||
const (
|
||||
partitionCount = 3000
|
||||
replicationFactor = 3
|
||||
)
|
||||
|
||||
var log = logger.NewNamed(CName)
|
||||
|
||||
type Service interface {
|
||||
GetLast() Configuration
|
||||
GetById(id string) Configuration
|
||||
app.Component
|
||||
}
|
||||
|
||||
type service struct {
|
||||
accountId string
|
||||
pool pool.Pool
|
||||
|
||||
last Configuration
|
||||
}
|
||||
|
||||
func (s *service) Init(ctx context.Context, a *app.App) (err error) {
|
||||
conf := a.MustComponent(config.CName).(*config.Config)
|
||||
s.accountId = conf.Account.PeerId
|
||||
s.pool = a.MustComponent(pool.CName).(pool.Pool)
|
||||
configNodes := a.MustComponent(node.CName).(node.Service).Nodes()
|
||||
config := &configuration{
|
||||
id: "config",
|
||||
accountId: s.accountId,
|
||||
pool: s.pool,
|
||||
}
|
||||
if config.chash, err = chash.New(chash.Config{
|
||||
PartitionCount: partitionCount,
|
||||
ReplicationFactor: replicationFactor,
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
members := make([]chash.Member, 0, len(configNodes))
|
||||
for _, n := range configNodes {
|
||||
members = append(members, n)
|
||||
}
|
||||
if err = config.chash.AddMembers(members...); err != nil {
|
||||
return
|
||||
}
|
||||
s.last = config
|
||||
return
|
||||
}
|
||||
|
||||
func (s *service) Name() (name string) {
|
||||
return CName
|
||||
}
|
||||
|
||||
func (s *service) GetLast() Configuration {
|
||||
return s.last
|
||||
}
|
||||
|
||||
func (s *service) GetById(id string) Configuration {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
@ -1,219 +0,0 @@
|
||||
package document
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list"
|
||||
testchanges "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/testchanges/proto"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/account"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/node"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/storage"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/sync/message"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/treecache"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/syncproto"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var CName = "DocumentService"
|
||||
|
||||
var log = logger.NewNamed("documentservice")
|
||||
|
||||
type service struct {
|
||||
messageService message.Service
|
||||
treeCache treecache.Service
|
||||
account account.Service
|
||||
storage storage.Service
|
||||
// to create new documents we need to know all nodes
|
||||
nodes []*node.Node
|
||||
}
|
||||
|
||||
type Service interface {
|
||||
UpdateDocumentTree(ctx context.Context, id, text string) error
|
||||
CreateDocumentTree(ctx context.Context, aclTreeId string, text string) (id string, err error)
|
||||
}
|
||||
|
||||
func New() app.Component {
|
||||
return &service{}
|
||||
}
|
||||
|
||||
func (s *service) Init(ctx context.Context, a *app.App) (err error) {
|
||||
s.account = a.MustComponent(account.CName).(account.Service)
|
||||
s.messageService = a.MustComponent(message.CName).(message.Service)
|
||||
s.treeCache = a.MustComponent(treecache.CName).(treecache.Service)
|
||||
s.storage = a.MustComponent(storage.CName).(storage.Service)
|
||||
|
||||
nodesService := a.MustComponent(node.CName).(node.Service)
|
||||
s.nodes = nodesService.Nodes()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Name() (name string) {
|
||||
return CName
|
||||
}
|
||||
|
||||
func (s *service) Run(ctx context.Context) (err error) {
|
||||
syncData := s.storage.ImportedACLSyncData()
|
||||
|
||||
// we could have added a timeout or some additional logic,
|
||||
// but let's just use the ACL id of the latest started node :-)
|
||||
return s.messageService.SendToSpaceAsync("", syncproto.WrapACLList(
|
||||
&syncproto.SyncACLList{Records: syncData.Records},
|
||||
syncData.Header,
|
||||
syncData.Id,
|
||||
))
|
||||
}
|
||||
|
||||
func (s *service) Close(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) UpdateDocumentTree(ctx context.Context, id, text string) (err error) {
|
||||
var (
|
||||
ch *aclpb.RawChange
|
||||
header *aclpb.Header
|
||||
snapshotPath []string
|
||||
heads []string
|
||||
)
|
||||
log.With(zap.String("id", id), zap.String("text", text)).
|
||||
Debug("updating document")
|
||||
|
||||
err = s.treeCache.Do(ctx, id, func(obj interface{}) error {
|
||||
docTree, ok := obj.(tree.ObjectTree)
|
||||
if !ok {
|
||||
return fmt.Errorf("can't update acl trees with text")
|
||||
}
|
||||
|
||||
docTree.Lock()
|
||||
defer docTree.Unlock()
|
||||
err = s.treeCache.Do(ctx, docTree.Header().AclListId, func(obj interface{}) error {
|
||||
aclTree := obj.(list.ACLList)
|
||||
aclTree.RLock()
|
||||
defer aclTree.RUnlock()
|
||||
|
||||
content := createAppendTextChange(text)
|
||||
signable := tree.SignableChangeContent{
|
||||
Proto: content,
|
||||
Key: s.account.Account().SignKey,
|
||||
Identity: s.account.Account().Identity,
|
||||
IsSnapshot: false,
|
||||
}
|
||||
ch, err = docTree.AddContent(ctx, signable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id = docTree.ID()
|
||||
heads = docTree.Heads()
|
||||
header = docTree.Header()
|
||||
snapshotPath = docTree.SnapshotPath()
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.With(
|
||||
zap.String("id", id),
|
||||
zap.Strings("heads", heads),
|
||||
zap.String("header", header.String())).
|
||||
Debug("document updated in the database")
|
||||
|
||||
return s.messageService.SendToSpaceAsync("", syncproto.WrapHeadUpdate(&syncproto.SyncHeadUpdate{
|
||||
Heads: heads,
|
||||
Changes: []*aclpb.RawChange{ch},
|
||||
SnapshotPath: snapshotPath,
|
||||
}, header, id))
|
||||
}
|
||||
|
||||
func (s *service) CreateDocumentTree(ctx context.Context, aclListId string, text string) (id string, err error) {
|
||||
acc := s.account.Account()
|
||||
var (
|
||||
ch *aclpb.RawChange
|
||||
header *aclpb.Header
|
||||
snapshotPath []string
|
||||
heads []string
|
||||
)
|
||||
err = s.treeCache.Do(ctx, aclListId, func(obj interface{}) error {
|
||||
t := obj.(list.ACLList)
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
|
||||
content := createInitialTextChange(text)
|
||||
doc, err := tree.CreateNewTreeStorage(acc, t, content, s.storage.CreateTreeStorage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id, err = doc.ID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
header, err = doc.Header()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
heads = []string{header.FirstId}
|
||||
snapshotPath = []string{header.FirstId}
|
||||
ch, err = doc.GetRawChange(ctx, header.FirstId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
log.With(zap.String("id", id), zap.String("text", text)).
|
||||
Debug("creating document")
|
||||
|
||||
err = s.messageService.SendToSpaceAsync("", syncproto.WrapHeadUpdate(&syncproto.SyncHeadUpdate{
|
||||
Heads: heads,
|
||||
Changes: []*aclpb.RawChange{ch},
|
||||
SnapshotPath: snapshotPath,
|
||||
}, header, id))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return id, err
|
||||
}
|
||||
|
||||
func createInitialTextChange(text string) proto.Marshaler {
|
||||
return &testchanges.PlainTextChangeData{
|
||||
Content: []*testchanges.PlainTextChangeContent{
|
||||
createAppendTextChangeContent(text),
|
||||
},
|
||||
Snapshot: &testchanges.PlainTextChangeSnapshot{Text: text},
|
||||
}
|
||||
}
|
||||
|
||||
func createAppendTextChange(text string) proto.Marshaler {
|
||||
return &testchanges.PlainTextChangeData{
|
||||
Content: []*testchanges.PlainTextChangeContent{
|
||||
createAppendTextChangeContent(text),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createAppendTextChangeContent(text string) *testchanges.PlainTextChangeContent {
|
||||
return &testchanges.PlainTextChangeContent{
|
||||
Value: &testchanges.PlainTextChangeContentValueOfTextAppend{
|
||||
TextAppend: &testchanges.PlainTextChangeTextAppend{
|
||||
Text: text,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
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