Merge pull request #8 from anytypeio/space-sync

This commit is contained in:
Mikhail Rakhmanov 2022-09-29 13:33:37 +02:00 committed by GitHub
commit 58b4dc760e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
131 changed files with 14745 additions and 16638 deletions

10
.gitignore vendored
View File

@ -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/

View File

@ -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))

View File

@ -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)
}

View File

@ -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
}

View File

@ -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())
}

View File

@ -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
}

View File

@ -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
View 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
View 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)
}

View 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()
}

View 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
}

View 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
}

View 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)
}

View 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)
}

View 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
View 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()
}

View 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))
)

View 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;
}

View 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,
}
}

File diff suppressed because it is too large Load Diff

View 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{})
}

View 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)
}

View 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)
}

View 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
}

View 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
}

View 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
}

View File

@ -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
View 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
View 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()
}

View 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")
}

View 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)
}

View File

@ -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()

View File

@ -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

View File

@ -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
View 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
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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
View 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
View 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
View File

@ -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
View File

@ -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
View 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
}

View 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
}

View 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
View 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()
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -1,12 +0,0 @@
package aclchanges
import (
"github.com/gogo/protobuf/proto"
)
type Change interface {
ProtoChange() proto.Marshaler
DecryptedChangeContent() []byte
Signature() []byte
CID() string
}

View 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)
}

File diff suppressed because it is too large Load Diff

View 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;
}

View File

@ -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
}

View 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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)

View File

@ -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
}

View File

@ -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),
}
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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]
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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"`

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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)
}

View File

@ -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"),

View 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
}

View File

@ -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
}

View 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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)

View File

@ -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
}

View 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;
}

File diff suppressed because it is too large Load Diff

View File

@ -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)
}

View File

@ -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
}

View File

@ -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))
}
}

View File

@ -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")
}

View File

@ -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