Compare commits

..
Author SHA1 Message Date
Sergey Cherepanov a51f26c785 Merge branch 'nodesync' 2023-01-14 16:57:59 +03:00
Mikhail Rakhmanov af753ec392 Merge pull request #3 from anytypeio/nodesync 2023-01-14 14:51:42 +01:00
Sergey Cherepanov 8de24af4b9 write space hash on open 2023-01-13 19:51:29 +03:00
Sergey Cherepanov 790be1b02b save space hash 2023-01-13 19:13:26 +03:00
Sergey Cherepanov a093e1c9f0 StateHash method for space 2023-01-13 17:02:25 +03:00
Sergey Cherepanov 3381a84dec nodeconf Partition + ReplKey + configuration tests 2023-01-13 15:41:08 +03:00
Sergey Cherepanov 42913d3d18 shorter repl key 2023-01-13 15:40:20 +03:00
Mikhail Rakhmanov b84dfc595f Merge pull request #1 from anytypeio/middleware-compatibility 2023-01-13 13:09:39 +01:00
mcrakhman b6be4e8975 Change PutTree 2023-01-13 12:59:13 +01:00
mcrakhman 25a52f41b8 Update synctree to connect with responsible node 2023-01-13 12:55:28 +01:00
Sergey Cherepanov 18bc3314fe Merge branch 'main' into middleware-compatibility 2023-01-13 14:19:49 +03:00
mcrakhman 4c4abc9266 Update any-sync to wait sync for docs 2023-01-13 11:42:44 +01:00
Sergey Cherepanov 8cf6ad560e test account improve 2023-01-13 12:11:20 +03:00
Sergey Cherepanov 059f0ca636 update chash 2023-01-13 10:40:34 +03:00
Sergey Cherepanov 4cb79eeab9 nodeconf: expose CHash 2023-01-13 10:14:50 +03:00
Sergey Cherepanov 9f0c7f4711 Merge branch 'middleware-compatibility' of github.com:anytypeio/any-sync into middleware-compatibility 2023-01-12 13:53:51 +03:00
Sergey Cherepanov cfbae15999 net pool metrics 2023-01-12 13:53:32 +03:00
mcrakhman a7a54bcc8b Add handling for root ids 2023-01-11 19:18:04 +01:00
mcrakhman 1eed7e5050 Change object tree debug dump 2023-01-11 19:04:47 +01:00
Grigory Efimov d21dd91a77 .github/workflows/coverage.yml update actions 2023-01-11 20:32:06 +03:00
Grigory Efimov c792ff1fcb fix set-output 2023-01-11 20:25:15 +03:00
Grigory Efimov ec96ecafcc .github/workflows/coverage.yml add go-cache-paths 2023-01-11 20:20:28 +03:00
Grigory Efimov b875c504a0 .github/workflows/coverage.yml add actions/cach 2023-01-11 20:09:54 +03:00
Grigory Efimov 1bb707f0a2 refact 2023-01-11 19:59:54 +03:00
Grigory Efimov 28dd15e07e fix go version 2023-01-11 19:48:47 +03:00
Grigory Efimov 0fb8ec2b7c fix .github/workflows/coverage.yml 2023-01-11 19:43:52 +03:00
Grigory Efimov ca1ebaaf14 testbytest 2023-01-11 19:39:50 +03:00
Grigory Efimov 7f165dd6f8 testbytest 2023-01-11 19:38:44 +03:00
Grigory Efimov abe4603639 testbytest 2023-01-11 19:36:30 +03:00
Grigory Efimov da61fec0a0 testbytest 2023-01-11 18:52:12 +03:00
Grigory Efimov 39bffdab9c testbytest 2023-01-11 18:51:30 +03:00
Grigory Efimov c12bb0f081 testbytest 2023-01-11 18:37:56 +03:00
Grigory Efimov 96896d3060 testbytest 2023-01-11 18:34:28 +03:00
Grigory Efimov d158b98b35 testbytest 2023-01-11 18:33:17 +03:00
Grigory Efimov 57eb691b53 testbytest 2023-01-11 18:06:13 +03:00
Grigory Efimov fe8f8c43f5 testbytest 2023-01-11 18:04:16 +03:00
Grigory Efimov 39f79e03ce testbytest 2023-01-11 17:49:00 +03:00
Grigory Efimov a9c564777a testbytest 2023-01-11 17:17:19 +03:00
Grigory Efimov 876f94ef4e testbytest 2023-01-11 17:11:52 +03:00
Grigory Efimov 3bc23edfd3 .github/workflows/coverage.yml add test go install 2023-01-11 17:09:12 +03:00
Grigory Efimov 88cc804d01 Makefile: revert tests 2023-01-11 17:08:43 +03:00
Grigory Efimov 993403bf76 fix .github/workflows/coverage.yml 2023-01-11 16:29:21 +03:00
Grigory Efimov 35e58ae20f fix .github/workflows/coverage.yml 2023-01-11 16:25:52 +03:00
Grigory Efimov 27b6f754b5 fix .github/workflows/coverage.yml 2023-01-11 15:53:34 +03:00
Grigory Efimov 4186e30912 fix .github/workflows/coverage.yml 2023-01-11 15:43:55 +03:00
Sergey Cherepanov 9205f414cc Merge branch 'middleware-compatibility' of github.com:anytypeio/any-sync into middleware-compatibility 2023-01-11 15:39:52 +03:00
Sergey Cherepanov d986290db8 fileservice.HasCid 2023-01-11 15:39:32 +03:00
Grigory Efimov dcae5da0ec fix .github/workflows/coverage.yml 2023-01-11 15:38:53 +03:00
Grigory Efimov bcc0aa8fd6 Merge branch 'middleware-compatibility' of github.com:anytypeio/any-sync into middleware-compatibility 2023-01-11 15:34:54 +03:00
Grigory Efimov 8c36953790 fix .github/workflows/coverage.yml 2023-01-11 15:34:49 +03:00
Sergey Cherepanov 18bc40a531 Merge branch 'middleware-compatibility' of github.com:anytypeio/any-sync into middleware-compatibility 2023-01-11 15:30:52 +03:00
Sergey Cherepanov da74f45d46 update chash 2023-01-11 15:30:31 +03:00
Grigory Efimov 527d104ba7 fix GOPRIVATE 2023-01-11 15:22:43 +03:00
Grigory Efimov 7e41284cc8 add export GIT_TERMINAL_PROMPT=1 2023-01-11 15:20:13 +03:00
Grigory Efimov 071d85cd22 fix .github/workflows/coverage.yml 2023-01-11 15:18:40 +03:00
Grigory Efimov d0ac577686 coverage.yml add GOPRIVATE 2023-01-11 15:16:18 +03:00
Grigory Efimov 0dd992b7a6 fix 2023-01-11 15:06:17 +03:00
Grigory Efimov 2f95c66360 coverage.yml test 2023-01-11 15:03:48 +03:00
Grigory Efimov c7254ed8be .github/workflows/coverage.yml add git config 2023-01-11 14:53:55 +03:00
Sergey Cherepanov bb25dd0ea1 files: export DAGService 2023-01-11 14:50:20 +03:00
Sergey Cherepanov c565071dad build deps locally 2023-01-11 14:15:20 +03:00
mcrakhman b96654dd14 Fix acl derivation 2023-01-10 17:54:04 +01:00
mcrakhman 048406edfd Fix incorrect Graph method 2023-01-07 12:41:13 +01:00
Sergey Cherepanov 62c4e80c36 go vet 2023-01-07 13:35:12 +03:00
Sergey Cherepanov b3194da23c fix CI 2023-01-07 13:26:46 +03:00
mcrakhman 207e2dce52 Update libp2p ver 2023-01-07 00:11:27 +01:00
mcrakhman dfd2b0e8b9 Use account service in secureservice instead of config 2023-01-06 20:55:10 +01:00
Sergey Cherepanov 248a112236 fix CI 2023-01-06 21:44:28 +03:00
Sergey Cherepanov dbfc62eef5 crypro: DeriveRSA 2023-01-06 21:39:36 +03:00
Sergey Cherepanov b8b4f937d4 CI coverage 2023-01-06 21:38:48 +03:00
Sergey Cherepanov f79d7f8eb4 Revert "remove key.Key.Equals for more compatibility"
This reverts commit 7f5df570631033b017e2d724f7cd118445834ec9.
2023-01-05 18:24:48 +03:00
Sergey Cherepanov c88b31ca2f remove key.Key.Equals for more compatibility 2023-01-05 17:53:24 +03:00
Sergey Cherepanov 4c5cb61802 remove consensus dep 2023-01-05 15:39:08 +03:00
Sergey Cherepanov 90f0d0bb37 remove consensus dep 2023-01-05 15:39:08 +03:00
Sergey Cherepanov 71cfefe6fa make common module as any-sync repo 2023-01-05 15:34:09 +03:00
Mikhail Rakhmanov 4aa8c9ac09 Merge pull request #28 from anytypeio/middleware-compatibility 2023-01-05 13:18:21 +01:00
Sergey Cherepanov 6bb542ca09 Merge branch 'middleware-compatibility' of github.com:anytypeio/go-anytype-infrastructure-experiments into middleware-compatibility 2023-01-05 15:02:38 +03:00
Sergey Cherepanov f9c65bf8e1 go.mod: update mb/v3 2023-01-05 15:02:08 +03:00
mcrakhman 411a67c31f Change object creation logic 2023-01-05 12:45:50 +01:00
mcrakhman 02e92a0db0 Update space service to return id in case the space storage is already created 2023-01-04 15:07:56 +01:00
mcrakhman 9b59e6029d Rename ObjectTree.Iterate to IterateRoot 2023-01-04 14:39:26 +01:00
mcrakhman ac84febce9 Add easy access to unmarshalled header 2023-01-03 18:55:43 +01:00
Sergey Cherepanov e57ef72be1 Merge pull request #27 from anytypeio/files
files: rpcstore.AddAsync + lazy connect in clientmgr
2023-01-03 17:02:16 +03:00
Sergey Cherepanov 2d605ca694 files: rpcstore.AddAsync + lazy connect in clientmgr 2023-01-03 15:08:07 +03:00
mcrakhman c64f732878 Refactor space storage and add tree root 2023-01-03 00:02:33 +01:00
mcrakhman 40f8fb503a Make tree creation logic more transparent 2023-01-02 23:30:58 +01:00
mcrakhman 987bd0154f Update ocache ObjectLocker 2023-01-02 14:27:24 +01:00
mcrakhman 8680c24329 Add generic mustcomponent as in middleware 2023-01-02 13:16:49 +01:00
mcrakhman 1a2ceb6e2b Remove unnecessary context cleanup 2023-01-02 11:31:43 +01:00
mcrakhman 8d8aff2680 Remove error handling on broadcasting
In theory this can cause a non-decrease of a treesUsed var
2023-01-02 11:26:26 +01:00
mcrakhman 397252d97a Change headsync logic (ensure space closing on nodes) 2023-01-02 10:54:17 +01:00
mcrakhman 58389432d7 Change object creation to do it through cache 2023-01-01 22:05:29 +01:00
Sergey Cherepanov a55306aa27 Merge pull request #26 from anytypeio/files
files:partial upload
2022-12-29 22:36:41 +03:00
mcrakhman e44bcfedaf Remove unused function in confconnector 2022-12-29 18:27:27 +01:00
Mikhail Rakhmanov 33a2b5cc84 Merge pull request #25 from anytypeio/naming-and-refactoring 2022-12-29 15:39:07 +01:00
mcrakhman a855f02f63 Add queue test 2022-12-29 15:16:30 +01:00
mcrakhman 8eafaa266d Merge branch 'bilienko/integration-tests-start' into naming-and-refactoring
# Conflicts:
#	common/commonspace/objectsync/streampool.go
#	util/cmd/debug/commands/scripts.go
2022-12-29 13:51:41 +01:00
mcrakhman b5dc56d72c Update action queue 2022-12-29 13:47:17 +01:00
mcrakhman a103c960a4 Some streampool fixes 2022-12-28 17:52:10 +01:00
mcrakhman 98b8584efc Change solution to use queue for better performance 2022-12-27 22:37:47 +01:00
mcrakhman fa076ee83f Fix waiters in streampool 2022-12-27 19:59:27 +01:00
mcrakhman d4b90e5202 Fix streampool waiters bug 2022-12-27 19:57:01 +01:00
Sergey Cherepanov 84b21c62c5 refactor configs 2022-12-27 16:50:20 +03:00
Sergey Cherepanov caec31dd7f files:partial upload 2022-12-27 13:55:51 +03:00
Dmitry f86337ffc8 Nodes descriptions added 2022-12-27 17:57:17 +08:00
Dmitry 70163c3aaf First integrational test! 2022-12-27 17:53:14 +08:00
mcrakhman 5b98550349 Change port number for file node in deploy 2022-12-26 18:05:33 +01:00
mcrakhman 0a24886ce6 Change ID to Id 2022-12-26 18:01:18 +01:00
mcrakhman 9da4e0e7e9 Change ACL to Acl 2022-12-26 17:52:39 +01:00
mcrakhman d35d5c9437 Add comments and change cnames 2022-12-26 17:36:42 +01:00
Sergey Cherepanov ac018be8b1 Merge pull request #24 from anytypeio/files
file fixes: rpcstore context listen
2022-12-26 19:13:43 +03:00
Sergey Cherepanov 6fb720e0ea files: rpcstore context listen 2022-12-26 18:59:33 +03:00
mcrakhman e736ff313f Add generation for textchanges in test client 2022-12-25 21:20:41 +01:00
mcrakhman f696d774bb Refactor spacesyncproto 2022-12-25 21:12:17 +01:00
mcrakhman 50dcc735c7 Moving around files and refactoring of common 2022-12-25 20:27:37 +01:00
mcrakhman 3c30f72cd5 WIP refactoring syncstatus headsync 2022-12-25 15:08:26 +01:00
Sergey Cherepanov 874d5bbca0 Merge pull request #22 from anytypeio/files
Move fileserver to filenode, filenode in debugapi
2022-12-23 23:26:56 +03:00
Sergey Cherepanov acf21c7d54 move fileserver to filenode, filenode in debugapi 2022-12-23 21:23:44 +03:00
Sergey Cherepanov b7d4c27920 Merge pull request #21 from anytypeio/files
Files
2022-12-23 13:35:50 +03:00
Sergey Cherepanov 555ff804fe files: fix syncer loop, check ctx.Done 2022-12-22 14:50:20 +03:00
Sergey Cherepanov ec900d6e9d go mod fixes 2022-12-22 14:08:15 +03:00
Sergey Cherepanov 0b27fe7642 merge 2022-12-22 14:01:36 +03:00
Sergey Cherepanov ad8055635d gitignore debug.json 2022-12-22 13:46:22 +03:00
Mikhail Rakhmanov f4a27fb1d3 Merge pull request #20 from anytypeio/status-service 2022-12-21 21:34:00 +01:00
mcrakhman b0b75ac8ea Fix sync bugs (connection with different nodes on stream and diff) 2022-12-21 21:24:17 +01:00
mcrakhman 108900e88a Send head notify message on object create 2022-12-21 19:50:46 +01:00
Sergey Cherepanov 439182cdfa files: ref, proxyStore, index 2022-12-21 21:23:57 +03:00
mcrakhman 9b0aba1339 Change some parameter names 2022-12-21 10:48:32 +01:00
mcrakhman b3024740d0 Change naming, add debug mode and remove sh script 2022-12-21 00:23:09 +01:00
mcrakhman cfd2ea8ece More cmd fixes 2022-12-20 23:49:36 +01:00
mcrakhman d0adbf5026 Change command a bit, change debug command parameter 2022-12-20 23:30:32 +01:00
mcrakhman 9d6e0886f4 Add preliminary deploy script 2022-12-20 18:26:47 +01:00
mcrakhman f1d1a26b64 Few more fixes in tests 2022-12-19 18:51:31 +01:00
mcrakhman 15ccf29f0b Statusservice refactoring 2022-12-19 17:54:06 +01:00
mcrakhman ad87d5e545 Update status service 2022-12-19 16:59:43 +01:00
mcrakhman 322a8d144a Add some comments and checks 2022-12-18 13:43:05 +01:00
mcrakhman 4aa9ce1a27 Change sync status update logic 2022-12-18 13:16:37 +01:00
mcrakhman 364ec32a39 Added simple watcher and statusservice upgrades 2022-12-17 21:48:48 +01:00
mcrakhman 6444042a02 Add status service implementation 2022-12-17 17:19:19 +01:00
mcrakhman 5df4f13938 Add preliminary interfaces for syncstatus 2022-12-17 16:55:15 +01:00
Mikhail Rakhmanov 4eba413631 Merge pull request #19 from anytypeio/sync-fixes 2022-12-17 14:50:26 +01:00
mcrakhman 060c14792c Fix tests 2022-12-17 14:00:39 +01:00
mcrakhman a43d25841d Fixing tests wip 2022-12-16 23:39:00 +01:00
mcrakhman da0398c103 Fix objecttree tests 2022-12-16 21:16:30 +01:00
mcrakhman 3b320087f3 Add comments and fix streampool test 2022-12-16 21:02:34 +01:00
mcrakhman 0c85336e82 Add queue to sync client 2022-12-16 16:40:23 +01:00
mcrakhman 367bd1978d Move queue to separate object 2022-12-16 13:09:15 +01:00
mcrakhman 8a6a5a6bc1 More fixes to synctree and objecttree 2022-12-15 21:53:58 +01:00
mcrakhman c064f89988 Add queue to handle quickly updating records 2022-12-15 17:20:11 +01:00
mcrakhman c36fe14272 Removed sending under a lock in sync tree handler 2022-12-14 21:37:36 +01:00
Sergey Cherepanov 8a19ea8dcb file to client 2022-12-14 19:14:47 +03:00
Sergey Cherepanov d0a4b05706 file-node 2022-12-14 16:37:59 +03:00
mcrakhman 38e055ed12 Add connection with timeout, message limits more logs etc 2022-12-14 14:27:43 +01:00
mcrakhman 0924413056 Fix treebuilder errors 2022-12-13 13:57:23 +01:00
mcrakhman 09acec0d11 More raw loader fixes 2022-12-12 21:22:35 +01:00
Sergey Cherepanov fa8ac48c2a merge 2022-12-12 20:53:18 +03:00
Sergey Cherepanov da70f7b78c commonfile 2022-12-12 20:49:43 +03:00
mcrakhman e0c1e6f19d Fix not taking lock 2022-12-12 17:30:21 +01:00
mcrakhman b71360ec01 Change obj tree logic to include their heads on add raw changes 2022-12-12 17:17:09 +01:00
mcrakhman 3807d8e6ef Remove sugared logger 2022-12-09 22:11:22 +01:00
mcrakhman d7cce3c9fb Few fixes to syncservice and streamchecker 2022-12-09 21:53:24 +01:00
mcrakhman 5eaa3bfe44 Fix stream checker and tests 2022-12-09 19:55:57 +01:00
mcrakhman eb078d311a Add stream checker 2022-12-09 17:16:43 +01:00
Mikhail Rakhmanov 462eb1cc7a Merge pull request #18 from anytypeio/node-client-rpc 2022-12-06 23:07:46 +01:00
mcrakhman 8b703efb55 Change sync period 2022-12-06 21:02:47 +01:00
mcrakhman a08c3873b4 Add config gen to Makefile 2022-12-06 21:02:37 +01:00
mcrakhman 2ffd735e25 Add debug client to script and change commands 2022-12-06 18:15:26 +01:00
mcrakhman 761772f09a Add build helpers for local testing 2022-12-06 16:36:17 +01:00
mcrakhman de12b35216 Change tree creation logic 2022-12-06 10:20:05 +01:00
mcrakhman 6f8a21e547 More fixes to settingsdocument 2022-12-05 23:04:56 +01:00
mcrakhman 50e3d70f70 Fix incorrect args in debug 2022-12-05 20:02:26 +01:00
mcrakhman f9a064e9aa Remove duplicated code 2022-12-05 19:11:16 +01:00
mcrakhman a8507d8120 Update synctree ping logic 2022-12-05 18:51:59 +01:00
mcrakhman acf5b95646 Fix notifiable when creating sync tree 2022-12-05 17:21:14 +01:00
mcrakhman cd197dc7d7 Add scripts 2022-12-05 16:35:14 +01:00
mcrakhman 016543db40 Use cobra command line 2022-12-05 12:43:36 +01:00
mcrakhman 6ed6142129 Add two client script and fix common snapshot bug 2022-12-04 15:02:50 +01:00
mcrakhman cdfee1232e Fix syncservice loop 2022-12-03 17:54:10 +01:00
mcrakhman 25191a2443 Fix dfsprev 2022-12-03 15:59:45 +01:00
mcrakhman 339dd2b006 Add tree params and debugging 2022-12-03 15:06:43 +01:00
mcrakhman eb7a6fb9dc WIP add snapshot methods to debug api 2022-12-03 14:43:18 +01:00
mcrakhman 8b86ad2918 Check space dir existence before creating new 2022-12-03 13:23:14 +01:00
mcrakhman 3d624adc09 WIP node api 2022-12-03 13:09:15 +01:00
mcrakhman 4062a76e21 WIP node api 2022-12-03 12:44:02 +01:00
mcrakhman 9ae5710854 Simplify space locked solution 2022-12-03 11:48:18 +01:00
mcrakhman 494b7552ce Stopped space from unloading if trees are still there 2022-12-02 19:45:43 +01:00
mcrakhman aaecd2ae6d Move notifiable logic to synctree instead of syncclient 2022-12-02 14:14:02 +01:00
mcrakhman 1e4a25c15d Add dump heads and new head when adding text 2022-12-02 12:18:38 +01:00
mcrakhman ea9a51ec02 Add load space methods and fix storage errors 2022-12-02 09:52:04 +01:00
mcrakhman 4593637e23 Add scripts 2022-12-01 22:08:01 +01:00
mcrakhman bdae3a76d0 Redirect logs and add more commands 2022-12-01 21:28:36 +01:00
mcrakhman a370efda87 WIP debug client 2022-12-01 16:30:41 +01:00
mcrakhman bab107ebf6 WIP debug client 2022-12-01 11:15:17 +01:00
mcrakhman ea6dd0dc99 Client api wip 2022-11-30 23:42:26 +01:00
mcrakhman 20b9a36604 Add api grpc server to client 2022-11-30 22:50:43 +01:00
mcrakhman b3b50aa8ab Abstract drpc server and add client drpc api 2022-11-30 22:09:18 +01:00
Mikhail Rakhmanov f749e967f1 Merge pull request #17 from anytypeio/object-delete 2022-11-30 18:59:11 +01:00
mcrakhman 75b5193dc4 Add idprovider tests 2022-11-29 20:54:44 +01:00
mcrakhman 6bc8124413 Add deleter tests 2022-11-29 19:45:51 +01:00
mcrakhman 06d68e25ac Add deletionstate tests 2022-11-29 19:37:18 +01:00
mcrakhman 19bc9a0c93 Add settingsdocument tests 2022-11-29 18:53:04 +01:00
mcrakhman 4a3f20b236 Tests and refactoring 2022-11-29 16:54:04 +01:00
mcrakhman 82400407b9 Improve interfaces for synctree, generate mocks 2022-11-28 16:54:31 +01:00
mcrakhman d49384b98b Changes for tests and database fixes 2022-11-28 16:15:25 +01:00
mcrakhman 3c1072db4a Update diffsyncer 2022-11-25 15:24:25 +01:00
mcrakhman 41fde6f1ad Fix diffservice tests 2022-11-25 15:09:44 +01:00
mcrakhman 620731b464 Re-generate mocks and fix sync tree tests 2022-11-25 14:37:00 +01:00
mcrakhman 558cecea76 Fix more bugs 2022-11-25 11:40:36 +01:00
mcrakhman ff81bf1fac Add sync listener nil check 2022-11-25 09:46:01 +01:00
mcrakhman 71717a5dbb Fix settings document 2022-11-24 22:41:34 +01:00
mcrakhman 9155a3cbf2 Fix tests 2022-11-24 22:20:01 +01:00
mcrakhman f98d629a78 Add deletion test 2022-11-24 21:03:43 +01:00
mcrakhman 55e04cd2cd Change push space and some tree deletion logic 2022-11-24 20:24:59 +01:00
mcrakhman f6d8183001 Simplify deletion pipeline and change other stuff 2022-11-24 01:17:56 +01:00
mcrakhman b9f4ddd480 Add remove notifial 2022-11-13 14:16:58 +01:00
mcrakhman 0d9360490d Change deletion logic 2022-11-13 09:35:47 +01:00
mcrakhman 8854bbbe95 Fix some document deletion bugs 2022-11-12 22:55:12 +01:00
mcrakhman b617cdc690 Add sync loop to settings document 2022-11-12 18:17:01 +01:00
mcrakhman 62d26fbafd WIP more deletion checks 2022-11-12 17:49:25 +01:00
mcrakhman 6c38474c40 Diff service notify logic 2022-11-12 16:24:06 +01:00
mcrakhman fed41ba3aa Add non-encrypted changes and settings document logic 2022-11-12 13:36:30 +01:00
Sergey Cherepanov 181d74a139 ipfs store + cache store impl 2022-11-12 15:22:33 +03:00
mcrakhman 72d83ca171 WIP document deletion 2022-11-11 12:37:45 +01:00
mcrakhman 020b1f1d71 Change pullspace and pushspace methods and handlers 2022-11-10 20:10:57 +01:00
mcrakhman 11e57a43f7 Create space with settings 2022-11-10 19:44:33 +01:00
Sergey Cherepanov f9647c0691 merge 2022-11-08 14:46:43 +03:00
Mikhail Rakhmanov 92bdf7d57b Merge pull request #16 from anytypeio/acl-invite 2022-11-07 15:56:05 +01:00
mcrakhman d5ef4f9313 Add client space 2022-11-07 15:53:27 +01:00
mcrakhman 925005a9de Fix tests and change peer id context logic 2022-11-07 13:36:10 +01:00
mcrakhman 354ee3b6c7 Merge remote-tracking branch 'origin/acl-sync' into acl-invite
# Conflicts:
#	common/commonspace/service.go
#	common/pkg/acl/aclrecordproto/aclrecord.pb.go
#	common/pkg/acl/aclrecordproto/protos/aclrecord.proto
#	common/pkg/acl/list/list.go
#	common/pkg/acl/storage/inmemory.go
#	common/pkg/acl/testutils/acllistbuilder/liststoragebuilder.go
#	node/nodespace/rpchandler.go
#	node/nodespace/service.go
2022-11-07 12:36:48 +01:00
Sergey Cherepanov 9f080b4533 acl sync handler [wip] 2022-11-07 11:22:48 +03:00
mcrakhman 16b7181ebc Fix bugs in syncer etc, add peerkey for clients 2022-11-06 22:43:28 +01:00
mcrakhman 7c1d449743 Add load space command 2022-11-06 18:36:25 +01:00
mcrakhman 9bbb1a02e5 Add pull space method to proto and provide get space 2022-11-06 14:25:27 +01:00
Sergey Cherepanov 37035bfacd node acl service 2022-11-02 13:54:57 +03:00
Sergey Cherepanov 434f149e04 merge 2022-10-31 11:33:42 +03:00
Sergey Cherepanov 25f30cacc2 acl service wip 2022-10-31 11:25:58 +03:00
mcrakhman 8f862fd1a1 Fix building list 2022-10-28 13:24:05 +02:00
mcrakhman 5904815908 Add test for user join build and fix bugs 2022-10-28 13:01:20 +02:00
mcrakhman 36b80c25d4 Add raw record to list 2022-10-28 12:04:23 +02:00
mcrakhman 994ffe3d5f Change lisstoragebuilder to use inmemorystorage 2022-10-28 11:26:39 +02:00
mcrakhman 08b8ecba32 Change invite logic 2022-10-27 17:09:07 +02:00
Mikhail Rakhmanov 675885d875 Merge pull request #15 from anytypeio/streampool-test 2022-10-27 16:58:01 +02:00
mcrakhman f0b8e5ab64 Further test refactoring 2022-10-27 16:57:01 +02:00
mcrakhman 9a202207b4 Refactor tests 2022-10-27 16:43:10 +02:00
mcrakhman f4027b433d Fix mocks and formatting 2022-10-26 22:05:55 +02:00
mcrakhman ab4fee8229 Update tests 2022-10-26 22:03:02 +02:00
mcrakhman 1920a190bc Add space storage test 2022-10-26 21:48:59 +02:00
mcrakhman b9db1881ca Add badger list storage and tree storage test 2022-10-26 21:26:36 +02:00
mcrakhman 9c1c1e609d Finish pogreb tests and fix some issues 2022-10-26 21:00:15 +02:00
Sergey Cherepanov 7a2d366bdd node acl service (wip), fixes 2022-10-26 17:02:27 +03:00
mcrakhman 6497e461bd Add list storage 2022-10-26 14:18:44 +02:00
mcrakhman 6a6b750b4e Add more tree tests 2022-10-26 13:27:57 +02:00
mcrakhman 7b5c51a198 Change tree methods and start adding tree tests 2022-10-26 12:55:47 +02:00
mcrakhman 600ae38c0f Fix ocache closing 2022-10-25 12:16:04 +02:00
mcrakhman f5b9275192 Test stream pool 2022-10-24 21:57:19 +02:00
Sergey Cherepanov c33e944ec4 Merge branch 'main' of github.com:anytypeio/go-anytype-infrastructure-experiments into acl-sync 2022-10-24 16:16:47 +03:00
Mikhail Rakhmanov 8263bbfde2 Merge pull request #14 from anytypeio/add-client-functionality 2022-10-24 14:46:48 +02:00
mcrakhman 305cd2d795 Remove lastusage from syncclient 2022-10-24 14:46:24 +02:00
mcrakhman 93bf6d4dc4 Remove fleet artefacts 2022-10-24 14:44:15 +02:00
mcrakhman 3546fabdfa Merge branch 'main' into add-client-functionality
# Conflicts:
#	common/commonspace/syncservice/streampool.go
#	consensus/stream/service.go
2022-10-24 14:40:44 +02:00
mcrakhman f507e4cabd Better logs in space storage 2022-10-24 12:09:59 +02:00
Sergey Cherepanov 9992b1fa7e remove debug 2022-10-24 13:05:51 +03:00
mcrakhman e419e59f1d Change space payload 2022-10-24 11:59:37 +02:00
Sergey Cherepanov a3d5a56077 Merge pull request #13 from anytypeio/acl-sync
Acl sync
2022-10-24 12:21:20 +03:00
mcrakhman 7bd593f0e5 Stream pool and synctree fixes 2022-10-24 10:31:29 +02:00
mcrakhman 63d353a078 Add tests for sync handler 2022-10-23 22:24:13 +02:00
mcrakhman bf7a8b0854 Test sync tree 2022-10-23 18:19:15 +02:00
mcrakhman 09f28cf829 Refactor synctree 2022-10-22 16:19:47 +02:00
mcrakhman 58f0cf2916 Change proto and generalise logic 2022-10-22 16:05:06 +02:00
mcrakhman d6a45cced0 Move concrete logic to concrete packages 2022-10-22 14:23:55 +02:00
mcrakhman 8cc44c4a79 WIP sync refactoring 2022-10-22 13:04:58 +02:00
mcrakhman 5b93c78ebd Add timeout when waiting in streampool 2022-10-21 23:50:14 +02:00
mcrakhman 2085e3ae40 Don't broadcast when no content added 2022-10-21 21:32:37 +02:00
mcrakhman 3886436841 Storage close and diff fixes and logs 2022-10-21 20:34:37 +02:00
mcrakhman 40ce73db68 Ocache fix tests and return deadline back 2022-10-21 11:21:20 +02:00
mcrakhman 27b3213bac Fix ranges deadlock 2022-10-21 11:00:38 +02:00
mcrakhman 829cfb3054 Fix bug with nested read locks in diff 2022-10-20 21:48:31 +02:00
mcrakhman 3a3d21cd01 Raw loader and key fixes 2022-10-20 20:12:00 +02:00
mcrakhman aa47d8ecf8 Fix listener sync tree and diff iter bug 2022-10-20 19:39:45 +02:00
Sergey Cherepanov e8353897eb fix tests + remove consensus testclient 2022-10-20 20:20:03 +03:00
Sergey Cherepanov 58f9e623a4 reconnection for consensus client + fixes 2022-10-20 20:14:27 +03:00
mcrakhman d871c9b061 Fix add text bug 2022-10-20 19:08:43 +02:00
mcrakhman 4da28bcd86 Fix bug with head update not being sent on first receive 2022-10-20 18:56:09 +02:00
mcrakhman f21f05585c Fix bugs with storage 2022-10-20 16:02:16 +02:00
Sergey Cherepanov a606e5f79e consensus stream watch error 2022-10-20 14:04:59 +03:00
Sergey Cherepanov 15bf4c7018 WIP: acl service 2022-10-20 14:04:26 +03:00
mcrakhman efa3acc368 Create waiters map 2022-10-19 22:57:34 +02:00
mcrakhman 64e0ba3b77 Fix ocache and streampool bugs, fix push space 2022-10-19 22:21:44 +02:00
mcrakhman c9ded8956c Fix bugs appearing while creating tree 2022-10-19 20:21:58 +02:00
mcrakhman 6cb6b91bd4 Document creation fixes 2022-10-19 17:59:12 +02:00
mcrakhman 6470454221 Fix client imports 2022-10-19 15:07:29 +02:00
mcrakhman a3814f644c Add client configs to nodegen 2022-10-19 14:09:04 +02:00
mcrakhman a89d6dc468 Add API service 2022-10-19 13:38:20 +02:00
mcrakhman ad8f90d12e Add controller which calls different api methods 2022-10-19 12:48:55 +02:00
mcrakhman 95cb91ffad Add space and tree close logic and fix some related tests 2022-10-19 12:20:24 +02:00
mcrakhman 04fd072ae9 Add client cmd 2022-10-18 15:16:06 +02:00
mcrakhman 6201951e3b Add space and document creation logic for client 2022-10-18 14:17:49 +02:00
mcrakhman 9f1735110c Revert "Push main go remove replace"
This reverts commit 440d63ade0.
2022-10-18 11:17:57 +02:00
mcrakhman 440d63ade0 Push main go remove replace 2022-10-18 11:17:15 +02:00
Sergey Cherepanov 09e3eb3e54 go mod fixes 2022-10-17 18:11:00 +03:00
Sergey Cherepanov 22074a5fbd mod replace 2022-10-17 17:05:42 +03:00
Sergey Cherepanov 5a17320c44 go workspaces 2022-10-17 15:31:09 +03:00
Sergey Cherepanov 7548082355 move pkgs 2022-10-17 14:11:55 +03:00
Mikhail Rakhmanov 36181b0ef8 Merge pull request #12 from anytypeio/add-node-db 2022-10-17 12:55:59 +02:00
mcrakhman d478d973fd Merge branch 'main' into add-node-db
# Conflicts:
#	config/config.go
#	etc/configs/node1.yml
#	go.mod
#	go.sum
#	pkg/ocache/ocache.go
2022-10-17 12:52:12 +02:00
mcrakhman d80986e6d0 Change space service methods and change list storage 2022-10-17 12:40:55 +02:00
Sergey Cherepanov f929bf280f Merge pull request #10 from anytypeio/metrics
Prometheus metrics
2022-10-17 12:19:18 +03:00
mcrakhman 0013f64c61 Add preliminary API and fix space service methods 2022-10-16 17:59:16 +02:00
mcrakhman a2d2c61f1e Further wip badger 2022-10-16 16:41:01 +02:00
mcrakhman 700e04cc14 Add transaction benchmarks for badger 2022-10-16 10:13:17 +02:00
mcrakhman baa275b05b Client badger WIP 2022-10-15 21:43:01 +02:00
mcrakhman e6d6cae35c Change tree keys logic 2022-10-15 17:09:25 +02:00
mcrakhman 3facea9a06 Change pogreb implementation 2022-10-15 16:26:03 +02:00
mcrakhman a1aacbffed Fix keys and remove close waiter 2022-10-14 13:32:40 +02:00
mcrakhman 401402021a Add tests for ocache 2022-10-14 12:37:45 +02:00
Sergey Cherepanov e7602a164a prometheus handle metrics 2022-10-13 17:00:51 +03:00
mcrakhman 980357509d Change ocache to include wait for closing 2022-10-13 13:27:18 +02:00
mcrakhman ad021e5758 Add close waiter and close logic for db 2022-10-12 21:36:16 +02:00
Sergey Cherepanov 5e2a6778f0 change copaction interval 2022-10-12 15:14:19 +03:00
mcrakhman 296d9a86d0 Naming fixes 2022-10-11 23:11:17 +02:00
mcrakhman 85d83ed9ba Add checks for bench 2022-10-11 23:01:41 +02:00
mcrakhman d2b64fb272 Add wip benchmarks 2022-10-11 21:30:06 +02:00
Sergey Cherepanov 8970730ec9 fix 2022-10-11 12:36:05 +03:00
Sergey Cherepanov bc9ffc1a79 merge and fix test 2022-10-11 12:20:49 +03:00
mcrakhman 46f562e91d Change mutex logic 2022-10-11 11:15:46 +02:00
Sergey Cherepanov 3695a29b32 drpc metrics to quantile 2022-10-11 12:13:31 +03:00
mcrakhman 03eb8834c1 Wire up node 2022-10-11 10:25:29 +02:00
mcrakhman 79f5fc6908 Fix incorrect storage key 2022-10-11 09:56:51 +02:00
mcrakhman 526ddf12d5 WIP storage 2022-10-10 23:08:44 +02:00
mcrakhman d3acc71d95 Add some raw prototypes for storage 2022-10-10 20:10:47 +02:00
Mikhail Rakhmanov 3438770f30 Merge pull request #11 from anytypeio/sync-tests 2022-10-10 16:02:58 +02:00
Sergey Cherepanov 46d5d464b6 log config 2022-10-10 13:48:56 +03:00
Sergey Cherepanov 01438d630d fix 2022-10-10 11:38:25 +03:00
mcrakhman c3d8273caa SyncTree tests 2022-10-10 09:10:47 +02:00
mcrakhman 2999f2dcdd Add conf connector and change configuration logic 2022-10-09 21:54:34 +02:00
Sergey Cherepanov 1676d02d80 metric service, ocache and drpc handlers 2022-10-08 20:16:20 +03:00
Sergey Cherepanov 65e7d0a2cd Merge pull request #9 from anytypeio/consensus-node
Consensus node
2022-10-07 17:15:52 +03:00
mcrakhman afc9141a7c Merge remote-tracking branch 'origin/consensus-node' into sync-tests
# Conflicts:
#	Makefile
#	go.mod
#	go.sum
2022-10-07 16:02:43 +02:00
Sergey Cherepanov fd8ba951dc cleanup and comments 2022-10-07 15:06:54 +03:00
Sergey Cherepanov 6374ef8d1b rewrite for single stream 2022-10-07 11:04:48 +03:00
Sergey Cherepanov 9f16e7c272 pool.Dial* methods + consesus test client 2022-10-05 11:41:48 +03:00
Sergey Cherepanov b59cc28dc5 consensus client 2022-10-04 17:55:30 +03:00
Sergey Cherepanov 9930db8dc7 ignore consensus nodes in configuratrion 2022-10-04 16:40:40 +03:00
Sergey Cherepanov 9f902bfbe9 nodesgen consensus configs 2022-10-04 16:36:11 +03:00
Sergey Cherepanov 5729a32959 consensus node 2022-10-04 15:39:29 +03:00
mcrakhman 86616b6936 Add sync handler tests 2022-10-03 23:35:25 +02:00
mcrakhman d7a2a12129 Add head update tests 2022-10-02 22:40:13 +02:00
mcrakhman 274573df28 Further updates to sync logic 2022-10-02 16:11:47 +02:00
mcrakhman 20234269e9 Update sync handler 2022-10-02 11:54:34 +02:00
mcrakhman edc9a14ef5 Few tree sync fixes and additions 2022-10-01 21:04:31 +02:00
Sergey Cherepanov 7e6c72a198 consistent node db component 2022-09-30 19:16:44 +03:00
mcrakhman 48ae45550d Add diffservice tests 2022-09-30 16:55:34 +02:00
mcrakhman aaaab9deca Add periodicsync test 2022-09-30 16:12:41 +02:00
mcrakhman 53d10dc597 Add more syncer tests 2022-09-30 16:02:47 +02:00
mcrakhman 86ef6d2dd6 Move mocks to separate packages 2022-09-30 15:53:33 +02:00
mcrakhman beccba6d95 Require no error in tests 2022-09-30 14:43:11 +02:00
mcrakhman c01441460b Add push space custom matchers 2022-09-30 14:41:36 +02:00
mcrakhman 68ed132e61 Make sync test 2022-09-30 14:21:48 +02:00
mcrakhman b9c9424103 Add mock to write diffservice tests 2022-09-30 13:49:31 +02:00
mcrakhman 82eda9a092 Prepare components for testing 2022-09-30 09:28:13 +02:00
mcrakhman 8e6237e885 Separate diff service into components 2022-09-29 16:51:05 +02:00
mcrakhman e99650615d Fix circular deps etc 2022-09-29 16:22:56 +02:00
Mikhail Rakhmanov 58b4dc760e Merge pull request #8 from anytypeio/space-sync 2022-09-29 13:33:37 +02:00
mcrakhman 131f4a6968 Change cache logic 2022-09-28 20:56:11 +02:00
mcrakhman 533880e9f1 Add better error handling 2022-09-28 19:55:47 +02:00
mcrakhman 9be23b4ff7 Add push space logic and change storage interface 2022-09-28 09:18:04 +02:00
mcrakhman 53af9c6c1e Merge remote-tracking branch 'origin/rpcerr' into space-sync 2022-09-27 21:07:19 +02:00
mcrakhman 51af8c223d Add space/tree creation and derivation and push space protocol changes 2022-09-27 21:06:49 +02:00
Sergey Cherepanov dbc951a9d0 rpc errors 2022-09-27 19:43:39 +03:00
mcrakhman ac3a50a31c Gen new configs 2022-09-27 14:05:21 +02:00
mcrakhman c111257f63 Change account logic and add space objects 2022-09-27 13:31:50 +02:00
mcrakhman 67f0b82ae4 Fix keys 2022-09-26 22:26:42 +02:00
mcrakhman 0b97aaa302 Remove unneeded files 2022-09-26 22:20:08 +02:00
mcrakhman 227e7d47ab Few more fixes, remove node service 2022-09-26 22:15:40 +02:00
mcrakhman 9d73e1d5c1 Remove decoders and fix sync tree 2022-09-26 20:32:59 +02:00
mcrakhman 632d73698f More test fixes 2022-09-26 14:41:50 +02:00
mcrakhman 674e784318 Make acl list etc compile 2022-09-26 13:47:34 +02:00
mcrakhman d77d8dbdd1 Fixes to acls 2022-09-26 12:03:20 +02:00
mcrakhman 58812a568f WIP root change refactoring 2022-09-25 13:50:35 +02:00
mcrakhman e768e83507 Update gitignore to include vendor 2022-09-21 09:26:33 +02:00
mcrakhman dc69e93064 Some renamings and fixes 2022-09-18 11:23:01 +02:00
mcrakhman 832d0d5bfa Add treecache to node 2022-09-17 22:18:11 +02:00
mcrakhman 04184b1582 Remove responsibility of objecttree to notify and move to synctree 2022-09-17 21:39:17 +02:00
mcrakhman 70dabf70dd Move diff logic to separate service, simplify send sync 2022-09-17 20:54:23 +02:00
mcrakhman f420ac144c Update stream configuration and sync service 2022-09-16 11:20:43 +02:00
mcrakhman 1bc9242644 Update sync handler 2022-09-16 10:17:54 +02:00
mcrakhman eb36eed4c0 Add tree validation before saving 2022-09-16 07:47:36 +02:00
mcrakhman 91aacdd955 Add build tree sync request 2022-09-16 00:22:22 +02:00
mcrakhman a48e8f357a Update peer reading logic 2022-09-15 14:11:49 +02:00
mcrakhman 34ef43786c Update tree sync logic 2022-09-15 13:42:51 +02:00
mcrakhman fbdf820734 Add sync tree and remove head send from sync handler 2022-09-15 12:34:28 +02:00
mcrakhman aecb95bc92 Add more logic re sync service 2022-09-15 11:35:16 +02:00
mcrakhman f900cbff0b WIP sync 2022-09-14 18:54:10 +02:00
mcrakhman 206b695e62 Add sync handler 2022-09-13 22:26:51 +02:00
mcrakhman c94a0c94f3 Add .idea to gitignore 2022-09-13 16:37:15 +02:00
mcrakhman 5599277bf2 Generate drpc and some fixes 2022-09-13 15:52:47 +02:00
mcrakhman 7a62b348cd Revert "Use string instead of bytes, to simplify code"
This reverts commit a907cde399.
2022-09-13 12:26:37 +02:00
mcrakhman a907cde399 Use string instead of bytes, to simplify code 2022-09-13 10:44:02 +02:00
mcrakhman 77bbb64feb Fix pkg tests 2022-09-13 09:04:47 +02:00
mcrakhman b5aa0b135f Fix list package and some storage stuff 2022-09-12 21:25:27 +02:00
mcrakhman 9976a84d42 WIP proto refactoring 2022-09-12 21:06:20 +02:00
mcrakhman 018406d25c Merge branch 'main' into space-sync 2022-09-11 21:26:43 +02:00
mcrakhman 639cc302d2 Fix tree append logic, validtate only new changes and check unattached changes which were added at this round 2022-09-11 18:45:47 +02:00
mcrakhman 3cbdf798a2 Merge branch 'drpc-services' into space-sync
# Conflicts:
#	Makefile
#	cmd/node/node.go
#	pkg/acl/aclchanges/aclpb/aclchanges.pb.go
#	pkg/acl/acltree/aclstatebuilder.go
#	pkg/acl/acltree/change.go
#	pkg/acl/example/plaintextdocument/document.go
#	pkg/acl/example/plaintextdocument/plaintextdocstate.go
#	pkg/acl/list/aclstate.go
#	pkg/acl/list/changebuilder.go
#	pkg/acl/testutils/testchanges/proto/test.pb.go
#	pkg/acl/testutils/treestoragebuilder/treestoragebuilder.go
#	pkg/acl/testutils/treestoragebuilder/treestoragebuildergraph_nix.go
#	pkg/acl/tree/treegraph_nix.go
#	service/document/service.go
#	service/space/service.go
#	service/space/space.go
#	service/sync/requesthandler/requesthandler.go
#	service/treecache/service.go
#	syncproto/helpers.go
#	syncproto/sync.pb.go
2022-09-11 15:36:38 +02:00
mcrakhman 74ebcc5616 Few more changes and tree fixes 2022-09-09 09:45:25 +02:00
mcrakhman 0685f401f3 Change tree Rebuild checks 2022-09-09 09:01:50 +02:00
mcrakhman 235c179d60 Refactor ACLState a little bit 2022-09-08 23:20:11 +02:00
Sergey Cherepanov caa725dc20 package moving + commonaspace and nidespace services 2022-09-08 21:56:32 +03:00
Sergey Cherepanov feff4ad890 rename protos-go -> protos 2022-09-08 12:15:54 +03:00
Sergey Cherepanov bee067ad76 space service periodic sync 2022-09-08 12:05:30 +03:00
Mikhail Rakhmanov 2e47b23827 Merge pull request #7 from anytypeio/new-tree-structure 2022-09-08 08:09:00 +02:00
mcrakhman 88bf93a2cc Fix tests 2022-09-08 08:08:33 +02:00
mcrakhman 3f284889ac Make stuff compile 2022-09-08 07:50:52 +02:00
Sergey Cherepanov 698acad3bd remotediff test 2022-09-08 00:24:19 +03:00
mcrakhman 20f462e603 Add load from database tests and fixes 2022-09-07 23:23:59 +02:00
Sergey Cherepanov 11cb1e1f31 drpc space service, new pool, new peer 2022-09-08 00:00:46 +03:00
mcrakhman 5b1228e14d More tests and fixes for reading from tree 2022-09-07 22:30:29 +02:00
mcrakhman de1a5baaca Change sync logic 2022-09-07 22:01:11 +02:00
mcrakhman 3ca8814e46 WIP algorithm 2022-09-07 21:02:42 +02:00
Sergey Cherepanov 7afde45ac6 change proto generator to std gogo: fix tests 2022-09-07 19:49:54 +03:00
Sergey Cherepanov 357e889dad change proto generator to std gogo 2022-09-07 19:43:01 +03:00
mcrakhman 024f316b3a Merge branch 'main' into new-tree-structure
# Conflicts:
#	Makefile
#	cmd/node/node.go
#	syncproto/sync.pb.go
2022-09-07 16:38:18 +02:00
Mikhail Rakhmanov 2e1c2cbc6e Merge pull request #6 from anytypeio/space-diff 2022-09-07 12:38:17 +02:00
Sergey Cherepanov 88832596da WIP: files 2022-09-07 13:01:15 +03:00
mcrakhman 1375854237 Fix addcontent acl list locks 2022-09-07 11:31:16 +02:00
mcrakhman c7fdd81691 More test cases 2022-09-07 01:02:53 +02:00
mcrakhman 276d935cee Add more tests and fix bugs 2022-09-07 00:12:11 +02:00
mcrakhman 9905b2608b Add first test for object tree 2022-09-06 20:58:41 +02:00
mcrakhman 06f80f0646 Change storages and add iterate 2022-09-06 20:23:57 +02:00
mcrakhman cde907e15a Add mocks for tree 2022-09-06 19:25:06 +02:00
mcrakhman 94c95b44ec Make better dependencies for object tree 2022-09-06 18:08:24 +02:00
mcrakhman b8e3ec8b7b Rename to object tree 2022-09-05 13:08:49 +02:00
mcrakhman 9a1dc89a99 Fixing several copy issues and other stuff in doc tree 2022-09-05 12:40:41 +02:00
mcrakhman 90cee00319 Further changes to treebuilder etc 2022-09-05 11:48:50 +02:00
mcrakhman 1ad4eba17e Fix algorithms and add tests 2022-09-04 17:05:00 +02:00
mcrakhman b87c38e232 Add tree reduce 2022-09-04 16:05:38 +02:00
mcrakhman b602c08603 Change tree iterate 2022-09-04 14:34:37 +02:00
Sergey Cherepanov b18514d9fe file bench service 2022-08-31 14:49:35 +03:00
Sergey Cherepanov e2dffad962 configuration to space 2022-08-24 13:53:32 +03:00
Sergey Cherepanov 3957fa3be6 configuration from config 2022-08-24 00:40:14 +03:00
Sergey Cherepanov 535358b057 configuration service 2022-08-23 21:32:04 +03:00
mcrakhman 9f4dbf0002 Fix change validator 2022-08-23 00:21:46 +02:00
mcrakhman 10a5677241 Add ACL exchange logic 2022-08-22 23:53:14 +02:00
mcrakhman f2cedfd08e Add generated read keys 2022-08-21 17:43:22 +02:00
mcrakhman 13236d8ebc Import acl document from the start 2022-08-21 16:44:05 +02:00
mcrakhman d19f0534e4 Add test for user remove 2022-08-21 15:50:06 +02:00
mcrakhman f5d79a5469 Add first ACL list test and other refactoring 2022-08-21 15:23:47 +02:00
mcrakhman 88c58237c1 Few tree fixes 2022-08-20 23:55:18 +02:00
mcrakhman 03d8c187ed Add tree tests and benchmarks 2022-08-20 23:22:01 +02:00
mcrakhman d3ed644b9c Optimize and simplify tree algorithm 2022-08-20 16:57:32 +02:00
mcrakhman 6bfda6743e Update code to work with acl lists and new models 2022-08-20 14:28:06 +02:00
mcrakhman 8cf5acd183 Add some logic in validator 2022-08-19 22:31:13 +02:00
mcrakhman b0432487cc Add ACLList struct 2022-08-19 21:44:38 +02:00
mcrakhman e5fe1b5a92 Change treeheader to header 2022-08-19 20:35:10 +02:00
mcrakhman 1f403291cc Change list builder 2022-08-19 18:17:02 +02:00
mcrakhman edaad85990 Create ACL list and update tree package 2022-08-18 23:03:27 +02:00
mcrakhman f4aeecae84 Add rollback after adding invalid changes 2022-08-16 00:57:38 +02:00
mcrakhman 9142f28d55 Refactoring doc tree 2022-08-15 23:31:00 +02:00
Sergey Cherepanov 23deea0c37 remove example service 2022-08-15 20:06:23 +03:00
Sergey Cherepanov cc2d6194a9 diff container and mod tidy 2022-08-15 20:03:30 +03:00
mcrakhman 340528f5af Fix update document 2022-08-12 10:01:01 +02:00
mcrakhman 66067a3506 Fix bugs related to docTree and aclTree 2022-08-11 19:04:29 +02:00
mcrakhman eed3c518cd Move treeheader to upper level in sync 2022-08-11 16:31:09 +02:00
mcrakhman 88b0e8cd7e Add storage and change API to include both tree types 2022-08-11 15:54:27 +02:00
mcrakhman 028cb99586 Update sync layer to work with different trees 2022-08-11 15:33:39 +02:00
mcrakhman 37eacb5d61 Rewrite document service methods 2022-08-11 11:04:03 +02:00
mcrakhman e7956901de Start tree cache and document rewrite 2022-08-10 22:16:16 +02:00
mcrakhman 0e3d7cc8ca Add doc tree 2022-08-10 18:10:28 +02:00
mcrakhman 103c8a4aef Further work acltree, aclstate etc 2022-08-10 14:48:17 +02:00
mcrakhman b7190b6a74 Add read and write methods for treecache 2022-08-10 12:53:15 +02:00
mcrakhman ddf976e240 Add tree removal for state build 2022-08-10 12:00:22 +02:00
mcrakhman d23cc0a239 Add acltree in new package 2022-08-10 11:52:45 +02:00
mcrakhman ff5a0d6a45 Start refactoring tree package 2022-08-09 21:48:14 +02:00
mcrakhman 6a1e7d9a20 Fix sync logic, move acks after handling finished, made message service async 2022-08-06 10:22:09 +02:00
mcrakhman 7eb965977a Merge branch 'sync-prototype' 2022-08-05 21:06:13 +02:00
mcrakhman 48627c0901 Fix replyId update and add goroutine in handleMessage 2022-08-05 21:05:47 +02:00
mcrakhman fbaf96eb38 Fix sync bugs and add logs 2022-08-05 18:14:35 +02:00
mcrakhman 31517db9bf Add contexts and fix bugs 2022-08-05 17:23:43 +02:00
mcrakhman d0ecf74836 Change dialling logic 2022-08-05 16:33:37 +02:00
mcrakhman 89fb94420a Fix compile errors and add dependencies 2022-08-05 14:19:03 +02:00
mcrakhman 3e81762f27 Add message to syncproto, fix request handler 2022-08-05 13:44:23 +02:00
mcrakhman 06a362225f Merge remote-tracking branch 'origin/net' into sync-prototype
# Conflicts:
#	cmd/node/node.go
#	config/config.go
#	config/grpc.go
#	etc/config.yml
#	service/net/rpc/server/drpcserver.go
#	service/net/secure/service.go
2022-08-05 12:40:02 +02:00
Sergey Cherepanov 62c5d8e3b9 net utils wip 2022-08-05 12:07:34 +03:00
mcrakhman 4724fab97e Add indication in tree graph for non-acl changes 2022-08-03 14:47:31 +02:00
mcrakhman b7e94f2b52 Change config generation logic 2022-08-03 14:20:20 +02:00
mcrakhman ad99bbb7e8 Add logs for receiving a message, descriptive messages for api 2022-08-02 20:50:35 +02:00
mcrakhman 7a1ddf9c83 Fix server starting, add some logs 2022-08-02 20:28:33 +02:00
mcrakhman cc134da1ed Change bootstrap, remove cycles 2022-08-02 20:08:07 +02:00
mcrakhman e6be9ea84e Add http server for debugging 2022-08-02 19:28:32 +02:00
mcrakhman 718df194fe Add component registration on start 2022-08-02 17:36:37 +02:00
mcrakhman 26eb52adf1 Add nodes generation 2022-08-02 17:30:59 +02:00
mcrakhman f5ec49b375 Add document service 2022-07-21 11:54:19 +02:00
mcrakhman 9aaa80dcc1 Update tree cache for document creation 2022-07-21 11:03:39 +02:00
mcrakhman 3463b8d374 WIP sync logic prototype with server 2022-07-19 10:40:31 +02:00
mcrakhman 16b3787258 WIP stream logic 2022-07-19 01:43:21 +02:00
mcrakhman 269f907d1d Add message service to handle streams 2022-07-19 00:47:22 +02:00
mcrakhman 5f2144db0f Use account key instead of generating new 2022-07-18 22:27:27 +02:00
mcrakhman 693c159360 Merge remote-tracking branch 'origin/grpc-experiments' into sync-prototype
# Conflicts:
#	cmd/node/node.go
2022-07-18 14:45:25 +02:00
mcrakhman 7a6c42df62 Add oneof to sync proto 2022-07-18 10:45:05 +02:00
mcrakhman 9f984054b6 Create new tree logic 2022-07-17 13:34:07 +02:00
Sergey Cherepanov 1400e57cf7 merge 2022-07-17 09:54:59 +03:00
Sergey Cherepanov 7f4b18104f drpc server, libp2p tls connections 2022-07-17 09:44:16 +03:00
mcrakhman d9452509b5 WIP new document logic 2022-07-16 14:24:27 +02:00
mcrakhman d120da8000 Move logic to different components 2022-07-16 13:21:46 +02:00
mcrakhman bc5ffe1828 Add getting common snapshot logic 2022-07-16 12:24:49 +02:00
mcrakhman 5211267cd2 Provide for getting new tree 2022-07-16 00:50:08 +02:00
mcrakhman 67e0956042 Fix request handler heads update logic 2022-07-15 18:10:31 +02:00
mcrakhman 0467933ae9 Start using raw changes + wip request handler 2022-07-15 16:27:10 +02:00
Sergey Cherepanov 48af2866d2 remove drpc 2022-07-15 12:35:54 +03:00
mcrakhman d3922d812a WIP handling sync requests 2022-07-15 10:14:39 +02:00
mcrakhman 2893c4b4ad Use pb rawchanges instead of treestorage.RawChange 2022-07-15 09:39:16 +02:00
mcrakhman b8dcac03cc WIP sync proto 2022-07-14 23:52:37 +02:00
mcrakhman 5f8dc32ed6 Simplify code in decoders 2022-07-14 21:29:10 +02:00
mcrakhman 293ada96c8 Add tree cache service 2022-07-14 21:23:40 +02:00
mcrakhman 6c4c4f1e9d Add account reading from file logic 2022-07-14 21:07:34 +02:00
Sergey Cherepanov cbfaf7fd91 drpc experiment 2022-07-14 16:59:01 +03:00
mcrakhman abc6f68062 WIP pubsub and sync logic 2022-07-14 14:28:37 +02:00
Mikhail Rakhmanov 1331123fb4 Merge pull request #3 from anytypeio/improve-acl-naming-and-packages 2022-07-14 13:17:16 +02:00
mcrakhman 5f92bc3d8d Rename pb packages to something more meaningful 2022-07-14 13:16:12 +02:00
mcrakhman c8c6b63cd2 Add context to acl tree 2022-07-14 11:21:52 +02:00
Sergey Cherepanov 8552b676cb grpc server and test client implementation 2022-07-14 00:22:28 +03:00
mcrakhman fe90278b8e Rename keys 2022-07-13 22:57:12 +02:00
mcrakhman 37e6722ffb Remove go-threads 2022-07-13 22:54:47 +02:00
mcrakhman 7b27fca2ad Change decoder logic 2022-07-13 22:36:56 +02:00
mcrakhman f9919bd72c Add error in all methods of tree storage 2022-07-13 22:25:32 +02:00
mcrakhman 3a72587a9e Remove last thread ids 2022-07-13 22:15:07 +02:00
mcrakhman 71d88cdcba Change thread to tree storage and other changes with protos 2022-07-13 22:11:38 +02:00
mcrakhman d439b804fe Update keys 2022-07-13 20:52:56 +02:00
Sergey Cherepanov 84c2ee1e1c Merge pull request #2 from anytypeio/initial-structure
Initial structure
2022-07-13 21:11:10 +03:00
Sergey Cherepanov fecae2228d add pkg/ocache 2022-07-13 21:10:48 +03:00
Sergey Cherepanov 2580b7693c gitignore for bin 2022-07-13 20:20:43 +03:00
Sergey Cherepanov a62e713219 project structure proposal 2022-07-13 20:09:19 +03:00
Sergey Cherepanov 410780d051 fix go.sum 2022-07-13 19:23:16 +03:00
Sergey Cherepanov bb910e20ac inital structure: app, config, node binary 2022-07-13 19:18:43 +03:00
Sergey Cherepanov 31a735ca25 move acl tree files to acl folder 2022-07-13 19:17:50 +03:00
mcrakhman 3d069dbb1c Merge branch 'add-data-package-with-acl' 2022-07-13 13:49:44 +02:00
mcrakhman 5b3f594b2b Remove unneded plaintextdocument file 2022-07-13 09:00:10 +02:00
mcrakhman 3643719161 Improve test 2022-07-12 23:13:51 +02:00
mcrakhman d743317069 Add document tests 2022-07-12 23:07:05 +02:00
mcrakhman f274181270 Add test for thread utility 2022-07-12 21:27:27 +02:00
mcrakhman c90af47f02 Change InviteId logic and add UserAdd to change builder 2022-07-12 20:25:13 +02:00
mcrakhman 7496c79ae5 Add test for rebuild useCase 2022-07-12 17:46:08 +02:00
mcrakhman 11d3d93693 Add first update test 2022-07-12 16:57:02 +02:00
mcrakhman 277d40761b Change updates format in yaml 2022-07-12 16:33:22 +02:00
mcrakhman 27ec72cdb9 Fix header parse and add todos 2022-07-12 13:26:06 +02:00
mcrakhman e17e8e15c7 Simplify paths to yaml tests 2022-07-12 10:55:09 +02:00
mcrakhman 4b6ec6f310 Change first thread entry handling 2022-07-12 10:27:02 +02:00
mcrakhman 04c55cd602 Add synchronization to inmemory store 2022-07-12 09:01:06 +02:00
mcrakhman c242ef49a6 WIP plaintextdocument 2022-07-12 08:55:30 +02:00
mcrakhman 4502ca7bf4 WIP plain text document 2022-07-12 08:45:13 +02:00
mcrakhman 9c72f05fbd Add inmemory thread and build logic 2022-07-12 00:30:33 +02:00
mcrakhman 1fb82a6d7f Update changebuilder to include CID creation 2022-07-11 23:56:13 +02:00
mcrakhman 9242f78b88 Add thread header and make snapshot 2022-07-11 23:40:44 +02:00
mcrakhman 5b9d403b7d Add tree updater to ACL tree 2022-07-11 11:28:58 +02:00
mcrakhman cc802ebc69 WIP ACLTree tests 2022-07-10 17:33:27 +02:00
mcrakhman 0129c97c83 Change ACLTree methods to include change builder 2022-07-10 17:14:15 +02:00
mcrakhman fc01b705a5 Add logic update and create logic 2022-07-10 15:53:50 +02:00
mcrakhman 126d080c63 WIP Change interface rework 2022-07-10 11:44:02 +02:00
mcrakhman d5614eb5ee WIP ACLTree 2022-07-08 15:13:01 +02:00
mcrakhman c07b26d568 WIP ACLTree 2022-07-08 12:31:06 +02:00
mcrakhman 11fcb088e5 WIP ACLTree and moving files to packages 2022-07-08 11:33:32 +02:00
mcrakhman f889aa1b71 Continue adjusting folder structure 2022-07-08 10:52:06 +02:00
mcrakhman ee123efd7d WIP change package structure 2022-07-08 10:19:43 +02:00
mcrakhman 94f53aabe1 WIP create change continue 2022-07-07 11:32:02 +02:00
mcrakhman 9fe502057e WIP change creation logic 2022-07-07 11:11:34 +02:00
mcrakhman 17c556607a Add simple update test 2022-07-07 09:18:55 +02:00
mcrakhman a18938d0b1 Add document build test 2022-07-07 08:53:47 +02:00
mcrakhman d08fb78357 Add updated changes to thread builder 2022-07-07 08:47:11 +02:00
mcrakhman 906d2b6614 Fix some bugs 2022-07-06 23:35:43 +02:00
mcrakhman a09e7ca1cb Add new heads logic 2022-07-06 23:24:11 +02:00
mcrakhman 3ad690d624 WIP document update 2022-07-06 21:51:00 +02:00
mcrakhman db06096169 Add Build method to document 2022-07-05 18:09:45 +02:00
mcrakhman 99d67d531b Change init logic and fix tests 2022-07-05 17:21:45 +02:00
mcrakhman 4e8585bf9a WIP change init logic 2022-07-05 15:58:11 +02:00
mcrakhman 376e5ba119 Unify changes loading in change loader 2022-07-05 15:42:17 +02:00
mcrakhman 3395f35b59 WIP work on document structure 2022-07-05 11:59:01 +02:00
mcrakhman 102b33c3dc Add some comments in tests 2022-07-04 13:42:29 +02:00
mcrakhman aa4222588a Further fixes including tests 2022-07-04 13:28:16 +02:00
mcrakhman 15deb66dff Fix tree builder bugs related to dfs 2022-07-04 11:22:37 +02:00
mcrakhman 4a5da29cfe Fix first ACL test and threadbuilder 2022-07-03 18:15:33 +02:00
mcrakhman 18963546ef Add plain text document state 2022-07-03 17:51:14 +02:00
mcrakhman 3404626dd6 Fix threadbuilder and change examples 2022-07-03 17:35:21 +02:00
mcrakhman e970fa1d4d Make doc state building more abstract 2022-07-03 16:38:48 +02:00
mcrakhman e5b3b7a3c0 Some more fixes for trees and stuff 2022-07-02 22:34:50 +02:00
mcrakhman 64eb6581e3 Further refactoring of ACL logic 2022-07-01 11:39:42 +02:00
mcrakhman dc9f6f3917 Add some deps for threadbuilder 2022-07-01 10:54:47 +02:00
mcrakhman 3c2cd5d936 Rework threadbuilder for new change format 2022-06-30 18:52:20 +02:00
mcrakhman 6e98744aab Add makefile and proto generating 2022-06-29 18:30:44 +02:00
mcrakhman 4beb0d2902 Start adding ACLs 2022-06-28 15:37:09 +02:00
Mikhail Rakhmanov 9c62ec1d2c Initial commit 2022-06-28 13:37:24 +02:00
260 changed files with 8448 additions and 25436 deletions

No files matched your search

-14
View File
@@ -1,14 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
ignore:
- dependency-name: "github.com/anyproto/go-chash"
+1 -1
View File
@@ -7,7 +7,7 @@ jobs:
test:
runs-on: ubuntu-latest
env:
GOPRIVATE: github.com/anyproto
GOPRIVATE: github.com/anytypeio
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2023 Any Association
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.
+3 -8
View File
@@ -1,5 +1,5 @@
.PHONY: proto test deps
export GOPRIVATE=github.com/anyproto
export GOPRIVATE=github.com/anytypeio
export PATH:=deps:$(PATH)
proto:
@@ -7,19 +7,14 @@ proto:
@$(eval P_ACL_RECORDS_PATH_PB := commonspace/object/acl/aclrecordproto)
@$(eval P_TREE_CHANGES_PATH_PB := commonspace/object/tree/treechangeproto)
@$(eval P_CRYPTO_PATH_PB := util/crypto/cryptoproto)
@$(eval P_ACL_RECORDS := M$(P_ACL_RECORDS_PATH_PB)/protos/aclrecord.proto=github.com/anyproto/any-sync/$(P_ACL_RECORDS_PATH_PB))
@$(eval P_TREE_CHANGES := M$(P_TREE_CHANGES_PATH_PB)/protos/treechange.proto=github.com/anyproto/any-sync/$(P_TREE_CHANGES_PATH_PB))
@$(eval P_ACL_RECORDS := M$(P_ACL_RECORDS_PATH_PB)/protos/aclrecord.proto=github.com/anytypeio/any-sync/$(P_ACL_RECORDS_PATH_PB))
@$(eval P_TREE_CHANGES := M$(P_TREE_CHANGES_PATH_PB)/protos/treechange.proto=github.com/anytypeio/any-sync/$(P_TREE_CHANGES_PATH_PB))
protoc --gogofaster_out=:. $(P_ACL_RECORDS_PATH_PB)/protos/*.proto
protoc --gogofaster_out=:. $(P_TREE_CHANGES_PATH_PB)/protos/*.proto
protoc --gogofaster_out=:. $(P_CRYPTO_PATH_PB)/protos/*.proto
$(eval PKGMAP := $$(P_TREE_CHANGES),$$(P_ACL_RECORDS))
protoc --gogofaster_out=$(PKGMAP):. --go-drpc_out=protolib=github.com/gogo/protobuf:. commonspace/spacesyncproto/protos/*.proto
protoc --gogofaster_out=$(PKGMAP):. --go-drpc_out=protolib=github.com/gogo/protobuf:. commonfile/fileproto/protos/*.proto
protoc --gogofaster_out=$(PKGMAP):. --go-drpc_out=protolib=github.com/gogo/protobuf:. net/streampool/testservice/protos/*.proto
protoc --gogofaster_out=:. net/secureservice/handshake/handshakeproto/protos/*.proto
protoc --gogofaster_out=$(PKGMAP):. --go-drpc_out=protolib=github.com/gogo/protobuf:. coordinator/coordinatorproto/protos/*.proto
deps:
go mod download
+8 -7
View File
@@ -1,22 +1,23 @@
//go:generate mockgen -destination mock_accountservice/mock_accountservice.go github.com/anyproto/any-sync/accountservice Service
//go:generate mockgen -destination mock_accountservice/mock_accountservice.go github.com/anytypeio/any-sync/accountservice Service
package accountservice
import (
"github.com/anyproto/any-sync/app"
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anytypeio/any-sync/app"
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
)
const CName = "common.accountservice"
type Service interface {
app.Component
Account() *accountdata.AccountKeys
Account() *accountdata.AccountData
}
type Config struct {
PeerId string `yaml:"peerId"`
PeerKey string `yaml:"peerKey"`
SigningKey string `yaml:"signingKey"`
PeerId string `yaml:"peerId"`
PeerKey string `yaml:"peerKey"`
SigningKey string `yaml:"signingKey"`
EncryptionKey string `yaml:"encryptionKey"`
}
type ConfigGetter interface {
+3 -3
View File
@@ -1,12 +1,12 @@
package mock_accountservice
import (
"github.com/anyproto/any-sync/accountservice"
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anytypeio/any-sync/accountservice"
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
"github.com/golang/mock/gomock"
)
func NewAccountServiceWithAccount(ctrl *gomock.Controller, acc *accountdata.AccountKeys) *MockService {
func NewAccountServiceWithAccount(ctrl *gomock.Controller, acc *accountdata.AccountData) *MockService {
mock := NewMockService(ctrl)
mock.EXPECT().Name().Return(accountservice.CName).AnyTimes()
mock.EXPECT().Init(gomock.Any()).AnyTimes()
@@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/anyproto/any-sync/accountservice (interfaces: Service)
// Source: github.com/anytypeio/any-sync/accountservice (interfaces: Service)
// Package mock_accountservice is a generated GoMock package.
package mock_accountservice
@@ -7,8 +7,8 @@ package mock_accountservice
import (
reflect "reflect"
app "github.com/anyproto/any-sync/app"
accountdata "github.com/anyproto/any-sync/commonspace/object/accountdata"
app "github.com/anytypeio/any-sync/app"
accountdata "github.com/anytypeio/any-sync/commonspace/object/accountdata"
gomock "github.com/golang/mock/gomock"
)
@@ -36,10 +36,10 @@ func (m *MockService) EXPECT() *MockServiceMockRecorder {
}
// Account mocks base method.
func (m *MockService) Account() *accountdata.AccountKeys {
func (m *MockService) Account() *accountdata.AccountData {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Account")
ret0, _ := ret[0].(*accountdata.AccountKeys)
ret0, _ := ret[0].(*accountdata.AccountData)
return ret0
}
+17 -101
View File
@@ -4,11 +4,10 @@ import (
"context"
"errors"
"fmt"
"github.com/anyproto/any-sync/app/logger"
"github.com/anytypeio/any-sync/app/logger"
"go.uber.org/zap"
"os"
"runtime"
"runtime/debug"
"strings"
"sync"
"time"
@@ -16,15 +15,12 @@ import (
var (
// values of this vars will be defined while compilation
AppName, GitCommit, GitBranch, GitState, GitSummary, BuildDate string
name string
GitCommit, GitBranch, GitState, GitSummary, BuildDate string
name string
)
var (
log = logger.NewNamed("app")
StopDeadline = time.Minute
StopWarningAfter = time.Second * 10
StartWarningAfter = time.Second * 10
log = logger.NewNamed("app")
)
// Component is a minimal interface for a common app.Component
@@ -55,12 +51,10 @@ type ComponentStatable interface {
// App is the central part of the application
// It contains and manages all components
type App struct {
components []Component
mu sync.RWMutex
startStat Stat
stopStat Stat
deviceState int
anySyncVersion string
components []Component
mu sync.RWMutex
startStat StartStat
deviceState int
}
// Name returns app name
@@ -68,34 +62,23 @@ func (app *App) Name() string {
return name
}
func (app *App) AppName() string {
return AppName
}
// Version return app version
func (app *App) Version() string {
return GitSummary
}
type Stat struct {
type StartStat struct {
SpentMsPerComp map[string]int64
SpentMsTotal int64
}
// StartStat returns total time spent per comp for the last Start
func (app *App) StartStat() Stat {
// StartStat returns total time spent per comp
func (app *App) StartStat() StartStat {
app.mu.Lock()
defer app.mu.Unlock()
return app.startStat
}
// StopStat returns total time spent per comp for the last Close
func (app *App) StopStat() Stat {
app.mu.Lock()
defer app.mu.Unlock()
return app.stopStat
}
// VersionDescription return the full info about the build
func (app *App) VersionDescription() string {
return VersionDescription()
@@ -175,17 +158,7 @@ func (app *App) Start(ctx context.Context) (err error) {
app.mu.RLock()
defer app.mu.RUnlock()
app.startStat.SpentMsPerComp = make(map[string]int64)
var currentComponentStarting string
done := make(chan struct{})
go func() {
select {
case <-done:
return
case <-time.After(StartWarningAfter):
l := statLogger(app.stopStat, log).With(zap.String("in_progress", currentComponentStarting))
l.Warn("components start in progress")
}
}()
closeServices := func(idx int) {
for i := idx; i >= 0; i-- {
if serviceClose, ok := app.components[i].(ComponentRunnable); ok {
@@ -199,7 +172,7 @@ func (app *App) Start(ctx context.Context) (err error) {
for i, s := range app.components {
if err = s.Init(app); err != nil {
closeServices(i)
return fmt.Errorf("can't init service '%s': %w", s.Name(), err)
return fmt.Errorf("can't init service '%s': %v", s.Name(), err)
}
}
@@ -208,20 +181,14 @@ func (app *App) Start(ctx context.Context) (err error) {
start := time.Now()
if err = serviceRun.Run(ctx); err != nil {
closeServices(i)
return fmt.Errorf("can't run service '%s': %w", serviceRun.Name(), err)
return fmt.Errorf("can't run service '%s': %v", serviceRun.Name(), err)
}
spent := time.Since(start).Milliseconds()
app.startStat.SpentMsTotal += spent
app.startStat.SpentMsPerComp[s.Name()] = spent
}
}
close(done)
l := statLogger(app.stopStat, log)
if app.startStat.SpentMsTotal > StartWarningAfter.Milliseconds() {
l.Warn("all components started")
}
l.Debug("all components started")
log.Debug("all components started")
return
}
@@ -236,41 +203,18 @@ func stackAllGoroutines() []byte {
}
}
func statLogger(stat Stat, ctxLogger logger.CtxLogger) logger.CtxLogger {
l := ctxLogger
for k, v := range stat.SpentMsPerComp {
l = l.With(zap.Int64(k, v))
}
l = l.With(zap.Int64("total", stat.SpentMsTotal))
return l
}
// Close stops the application
// All components with ComponentRunnable implementation will be closed in the reversed order
func (app *App) Close(ctx context.Context) error {
log.Debug("close components...")
app.mu.RLock()
defer app.mu.RUnlock()
app.stopStat.SpentMsPerComp = make(map[string]int64)
var currentComponentStopping string
done := make(chan struct{})
go func() {
select {
case <-done:
return
case <-time.After(StopWarningAfter):
statLogger(app.stopStat, log).
With(zap.String("in_progress", currentComponentStopping)).
Warn("components close in progress")
}
}()
go func() {
select {
case <-done:
return
case <-time.After(StopDeadline):
case <-time.After(time.Minute):
_, _ = os.Stderr.Write([]byte("app.Close timeout\n"))
_, _ = os.Stderr.Write(stackAllGoroutines())
panic("app.Close timeout")
@@ -280,27 +224,16 @@ func (app *App) Close(ctx context.Context) error {
var errs []string
for i := len(app.components) - 1; i >= 0; i-- {
if serviceClose, ok := app.components[i].(ComponentRunnable); ok {
start := time.Now()
currentComponentStopping = app.components[i].Name()
if e := serviceClose.Close(ctx); e != nil {
errs = append(errs, fmt.Sprintf("Component '%s' close error: %v", serviceClose.Name(), e))
}
spent := time.Since(start).Milliseconds()
app.stopStat.SpentMsTotal += spent
app.stopStat.SpentMsPerComp[app.components[i].Name()] = spent
}
}
close(done)
if len(errs) > 0 {
return errors.New(strings.Join(errs, "\n"))
}
l := statLogger(app.stopStat, log)
if app.stopStat.SpentMsTotal > StopWarningAfter.Milliseconds() {
l.Warn("all components have been closed")
}
l.Debug("all components have been closed")
log.Debug("all components have been closed")
return nil
}
@@ -317,20 +250,3 @@ func (app *App) SetDeviceState(state int) {
}
}
}
var onceVersion sync.Once
func (app *App) AnySyncVersion() string {
onceVersion.Do(func() {
info, ok := debug.ReadBuildInfo()
if ok {
for _, mod := range info.Deps {
if mod.Path == "github.com/anyproto/any-sync" {
app.anySyncVersion = mod.Version
break
}
}
}
})
return app.anySyncVersion
}
+2 -25
View File
@@ -1,7 +1,7 @@
// Package ldiff provides a container of elements with fixed id and changeable content.
// Diff can calculate the difference with another diff container (you can make it remote) with minimum hops and traffic.
//
//go:generate mockgen -destination mock_ldiff/mock_ldiff.go github.com/anyproto/any-sync/app/ldiff Diff,Remote
//go:generate mockgen -destination mock_ldiff/mock_ldiff.go github.com/anytypeio/any-sync/app/ldiff Diff,Remote
package ldiff
import (
@@ -51,7 +51,7 @@ var hashersPool = &sync.Pool{
},
}
var ErrElementNotFound = errors.New("ldiff: element not found")
var ErrElementNotFound = errors.New("element not found")
// Element of data
type Element struct {
@@ -88,14 +88,10 @@ type Diff interface {
Diff(ctx context.Context, dl Remote) (newIds, changedIds, removedIds []string, err error)
// Elements retrieves all elements in the Diff
Elements() []Element
// Element returns an element by id
Element(id string) (Element, error)
// Ids retrieves ids of all elements in the Diff
Ids() []string
// Hash returns hash of all elements in the diff
Hash() string
// Len returns count of elements in the diff
Len() int
}
// Remote interface for using in the Diff
@@ -161,12 +157,6 @@ func (d *diff) Ids() (ids []string) {
return
}
func (d *diff) Len() int {
d.mu.RLock()
defer d.mu.RUnlock()
return d.sl.Len()
}
func (d *diff) Elements() (elements []Element) {
d.mu.RLock()
defer d.mu.RUnlock()
@@ -182,19 +172,6 @@ func (d *diff) Elements() (elements []Element) {
return
}
func (d *diff) Element(id string) (Element, error) {
d.mu.RLock()
defer d.mu.RUnlock()
el := d.sl.Get(&element{Element: Element{Id: id}, hash: xxhash.Sum64([]byte(id))})
if el == nil {
return Element{}, ErrElementNotFound
}
if e, ok := el.Key().(*element); ok {
return e.Element, nil
}
return Element{}, ErrElementNotFound
}
func (d *diff) Hash() string {
d.mu.RLock()
defer d.mu.RUnlock()
+5 -54
View File
@@ -3,11 +3,10 @@ package ldiff
import (
"context"
"fmt"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/mgo.v2/bson"
"math"
"sort"
"testing"
)
@@ -44,7 +43,7 @@ func TestDiff_Diff(t *testing.T) {
d2 := New(16, 16)
for i := 0; i < 1000; i++ {
id := fmt.Sprint(i)
head := uuid.NewString()
head := bson.NewObjectId().Hex()
d1.Set(Element{
Id: id,
Head: head,
@@ -92,7 +91,7 @@ func TestDiff_Diff(t *testing.T) {
for i := 0; i < 10; i++ {
d2.Set(Element{
Id: fmt.Sprint(i),
Head: uuid.NewString(),
Head: bson.NewObjectId().Hex(),
})
}
@@ -108,7 +107,7 @@ func TestDiff_Diff(t *testing.T) {
for i := 0; i < 10; i++ {
d2.Set(Element{
Id: fmt.Sprint(i),
Head: uuid.NewString(),
Head: bson.NewObjectId().Hex(),
})
}
var cancel func()
@@ -123,7 +122,7 @@ func BenchmarkDiff_Ranges(b *testing.B) {
d := New(16, 16)
for i := 0; i < 10000; i++ {
id := fmt.Sprint(i)
head := uuid.NewString()
head := bson.NewObjectId().Hex()
d.Set(Element{
Id: id,
Head: head,
@@ -149,51 +148,3 @@ func TestDiff_Hash(t *testing.T) {
assert.NotEmpty(t, h2)
assert.NotEqual(t, h1, h2)
}
func TestDiff_Element(t *testing.T) {
d := New(16, 16)
for i := 0; i < 10; i++ {
d.Set(Element{Id: fmt.Sprint("id", i), Head: fmt.Sprint("head", i)})
}
_, err := d.Element("not found")
assert.Equal(t, ErrElementNotFound, err)
el, err := d.Element("id5")
require.NoError(t, err)
assert.Equal(t, "head5", el.Head)
d.Set(Element{"id5", "otherHead"})
el, err = d.Element("id5")
require.NoError(t, err)
assert.Equal(t, "otherHead", el.Head)
}
func TestDiff_Ids(t *testing.T) {
d := New(16, 16)
var ids []string
for i := 0; i < 10; i++ {
id := fmt.Sprint("id", i)
d.Set(Element{Id: id, Head: fmt.Sprint("head", i)})
ids = append(ids, id)
}
gotIds := d.Ids()
sort.Strings(gotIds)
assert.Equal(t, ids, gotIds)
assert.Equal(t, len(ids), d.Len())
}
func TestDiff_Elements(t *testing.T) {
d := New(16, 16)
var els []Element
for i := 0; i < 10; i++ {
id := fmt.Sprint("id", i)
el := Element{Id: id, Head: fmt.Sprint("head", i)}
d.Set(el)
els = append(els, el)
}
gotEls := d.Elements()
sort.Slice(gotEls, func(i, j int) bool {
return gotEls[i].Id < gotEls[j].Id
})
assert.Equal(t, els, gotEls)
}
+2 -31
View File
@@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/anyproto/any-sync/app/ldiff (interfaces: Diff,Remote)
// Source: github.com/anytypeio/any-sync/app/ldiff (interfaces: Diff,Remote)
// Package mock_ldiff is a generated GoMock package.
package mock_ldiff
@@ -8,7 +8,7 @@ import (
context "context"
reflect "reflect"
ldiff "github.com/anyproto/any-sync/app/ldiff"
ldiff "github.com/anytypeio/any-sync/app/ldiff"
gomock "github.com/golang/mock/gomock"
)
@@ -52,21 +52,6 @@ func (mr *MockDiffMockRecorder) Diff(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Diff", reflect.TypeOf((*MockDiff)(nil).Diff), arg0, arg1)
}
// Element mocks base method.
func (m *MockDiff) Element(arg0 string) (ldiff.Element, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Element", arg0)
ret0, _ := ret[0].(ldiff.Element)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Element indicates an expected call of Element.
func (mr *MockDiffMockRecorder) Element(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Element", reflect.TypeOf((*MockDiff)(nil).Element), arg0)
}
// Elements mocks base method.
func (m *MockDiff) Elements() []ldiff.Element {
m.ctrl.T.Helper()
@@ -109,20 +94,6 @@ func (mr *MockDiffMockRecorder) Ids() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ids", reflect.TypeOf((*MockDiff)(nil).Ids))
}
// Len mocks base method.
func (m *MockDiff) Len() int {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Len")
ret0, _ := ret[0].(int)
return ret0
}
// Len indicates an expected call of Len.
func (mr *MockDiffMockRecorder) Len() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Len", reflect.TypeOf((*MockDiff)(nil).Len))
}
// Ranges mocks base method.
func (m *MockDiff) Ranges(arg0 context.Context, arg1 []ldiff.Range, arg2 []ldiff.RangeResult) ([]ldiff.RangeResult, error) {
m.ctrl.T.Helper()
+18 -109
View File
@@ -1,124 +1,33 @@
package logger
import (
"fmt"
"strings"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"github.com/anyproto/any-sync/util/slice"
)
type LogFormat int
const (
ColorizedOutput LogFormat = iota
PlaintextOutput
JSONOutput
)
type NamedLevel struct {
Name string `yaml:"name"`
Level string `yaml:"level"`
}
import "go.uber.org/zap"
type Config struct {
Production bool `yaml:"production"`
DefaultLevel string `yaml:"defaultLevel"`
Levels []NamedLevel `yaml:"levels"` // first match will be used
AddOutputPaths []string `yaml:"outputPaths"`
DisableStdErr bool `yaml:"disableStdErr"`
Format LogFormat `yaml:"format"`
ZapConfig *zap.Config `yaml:"-"` // optional, if set it will be used instead of other config options
Production bool `yaml:"production"`
DefaultLevel string `yaml:"defaultLevel"`
NamedLevels map[string]string `yaml:"namedLevels"`
}
func (l Config) ApplyGlobal() {
var conf zap.Config
if l.ZapConfig != nil {
conf = *l.ZapConfig
if l.Production {
conf = zap.NewProductionConfig()
} else {
if l.Production {
conf = zap.NewProductionConfig()
} else {
conf = zap.NewDevelopmentConfig()
}
encConfig := conf.EncoderConfig
switch l.Format {
case PlaintextOutput:
encConfig.EncodeLevel = zapcore.CapitalLevelEncoder
conf.Encoding = "console"
case JSONOutput:
encConfig.MessageKey = "msg"
encConfig.TimeKey = "ts"
encConfig.LevelKey = "level"
encConfig.NameKey = "logger"
encConfig.CallerKey = "caller"
encConfig.EncodeTime = zapcore.ISO8601TimeEncoder
conf.Encoding = "json"
default:
// default is ColorizedOutput
conf.Encoding = "console"
encConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
}
conf.EncoderConfig = encConfig
if len(l.AddOutputPaths) > 0 {
conf.OutputPaths = append(conf.OutputPaths, l.AddOutputPaths...)
}
if l.DisableStdErr {
conf.OutputPaths = slice.Filter(conf.OutputPaths, func(path string) bool {
return path != "stderr"
})
}
if defaultLevel, err := zap.ParseAtomicLevel(l.DefaultLevel); err == nil {
conf.Level = defaultLevel
conf = zap.NewDevelopmentConfig()
}
if defaultLevel, err := zap.ParseAtomicLevel(l.DefaultLevel); err == nil {
conf.Level = defaultLevel
}
var levels = make(map[string]zap.AtomicLevel)
for k, v := range l.NamedLevels {
if lev, err := zap.ParseAtomicLevel(v); err != nil {
levels[k] = lev
}
}
for _, v := range l.Levels {
if lev, err := zap.ParseAtomicLevel(v.Level); err == nil {
// we need to have a minimum level of all named loggers for the main logger
if lev.Level() < conf.Level.Level() {
conf.Level.SetLevel(lev.Level())
}
}
}
lg, err := conf.Build()
defaultLogger, err := conf.Build()
if err != nil {
Default().Fatal("can't build logger", zap.Error(err))
}
SetDefault(lg)
SetNamedLevels(l.Levels)
}
// LevelsFromStr parses a string of the form "name1=DEBUG;prefix*=WARN;*=ERROR" into a slice of NamedLevel
// it may be useful to parse the log level from the OS env var
func LevelsFromStr(s string) (levels []NamedLevel) {
for _, kv := range strings.Split(s, ";") {
strings.TrimSpace(kv)
parts := strings.Split(kv, "=")
var key, value string
if len(parts) == 1 {
key = "*"
value = parts[0]
_, err := zap.ParseAtomicLevel(value)
if err != nil {
fmt.Printf("Can't parse log level %s: %s\n", parts[0], err.Error())
continue
}
levels = append(levels, NamedLevel{Name: key, Level: value})
} else if len(parts) == 2 {
key = parts[0]
value = parts[1]
}
_, err := zap.ParseAtomicLevel(value)
if err != nil {
fmt.Printf("Can't parse log level %s: %s\n", parts[0], err.Error())
continue
}
levels = append(levels, NamedLevel{Name: key, Level: value})
}
return levels
SetDefault(defaultLogger)
SetNamedLevels(levels)
}
-60
View File
@@ -1,60 +0,0 @@
package logger
import (
"context"
"go.uber.org/zap"
)
type ctxKey uint
const (
ctxKeyFields ctxKey = iota
)
func WithCtx(ctx context.Context, l *zap.Logger) *zap.Logger {
return l.With(CtxGetFields(ctx)...)
}
func CtxWithFields(ctx context.Context, fields ...zap.Field) context.Context {
existingFields := CtxGetFields(ctx)
if existingFields != nil {
existingFields = append(existingFields, fields...)
}
return context.WithValue(ctx, ctxKeyFields, fields)
}
func CtxGetFields(ctx context.Context) (fields []zap.Field) {
if v := ctx.Value(ctxKeyFields); v != nil {
return v.([]zap.Field)
}
return
}
type CtxLogger struct {
*zap.Logger
name string
}
func (cl CtxLogger) DebugCtx(ctx context.Context, msg string, fields ...zap.Field) {
cl.Logger.Debug(msg, append(CtxGetFields(ctx), fields...)...)
}
func (cl CtxLogger) InfoCtx(ctx context.Context, msg string, fields ...zap.Field) {
cl.Logger.Info(msg, append(CtxGetFields(ctx), fields...)...)
}
func (cl CtxLogger) WarnCtx(ctx context.Context, msg string, fields ...zap.Field) {
cl.Logger.Warn(msg, append(CtxGetFields(ctx), fields...)...)
}
func (cl CtxLogger) ErrorCtx(ctx context.Context, msg string, fields ...zap.Field) {
cl.Logger.Error(msg, append(CtxGetFields(ctx), fields...)...)
}
func (cl CtxLogger) With(fields ...zap.Field) CtxLogger {
return CtxLogger{cl.Logger.With(fields...), cl.name}
}
func (cl CtxLogger) Sugar() *zap.SugaredLogger {
return NewNamedSugared(cl.name)
}
+19 -106
View File
@@ -1,137 +1,50 @@
package logger
import (
"sync"
"github.com/gobwas/glob"
"go.uber.org/zap"
"sync"
)
var (
mu sync.Mutex
logger *zap.Logger
loggerConfig zap.Config
namedLevels []namedLevel
namedGlobs = make(map[string]glob.Glob)
namedLoggers = make(map[string]CtxLogger)
namedSugarLoggers = make(map[string]*zap.SugaredLogger)
mu sync.Mutex
defaultLogger *zap.Logger
levels = make(map[string]zap.AtomicLevel)
loggers = make(map[string]*zap.Logger)
)
type namedLevel struct {
name string
level zap.AtomicLevel
}
func init() {
loggerConfig = zap.NewDevelopmentConfig()
logger, _ = loggerConfig.Build()
defaultLogger, _ = zap.NewDevelopment()
zap.NewProduction()
}
// SetDefault replaces the default logger
// you need to call SetNamedLevels after in case you have named loggers,
// otherwise they will use the old logger
func SetDefault(l *zap.Logger) {
mu.Lock()
defer mu.Unlock()
*logger = *l
*defaultLogger = *l
for name, l := range loggers {
*l = *defaultLogger.Named(name)
}
}
// SetNamedLevels sets the namedLevels for named loggers
// it also supports glob patterns for names, like "app*"
// can be racy in case there are existing named loggers
// so consider to call only once at the beginning
func SetNamedLevels(nls []NamedLevel) {
func SetNamedLevels(l map[string]zap.AtomicLevel) {
mu.Lock()
defer mu.Unlock()
namedLevels = namedLevels[:0]
var minLevel = logger.Level()
for _, nl := range nls {
l, err := zap.ParseAtomicLevel(nl.Level)
if err != nil {
continue
}
namedLevels = append(namedLevels, namedLevel{name: nl.Name, level: l})
g, err := glob.Compile(nl.Name)
if err == nil {
namedGlobs[nl.Name] = g
}
if l.Level() < minLevel {
minLevel = l.Level()
}
}
if minLevel < logger.Level() {
// recreate logger if the min level is lower than the current min one
loggerConfig.Level = zap.NewAtomicLevelAt(minLevel)
logger, _ = loggerConfig.Build()
}
for name, nl := range namedLoggers {
level := getLevel(name)
newCore := zap.New(logger.Core()).Named(name).WithOptions(
zap.IncreaseLevel(level),
)
*(nl.Logger) = *newCore
}
for name, nl := range namedSugarLoggers {
level := getLevel(name)
newCore := zap.New(logger.Core()).Named(name).WithOptions(
zap.IncreaseLevel(level),
).Sugar()
*(nl) = *newCore
}
levels = l
}
func Default() *zap.Logger {
mu.Lock()
defer mu.Unlock()
return logger
return defaultLogger
}
// getLevel returns the level for the given name
// it return the first matching name or glob pattern whatever comes first
func getLevel(name string) zap.AtomicLevel {
for _, nl := range namedLevels {
if nl.name == name {
return nl.level
}
if g, ok := namedGlobs[nl.name]; ok && g.Match(name) {
return nl.level
}
}
return zap.NewAtomicLevelAt(logger.Level())
}
func NewNamed(name string, fields ...zap.Field) CtxLogger {
func NewNamed(name string, fields ...zap.Field) *zap.Logger {
mu.Lock()
defer mu.Unlock()
if l, nameExists := namedLoggers[name]; nameExists {
return l
l := defaultLogger.Named(name)
if len(fields) > 0 {
l = l.With(fields...)
}
level := getLevel(name)
l := zap.New(logger.Core()).Named(name).WithOptions(zap.IncreaseLevel(level),
zap.Fields(fields...))
ctxL := CtxLogger{Logger: l, name: name}
namedLoggers[name] = ctxL
return ctxL
}
func NewNamedSugared(name string) *zap.SugaredLogger {
mu.Lock()
defer mu.Unlock()
if l, nameExists := namedSugarLoggers[name]; nameExists {
return l
}
level := getLevel(name)
l := zap.New(logger.Core()).Named(name).Sugar().WithOptions(zap.IncreaseLevel(level))
namedSugarLoggers[name] = l
loggers[name] = l
return l
}
-150
View File
@@ -1,150 +0,0 @@
package logger
import (
"reflect"
"testing"
"go.uber.org/zap"
)
func Test_getLevel1(t *testing.T) {
SetNamedLevels([]NamedLevel{
{Name: "app", Level: "debug"},
{Name: "app*", Level: "info"},
{Name: "app.sub", Level: "warn"},
{Name: "*", Level: "fatal"},
})
tests := []struct {
name string
want zap.AtomicLevel
}{
{
name: "app",
want: zap.NewAtomicLevelAt(zap.DebugLevel),
},
{
name: "app.aaa",
want: zap.NewAtomicLevelAt(zap.InfoLevel),
},
{
name: "app.sub",
want: zap.NewAtomicLevelAt(zap.InfoLevel),
},
{
name: "random",
want: zap.NewAtomicLevelAt(zap.FatalLevel),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := getLevel(tt.name); !reflect.DeepEqual(got, tt.want) {
t.Errorf("getLevel() = %v, want %v", got, tt.want)
}
})
}
}
func Test_getLevel2(t *testing.T) {
SetNamedLevels([]NamedLevel{
{Name: "*", Level: "ERROR"},
{Name: "app", Level: "info"},
{Name: "app.sub", Level: "warn"},
{Name: "*", Level: "fatal"},
})
tests := []struct {
name string
want zap.AtomicLevel
}{
{
name: "app",
want: zap.NewAtomicLevelAt(zap.ErrorLevel),
},
{
name: "app.aaa",
want: zap.NewAtomicLevelAt(zap.ErrorLevel),
},
{
name: "app.sub",
want: zap.NewAtomicLevelAt(zap.ErrorLevel),
},
{
name: "random",
want: zap.NewAtomicLevelAt(zap.ErrorLevel),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := getLevel(tt.name); !reflect.DeepEqual(got, tt.want) {
t.Errorf("getLevel() = %v, want %v", got, tt.want)
}
})
}
}
func Test_getLevel3(t *testing.T) {
SetNamedLevels([]NamedLevel{
{Name: "app", Level: "info"},
{Name: "*.sub", Level: "warn"},
{Name: "*", Level: "fatal"},
})
tests := []struct {
name string
want zap.AtomicLevel
}{
{
name: "app",
want: zap.NewAtomicLevelAt(zap.InfoLevel),
},
{
name: "app.sub",
want: zap.NewAtomicLevelAt(zap.WarnLevel),
},
{
name: "random",
want: zap.NewAtomicLevelAt(zap.FatalLevel),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := getLevel(tt.name); !reflect.DeepEqual(got, tt.want) {
t.Errorf("getLevel() = %v, want %v", got, tt.want)
}
})
}
}
func Test_getLevel4(t *testing.T) {
SetNamedLevels([]NamedLevel{
{Name: "*", Level: "invalid"},
{Name: "app", Level: "info"},
{Name: "b", Level: "invalid"},
})
tests := []struct {
name string
want zap.AtomicLevel
}{
{
name: "app",
want: zap.NewAtomicLevelAt(zap.InfoLevel),
},
{
name: "app.sub",
want: zap.NewAtomicLevelAt(logger.Level()),
},
{
name: "b",
want: zap.NewAtomicLevelAt(logger.Level()),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := getLevel(tt.name); !reflect.DeepEqual(got, tt.want) {
t.Errorf("getLevel() = %v, want %v", got, tt.want)
}
})
}
}
-120
View File
@@ -1,120 +0,0 @@
package ocache
import (
"context"
"go.uber.org/zap"
"sync"
"time"
)
type entryState int
const (
entryStateLoading = iota
entryStateActive
entryStateClosing
entryStateClosed
)
type entry struct {
id string
state entryState
lastUsage time.Time
load chan struct{}
loadErr error
value Object
close chan struct{}
mx sync.Mutex
}
func newEntry(id string, value Object, state entryState) *entry {
return &entry{
id: id,
load: make(chan struct{}),
lastUsage: time.Now(),
state: state,
value: value,
}
}
func (e *entry) isActive() bool {
e.mx.Lock()
defer e.mx.Unlock()
return e.state == entryStateActive
}
func (e *entry) isClosing() bool {
e.mx.Lock()
defer e.mx.Unlock()
return e.state == entryStateClosed || e.state == entryStateClosing
}
func (e *entry) waitLoad(ctx context.Context, id string) (value Object, err error) {
select {
case <-ctx.Done():
log.DebugCtx(ctx, "ctx done while waiting on object load", zap.String("id", id))
return nil, ctx.Err()
case <-e.load:
return e.value, e.loadErr
}
}
func (e *entry) waitClose(ctx context.Context, id string) (res bool, err error) {
e.mx.Lock()
switch e.state {
case entryStateClosing:
waitCh := e.close
e.mx.Unlock()
select {
case <-ctx.Done():
log.DebugCtx(ctx, "ctx done while waiting on object close", zap.String("id", id))
return false, ctx.Err()
case <-waitCh:
return true, nil
}
case entryStateClosed:
e.mx.Unlock()
return true, nil
default:
e.mx.Unlock()
return false, nil
}
}
func (e *entry) setClosing(wait bool) (prevState, curState entryState) {
e.mx.Lock()
prevState = e.state
curState = e.state
if e.state == entryStateClosing {
waitCh := e.close
e.mx.Unlock()
if !wait {
return
}
<-waitCh
e.mx.Lock()
}
if e.state != entryStateClosed {
e.state = entryStateClosing
e.close = make(chan struct{})
}
curState = e.state
e.mx.Unlock()
return
}
func (e *entry) setActive(chClose bool) {
e.mx.Lock()
defer e.mx.Unlock()
if chClose {
close(e.close)
}
e.state = entryStateActive
}
func (e *entry) setClosed() {
e.mx.Lock()
defer e.mx.Unlock()
close(e.close)
e.state = entryStateClosed
}
+1 -8
View File
@@ -1,18 +1,11 @@
package ocache
import (
"github.com/prometheus/client_golang/prometheus"
"strings"
)
import "github.com/prometheus/client_golang/prometheus"
func WithPrometheus(reg *prometheus.Registry, namespace, subsystem string) Option {
if subsystem == "" {
subsystem = "cache"
}
nameSplit := strings.Split(namespace, ".")
subSplit := strings.Split(subsystem, ".")
namespace = strings.Join(nameSplit, "_")
subsystem = strings.Join(subSplit, "_")
if reg == nil {
return nil
}
-19
View File
@@ -1,19 +0,0 @@
package ocache
import (
"context"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/require"
"strings"
"testing"
)
func TestWithPrometheus_MetricsConvertsDots(t *testing.T) {
opt := WithPrometheus(prometheus.NewRegistry(), "some.name", "some.system")
cache := New(func(ctx context.Context, id string) (value Object, err error) {
return &testObject{}, nil
}, opt).(*oCache)
_, err := cache.Get(context.Background(), "id")
require.NoError(t, err)
require.True(t, strings.Contains(cache.metrics.hit.Desc().String(), "some_name_some_system_hit"))
}
+178 -88
View File
@@ -3,7 +3,7 @@ package ocache
import (
"context"
"errors"
"github.com/anyproto/any-sync/app/logger"
"github.com/anytypeio/any-sync/app/logger"
"go.uber.org/zap"
"sync"
"time"
@@ -44,6 +44,12 @@ var WithGCPeriod = func(gcPeriod time.Duration) Option {
}
}
var WithRefCounter = func(enable bool) Option {
return func(cache *oCache) {
cache.refCounter = enable
}
}
func New(loadFunc LoadFunc, opts ...Option) OCache {
c := &oCache{
data: make(map[string]*entry),
@@ -67,7 +73,33 @@ func New(loadFunc LoadFunc, opts ...Option) OCache {
type Object interface {
Close() (err error)
TryClose(objectTTL time.Duration) (res bool, err error)
}
type ObjectLocker interface {
Object
Locked() bool
}
type ObjectLastUsage interface {
LastUsage() time.Time
}
type entry struct {
id string
lastUsage time.Time
refCount uint32
isClosing bool
load chan struct{}
loadErr error
value Object
close chan struct{}
}
func (e *entry) locked() bool {
if locker, ok := e.value.(ObjectLocker); ok {
return locker.Locked()
}
return false
}
type OCache interface {
@@ -84,8 +116,12 @@ type OCache interface {
// Add adds new object to cache
// Returns error when object exists
Add(id string, value Object) (err error)
// Release decreases the refs counter
Release(id string) bool
// Reset sets refs counter to 0
Reset(id string) bool
// Remove closes and removes object
Remove(ctx context.Context, id string) (ok bool, err error)
Remove(id string) (ok bool, err error)
// ForEach iterates over all loaded objects, breaks when callback returns false
ForEach(f func(v Object) (isContinue bool))
// GC frees not used and expired objects
@@ -98,16 +134,17 @@ 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
metrics *metrics
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
metrics *metrics
refCounter bool
}
func (c *oCache) Get(ctx context.Context, id string) (value Object, err error) {
@@ -123,36 +160,63 @@ Load:
return nil, ErrClosed
}
if e, ok = c.data[id]; !ok {
e = newEntry(id, nil, entryStateLoading)
load = true
e = &entry{
id: id,
load: make(chan struct{}),
}
c.data[id] = e
}
e.lastUsage = time.Now()
c.mu.Unlock()
reload, err := e.waitClose(ctx, id)
if err != nil {
return nil, err
closing := e.isClosing
if !e.isClosing {
e.lastUsage = c.timeNow()
if c.refCounter {
e.refCount++
}
}
if reload {
c.mu.Unlock()
if closing {
<-e.close
goto Load
}
if load {
go c.load(ctx, id, e)
}
c.metricsGet(!load)
return e.waitLoad(ctx, id)
if c.metrics != nil {
if load {
c.metrics.miss.Inc()
} else {
c.metrics.hit.Inc()
}
}
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-e.load:
}
return e.value, e.loadErr
}
func (c *oCache) Pick(ctx context.Context, id string) (value Object, err error) {
c.mu.Lock()
val, ok := c.data[id]
if !ok || val.isClosing() {
if !ok || val.isClosing {
c.mu.Unlock()
return nil, ErrNotExists
}
c.mu.Unlock()
c.metricsGet(true)
return val.waitLoad(ctx, id)
if c.metrics != nil {
c.metrics.hit.Inc()
}
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-val.load:
return val.value, val.loadErr
}
}
func (c *oCache) load(ctx context.Context, id string, e *entry) {
@@ -166,39 +230,63 @@ func (c *oCache) load(ctx context.Context, id string, e *entry) {
delete(c.data, id)
} else {
e.value = value
e.setActive(false)
}
}
func (c *oCache) Remove(ctx context.Context, id string) (ok bool, err error) {
func (c *oCache) Release(id string) bool {
c.mu.Lock()
defer c.mu.Unlock()
if c.closed {
return false
}
if e, ok := c.data[id]; ok {
if c.refCounter && e.refCount > 0 {
e.refCount--
return true
}
}
return false
}
func (c *oCache) Reset(id string) bool {
c.mu.Lock()
defer c.mu.Unlock()
if c.closed {
return false
}
if e, ok := c.data[id]; ok {
e.refCount = 0
return true
}
return false
}
func (c *oCache) Remove(id string) (ok bool, err error) {
c.mu.Lock()
if c.closed {
c.mu.Unlock()
err = ErrClosed
return
}
e, ok := c.data[id]
if !ok {
var e *entry
e, ok = c.data[id]
if !ok || e.isClosing {
c.mu.Unlock()
return false, ErrNotExists
return
}
e.isClosing = true
e.close = make(chan struct{})
c.mu.Unlock()
return c.remove(ctx, e)
}
func (c *oCache) remove(ctx context.Context, e *entry) (ok bool, err error) {
if _, err = e.waitLoad(ctx, e.id); err != nil {
return false, err
}
_, curState := e.setClosing(true)
if curState == entryStateClosing {
ok = true
<-e.load
if e.value != nil {
err = e.value.Close()
c.mu.Lock()
e.setClosed()
delete(c.data, e.id)
c.mu.Unlock()
}
c.mu.Lock()
close(e.close)
delete(c.data, e.id)
c.mu.Unlock()
return
}
@@ -220,7 +308,13 @@ func (c *oCache) Add(id string, value Object) (err error) {
if _, ok := c.data[id]; ok {
return ErrExists
}
e := newEntry(id, value, entryStateActive)
e := &entry{
id: id,
lastUsage: time.Now(),
refCount: 0,
load: make(chan struct{}),
value: value,
}
close(e.load)
c.data[id] = e
return
@@ -232,7 +326,7 @@ func (c *oCache) ForEach(f func(obj Object) (isContinue bool)) {
for _, v := range c.data {
select {
case <-v.load:
if v.value != nil && !v.isClosing() {
if v.value != nil && !v.isClosing {
objects = append(objects, v.value)
}
default:
@@ -268,35 +362,40 @@ func (c *oCache) GC() {
deadline := c.timeNow().Add(-c.ttl)
var toClose []*entry
for _, e := range c.data {
if e.isActive() && e.lastUsage.Before(deadline) {
if e.isClosing {
continue
}
lu := e.lastUsage
if lug, ok := e.value.(ObjectLastUsage); ok {
lu = lug.LastUsage()
}
if !e.locked() && e.refCount <= 0 && lu.Before(deadline) {
e.isClosing = true
e.close = make(chan struct{})
toClose = append(toClose, e)
}
}
size := len(c.data)
c.mu.Unlock()
closedNum := 0
for _, e := range toClose {
prevState, _ := e.setClosing(false)
if prevState == entryStateClosing || prevState == entryStateClosed {
continue
}
closed, err := e.value.TryClose(c.ttl)
if err != nil {
c.log.With("object_id", e.id).Warnf("GC: object close error: %v", err)
}
if !closed {
e.setActive(true)
continue
} else {
closedNum++
c.mu.Lock()
e.setClosed()
delete(c.data, e.id)
c.mu.Unlock()
<-e.load
if e.value != nil {
if err := e.value.Close(); err != nil {
c.log.With("object_id", e.id).Warnf("GC: object close error: %v", err)
}
}
}
c.metricsClosed(closedNum, size)
c.log.Infof("GC: removed %d; cache size: %d", len(toClose), size)
if len(toClose) > 0 && c.metrics != nil {
c.metrics.gc.Add(float64(len(toClose)))
}
c.mu.Lock()
for _, e := range toClose {
close(e.close)
delete(c.data, e.id)
}
c.mu.Unlock()
}
func (c *oCache) Len() int {
@@ -313,34 +412,25 @@ func (c *oCache) Close() (err error) {
}
c.closed = true
close(c.closeCh)
var toClose []*entry
var toClose, alreadyClosing []*entry
for _, e := range c.data {
toClose = append(toClose, e)
if e.isClosing {
alreadyClosing = append(alreadyClosing, e)
} else {
toClose = append(toClose, e)
}
}
c.mu.Unlock()
for _, e := range toClose {
if _, err := c.remove(context.Background(), e); err != nil && err != ErrNotExists {
c.log.With("object_id", e.id).Warnf("cache close: object close error: %v", err)
<-e.load
if e.value != nil {
if clErr := e.value.Close(); clErr != nil {
c.log.With("object_id", e.id).Warnf("cache close: object close error: %v", clErr)
}
}
}
for _, e := range alreadyClosing {
<-e.close
}
return nil
}
func (c *oCache) metricsGet(hit bool) {
if c.metrics == nil {
return
}
if hit {
c.metrics.hit.Inc()
} else {
c.metrics.miss.Inc()
}
}
func (c *oCache) metricsClosed(closedLen, size int) {
c.log.Infof("GC: removed %d; cache size: %d", closedLen, size)
if c.metrics == nil || closedLen == 0 {
return
}
c.metrics.gc.Add(float64(closedLen))
}
+50 -335
View File
@@ -3,8 +3,6 @@ package ocache
import (
"context"
"errors"
"fmt"
"sync"
"sync/atomic"
"testing"
"time"
@@ -13,48 +11,26 @@ import (
"github.com/stretchr/testify/require"
)
var ctx = context.Background()
type testObject struct {
name string
closeErr error
closeCh chan struct{}
tryReturn bool
closeCalled bool
tryCloseCalled bool
name string
closeErr error
closeCh chan struct{}
}
func NewTestObject(name string, tryReturn bool, closeCh chan struct{}) *testObject {
func NewTestObject(name string, closeCh chan struct{}) *testObject {
return &testObject{
name: name,
closeCh: closeCh,
tryReturn: tryReturn,
name: name,
closeCh: closeCh,
}
}
func (t *testObject) Close() (err error) {
if t.closeCalled || (t.tryCloseCalled && t.tryReturn) {
panic("close called twice")
}
t.closeCalled = true
if t.closeCh != nil {
<-t.closeCh
}
return t.closeErr
}
func (t *testObject) TryClose(objectTTL time.Duration) (res bool, err error) {
if t.closeCalled || (t.tryCloseCalled && t.tryReturn) {
panic("close called twice")
}
t.tryCloseCalled = true
if t.closeCh != nil {
<-t.closeCh
return t.tryReturn, t.closeErr
}
return t.tryReturn, nil
}
func TestOCache_Get(t *testing.T) {
t.Run("successful", func(t *testing.T) {
c := New(func(ctx context.Context, id string) (value Object, err error) {
@@ -140,37 +116,42 @@ func TestOCache_Get(t *testing.T) {
}
func TestOCache_GC(t *testing.T) {
t.Run("test gc expired object", func(t *testing.T) {
t.Run("test without close wait", func(t *testing.T) {
c := New(func(ctx context.Context, id string) (value Object, err error) {
return NewTestObject(id, true, nil), nil
}, WithTTL(time.Millisecond*10))
return &testObject{name: id}, nil
}, WithTTL(time.Millisecond*10), WithRefCounter(true))
val, err := c.Get(context.TODO(), "id")
require.NoError(t, err)
require.NotNil(t, val)
assert.Equal(t, 1, c.Len())
c.GC()
assert.Equal(t, 1, c.Len())
time.Sleep(time.Millisecond * 20)
time.Sleep(time.Millisecond * 30)
c.GC()
assert.Equal(t, 1, c.Len())
assert.True(t, c.Release("id"))
c.GC()
assert.Equal(t, 0, c.Len())
assert.False(t, c.Release("id"))
})
t.Run("test gc tryClose true, close before get", func(t *testing.T) {
t.Run("test with close wait", func(t *testing.T) {
closeCh := make(chan struct{})
getCh := make(chan struct{})
c := New(func(ctx context.Context, id string) (value Object, err error) {
return NewTestObject(id, true, closeCh), nil
}, WithTTL(time.Millisecond*10))
return NewTestObject(id, closeCh), nil
}, WithTTL(time.Millisecond*10), WithRefCounter(true))
val, err := c.Get(context.TODO(), "id")
require.NoError(t, err)
require.NotNil(t, val)
assert.Equal(t, 1, c.Len())
assert.True(t, c.Release("id"))
// making ttl pass
time.Sleep(time.Millisecond * 20)
time.Sleep(time.Millisecond * 40)
// first gc will be run after 20 secs, so calling it manually
go c.GC()
// waiting until all objects are marked as closing
time.Sleep(time.Millisecond * 20)
time.Sleep(time.Millisecond * 40)
var events []string
go func() {
_, err := c.Get(context.TODO(), "id")
@@ -179,313 +160,47 @@ func TestOCache_GC(t *testing.T) {
events = append(events, "get")
close(getCh)
}()
// sleeping to make sure that Get is called
time.Sleep(time.Millisecond * 20)
events = append(events, "close")
// sleeping to make sure that Get is called
time.Sleep(time.Millisecond * 40)
close(closeCh)
<-getCh
require.Equal(t, []string{"close", "get"}, events)
})
t.Run("test gc tryClose false, many parallel get", func(t *testing.T) {
timesCalled := &atomic.Int32{}
obj := NewTestObject("id", false, nil)
c := New(func(ctx context.Context, id string) (value Object, err error) {
timesCalled.Add(1)
return obj, nil
}, WithTTL(0))
val, err := c.Get(context.TODO(), "id")
require.NoError(t, err)
require.NotNil(t, val)
assert.Equal(t, 1, c.Len())
begin := make(chan struct{})
wg := sync.WaitGroup{}
once := sync.Once{}
wg.Add(1)
go func() {
<-begin
c.GC()
wg.Done()
}()
for i := 0; i < 50; i++ {
wg.Add(1)
go func(i int) {
once.Do(func() {
close(begin)
})
if i%2 != 0 {
time.Sleep(time.Millisecond)
}
_, err := c.Get(context.TODO(), "id")
require.NoError(t, err)
wg.Done()
}(i)
}
require.NoError(t, err)
wg.Wait()
require.Equal(t, timesCalled.Load(), int32(1))
require.True(t, obj.tryCloseCalled)
})
t.Run("test gc tryClose different, many objects", func(t *testing.T) {
tryCloseIds := make(map[string]bool)
called := make(map[string]int)
max := 1000
getId := func(i int) string {
return fmt.Sprintf("id%d", i)
}
for i := 0; i < max; i++ {
if i%2 == 1 {
tryCloseIds[getId(i)] = true
} else {
tryCloseIds[getId(i)] = false
}
}
c := New(func(ctx context.Context, id string) (value Object, err error) {
called[id] = called[id] + 1
return NewTestObject(id, tryCloseIds[id], nil), nil
}, WithTTL(time.Millisecond*10))
for i := 0; i < max; i++ {
val, err := c.Get(context.TODO(), getId(i))
require.NoError(t, err)
require.NotNil(t, val)
}
assert.Equal(t, max, c.Len())
time.Sleep(time.Millisecond * 20)
c.GC()
for i := 0; i < max; i++ {
val, err := c.Get(context.TODO(), getId(i))
require.NoError(t, err)
require.NotNil(t, val)
}
for i := 0; i < max; i++ {
val, err := c.Get(context.TODO(), getId(i))
require.NoError(t, err)
require.NotNil(t, val)
require.Equal(t, called[getId(i)], i%2+1)
}
})
}
func Test_OCache_Remove(t *testing.T) {
t.Run("remove simple", func(t *testing.T) {
closeCh := make(chan struct{})
getCh := make(chan struct{})
c := New(func(ctx context.Context, id string) (value Object, err error) {
return NewTestObject(id, false, closeCh), nil
}, WithTTL(time.Millisecond*10))
closeCh := make(chan struct{})
getCh := make(chan struct{})
val, err := c.Get(context.TODO(), "id")
c := New(func(ctx context.Context, id string) (value Object, err error) {
return NewTestObject(id, closeCh), nil
}, WithTTL(time.Millisecond*10))
val, err := c.Get(context.TODO(), "id")
require.NoError(t, err)
require.NotNil(t, val)
assert.Equal(t, 1, c.Len())
// removing the object, so we will wait on closing
go func() {
_, err := c.Remove("id")
require.NoError(t, err)
}()
time.Sleep(time.Millisecond * 40)
var events []string
go func() {
_, err := c.Get(context.TODO(), "id")
require.NoError(t, err)
require.NotNil(t, val)
assert.Equal(t, 1, c.Len())
// removing the object, so we will wait on closing
go func() {
_, err := c.Remove(ctx, "id")
require.NoError(t, err)
}()
time.Sleep(time.Millisecond * 20)
events = append(events, "get")
close(getCh)
}()
events = append(events, "close")
// sleeping to make sure that Get is called
time.Sleep(time.Millisecond * 40)
close(closeCh)
var events []string
go func() {
_, err := c.Get(context.TODO(), "id")
require.NoError(t, err)
require.NotNil(t, val)
events = append(events, "get")
close(getCh)
}()
// sleeping to make sure that Get is called
time.Sleep(time.Millisecond * 20)
events = append(events, "close")
close(closeCh)
<-getCh
require.Equal(t, []string{"close", "get"}, events)
})
t.Run("test remove while gc, tryClose false", func(t *testing.T) {
closeCh := make(chan struct{})
removeCh := make(chan struct{})
c := New(func(ctx context.Context, id string) (value Object, err error) {
return NewTestObject(id, false, closeCh), nil
}, WithTTL(time.Millisecond*10))
val, err := c.Get(context.TODO(), "id")
require.NoError(t, err)
require.NotNil(t, val)
assert.Equal(t, 1, c.Len())
time.Sleep(time.Millisecond * 20)
go c.GC()
time.Sleep(time.Millisecond * 20)
var events []string
go func() {
ok, err := c.Remove(ctx, "id")
require.NoError(t, err)
require.True(t, ok)
events = append(events, "remove")
close(removeCh)
}()
time.Sleep(time.Millisecond * 20)
events = append(events, "close")
close(closeCh)
<-removeCh
require.Equal(t, []string{"close", "remove"}, events)
})
t.Run("test remove while gc, tryClose true", func(t *testing.T) {
closeCh := make(chan struct{})
removeCh := make(chan struct{})
c := New(func(ctx context.Context, id string) (value Object, err error) {
return NewTestObject(id, true, closeCh), nil
}, WithTTL(time.Millisecond*10))
val, err := c.Get(context.TODO(), "id")
require.NoError(t, err)
require.NotNil(t, val)
assert.Equal(t, 1, c.Len())
time.Sleep(time.Millisecond * 20)
go c.GC()
time.Sleep(time.Millisecond * 20)
var events []string
go func() {
ok, err := c.Remove(ctx, "id")
require.NoError(t, err)
require.False(t, ok)
events = append(events, "remove")
close(removeCh)
}()
time.Sleep(time.Millisecond * 20)
events = append(events, "close")
close(closeCh)
<-removeCh
require.Equal(t, []string{"close", "remove"}, events)
})
t.Run("test gc while remove, tryClose true", func(t *testing.T) {
closeCh := make(chan struct{})
removeCh := make(chan struct{})
c := New(func(ctx context.Context, id string) (value Object, err error) {
return NewTestObject(id, true, closeCh), nil
}, WithTTL(time.Millisecond*10))
val, err := c.Get(context.TODO(), "id")
require.NoError(t, err)
require.NotNil(t, val)
assert.Equal(t, 1, c.Len())
go func() {
ok, err := c.Remove(ctx, "id")
require.NoError(t, err)
require.True(t, ok)
close(removeCh)
}()
time.Sleep(20 * time.Millisecond)
c.GC()
close(closeCh)
<-removeCh
})
}
func TestOCacheFuzzy(t *testing.T) {
t.Run("test many objects gc, get and remove simultaneously, close after", func(t *testing.T) {
tryCloseIds := make(map[string]bool)
max := 2000
getId := func(i int) string {
return fmt.Sprintf("id%d", i)
}
for i := 0; i < max; i++ {
if i%2 == 1 {
tryCloseIds[getId(i)] = true
} else {
tryCloseIds[getId(i)] = false
}
}
c := New(func(ctx context.Context, id string) (value Object, err error) {
return NewTestObject(id, tryCloseIds[id], nil), nil
}, WithTTL(time.Nanosecond))
stopGC := make(chan struct{})
wg := sync.WaitGroup{}
go func() {
for {
select {
case <-stopGC:
return
default:
c.GC()
}
}
}()
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 10; j++ {
for i := 0; i < max; i++ {
val, err := c.Get(context.TODO(), getId(i))
require.NoError(t, err)
require.NotNil(t, val)
}
}
}()
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 10; j++ {
for i := 0; i < max; i++ {
c.Remove(ctx, getId(i))
}
}
}()
wg.Wait()
close(stopGC)
err := c.Close()
require.NoError(t, err)
require.Equal(t, 0, c.Len())
})
t.Run("test many objects gc, get, remove and close simultaneously", func(t *testing.T) {
tryCloseIds := make(map[string]bool)
max := 2000
getId := func(i int) string {
return fmt.Sprintf("id%d", i)
}
for i := 0; i < max; i++ {
if i%2 == 1 {
tryCloseIds[getId(i)] = true
} else {
tryCloseIds[getId(i)] = false
}
}
c := New(func(ctx context.Context, id string) (value Object, err error) {
return NewTestObject(id, tryCloseIds[id], nil), nil
}, WithTTL(time.Nanosecond))
go func() {
for {
c.GC()
}
}()
go func() {
for j := 0; j < 10; j++ {
for i := 0; i < max; i++ {
val, err := c.Get(context.TODO(), getId(i))
if err == ErrClosed {
return
}
require.NoError(t, err)
require.NotNil(t, val)
}
}
}()
go func() {
for j := 0; j < 10; j++ {
for i := 0; i < max; i++ {
c.Remove(ctx, getId(i))
}
}
}()
time.Sleep(time.Millisecond)
err := c.Close()
require.NoError(t, err)
require.Equal(t, 0, c.Len())
})
<-getCh
require.Equal(t, []string{"close", "get"}, events)
}
+2 -12
View File
@@ -2,8 +2,8 @@ package fileblockstore
import (
"context"
"github.com/anyproto/any-sync/app/logger"
"github.com/anyproto/any-sync/commonfile/fileproto/fileprotoerr"
"github.com/anytypeio/any-sync/app/logger"
"github.com/anytypeio/any-sync/commonfile/fileproto/fileprotoerr"
blocks "github.com/ipfs/go-block-format"
"github.com/ipfs/go-cid"
)
@@ -21,7 +21,6 @@ type ctxKey uint
const (
ctxKeySpaceId ctxKey = iota
ctxKeyFileId
)
type BlockStore interface {
@@ -49,12 +48,3 @@ func CtxGetSpaceId(ctx context.Context) (spaceId string) {
spaceId, _ = ctx.Value(ctxKeySpaceId).(string)
return
}
func CtxWithFileId(ctx context.Context, spaceId string) context.Context {
return context.WithValue(ctx, ctxKeyFileId, spaceId)
}
func CtxGetFileId(ctx context.Context) (spaceId string) {
spaceId, _ = ctx.Value(ctxKeyFileId).(string)
return
}
File diff suppressed because it is too large. Load diff
+103 -228
View File
@@ -40,14 +40,10 @@ func (drpcEncoding_File_commonfile_fileproto_protos_file_proto) JSONUnmarshal(bu
type DRPCFileClient interface {
DRPCConn() drpc.Conn
BlockGet(ctx context.Context, in *BlockGetRequest) (*BlockGetResponse, error)
BlockPush(ctx context.Context, in *BlockPushRequest) (*BlockPushResponse, error)
BlocksCheck(ctx context.Context, in *BlocksCheckRequest) (*BlocksCheckResponse, error)
BlocksBind(ctx context.Context, in *BlocksBindRequest) (*BlocksBindResponse, error)
FilesDelete(ctx context.Context, in *FilesDeleteRequest) (*FilesDeleteResponse, error)
FilesInfo(ctx context.Context, in *FilesInfoRequest) (*FilesInfoResponse, error)
GetBlocks(ctx context.Context) (DRPCFile_GetBlocksClient, error)
PushBlock(ctx context.Context, in *PushBlockRequest) (*PushBlockResponse, error)
DeleteBlocks(ctx context.Context, in *DeleteBlocksRequest) (*DeleteBlocksResponse, error)
Check(ctx context.Context, in *CheckRequest) (*CheckResponse, error)
SpaceInfo(ctx context.Context, in *SpaceInfoRequest) (*SpaceInfoResponse, error)
}
type drpcFileClient struct {
@@ -60,54 +56,53 @@ func NewDRPCFileClient(cc drpc.Conn) DRPCFileClient {
func (c *drpcFileClient) DRPCConn() drpc.Conn { return c.cc }
func (c *drpcFileClient) BlockGet(ctx context.Context, in *BlockGetRequest) (*BlockGetResponse, error) {
out := new(BlockGetResponse)
err := c.cc.Invoke(ctx, "/filesync.File/BlockGet", drpcEncoding_File_commonfile_fileproto_protos_file_proto{}, in, out)
func (c *drpcFileClient) GetBlocks(ctx context.Context) (DRPCFile_GetBlocksClient, error) {
stream, err := c.cc.NewStream(ctx, "/anyFile.File/GetBlocks", drpcEncoding_File_commonfile_fileproto_protos_file_proto{})
if err != nil {
return nil, err
}
x := &drpcFile_GetBlocksClient{stream}
return x, nil
}
type DRPCFile_GetBlocksClient interface {
drpc.Stream
Send(*GetBlockRequest) error
Recv() (*GetBlockResponse, error)
}
type drpcFile_GetBlocksClient struct {
drpc.Stream
}
func (x *drpcFile_GetBlocksClient) Send(m *GetBlockRequest) error {
return x.MsgSend(m, drpcEncoding_File_commonfile_fileproto_protos_file_proto{})
}
func (x *drpcFile_GetBlocksClient) Recv() (*GetBlockResponse, error) {
m := new(GetBlockResponse)
if err := x.MsgRecv(m, drpcEncoding_File_commonfile_fileproto_protos_file_proto{}); err != nil {
return nil, err
}
return m, nil
}
func (x *drpcFile_GetBlocksClient) RecvMsg(m *GetBlockResponse) error {
return x.MsgRecv(m, drpcEncoding_File_commonfile_fileproto_protos_file_proto{})
}
func (c *drpcFileClient) PushBlock(ctx context.Context, in *PushBlockRequest) (*PushBlockResponse, error) {
out := new(PushBlockResponse)
err := c.cc.Invoke(ctx, "/anyFile.File/PushBlock", drpcEncoding_File_commonfile_fileproto_protos_file_proto{}, in, out)
if err != nil {
return nil, err
}
return out, nil
}
func (c *drpcFileClient) BlockPush(ctx context.Context, in *BlockPushRequest) (*BlockPushResponse, error) {
out := new(BlockPushResponse)
err := c.cc.Invoke(ctx, "/filesync.File/BlockPush", drpcEncoding_File_commonfile_fileproto_protos_file_proto{}, in, out)
if err != nil {
return nil, err
}
return out, nil
}
func (c *drpcFileClient) BlocksCheck(ctx context.Context, in *BlocksCheckRequest) (*BlocksCheckResponse, error) {
out := new(BlocksCheckResponse)
err := c.cc.Invoke(ctx, "/filesync.File/BlocksCheck", drpcEncoding_File_commonfile_fileproto_protos_file_proto{}, in, out)
if err != nil {
return nil, err
}
return out, nil
}
func (c *drpcFileClient) BlocksBind(ctx context.Context, in *BlocksBindRequest) (*BlocksBindResponse, error) {
out := new(BlocksBindResponse)
err := c.cc.Invoke(ctx, "/filesync.File/BlocksBind", drpcEncoding_File_commonfile_fileproto_protos_file_proto{}, in, out)
if err != nil {
return nil, err
}
return out, nil
}
func (c *drpcFileClient) FilesDelete(ctx context.Context, in *FilesDeleteRequest) (*FilesDeleteResponse, error) {
out := new(FilesDeleteResponse)
err := c.cc.Invoke(ctx, "/filesync.File/FilesDelete", drpcEncoding_File_commonfile_fileproto_protos_file_proto{}, in, out)
if err != nil {
return nil, err
}
return out, nil
}
func (c *drpcFileClient) FilesInfo(ctx context.Context, in *FilesInfoRequest) (*FilesInfoResponse, error) {
out := new(FilesInfoResponse)
err := c.cc.Invoke(ctx, "/filesync.File/FilesInfo", drpcEncoding_File_commonfile_fileproto_protos_file_proto{}, in, out)
func (c *drpcFileClient) DeleteBlocks(ctx context.Context, in *DeleteBlocksRequest) (*DeleteBlocksResponse, error) {
out := new(DeleteBlocksResponse)
err := c.cc.Invoke(ctx, "/anyFile.File/DeleteBlocks", drpcEncoding_File_commonfile_fileproto_protos_file_proto{}, in, out)
if err != nil {
return nil, err
}
@@ -116,16 +111,7 @@ func (c *drpcFileClient) FilesInfo(ctx context.Context, in *FilesInfoRequest) (*
func (c *drpcFileClient) Check(ctx context.Context, in *CheckRequest) (*CheckResponse, error) {
out := new(CheckResponse)
err := c.cc.Invoke(ctx, "/filesync.File/Check", drpcEncoding_File_commonfile_fileproto_protos_file_proto{}, in, out)
if err != nil {
return nil, err
}
return out, nil
}
func (c *drpcFileClient) SpaceInfo(ctx context.Context, in *SpaceInfoRequest) (*SpaceInfoResponse, error) {
out := new(SpaceInfoResponse)
err := c.cc.Invoke(ctx, "/filesync.File/SpaceInfo", drpcEncoding_File_commonfile_fileproto_protos_file_proto{}, in, out)
err := c.cc.Invoke(ctx, "/anyFile.File/Check", drpcEncoding_File_commonfile_fileproto_protos_file_proto{}, in, out)
if err != nil {
return nil, err
}
@@ -133,39 +119,23 @@ func (c *drpcFileClient) SpaceInfo(ctx context.Context, in *SpaceInfoRequest) (*
}
type DRPCFileServer interface {
BlockGet(context.Context, *BlockGetRequest) (*BlockGetResponse, error)
BlockPush(context.Context, *BlockPushRequest) (*BlockPushResponse, error)
BlocksCheck(context.Context, *BlocksCheckRequest) (*BlocksCheckResponse, error)
BlocksBind(context.Context, *BlocksBindRequest) (*BlocksBindResponse, error)
FilesDelete(context.Context, *FilesDeleteRequest) (*FilesDeleteResponse, error)
FilesInfo(context.Context, *FilesInfoRequest) (*FilesInfoResponse, error)
GetBlocks(DRPCFile_GetBlocksStream) error
PushBlock(context.Context, *PushBlockRequest) (*PushBlockResponse, error)
DeleteBlocks(context.Context, *DeleteBlocksRequest) (*DeleteBlocksResponse, error)
Check(context.Context, *CheckRequest) (*CheckResponse, error)
SpaceInfo(context.Context, *SpaceInfoRequest) (*SpaceInfoResponse, error)
}
type DRPCFileUnimplementedServer struct{}
func (s *DRPCFileUnimplementedServer) BlockGet(context.Context, *BlockGetRequest) (*BlockGetResponse, error) {
func (s *DRPCFileUnimplementedServer) GetBlocks(DRPCFile_GetBlocksStream) error {
return drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
}
func (s *DRPCFileUnimplementedServer) PushBlock(context.Context, *PushBlockRequest) (*PushBlockResponse, error) {
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
}
func (s *DRPCFileUnimplementedServer) BlockPush(context.Context, *BlockPushRequest) (*BlockPushResponse, error) {
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
}
func (s *DRPCFileUnimplementedServer) BlocksCheck(context.Context, *BlocksCheckRequest) (*BlocksCheckResponse, error) {
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
}
func (s *DRPCFileUnimplementedServer) BlocksBind(context.Context, *BlocksBindRequest) (*BlocksBindResponse, error) {
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
}
func (s *DRPCFileUnimplementedServer) FilesDelete(context.Context, *FilesDeleteRequest) (*FilesDeleteResponse, error) {
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
}
func (s *DRPCFileUnimplementedServer) FilesInfo(context.Context, *FilesInfoRequest) (*FilesInfoResponse, error) {
func (s *DRPCFileUnimplementedServer) DeleteBlocks(context.Context, *DeleteBlocksRequest) (*DeleteBlocksResponse, error) {
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
}
@@ -173,72 +143,40 @@ func (s *DRPCFileUnimplementedServer) Check(context.Context, *CheckRequest) (*Ch
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
}
func (s *DRPCFileUnimplementedServer) SpaceInfo(context.Context, *SpaceInfoRequest) (*SpaceInfoResponse, error) {
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
}
type DRPCFileDescription struct{}
func (DRPCFileDescription) NumMethods() int { return 8 }
func (DRPCFileDescription) NumMethods() int { return 4 }
func (DRPCFileDescription) Method(n int) (string, drpc.Encoding, drpc.Receiver, interface{}, bool) {
switch n {
case 0:
return "/filesync.File/BlockGet", drpcEncoding_File_commonfile_fileproto_protos_file_proto{},
return "/anyFile.File/GetBlocks", drpcEncoding_File_commonfile_fileproto_protos_file_proto{},
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
return srv.(DRPCFileServer).
BlockGet(
ctx,
in1.(*BlockGetRequest),
return nil, srv.(DRPCFileServer).
GetBlocks(
&drpcFile_GetBlocksStream{in1.(drpc.Stream)},
)
}, DRPCFileServer.BlockGet, true
}, DRPCFileServer.GetBlocks, true
case 1:
return "/filesync.File/BlockPush", drpcEncoding_File_commonfile_fileproto_protos_file_proto{},
return "/anyFile.File/PushBlock", drpcEncoding_File_commonfile_fileproto_protos_file_proto{},
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
return srv.(DRPCFileServer).
BlockPush(
PushBlock(
ctx,
in1.(*BlockPushRequest),
in1.(*PushBlockRequest),
)
}, DRPCFileServer.BlockPush, true
}, DRPCFileServer.PushBlock, true
case 2:
return "/filesync.File/BlocksCheck", drpcEncoding_File_commonfile_fileproto_protos_file_proto{},
return "/anyFile.File/DeleteBlocks", drpcEncoding_File_commonfile_fileproto_protos_file_proto{},
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
return srv.(DRPCFileServer).
BlocksCheck(
DeleteBlocks(
ctx,
in1.(*BlocksCheckRequest),
in1.(*DeleteBlocksRequest),
)
}, DRPCFileServer.BlocksCheck, true
}, DRPCFileServer.DeleteBlocks, true
case 3:
return "/filesync.File/BlocksBind", drpcEncoding_File_commonfile_fileproto_protos_file_proto{},
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
return srv.(DRPCFileServer).
BlocksBind(
ctx,
in1.(*BlocksBindRequest),
)
}, DRPCFileServer.BlocksBind, true
case 4:
return "/filesync.File/FilesDelete", drpcEncoding_File_commonfile_fileproto_protos_file_proto{},
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
return srv.(DRPCFileServer).
FilesDelete(
ctx,
in1.(*FilesDeleteRequest),
)
}, DRPCFileServer.FilesDelete, true
case 5:
return "/filesync.File/FilesInfo", drpcEncoding_File_commonfile_fileproto_protos_file_proto{},
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
return srv.(DRPCFileServer).
FilesInfo(
ctx,
in1.(*FilesInfoRequest),
)
}, DRPCFileServer.FilesInfo, true
case 6:
return "/filesync.File/Check", drpcEncoding_File_commonfile_fileproto_protos_file_proto{},
return "/anyFile.File/Check", drpcEncoding_File_commonfile_fileproto_protos_file_proto{},
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
return srv.(DRPCFileServer).
Check(
@@ -246,15 +184,6 @@ func (DRPCFileDescription) Method(n int) (string, drpc.Encoding, drpc.Receiver,
in1.(*CheckRequest),
)
}, DRPCFileServer.Check, true
case 7:
return "/filesync.File/SpaceInfo", drpcEncoding_File_commonfile_fileproto_protos_file_proto{},
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
return srv.(DRPCFileServer).
SpaceInfo(
ctx,
in1.(*SpaceInfoRequest),
)
}, DRPCFileServer.SpaceInfo, true
default:
return "", nil, nil, nil, false
}
@@ -264,96 +193,58 @@ func DRPCRegisterFile(mux drpc.Mux, impl DRPCFileServer) error {
return mux.Register(impl, DRPCFileDescription{})
}
type DRPCFile_BlockGetStream interface {
type DRPCFile_GetBlocksStream interface {
drpc.Stream
SendAndClose(*BlockGetResponse) error
Send(*GetBlockResponse) error
Recv() (*GetBlockRequest, error)
}
type drpcFile_BlockGetStream struct {
type drpcFile_GetBlocksStream struct {
drpc.Stream
}
func (x *drpcFile_BlockGetStream) SendAndClose(m *BlockGetResponse) error {
func (x *drpcFile_GetBlocksStream) Send(m *GetBlockResponse) error {
return x.MsgSend(m, drpcEncoding_File_commonfile_fileproto_protos_file_proto{})
}
func (x *drpcFile_GetBlocksStream) Recv() (*GetBlockRequest, error) {
m := new(GetBlockRequest)
if err := x.MsgRecv(m, drpcEncoding_File_commonfile_fileproto_protos_file_proto{}); err != nil {
return nil, err
}
return m, nil
}
func (x *drpcFile_GetBlocksStream) RecvMsg(m *GetBlockRequest) error {
return x.MsgRecv(m, drpcEncoding_File_commonfile_fileproto_protos_file_proto{})
}
type DRPCFile_PushBlockStream interface {
drpc.Stream
SendAndClose(*PushBlockResponse) error
}
type drpcFile_PushBlockStream struct {
drpc.Stream
}
func (x *drpcFile_PushBlockStream) SendAndClose(m *PushBlockResponse) error {
if err := x.MsgSend(m, drpcEncoding_File_commonfile_fileproto_protos_file_proto{}); err != nil {
return err
}
return x.CloseSend()
}
type DRPCFile_BlockPushStream interface {
type DRPCFile_DeleteBlocksStream interface {
drpc.Stream
SendAndClose(*BlockPushResponse) error
SendAndClose(*DeleteBlocksResponse) error
}
type drpcFile_BlockPushStream struct {
type drpcFile_DeleteBlocksStream struct {
drpc.Stream
}
func (x *drpcFile_BlockPushStream) SendAndClose(m *BlockPushResponse) error {
if err := x.MsgSend(m, drpcEncoding_File_commonfile_fileproto_protos_file_proto{}); err != nil {
return err
}
return x.CloseSend()
}
type DRPCFile_BlocksCheckStream interface {
drpc.Stream
SendAndClose(*BlocksCheckResponse) error
}
type drpcFile_BlocksCheckStream struct {
drpc.Stream
}
func (x *drpcFile_BlocksCheckStream) SendAndClose(m *BlocksCheckResponse) error {
if err := x.MsgSend(m, drpcEncoding_File_commonfile_fileproto_protos_file_proto{}); err != nil {
return err
}
return x.CloseSend()
}
type DRPCFile_BlocksBindStream interface {
drpc.Stream
SendAndClose(*BlocksBindResponse) error
}
type drpcFile_BlocksBindStream struct {
drpc.Stream
}
func (x *drpcFile_BlocksBindStream) SendAndClose(m *BlocksBindResponse) error {
if err := x.MsgSend(m, drpcEncoding_File_commonfile_fileproto_protos_file_proto{}); err != nil {
return err
}
return x.CloseSend()
}
type DRPCFile_FilesDeleteStream interface {
drpc.Stream
SendAndClose(*FilesDeleteResponse) error
}
type drpcFile_FilesDeleteStream struct {
drpc.Stream
}
func (x *drpcFile_FilesDeleteStream) SendAndClose(m *FilesDeleteResponse) error {
if err := x.MsgSend(m, drpcEncoding_File_commonfile_fileproto_protos_file_proto{}); err != nil {
return err
}
return x.CloseSend()
}
type DRPCFile_FilesInfoStream interface {
drpc.Stream
SendAndClose(*FilesInfoResponse) error
}
type drpcFile_FilesInfoStream struct {
drpc.Stream
}
func (x *drpcFile_FilesInfoStream) SendAndClose(m *FilesInfoResponse) error {
func (x *drpcFile_DeleteBlocksStream) SendAndClose(m *DeleteBlocksResponse) error {
if err := x.MsgSend(m, drpcEncoding_File_commonfile_fileproto_protos_file_proto{}); err != nil {
return err
}
@@ -375,19 +266,3 @@ func (x *drpcFile_CheckStream) SendAndClose(m *CheckResponse) error {
}
return x.CloseSend()
}
type DRPCFile_SpaceInfoStream interface {
drpc.Stream
SendAndClose(*SpaceInfoResponse) error
}
type drpcFile_SpaceInfoStream struct {
drpc.Stream
}
func (x *drpcFile_SpaceInfoStream) SendAndClose(m *SpaceInfoResponse) error {
if err := x.MsgSend(m, drpcEncoding_File_commonfile_fileproto_protos_file_proto{}); err != nil {
return err
}
return x.CloseSend()
}
@@ -2,16 +2,12 @@ package fileprotoerr
import (
"fmt"
"github.com/anyproto/any-sync/commonfile/fileproto"
"github.com/anyproto/any-sync/net/rpc/rpcerr"
"github.com/anytypeio/any-sync/commonfile/fileproto"
"github.com/anytypeio/any-sync/net/rpc/rpcerr"
)
var (
errGroup = rpcerr.ErrGroup(fileproto.ErrCodes_ErrorOffset)
ErrUnexpected = errGroup.Register(fmt.Errorf("unexpected fileproto error"), uint64(fileproto.ErrCodes_Unexpected))
ErrCIDNotFound = errGroup.Register(fmt.Errorf("CID not found"), uint64(fileproto.ErrCodes_CIDNotFound))
ErrForbidden = errGroup.Register(fmt.Errorf("forbidden"), uint64(fileproto.ErrCodes_Forbidden))
ErrSpaceLimitExceeded = errGroup.Register(fmt.Errorf("space limit exceeded"), uint64(fileproto.ErrCodes_SpaceLimitExceeded))
ErrQuerySizeExceeded = errGroup.Register(fmt.Errorf("query size exceeded"), uint64(fileproto.ErrCodes_QuerySizeExceeded))
ErrWrongHash = errGroup.Register(fmt.Errorf("wrong block hash"), uint64(fileproto.ErrCodes_WrongHash))
errGroup = rpcerr.ErrGroup(fileproto.ErrCodes_ErrorOffset)
ErrUnexpected = errGroup.Register(fmt.Errorf("unexpected fileproto error"), uint64(fileproto.ErrCodes_Unexpected))
ErrCIDNotFound = errGroup.Register(fmt.Errorf("CID not found"), uint64(fileproto.ErrCodes_CIDNotFound))
)
+23 -86
View File
@@ -1,123 +1,60 @@
syntax = "proto3";
package filesync;
package anyFile;
option go_package = "commonfile/fileproto";
enum ErrCodes {
Unexpected = 0;
CIDNotFound = 1;
Forbidden = 2;
SpaceLimitExceeded = 3;
QuerySizeExceeded = 4;
WrongHash = 5;
ErrorOffset = 200;
}
service File {
// BlockGet gets one block from a server
rpc BlockGet(BlockGetRequest) returns (BlockGetResponse);
// BlockPush pushes one block to a server
rpc BlockPush(BlockPushRequest) returns (BlockPushResponse);
// BlocksCheck checks is CIDs exists
rpc BlocksCheck(BlocksCheckRequest) returns (BlocksCheckResponse);
// BlocksBind binds CIDs to space
rpc BlocksBind(BlocksBindRequest) returns (BlocksBindResponse);
// FilesDelete deletes files by id
rpc FilesDelete(FilesDeleteRequest) returns (FilesDeleteResponse);
// FilesInfo return info by given files id
rpc FilesInfo(FilesInfoRequest) returns (FilesInfoResponse);
// Check checks the connection and credentials
// GetBlocks streams ipfs blocks from server to client
rpc GetBlocks(stream GetBlockRequest) returns (stream GetBlockResponse);
// PushBlock pushes one block to server
rpc PushBlock(PushBlockRequest) returns (PushBlockResponse);
// DeleteBlock deletes block from space
rpc DeleteBlocks(DeleteBlocksRequest) returns (DeleteBlocksResponse);
// Ping checks the connection
rpc Check(CheckRequest) returns (CheckResponse);
// SpaceInfo returns usage, limit, etc about space
rpc SpaceInfo(SpaceInfoRequest) returns (SpaceInfoResponse);
}
message BlockGetRequest {
message GetBlockRequest {
string spaceId = 1;
bytes cid = 2;
}
message BlockGetResponse {
message GetBlockResponse {
bytes cid = 1;
bytes data = 2;
CIDError code = 3;
}
message BlockPushRequest {
message PushBlockRequest {
string spaceId = 1;
string fileId = 2;
bytes cid = 3;
bytes data = 4;
bytes cid = 2;
bytes data = 3;
}
message BlockPushResponse {}
message PushBlockResponse {}
message BlocksCheckRequest {
message DeleteBlocksRequest {
string spaceId = 1;
repeated bytes cids = 2;
repeated bytes cid = 2;
}
message BlocksCheckResponse {
repeated BlockAvailability blocksAvailability = 1;
}
message BlockAvailability {
bytes cid = 1;
AvailabilityStatus status = 2;
}
enum AvailabilityStatus {
NotExists = 0;
Exists = 1;
ExistsInSpace = 2;
}
message BlocksBindRequest {
string spaceId = 1;
string fileId = 2;
repeated bytes cids = 3;
}
message BlocksBindResponse {}
message FilesDeleteRequest {
string spaceId = 1;
repeated string fileIds = 2;
}
message FilesDeleteResponse {}
message FilesInfoRequest {
string spaceId = 1;
repeated string fileIds = 2;
}
message FilesInfoResponse {
repeated FileInfo filesInfo = 1;
}
message FileInfo {
string fileId = 1;
uint64 usageBytes = 2;
uint32 cidsCount = 3;
}
message DeleteBlocksResponse {}
message CheckRequest {}
message CheckResponse {
repeated string spaceIds = 1;
bool allowWrite = 2;
}
message SpaceInfoRequest {
string spaceId = 1;
}
message SpaceInfoResponse {
uint64 limitBytes = 1;
uint64 usageBytes = 2;
uint64 cidsCount = 3;
uint64 filesCount = 4;
}
enum CIDError {
CIDErrorOk = 0;
CIDErrorNotFound = 1;
CIDErrorUnexpected = 2;
}
+1 -1
View File
@@ -2,7 +2,7 @@ package fileservice
import (
"context"
"github.com/anyproto/any-sync/commonfile/fileblockstore"
"github.com/anytypeio/any-sync/commonfile/fileblockstore"
blocks "github.com/ipfs/go-block-format"
"github.com/ipfs/go-blockservice"
"github.com/ipfs/go-cid"
+4 -8
View File
@@ -3,9 +3,9 @@ package fileservice
import (
"context"
"fmt"
"github.com/anyproto/any-sync/app"
"github.com/anyproto/any-sync/app/logger"
"github.com/anyproto/any-sync/commonfile/fileblockstore"
"github.com/anytypeio/any-sync/app"
"github.com/anytypeio/any-sync/app/logger"
"github.com/anytypeio/any-sync/commonfile/fileblockstore"
"github.com/ipfs/go-cid"
chunker "github.com/ipfs/go-ipfs-chunker"
ipld "github.com/ipfs/go-ipld-format"
@@ -22,10 +22,6 @@ const CName = "common.commonfile.fileservice"
var log = logger.NewNamed(CName)
const (
ChunkSize = 1 << 20
)
func New() FileService {
return &fileService{}
}
@@ -78,7 +74,7 @@ func (fs *fileService) AddFile(ctx context.Context, r io.Reader) (ipld.Node, err
Maxlinks: helpers.DefaultLinksPerBlock,
CidBuilder: &fs.prefix,
}
dbh, err := dbp.New(chunker.NewSizeSplitter(r, ChunkSize))
dbh, err := dbp.New(chunker.DefaultSplitter(r))
if err != nil {
return nil, err
}
+23 -43
View File
@@ -2,58 +2,38 @@ package commonspace
import (
"context"
"github.com/anyproto/any-sync/commonspace/object/syncobjectgetter"
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
"github.com/anyproto/any-sync/commonspace/object/treemanager"
"sync/atomic"
"github.com/anytypeio/any-sync/commonspace/object/acl/syncacl"
"github.com/anytypeio/any-sync/commonspace/object/syncobjectgetter"
"github.com/anytypeio/any-sync/commonspace/object/treegetter"
"github.com/anytypeio/any-sync/commonspace/settings"
)
type commonGetter struct {
treemanager.TreeManager
spaceId string
reservedObjects []syncobjectgetter.SyncObject
spaceIsClosed *atomic.Bool
type commonSpaceGetter struct {
spaceId string
aclList *syncacl.SyncAcl
treeGetter treegetter.TreeGetter
settings settings.SettingsObject
}
func newCommonGetter(spaceId string, getter treemanager.TreeManager, spaceIsClosed *atomic.Bool) *commonGetter {
return &commonGetter{
TreeManager: getter,
spaceId: spaceId,
spaceIsClosed: spaceIsClosed,
func newCommonSpaceGetter(spaceId string, aclList *syncacl.SyncAcl, treeGetter treegetter.TreeGetter, settings settings.SettingsObject) syncobjectgetter.SyncObjectGetter {
return &commonSpaceGetter{
spaceId: spaceId,
aclList: aclList,
treeGetter: treeGetter,
settings: settings,
}
}
func (c *commonGetter) AddObject(object syncobjectgetter.SyncObject) {
c.reservedObjects = append(c.reservedObjects, object)
}
func (c *commonGetter) GetTree(ctx context.Context, spaceId, treeId string) (objecttree.ObjectTree, error) {
if c.spaceIsClosed.Load() {
return nil, ErrSpaceClosed
func (c *commonSpaceGetter) GetObject(ctx context.Context, objectId string) (obj syncobjectgetter.SyncObject, err error) {
if c.aclList.Id() == objectId {
obj = c.aclList
return
}
if obj := c.getReservedObject(treeId); obj != nil {
return obj.(objecttree.ObjectTree), nil
if c.settings.Id() == objectId {
obj = c.settings.(syncobjectgetter.SyncObject)
return
}
return c.TreeManager.GetTree(ctx, spaceId, treeId)
}
func (c *commonGetter) getReservedObject(id string) syncobjectgetter.SyncObject {
for _, obj := range c.reservedObjects {
if obj != nil && obj.Id() == id {
return obj
}
}
return nil
}
func (c *commonGetter) GetObject(ctx context.Context, objectId string) (obj syncobjectgetter.SyncObject, err error) {
if c.spaceIsClosed.Load() {
return nil, ErrSpaceClosed
}
if obj := c.getReservedObject(objectId); obj != nil {
return obj, nil
}
t, err := c.TreeManager.GetTree(ctx, c.spaceId, objectId)
t, err := c.treeGetter.GetTree(ctx, c.spaceId, objectId)
if err != nil {
return
}
+2 -2
View File
@@ -1,8 +1,8 @@
package commonspace
import (
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
"github.com/anyproto/any-sync/commonspace/spacestorage"
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
"github.com/anytypeio/any-sync/commonspace/spacestorage"
)
type commonStorage struct {
@@ -0,0 +1,85 @@
//go:generate mockgen -destination mock_confconnector/mock_confconnector.go github.com/anytypeio/any-sync/commonspace/confconnector ConfConnector
package confconnector
import (
"context"
"github.com/anytypeio/any-sync/net/peer"
"github.com/anytypeio/any-sync/net/pool"
"github.com/anytypeio/any-sync/nodeconf"
"golang.org/x/exp/slices"
)
type ConfConnector interface {
Configuration() nodeconf.Configuration
Pool() pool.Pool
GetResponsiblePeers(ctx context.Context, spaceId string) ([]peer.Peer, error)
DialInactiveResponsiblePeers(ctx context.Context, spaceId string, activeNodeIds []string) ([]peer.Peer, error)
}
type confConnector struct {
conf nodeconf.Configuration
pool pool.Pool
}
func NewConfConnector(conf nodeconf.Configuration, pool pool.Pool) ConfConnector {
return &confConnector{conf: conf, pool: pool}
}
func (s *confConnector) Configuration() nodeconf.Configuration {
return s.conf
}
func (s *confConnector) Pool() pool.Pool {
return s.pool
}
func (s *confConnector) GetResponsiblePeers(ctx context.Context, spaceId string) ([]peer.Peer, error) {
return s.connectOneOrMany(ctx, spaceId, nil, s.pool.Get)
}
func (s *confConnector) DialInactiveResponsiblePeers(ctx context.Context, spaceId string, activeNodeIds []string) ([]peer.Peer, error) {
return s.connectOneOrMany(ctx, spaceId, activeNodeIds, s.pool.Dial)
}
func (s *confConnector) connectOneOrMany(
ctx context.Context,
spaceId string,
activeNodeIds []string,
connectOne func(context.Context, string) (peer.Peer, error)) (peers []peer.Peer, err error) {
var (
inactiveNodeIds []string
allNodes = s.conf.NodeIds(spaceId)
)
for _, id := range allNodes {
if !slices.Contains(activeNodeIds, id) {
inactiveNodeIds = append(inactiveNodeIds, id)
}
}
if s.conf.IsResponsible(spaceId) {
for _, id := range inactiveNodeIds {
var p peer.Peer
p, err = connectOne(ctx, id)
if err != nil {
continue
}
peers = append(peers, p)
}
} else if len(activeNodeIds) == 0 {
// that means that all connected ids
var p peer.Peer
p, err = s.pool.GetOneOf(ctx, allNodes)
if err != nil {
return
}
// if we are dialling someone, we want to dial to the same peer which we cached
// thus communication through streams and through diff will go to the same node
p, err = connectOne(ctx, p.Id())
if err != nil {
return
}
peers = []peer.Peer{p}
}
return
}
@@ -0,0 +1,96 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/anytypeio/any-sync/commonspace/confconnector (interfaces: ConfConnector)
// Package mock_confconnector is a generated GoMock package.
package mock_confconnector
import (
context "context"
reflect "reflect"
peer "github.com/anytypeio/any-sync/net/peer"
pool "github.com/anytypeio/any-sync/net/pool"
nodeconf "github.com/anytypeio/any-sync/nodeconf"
gomock "github.com/golang/mock/gomock"
)
// MockConfConnector is a mock of ConfConnector interface.
type MockConfConnector struct {
ctrl *gomock.Controller
recorder *MockConfConnectorMockRecorder
}
// MockConfConnectorMockRecorder is the mock recorder for MockConfConnector.
type MockConfConnectorMockRecorder struct {
mock *MockConfConnector
}
// NewMockConfConnector creates a new mock instance.
func NewMockConfConnector(ctrl *gomock.Controller) *MockConfConnector {
mock := &MockConfConnector{ctrl: ctrl}
mock.recorder = &MockConfConnectorMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockConfConnector) EXPECT() *MockConfConnectorMockRecorder {
return m.recorder
}
// Configuration mocks base method.
func (m *MockConfConnector) Configuration() nodeconf.Configuration {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Configuration")
ret0, _ := ret[0].(nodeconf.Configuration)
return ret0
}
// Configuration indicates an expected call of Configuration.
func (mr *MockConfConnectorMockRecorder) Configuration() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Configuration", reflect.TypeOf((*MockConfConnector)(nil).Configuration))
}
// DialInactiveResponsiblePeers mocks base method.
func (m *MockConfConnector) DialInactiveResponsiblePeers(arg0 context.Context, arg1 string, arg2 []string) ([]peer.Peer, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DialInactiveResponsiblePeers", arg0, arg1, arg2)
ret0, _ := ret[0].([]peer.Peer)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DialInactiveResponsiblePeers indicates an expected call of DialInactiveResponsiblePeers.
func (mr *MockConfConnectorMockRecorder) DialInactiveResponsiblePeers(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DialInactiveResponsiblePeers", reflect.TypeOf((*MockConfConnector)(nil).DialInactiveResponsiblePeers), arg0, arg1, arg2)
}
// GetResponsiblePeers mocks base method.
func (m *MockConfConnector) GetResponsiblePeers(arg0 context.Context, arg1 string) ([]peer.Peer, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetResponsiblePeers", arg0, arg1)
ret0, _ := ret[0].([]peer.Peer)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetResponsiblePeers indicates an expected call of GetResponsiblePeers.
func (mr *MockConfConnectorMockRecorder) GetResponsiblePeers(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetResponsiblePeers", reflect.TypeOf((*MockConfConnector)(nil).GetResponsiblePeers), arg0, arg1)
}
// Pool mocks base method.
func (m *MockConfConnector) Pool() pool.Pool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Pool")
ret0, _ := ret[0].(pool.Pool)
return ret0
}
// Pool indicates an expected call of Pool.
func (mr *MockConfConnectorMockRecorder) Pool() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Pool", reflect.TypeOf((*MockConfConnector)(nil).Pool))
}
+2 -3
View File
@@ -5,7 +5,6 @@ type ConfigGetter interface {
}
type Config struct {
GCTTL int `yaml:"gcTTL"`
SyncPeriod int `yaml:"syncPeriod"`
KeepTreeDataInMemory bool `yaml:"keepTreeDataInMemory"`
GCTTL int `yaml:"gcTTL"`
SyncPeriod int `yaml:"syncPeriod"`
}
@@ -1,24 +0,0 @@
//go:generate mockgen -destination mock_credentialprovider/mock_credentialprovider.go github.com/anyproto/any-sync/commonspace/credentialprovider CredentialProvider
package credentialprovider
import (
"context"
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
)
const CName = "common.commonspace.credentialprovider"
func NewNoOp() CredentialProvider {
return &noOpProvider{}
}
type CredentialProvider interface {
GetCredential(ctx context.Context, spaceHeader *spacesyncproto.RawSpaceHeaderWithId) ([]byte, error)
}
type noOpProvider struct {
}
func (n noOpProvider) GetCredential(ctx context.Context, spaceHeader *spacesyncproto.RawSpaceHeaderWithId) ([]byte, error) {
return nil, nil
}
@@ -1,51 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/anyproto/any-sync/commonspace/credentialprovider (interfaces: CredentialProvider)
// Package mock_credentialprovider is a generated GoMock package.
package mock_credentialprovider
import (
context "context"
reflect "reflect"
spacesyncproto "github.com/anyproto/any-sync/commonspace/spacesyncproto"
gomock "github.com/golang/mock/gomock"
)
// MockCredentialProvider is a mock of CredentialProvider interface.
type MockCredentialProvider struct {
ctrl *gomock.Controller
recorder *MockCredentialProviderMockRecorder
}
// MockCredentialProviderMockRecorder is the mock recorder for MockCredentialProvider.
type MockCredentialProviderMockRecorder struct {
mock *MockCredentialProvider
}
// NewMockCredentialProvider creates a new mock instance.
func NewMockCredentialProvider(ctrl *gomock.Controller) *MockCredentialProvider {
mock := &MockCredentialProvider{ctrl: ctrl}
mock.recorder = &MockCredentialProviderMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockCredentialProvider) EXPECT() *MockCredentialProviderMockRecorder {
return m.recorder
}
// GetCredential mocks base method.
func (m *MockCredentialProvider) GetCredential(arg0 context.Context, arg1 *spacesyncproto.RawSpaceHeaderWithId) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetCredential", arg0, arg1)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetCredential indicates an expected call of GetCredential.
func (mr *MockCredentialProviderMockRecorder) GetCredential(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCredential", reflect.TypeOf((*MockCredentialProvider)(nil).GetCredential), arg0, arg1)
}
-271
View File
@@ -1,271 +0,0 @@
package commonspace
import (
"context"
"fmt"
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
"github.com/anyproto/any-sync/commonspace/settings"
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
"github.com/anyproto/any-sync/commonspace/spacestorage"
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
"github.com/anyproto/any-sync/util/crypto"
"github.com/stretchr/testify/require"
"math/rand"
"testing"
"time"
)
func addIncorrectSnapshot(settingsObject settings.SettingsObject, acc *accountdata.AccountKeys, partialIds map[string]struct{}, newId string) (err error) {
factory := settingsstate.NewChangeFactory()
bytes, err := factory.CreateObjectDeleteChange(newId, &settingsstate.State{DeletedIds: partialIds}, true)
if err != nil {
return
}
ch, err := settingsObject.PrepareChange(objecttree.SignableChangeContent{
Data: bytes,
Key: acc.SignKey,
IsSnapshot: true,
IsEncrypted: false,
Timestamp: time.Now().Unix(),
})
if err != nil {
return
}
res, err := settingsObject.AddRawChanges(context.Background(), objecttree.RawChangesPayload{
NewHeads: []string{ch.Id},
RawChanges: []*treechangeproto.RawTreeChangeWithId{ch},
})
if err != nil {
return
}
if res.Mode != objecttree.Rebuild {
return fmt.Errorf("incorrect mode: %d", res.Mode)
}
return
}
func TestSpaceDeleteIds(t *testing.T) {
fx := newFixture(t)
acc := fx.account.Account()
rk := crypto.NewAES()
ctx := context.Background()
totalObjs := 1500
// creating space
sp, err := fx.spaceService.CreateSpace(ctx, SpaceCreatePayload{
SigningKey: acc.SignKey,
SpaceType: "type",
ReadKey: rk.Bytes(),
ReplicationKey: 10,
MasterKey: acc.PeerKey,
})
require.NoError(t, err)
require.NotNil(t, sp)
// initializing space
spc, err := fx.spaceService.NewSpace(ctx, sp)
require.NoError(t, err)
require.NotNil(t, spc)
// adding space to tree manager
fx.treeManager.space = spc
err = spc.Init(ctx)
require.NoError(t, err)
var ids []string
for i := 0; i < totalObjs; i++ {
// creating a tree
bytes := make([]byte, 32)
rand.Read(bytes)
doc, err := spc.CreateTree(ctx, objecttree.ObjectTreeCreatePayload{
PrivKey: acc.SignKey,
ChangeType: "some",
SpaceId: spc.Id(),
IsEncrypted: false,
Seed: bytes,
Timestamp: time.Now().Unix(),
})
require.NoError(t, err)
tr, err := spc.PutTree(ctx, doc, nil)
require.NoError(t, err)
ids = append(ids, tr.Id())
tr.Close()
}
// deleting trees
for _, id := range ids {
err = spc.DeleteTree(ctx, id)
require.NoError(t, err)
}
time.Sleep(3 * time.Second)
spc.Close()
require.Equal(t, len(ids), len(fx.treeManager.deletedIds))
}
func createTree(t *testing.T, ctx context.Context, spc Space, acc *accountdata.AccountKeys) string {
bytes := make([]byte, 32)
rand.Read(bytes)
doc, err := spc.CreateTree(ctx, objecttree.ObjectTreeCreatePayload{
PrivKey: acc.SignKey,
ChangeType: "some",
SpaceId: spc.Id(),
IsEncrypted: false,
Seed: bytes,
Timestamp: time.Now().Unix(),
})
require.NoError(t, err)
tr, err := spc.PutTree(ctx, doc, nil)
require.NoError(t, err)
tr.Close()
return tr.Id()
}
func TestSpaceDeleteIdsIncorrectSnapshot(t *testing.T) {
fx := newFixture(t)
acc := fx.account.Account()
rk := crypto.NewAES()
ctx := context.Background()
totalObjs := 1500
partialObjs := 300
// creating space
sp, err := fx.spaceService.CreateSpace(ctx, SpaceCreatePayload{
SigningKey: acc.SignKey,
SpaceType: "type",
ReadKey: rk.Bytes(),
ReplicationKey: 10,
MasterKey: acc.PeerKey,
})
require.NoError(t, err)
require.NotNil(t, sp)
// initializing space
spc, err := fx.spaceService.NewSpace(ctx, sp)
require.NoError(t, err)
require.NotNil(t, spc)
// adding space to tree manager
fx.treeManager.space = spc
err = spc.Init(ctx)
require.NoError(t, err)
settingsObject := spc.(*space).settingsObject
var ids []string
for i := 0; i < totalObjs; i++ {
id := createTree(t, ctx, spc, acc)
ids = append(ids, id)
}
// copying storage, so we will have all the trees locally
inmemory := spc.Storage().(*commonStorage).SpaceStorage.(*spacestorage.InMemorySpaceStorage)
storageCopy := inmemory.CopyStorage()
treesCopy := inmemory.AllTrees()
// deleting trees
for _, id := range ids {
err = spc.DeleteTree(ctx, id)
require.NoError(t, err)
}
mapIds := map[string]struct{}{}
for _, id := range ids[:partialObjs] {
mapIds[id] = struct{}{}
}
// adding snapshot that breaks the state
err = addIncorrectSnapshot(settingsObject, acc, mapIds, ids[partialObjs])
require.NoError(t, err)
// copying the contents of the settings tree
treesCopy[settingsObject.Id()] = settingsObject.Storage()
storageCopy.SetTrees(treesCopy)
spc.Close()
time.Sleep(100 * time.Millisecond)
// now we replace the storage, so the trees are back, but the settings object says that they are deleted
fx.storageProvider.(*spacestorage.InMemorySpaceStorageProvider).SetStorage(storageCopy)
spc, err = fx.spaceService.NewSpace(ctx, sp)
require.NoError(t, err)
require.NotNil(t, spc)
fx.treeManager.space = spc
fx.treeManager.deletedIds = nil
err = spc.Init(ctx)
require.NoError(t, err)
// waiting until everything is deleted
time.Sleep(3 * time.Second)
require.Equal(t, len(ids), len(fx.treeManager.deletedIds))
// checking that new snapshot will contain all the changes
settingsObject = spc.(*space).settingsObject
settings.DoSnapshot = func(treeLen int) bool {
return true
}
id := createTree(t, ctx, spc, acc)
err = spc.DeleteTree(ctx, id)
require.NoError(t, err)
delIds := settingsObject.Root().Model.(*spacesyncproto.SettingsData).Snapshot.DeletedIds
require.Equal(t, totalObjs+1, len(delIds))
}
func TestSpaceDeleteIdsMarkDeleted(t *testing.T) {
fx := newFixture(t)
acc := fx.account.Account()
rk := crypto.NewAES()
ctx := context.Background()
totalObjs := 1500
// creating space
sp, err := fx.spaceService.CreateSpace(ctx, SpaceCreatePayload{
SigningKey: acc.SignKey,
SpaceType: "type",
ReadKey: rk.Bytes(),
ReplicationKey: 10,
MasterKey: acc.PeerKey,
})
require.NoError(t, err)
require.NotNil(t, sp)
// initializing space
spc, err := fx.spaceService.NewSpace(ctx, sp)
require.NoError(t, err)
require.NotNil(t, spc)
// adding space to tree manager
fx.treeManager.space = spc
err = spc.Init(ctx)
require.NoError(t, err)
settingsObject := spc.(*space).settingsObject
var ids []string
for i := 0; i < totalObjs; i++ {
id := createTree(t, ctx, spc, acc)
ids = append(ids, id)
}
// copying storage, so we will have the same contents, except for empty trees
inmemory := spc.Storage().(*commonStorage).SpaceStorage.(*spacestorage.InMemorySpaceStorage)
storageCopy := inmemory.CopyStorage()
// deleting trees, this will prepare the document to have all the deletion changes
for _, id := range ids {
err = spc.DeleteTree(ctx, id)
require.NoError(t, err)
}
treesMap := map[string]treestorage.TreeStorage{}
// copying the contents of the settings tree
treesMap[settingsObject.Id()] = settingsObject.Storage()
storageCopy.SetTrees(treesMap)
spc.Close()
time.Sleep(100 * time.Millisecond)
// now we replace the storage, so the trees are back, but the settings object says that they are deleted
fx.storageProvider.(*spacestorage.InMemorySpaceStorageProvider).SetStorage(storageCopy)
spc, err = fx.spaceService.NewSpace(ctx, sp)
require.NoError(t, err)
require.NotNil(t, spc)
fx.treeManager.space = spc
fx.treeManager.deletedIds = nil
fx.treeManager.markedIds = nil
err = spc.Init(ctx)
require.NoError(t, err)
// waiting until everything is deleted
time.Sleep(3 * time.Second)
require.Equal(t, len(ids), len(fx.treeManager.markedIds))
require.Zero(t, len(fx.treeManager.deletedIds))
}
+64 -98
View File
@@ -2,18 +2,16 @@ package headsync
import (
"context"
"fmt"
"github.com/anyproto/any-sync/app/ldiff"
"github.com/anyproto/any-sync/app/logger"
"github.com/anyproto/any-sync/commonspace/credentialprovider"
"github.com/anyproto/any-sync/commonspace/object/treemanager"
"github.com/anyproto/any-sync/commonspace/peermanager"
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
"github.com/anyproto/any-sync/commonspace/spacestorage"
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
"github.com/anyproto/any-sync/commonspace/syncstatus"
"github.com/anyproto/any-sync/net/peer"
"github.com/anyproto/any-sync/net/rpc/rpcerr"
"github.com/anytypeio/any-sync/app/ldiff"
"github.com/anytypeio/any-sync/commonspace/confconnector"
"github.com/anytypeio/any-sync/commonspace/object/tree/synctree"
"github.com/anytypeio/any-sync/commonspace/object/treegetter"
"github.com/anytypeio/any-sync/commonspace/settings/deletionstate"
"github.com/anytypeio/any-sync/commonspace/spacestorage"
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
"github.com/anytypeio/any-sync/commonspace/syncstatus"
"github.com/anytypeio/any-sync/net/peer"
"github.com/anytypeio/any-sync/net/rpc/rpcerr"
"go.uber.org/zap"
"time"
)
@@ -22,51 +20,45 @@ type DiffSyncer interface {
Sync(ctx context.Context) error
RemoveObjects(ids []string)
UpdateHeads(id string, heads []string)
Init(deletionState settingsstate.ObjectDeletionState)
Close() error
Init(deletionState deletionstate.DeletionState)
}
func newDiffSyncer(
spaceId string,
diff ldiff.Diff,
peerManager peermanager.PeerManager,
cache treemanager.TreeManager,
confConnector confconnector.ConfConnector,
cache treegetter.TreeGetter,
storage spacestorage.SpaceStorage,
clientFactory spacesyncproto.ClientFactory,
syncStatus syncstatus.StatusUpdater,
credentialProvider credentialprovider.CredentialProvider,
log logger.CtxLogger) DiffSyncer {
log *zap.Logger) DiffSyncer {
return &diffSyncer{
diff: diff,
spaceId: spaceId,
treeManager: cache,
storage: storage,
peerManager: peerManager,
clientFactory: clientFactory,
credentialProvider: credentialProvider,
log: log,
syncStatus: syncStatus,
diff: diff,
spaceId: spaceId,
cache: cache,
storage: storage,
confConnector: confConnector,
clientFactory: clientFactory,
log: log,
syncStatus: syncStatus,
}
}
type diffSyncer struct {
spaceId string
diff ldiff.Diff
peerManager peermanager.PeerManager
treeManager treemanager.TreeManager
storage spacestorage.SpaceStorage
clientFactory spacesyncproto.ClientFactory
log logger.CtxLogger
deletionState settingsstate.ObjectDeletionState
credentialProvider credentialprovider.CredentialProvider
syncStatus syncstatus.StatusUpdater
treeSyncer treemanager.TreeSyncer
spaceId string
diff ldiff.Diff
confConnector confconnector.ConfConnector
cache treegetter.TreeGetter
storage spacestorage.SpaceStorage
clientFactory spacesyncproto.ClientFactory
log *zap.Logger
deletionState deletionstate.DeletionState
syncStatus syncstatus.StatusUpdater
}
func (d *diffSyncer) Init(deletionState settingsstate.ObjectDeletionState) {
func (d *diffSyncer) Init(deletionState deletionstate.DeletionState) {
d.deletionState = deletionState
d.deletionState.AddObserver(d.RemoveObjects)
d.treeSyncer = d.treeManager.NewTreeSyncer(d.spaceId, d.treeManager)
}
func (d *diffSyncer) RemoveObjects(ids []string) {
@@ -92,29 +84,22 @@ func (d *diffSyncer) UpdateHeads(id string, heads []string) {
}
func (d *diffSyncer) Sync(ctx context.Context) error {
// TODO: split diffsyncer into components
st := time.Now()
// diffing with responsible peers according to configuration
peers, err := d.peerManager.GetResponsiblePeers(ctx)
peers, err := d.confConnector.GetResponsiblePeers(ctx, d.spaceId)
if err != nil {
return err
}
var peerIds = make([]string, 0, len(peers))
for _, p := range peers {
peerIds = append(peerIds, p.Id())
}
d.log.DebugCtx(ctx, "start diffsync", zap.Strings("peerIds", peerIds))
for _, p := range peers {
if err = d.syncWithPeer(peer.CtxWithPeerId(ctx, p.Id()), p); err != nil {
d.log.ErrorCtx(ctx, "can't sync with peer", zap.String("peer", p.Id()), zap.Error(err))
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.InfoCtx(ctx, "diff done", zap.String("spaceId", d.spaceId), zap.Duration("dur", time.Since(st)))
d.log.Info("synced", zap.String("spaceId", d.spaceId), zap.Duration("dur", time.Since(st)))
return nil
}
func (d *diffSyncer) syncWithPeer(ctx context.Context, p peer.Peer) (err error) {
ctx = logger.CtxWithFields(ctx, zap.String("peerId", p.Id()))
var (
cl = d.clientFactory.Client(p)
rdiff = NewRemoteDiff(d.spaceId, cl)
@@ -124,40 +109,50 @@ func (d *diffSyncer) syncWithPeer(ctx context.Context, p peer.Peer) (err error)
newIds, changedIds, removedIds, err := d.diff.Diff(ctx, rdiff)
err = rpcerr.Unwrap(err)
if err != nil && err != spacesyncproto.ErrSpaceMissing {
if err == spacesyncproto.ErrSpaceIsDeleted {
d.log.Debug("got space deleted while syncing")
d.treeSyncer.SyncAll(ctx, p.Id(), []string{d.storage.SpaceSettingsId()}, nil)
}
d.syncStatus.SetNodesOnline(p.Id(), false)
return fmt.Errorf("diff error: %v", err)
return err
}
d.syncStatus.SetNodesOnline(p.Id(), true)
if err == spacesyncproto.ErrSpaceMissing {
return d.sendPushSpaceRequest(ctx, p.Id(), cl)
return d.sendPushSpaceRequest(ctx, cl)
}
totalLen := len(newIds) + len(changedIds) + len(removedIds)
// not syncing ids which were removed through settings document
missingIds := d.deletionState.Filter(newIds)
existingIds := append(d.deletionState.Filter(removedIds), d.deletionState.Filter(changedIds)...)
filteredIds := d.deletionState.FilterJoin(newIds, changedIds, removedIds)
d.syncStatus.RemoveAllExcept(p.Id(), existingIds, stateCounter)
d.syncStatus.RemoveAllExcept(p.Id(), filteredIds, stateCounter)
ctx = peer.CtxWithPeerId(ctx, p.Id())
d.pingTreesInCache(ctx, filteredIds)
err = d.treeSyncer.SyncAll(ctx, p.Id(), existingIds, missingIds)
if err != nil {
return err
}
d.log.Info("sync done:", zap.Int("newIds", len(newIds)),
zap.Int("changedIds", len(changedIds)),
zap.Int("removedIds", len(removedIds)),
zap.Int("already deleted ids", totalLen-len(existingIds)-len(missingIds)),
zap.String("peerId", p.Id()),
)
zap.Int("already deleted ids", totalLen-len(filteredIds)))
return
}
func (d *diffSyncer) sendPushSpaceRequest(ctx context.Context, peerId string, cl spacesyncproto.DRPCSpaceSyncClient) (err error) {
func (d *diffSyncer) pingTreesInCache(ctx context.Context, trees []string) {
for _, tId := range trees {
tree, err := d.cache.GetTree(ctx, d.spaceId, tId)
if err != nil {
continue
}
syncTree, ok := tree.(synctree.SyncTree)
if !ok {
continue
}
// the idea why we call it directly is that if we try to get it from cache
// it may be already there (i.e. loaded)
// and build func will not be called, thus we won't sync the tree
// therefore we just do it manually
syncTree.Ping()
}
}
func (d *diffSyncer) sendPushSpaceRequest(ctx context.Context, cl spacesyncproto.DRPCSpaceSyncClient) (err error) {
aclStorage, err := d.storage.AclStorage()
if err != nil {
return
@@ -182,10 +177,6 @@ func (d *diffSyncer) sendPushSpaceRequest(ctx context.Context, peerId string, cl
return
}
cred, err := d.credentialProvider.GetCredential(ctx, header)
if err != nil {
return
}
spacePayload := &spacesyncproto.SpacePayload{
SpaceHeader: header,
AclPayload: root.Payload,
@@ -194,32 +185,7 @@ func (d *diffSyncer) sendPushSpaceRequest(ctx context.Context, peerId string, cl
SpaceSettingsPayloadId: spaceSettingsRoot.Id,
}
_, err = cl.SpacePush(ctx, &spacesyncproto.SpacePushRequest{
Payload: spacePayload,
Credential: cred,
Payload: spacePayload,
})
if err != nil {
return
}
if e := d.subscribe(ctx, peerId); e != nil {
d.log.WarnCtx(ctx, "error subscribing for space", zap.Error(e))
}
return
}
func (d *diffSyncer) subscribe(ctx context.Context, peerId string) (err error) {
var msg = &spacesyncproto.SpaceSubscription{
SpaceIds: []string{d.spaceId},
Action: spacesyncproto.SpaceSubscriptionAction_Subscribe,
}
payload, err := msg.Marshal()
if err != nil {
return
}
return d.peerManager.SendPeer(ctx, peerId, &spacesyncproto.ObjectSyncMessage{
Payload: payload,
})
}
func (d *diffSyncer) Close() error {
return d.treeSyncer.Close()
}
+37 -72
View File
@@ -1,25 +1,22 @@
package headsync
import (
"bytes"
"context"
"fmt"
"github.com/anyproto/any-sync/app/ldiff"
"github.com/anyproto/any-sync/app/ldiff/mock_ldiff"
"github.com/anyproto/any-sync/app/logger"
"github.com/anyproto/any-sync/commonspace/credentialprovider/mock_credentialprovider"
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage/mock_liststorage"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
mock_treestorage "github.com/anyproto/any-sync/commonspace/object/tree/treestorage/mock_treestorage"
"github.com/anyproto/any-sync/commonspace/object/treemanager/mock_treemanager"
"github.com/anyproto/any-sync/commonspace/peermanager/mock_peermanager"
"github.com/anyproto/any-sync/commonspace/settings/settingsstate/mock_settingsstate"
"github.com/anyproto/any-sync/commonspace/spacestorage/mock_spacestorage"
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
"github.com/anyproto/any-sync/commonspace/spacesyncproto/mock_spacesyncproto"
"github.com/anyproto/any-sync/commonspace/syncstatus"
"github.com/anyproto/any-sync/net/peer"
"github.com/anytypeio/any-sync/app/ldiff"
"github.com/anytypeio/any-sync/app/ldiff/mock_ldiff"
"github.com/anytypeio/any-sync/app/logger"
"github.com/anytypeio/any-sync/commonspace/confconnector/mock_confconnector"
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
mock_treestorage "github.com/anytypeio/any-sync/commonspace/object/tree/treestorage/mock_treestorage"
"github.com/anytypeio/any-sync/commonspace/object/treegetter/mock_treegetter"
"github.com/anytypeio/any-sync/commonspace/settings/deletionstate/mock_deletionstate"
"github.com/anytypeio/any-sync/commonspace/spacestorage/mock_spacestorage"
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
"github.com/anytypeio/any-sync/commonspace/spacesyncproto/mock_spacesyncproto"
"github.com/anytypeio/any-sync/commonspace/syncstatus"
"github.com/anytypeio/any-sync/net/peer"
"github.com/golang/mock/gomock"
"github.com/libp2p/go-libp2p/core/sec"
"github.com/stretchr/testify/require"
@@ -32,7 +29,6 @@ type pushSpaceRequestMatcher struct {
spaceId string
aclRootId string
settingsId string
credential []byte
spaceHeader *spacesyncproto.RawSpaceHeaderWithId
}
@@ -42,7 +38,7 @@ func (p pushSpaceRequestMatcher) Matches(x interface{}) bool {
return false
}
return res.Payload.AclPayloadId == p.aclRootId && res.Payload.SpaceHeader == p.spaceHeader && res.Payload.SpaceSettingsPayloadId == p.settingsId && bytes.Equal(p.credential, res.Credential)
return res.Payload.AclPayloadId == p.aclRootId && res.Payload.SpaceHeader == p.spaceHeader && res.Payload.SpaceSettingsPayloadId == p.settingsId
}
func (p pushSpaceRequestMatcher) String() string {
@@ -51,14 +47,6 @@ func (p pushSpaceRequestMatcher) String() string {
type mockPeer struct{}
func (m mockPeer) Addr() string {
return ""
}
func (m mockPeer) TryClose(objectTTL time.Duration) (res bool, err error) {
return true, m.Close()
}
func (m mockPeer) Id() string {
return "mockId"
}
@@ -94,13 +82,11 @@ func newPushSpaceRequestMatcher(
spaceId string,
aclRootId string,
settingsId string,
credential []byte,
spaceHeader *spacesyncproto.RawSpaceHeaderWithId) *pushSpaceRequestMatcher {
return &pushSpaceRequestMatcher{
spaceId: spaceId,
aclRootId: aclRootId,
settingsId: settingsId,
credential: credential,
spaceHeader: spaceHeader,
}
}
@@ -112,42 +98,40 @@ func TestDiffSyncer_Sync(t *testing.T) {
defer ctrl.Finish()
diffMock := mock_ldiff.NewMockDiff(ctrl)
peerManagerMock := mock_peermanager.NewMockPeerManager(ctrl)
cacheMock := mock_treemanager.NewMockTreeManager(ctrl)
connectorMock := mock_confconnector.NewMockConfConnector(ctrl)
cacheMock := mock_treegetter.NewMockTreeGetter(ctrl)
stMock := mock_spacestorage.NewMockSpaceStorage(ctrl)
clientMock := mock_spacesyncproto.NewMockDRPCSpaceSyncClient(ctrl)
factory := spacesyncproto.ClientFactoryFunc(func(cc drpc.Conn) spacesyncproto.DRPCSpaceSyncClient {
return clientMock
})
treeSyncerMock := mock_treemanager.NewMockTreeSyncer(ctrl)
credentialProvider := mock_credentialprovider.NewMockCredentialProvider(ctrl)
delState := mock_settingsstate.NewMockObjectDeletionState(ctrl)
delState := mock_deletionstate.NewMockDeletionState(ctrl)
spaceId := "spaceId"
aclRootId := "aclRootId"
l := logger.NewNamed(spaceId)
diffSyncer := newDiffSyncer(spaceId, diffMock, peerManagerMock, cacheMock, stMock, factory, syncstatus.NewNoOpSyncStatus(), credentialProvider, l)
diffSyncer := newDiffSyncer(spaceId, diffMock, connectorMock, cacheMock, stMock, factory, syncstatus.NewNoOpSyncStatus(), l)
delState.EXPECT().AddObserver(gomock.Any())
cacheMock.EXPECT().NewTreeSyncer(spaceId, gomock.Any()).Return(treeSyncerMock)
diffSyncer.Init(delState)
t.Run("diff syncer sync", func(t *testing.T) {
mPeer := mockPeer{}
peerManagerMock.EXPECT().
GetResponsiblePeers(gomock.Any()).
Return([]peer.Peer{mPeer}, nil)
connectorMock.EXPECT().
GetResponsiblePeers(gomock.Any(), spaceId).
Return([]peer.Peer{mockPeer{}}, nil)
diffMock.EXPECT().
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(spaceId, clientMock))).
Return([]string{"new"}, []string{"changed"}, nil, nil)
delState.EXPECT().Filter([]string{"new"}).Return([]string{"new"}).Times(1)
delState.EXPECT().Filter([]string{"changed"}).Return([]string{"changed"}).Times(1)
delState.EXPECT().Filter(nil).Return(nil).Times(1)
treeSyncerMock.EXPECT().SyncAll(gomock.Any(), mPeer.Id(), []string{"changed"}, []string{"new"}).Return(nil)
delState.EXPECT().FilterJoin(gomock.Any()).Return([]string{"new", "changed"})
for _, arg := range []string{"new", "changed"} {
cacheMock.EXPECT().
GetTree(gomock.Any(), spaceId, arg).
Return(nil, nil)
}
require.NoError(t, diffSyncer.Sync(ctx))
})
t.Run("diff syncer sync conf error", func(t *testing.T) {
peerManagerMock.EXPECT().
GetResponsiblePeers(gomock.Any()).
connectorMock.EXPECT().
GetResponsiblePeers(gomock.Any(), spaceId).
Return(nil, fmt.Errorf("some error"))
require.Error(t, diffSyncer.Sync(ctx))
@@ -176,7 +160,7 @@ func TestDiffSyncer_Sync(t *testing.T) {
})
t.Run("diff syncer sync space missing", func(t *testing.T) {
aclStorageMock := mock_liststorage.NewMockListStorage(ctrl)
aclStorageMock := mock_treestorage.NewMockListStorage(ctrl)
settingsStorage := mock_treestorage.NewMockTreeStorage(ctrl)
settingsId := "settingsId"
aclRoot := &aclrecordproto.RawAclRecordWithId{
@@ -187,10 +171,9 @@ func TestDiffSyncer_Sync(t *testing.T) {
}
spaceHeader := &spacesyncproto.RawSpaceHeaderWithId{}
spaceSettingsId := "spaceSettingsId"
credential := []byte("credential")
peerManagerMock.EXPECT().
GetResponsiblePeers(gomock.Any()).
connectorMock.EXPECT().
GetResponsiblePeers(gomock.Any(), spaceId).
Return([]peer.Peer{mockPeer{}}, nil)
diffMock.EXPECT().
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(spaceId, clientMock))).
@@ -205,20 +188,16 @@ func TestDiffSyncer_Sync(t *testing.T) {
aclStorageMock.EXPECT().
Root().
Return(aclRoot, nil)
credentialProvider.EXPECT().
GetCredential(gomock.Any(), spaceHeader).
Return(credential, nil)
clientMock.EXPECT().
SpacePush(gomock.Any(), newPushSpaceRequestMatcher(spaceId, aclRootId, settingsId, credential, spaceHeader)).
SpacePush(gomock.Any(), newPushSpaceRequestMatcher(spaceId, aclRootId, settingsId, spaceHeader)).
Return(nil, nil)
peerManagerMock.EXPECT().SendPeer(gomock.Any(), "mockId", gomock.Any())
require.NoError(t, diffSyncer.Sync(ctx))
})
t.Run("diff syncer sync unexpected", func(t *testing.T) {
peerManagerMock.EXPECT().
GetResponsiblePeers(gomock.Any()).
t.Run("diff syncer sync other error", func(t *testing.T) {
connectorMock.EXPECT().
GetResponsiblePeers(gomock.Any(), spaceId).
Return([]peer.Peer{mockPeer{}}, nil)
diffMock.EXPECT().
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(spaceId, clientMock))).
@@ -226,18 +205,4 @@ func TestDiffSyncer_Sync(t *testing.T) {
require.NoError(t, diffSyncer.Sync(ctx))
})
t.Run("diff syncer sync space is deleted error", func(t *testing.T) {
mPeer := mockPeer{}
peerManagerMock.EXPECT().
GetResponsiblePeers(gomock.Any()).
Return([]peer.Peer{mPeer}, nil)
diffMock.EXPECT().
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(spaceId, clientMock))).
Return(nil, nil, nil, spacesyncproto.ErrSpaceIsDeleted)
stMock.EXPECT().SpaceSettingsId().Return("settingsId")
treeSyncerMock.EXPECT().SyncAll(gomock.Any(), mPeer.Id(), []string{"settingsId"}, nil).Return(nil)
require.NoError(t, diffSyncer.Sync(ctx))
})
}
+30 -60
View File
@@ -1,24 +1,18 @@
//go:generate mockgen -destination mock_headsync/mock_headsync.go github.com/anyproto/any-sync/commonspace/headsync DiffSyncer
//go:generate mockgen -destination mock_headsync/mock_headsync.go github.com/anytypeio/any-sync/commonspace/headsync DiffSyncer
package headsync
import (
"context"
"github.com/anyproto/any-sync/app/ldiff"
"github.com/anyproto/any-sync/app/logger"
"github.com/anyproto/any-sync/commonspace/credentialprovider"
"github.com/anyproto/any-sync/commonspace/object/treemanager"
"github.com/anyproto/any-sync/commonspace/peermanager"
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
"github.com/anyproto/any-sync/commonspace/spacestorage"
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
"github.com/anyproto/any-sync/commonspace/syncstatus"
"github.com/anyproto/any-sync/net/peer"
"github.com/anyproto/any-sync/nodeconf"
"github.com/anyproto/any-sync/util/periodicsync"
"github.com/anytypeio/any-sync/app/ldiff"
"github.com/anytypeio/any-sync/commonspace/confconnector"
"github.com/anytypeio/any-sync/commonspace/object/treegetter"
"github.com/anytypeio/any-sync/commonspace/settings/deletionstate"
"github.com/anytypeio/any-sync/commonspace/spacestorage"
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
"github.com/anytypeio/any-sync/commonspace/syncstatus"
"github.com/anytypeio/any-sync/util/periodicsync"
"go.uber.org/zap"
"golang.org/x/exp/slices"
"strings"
"sync/atomic"
"time"
)
@@ -28,7 +22,7 @@ type TreeHeads struct {
}
type HeadSync interface {
Init(objectIds []string, deletionState settingsstate.ObjectDeletionState)
Init(objectIds []string, deletionState deletionstate.DeletionState)
UpdateHeads(id string, heads []string)
HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error)
@@ -40,73 +34,49 @@ type HeadSync interface {
}
type headSync struct {
spaceId string
periodicSync periodicsync.PeriodicSync
storage spacestorage.SpaceStorage
diff ldiff.Diff
log logger.CtxLogger
syncer DiffSyncer
configuration nodeconf.NodeConf
spaceIsDeleted *atomic.Bool
spaceId string
periodicSync periodicsync.PeriodicSync
storage spacestorage.SpaceStorage
diff ldiff.Diff
log *zap.Logger
syncer DiffSyncer
syncPeriod int
}
func NewHeadSync(
spaceId string,
spaceIsDeleted *atomic.Bool,
syncPeriod int,
configuration nodeconf.NodeConf,
storage spacestorage.SpaceStorage,
peerManager peermanager.PeerManager,
cache treemanager.TreeManager,
confConnector confconnector.ConfConnector,
cache treegetter.TreeGetter,
syncStatus syncstatus.StatusUpdater,
credentialProvider credentialprovider.CredentialProvider,
log logger.CtxLogger) HeadSync {
log *zap.Logger) HeadSync {
diff := ldiff.New(16, 16)
l := log.With(zap.String("spaceId", spaceId))
factory := spacesyncproto.ClientFactoryFunc(spacesyncproto.NewDRPCSpaceSyncClient)
syncer := newDiffSyncer(spaceId, diff, peerManager, cache, storage, factory, syncStatus, credentialProvider, l)
sync := func(ctx context.Context) (err error) {
// for clients cancelling the sync process
if spaceIsDeleted.Load() && !configuration.IsResponsible(spaceId) {
return spacesyncproto.ErrSpaceIsDeleted
}
return syncer.Sync(ctx)
}
periodicSync := periodicsync.NewPeriodicSync(syncPeriod, time.Minute, sync, l)
syncer := newDiffSyncer(spaceId, diff, confConnector, cache, storage, factory, syncStatus, l)
periodicSync := periodicsync.NewPeriodicSync(syncPeriod, time.Minute, syncer.Sync, l)
return &headSync{
spaceId: spaceId,
storage: storage,
syncer: syncer,
periodicSync: periodicSync,
diff: diff,
log: log,
syncPeriod: syncPeriod,
configuration: configuration,
spaceIsDeleted: spaceIsDeleted,
spaceId: spaceId,
storage: storage,
syncer: syncer,
periodicSync: periodicSync,
diff: diff,
log: log,
syncPeriod: syncPeriod,
}
}
func (d *headSync) Init(objectIds []string, deletionState settingsstate.ObjectDeletionState) {
func (d *headSync) Init(objectIds []string, deletionState deletionstate.DeletionState) {
d.fillDiff(objectIds)
d.syncer.Init(deletionState)
d.periodicSync.Run()
}
func (d *headSync) HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error) {
if d.spaceIsDeleted.Load() {
peerId, err := peer.CtxPeerId(ctx)
if err != nil {
return nil, err
}
// stop receiving all request for sync from clients
if !slices.Contains(d.configuration.NodeIds(d.spaceId), peerId) {
return nil, spacesyncproto.ErrSpaceIsDeleted
}
}
return HandleRangeRequest(ctx, d.diff, req)
}
@@ -136,7 +106,7 @@ func (d *headSync) RemoveObjects(ids []string) {
func (d *headSync) Close() (err error) {
d.periodicSync.Close()
return d.syncer.Close()
return nil
}
func (d *headSync) fillDiff(objectIds []string) {
+9 -10
View File
@@ -1,14 +1,14 @@
package headsync
import (
"github.com/anyproto/any-sync/app/ldiff"
"github.com/anyproto/any-sync/app/ldiff/mock_ldiff"
"github.com/anyproto/any-sync/app/logger"
"github.com/anyproto/any-sync/commonspace/headsync/mock_headsync"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage/mock_treestorage"
"github.com/anyproto/any-sync/commonspace/settings/settingsstate/mock_settingsstate"
"github.com/anyproto/any-sync/commonspace/spacestorage/mock_spacestorage"
"github.com/anyproto/any-sync/util/periodicsync/mock_periodicsync"
"github.com/anytypeio/any-sync/app/ldiff"
"github.com/anytypeio/any-sync/app/ldiff/mock_ldiff"
"github.com/anytypeio/any-sync/app/logger"
"github.com/anytypeio/any-sync/commonspace/headsync/mock_headsync"
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage/mock_treestorage"
"github.com/anytypeio/any-sync/commonspace/settings/deletionstate/mock_deletionstate"
"github.com/anytypeio/any-sync/commonspace/spacestorage/mock_spacestorage"
"github.com/anytypeio/any-sync/util/periodicsync/mock_periodicsync"
"github.com/golang/mock/gomock"
"testing"
)
@@ -24,7 +24,7 @@ func TestDiffService(t *testing.T) {
treeStorageMock := mock_treestorage.NewMockTreeStorage(ctrl)
diffMock := mock_ldiff.NewMockDiff(ctrl)
syncer := mock_headsync.NewMockDiffSyncer(ctrl)
delState := mock_settingsstate.NewMockObjectDeletionState(ctrl)
delState := mock_deletionstate.NewMockDeletionState(ctrl)
syncPeriod := 1
initId := "initId"
@@ -65,7 +65,6 @@ func TestDiffService(t *testing.T) {
t.Run("close", func(t *testing.T) {
pSyncMock.EXPECT().Close()
syncer.EXPECT().Close()
service.Close()
})
}
@@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/anyproto/any-sync/commonspace/headsync (interfaces: DiffSyncer)
// Source: github.com/anytypeio/any-sync/commonspace/headsync (interfaces: DiffSyncer)
// Package mock_headsync is a generated GoMock package.
package mock_headsync
@@ -8,7 +8,7 @@ import (
context "context"
reflect "reflect"
settingsstate "github.com/anyproto/any-sync/commonspace/settings/settingsstate"
deletionstate "github.com/anytypeio/any-sync/commonspace/settings/deletionstate"
gomock "github.com/golang/mock/gomock"
)
@@ -35,22 +35,8 @@ func (m *MockDiffSyncer) EXPECT() *MockDiffSyncerMockRecorder {
return m.recorder
}
// Close mocks base method.
func (m *MockDiffSyncer) Close() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Close")
ret0, _ := ret[0].(error)
return ret0
}
// Close indicates an expected call of Close.
func (mr *MockDiffSyncerMockRecorder) Close() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockDiffSyncer)(nil).Close))
}
// Init mocks base method.
func (m *MockDiffSyncer) Init(arg0 settingsstate.ObjectDeletionState) {
func (m *MockDiffSyncer) Init(arg0 deletionstate.DeletionState) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Init", arg0)
}
+2 -2
View File
@@ -2,8 +2,8 @@ package headsync
import (
"context"
"github.com/anyproto/any-sync/app/ldiff"
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
"github.com/anytypeio/any-sync/app/ldiff"
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
)
type Client interface {
+2 -2
View File
@@ -3,8 +3,8 @@ package headsync
import (
"context"
"fmt"
"github.com/anyproto/any-sync/app/ldiff"
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
"github.com/anytypeio/any-sync/app/ldiff"
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
+8 -30
View File
@@ -1,36 +1,14 @@
package accountdata
import (
"crypto/rand"
"github.com/anyproto/any-sync/util/crypto"
"github.com/anytypeio/any-sync/util/keys/asymmetric/encryptionkey"
"github.com/anytypeio/any-sync/util/keys/asymmetric/signingkey"
)
type AccountKeys struct {
PeerKey crypto.PrivKey
SignKey crypto.PrivKey
PeerId string
}
func New(peerKey, signKey crypto.PrivKey) *AccountKeys {
return &AccountKeys{
PeerKey: peerKey,
SignKey: signKey,
PeerId: peerKey.GetPublic().PeerId(),
}
}
func NewRandom() (*AccountKeys, error) {
peerKey, _, err := crypto.GenerateEd25519Key(rand.Reader)
if err != nil {
return nil, err
}
signKey, _, err := crypto.GenerateEd25519Key(rand.Reader)
if err != nil {
return nil, err
}
return &AccountKeys{
PeerKey: peerKey,
SignKey: signKey,
PeerId: peerKey.GetPublic().PeerId(),
}, nil
type AccountData struct { // TODO: create a convenient constructor for this
Identity []byte // public key
PeerKey signingkey.PrivKey
SignKey signingkey.PrivKey
EncKey encryptionkey.PrivKey
PeerId string
}
@@ -0,0 +1,12 @@
package aclrecordproto
import (
"github.com/anytypeio/any-sync/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
@@ -18,17 +18,18 @@ message AclRecord {
string prevId = 1;
bytes identity = 2;
bytes data = 3;
string readKeyId = 4;
uint64 currentReadKeyHash = 4;
int64 timestamp = 5;
}
message AclRoot {
bytes identity = 1;
bytes masterKey = 2;
bytes encryptionKey = 2;
string spaceId = 3;
bytes encryptedReadKey = 4;
int64 timestamp = 5;
bytes identitySignature = 6;
string derivationScheme = 5;
uint64 currentReadKeyHash = 6;
int64 timestamp = 7;
}
message AclContentValue {
@@ -46,33 +47,37 @@ message AclData {
}
message AclState {
repeated string readKeyIds = 1;
repeated uint64 readKeyHashes = 1;
repeated AclUserState userStates = 2;
map<string, AclUserInvite> invites = 3;
}
message AclUserState {
bytes identity = 1;
AclUserPermissions permissions = 2;
bytes encryptionKey = 2;
AclUserPermissions permissions = 3;
}
message AclUserAdd {
bytes identity = 1;
repeated bytes encryptedReadKeys = 2;
AclUserPermissions permissions = 3;
bytes encryptionKey = 2;
repeated bytes encryptedReadKeys = 3;
AclUserPermissions permissions = 4;
}
message AclUserInvite {
bytes acceptPublicKey = 1;
repeated bytes encryptedReadKeys = 2;
AclUserPermissions permissions = 3;
uint64 encryptSymKeyHash = 2;
repeated bytes encryptedReadKeys = 3;
AclUserPermissions permissions = 4;
}
message AclUserJoin {
bytes identity = 1;
bytes acceptSignature = 2;
bytes acceptPubKey = 3;
repeated bytes encryptedReadKeys = 4;
bytes encryptionKey = 2;
bytes acceptSignature = 3;
bytes acceptPubKey = 4;
repeated bytes encryptedReadKeys = 5;
}
message AclUserRemove {
@@ -82,7 +87,8 @@ message AclUserRemove {
message AclReadKeyReplace {
bytes identity = 1;
bytes encryptedReadKey = 2;
bytes encryptionKey = 2;
bytes encryptedReadKey = 3;
}
message AclUserPermissionChange {
@@ -97,7 +103,7 @@ enum AclUserPermissions {
}
message AclSyncMessage {
AclSyncContentValue content = 1;
AclSyncContentValue content = 2;
}
// AclSyncContentValue provides different types for acl sync
+118 -107
View File
@@ -1,63 +1,131 @@
package list
import (
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anyproto/any-sync/util/cidutil"
"github.com/anyproto/any-sync/util/crypto"
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anytypeio/any-sync/commonspace/object/keychain"
"github.com/anytypeio/any-sync/util/cidutil"
"github.com/anytypeio/any-sync/util/keys/asymmetric/signingkey"
"github.com/anytypeio/any-sync/util/keys/symmetric"
"github.com/gogo/protobuf/proto"
"time"
)
type RootContent struct {
PrivKey crypto.PrivKey
MasterKey crypto.PrivKey
SpaceId string
EncryptedReadKey []byte
}
// remove interface
type AclRecordBuilder interface {
Unmarshall(rawIdRecord *aclrecordproto.RawAclRecordWithId) (rec *AclRecord, err error)
BuildRoot(content RootContent) (rec *aclrecordproto.RawAclRecordWithId, err error)
ConvertFromRaw(rawIdRecord *aclrecordproto.RawAclRecordWithId) (rec *AclRecord, err error)
BuildUserJoin(acceptPrivKeyBytes []byte, encSymKeyBytes []byte, state *AclState) (rec *aclrecordproto.RawAclRecord, err error)
}
type aclRecordBuilder struct {
id string
keyStorage crypto.KeyStorage
id string
keychain *keychain.Keychain
}
func NewAclRecordBuilder(id string, keyStorage crypto.KeyStorage) AclRecordBuilder {
func newAclRecordBuilder(id string, keychain *keychain.Keychain) AclRecordBuilder {
return &aclRecordBuilder{
id: id,
keyStorage: keyStorage,
id: id,
keychain: keychain,
}
}
func (a *aclRecordBuilder) Unmarshall(rawIdRecord *aclrecordproto.RawAclRecordWithId) (rec *AclRecord, err error) {
var (
rawRec = &aclrecordproto.RawAclRecord{}
pubKey crypto.PubKey
)
func (a *aclRecordBuilder) BuildUserJoin(acceptPrivKeyBytes []byte, encSymKeyBytes []byte, state *AclState) (rec *aclrecordproto.RawAclRecord, err error) {
acceptPrivKey, err := signingkey.NewSigningEd25519PrivKeyFromBytes(acceptPrivKeyBytes)
if err != nil {
return
}
acceptPubKeyBytes, err := acceptPrivKey.GetPublic().Raw()
if err != nil {
return
}
encSymKey, err := symmetric.FromBytes(encSymKeyBytes)
if err != nil {
return
}
invite, err := state.Invite(acceptPubKeyBytes)
if err != nil {
return
}
encPrivKey, signPrivKey := state.UserKeys()
var symKeys [][]byte
for _, rk := range invite.EncryptedReadKeys {
dec, err := encSymKey.Decrypt(rk)
if err != nil {
return nil, err
}
newEnc, err := encPrivKey.GetPublic().Encrypt(dec)
if err != nil {
return nil, err
}
symKeys = append(symKeys, newEnc)
}
idSignature, err := acceptPrivKey.Sign(state.Identity())
if err != nil {
return
}
encPubKeyBytes, err := encPrivKey.GetPublic().Raw()
if err != nil {
return
}
userJoin := &aclrecordproto.AclUserJoin{
Identity: state.Identity(),
EncryptionKey: encPubKeyBytes,
AcceptSignature: idSignature,
AcceptPubKey: acceptPubKeyBytes,
EncryptedReadKeys: symKeys,
}
aclData := &aclrecordproto.AclData{AclContent: []*aclrecordproto.AclContentValue{
{Value: &aclrecordproto.AclContentValue_UserJoin{UserJoin: userJoin}},
}}
marshalledJoin, err := aclData.Marshal()
if err != nil {
return
}
aclRecord := &aclrecordproto.AclRecord{
PrevId: state.LastRecordId(),
Identity: state.Identity(),
Data: marshalledJoin,
CurrentReadKeyHash: state.CurrentReadKeyHash(),
Timestamp: time.Now().UnixNano(),
}
marshalledRecord, err := aclRecord.Marshal()
if err != nil {
return
}
recSignature, err := signPrivKey.Sign(marshalledRecord)
if err != nil {
return
}
rec = &aclrecordproto.RawAclRecord{
Payload: marshalledRecord,
Signature: recSignature,
}
return
}
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
}
pubKey, err = a.keyStorage.PubKeyFromProto(aclRoot.Identity)
if err != nil {
return
}
rec = &AclRecord{
Id: rawIdRecord.Id,
ReadKeyId: rawIdRecord.Id,
Timestamp: aclRoot.Timestamp,
Signature: rawRec.Signature,
Identity: pubKey,
Model: aclRoot,
Id: rawIdRecord.Id,
CurrentReadKeyHash: aclRoot.CurrentReadKeyHash,
Timestamp: aclRoot.Timestamp,
Signature: rawRec.Signature,
Identity: aclRoot.Identity,
Model: aclRoot,
}
} else {
aclRecord := &aclrecordproto.AclRecord{}
@@ -65,63 +133,34 @@ func (a *aclRecordBuilder) Unmarshall(rawIdRecord *aclrecordproto.RawAclRecordWi
if err != nil {
return
}
pubKey, err = a.keyStorage.PubKeyFromProto(aclRecord.Identity)
if err != nil {
return
}
rec = &AclRecord{
Id: rawIdRecord.Id,
PrevId: aclRecord.PrevId,
ReadKeyId: aclRecord.ReadKeyId,
Timestamp: aclRecord.Timestamp,
Data: aclRecord.Data,
Signature: rawRec.Signature,
Identity: pubKey,
Id: rawIdRecord.Id,
PrevId: aclRecord.PrevId,
CurrentReadKeyHash: aclRecord.CurrentReadKeyHash,
Timestamp: aclRecord.Timestamp,
Data: aclRecord.Data,
Signature: rawRec.Signature,
Identity: aclRecord.Identity,
}
}
err = verifyRaw(pubKey, rawRec, rawIdRecord)
err = verifyRaw(a.keychain, rawRec, rawIdRecord, rec.Identity)
return
}
func (a *aclRecordBuilder) BuildRoot(content RootContent) (rec *aclrecordproto.RawAclRecordWithId, err error) {
rawIdentity, err := content.PrivKey.GetPublic().Raw()
if err != nil {
return
}
identity, err := content.PrivKey.GetPublic().Marshall()
if err != nil {
return
}
masterKey, err := content.MasterKey.GetPublic().Marshall()
if err != nil {
return
}
identitySignature, err := content.MasterKey.Sign(rawIdentity)
if err != nil {
return
}
var timestamp int64
if content.EncryptedReadKey != nil {
timestamp = time.Now().Unix()
}
aclRoot := &aclrecordproto.AclRoot{
Identity: identity,
SpaceId: content.SpaceId,
EncryptedReadKey: content.EncryptedReadKey,
MasterKey: masterKey,
IdentitySignature: identitySignature,
Timestamp: timestamp,
}
return marshalAclRoot(aclRoot, content.PrivKey)
}
func verifyRaw(
pubKey crypto.PubKey,
keychain *keychain.Keychain,
rawRec *aclrecordproto.RawAclRecord,
recWithId *aclrecordproto.RawAclRecordWithId) (err error) {
recWithId *aclrecordproto.RawAclRecordWithId,
identity []byte) (err error) {
identityKey, err := keychain.GetOrAdd(string(identity))
if err != nil {
return
}
// verifying signature
res, err := pubKey.Verify(rawRec.Payload, rawRec.Signature)
res, err := identityKey.Verify(rawRec.Payload, rawRec.Signature)
if err != nil {
return
}
@@ -136,31 +175,3 @@ func verifyRaw(
}
return
}
func marshalAclRoot(aclRoot *aclrecordproto.AclRoot, key crypto.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 := cidutil.NewCidFromBytes(marshalledRaw)
if err != nil {
return
}
rawWithId = &aclrecordproto.RawAclRecordWithId{
Payload: marshalledRaw,
Id: aclHeadId,
}
return
}
@@ -1,9 +1,50 @@
package list
import (
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
acllistbuilder2 "github.com/anytypeio/any-sync/commonspace/object/acl/testutils/acllistbuilder"
"github.com/anytypeio/any-sync/commonspace/object/keychain"
"github.com/anytypeio/any-sync/util/cidutil"
"github.com/anytypeio/any-sync/util/keys/asymmetric/signingkey"
"github.com/stretchr/testify/require"
"testing"
)
func TestAclRecordBuilder_BuildUserJoin(t *testing.T) {
return
st, err := acllistbuilder2.NewListStorageWithTestName("userjoinexample.yml")
require.NoError(t, err, "building storage should not result in error")
testKeychain := st.(*acllistbuilder2.AclListStorageBuilder).GetKeychain()
identity := testKeychain.GeneratedIdentities["D"]
signPrivKey := testKeychain.SigningKeysByYAMLName["D"]
encPrivKey := testKeychain.EncryptionKeysByYAMLName["D"]
acc := &accountdata.AccountData{
Identity: []byte(identity),
SignKey: signPrivKey,
EncKey: encPrivKey,
}
aclList, err := BuildAclListWithIdentity(acc, st)
require.NoError(t, err, "building acl list should be without error")
recordBuilder := newAclRecordBuilder(aclList.Id(), keychain.NewKeychain())
rk, err := testKeychain.GetKey("key.Read.EncKey").(*acllistbuilder2.SymKey).Key.Raw()
require.NoError(t, err)
privKey, err := testKeychain.GetKey("key.Sign.Onetime1").(signingkey.PrivKey).Raw()
require.NoError(t, err)
userJoin, err := recordBuilder.BuildUserJoin(privKey, rk, aclList.AclState())
require.NoError(t, err)
marshalledJoin, err := userJoin.Marshal()
require.NoError(t, err)
id, err := cidutil.NewCidFromBytes(marshalledJoin)
require.NoError(t, err)
rawRec := &aclrecordproto.RawAclRecordWithId{
Payload: marshalledJoin,
Id: id,
}
res, err := aclList.AddRawRecord(rawRec)
require.True(t, res)
require.NoError(t, err)
require.Equal(t, aclrecordproto.AclUserPermissions_Writer, aclList.AclState().UserStates()[identity].Permissions)
}
+267 -129
View File
@@ -1,16 +1,22 @@
package list
import (
"bytes"
"errors"
"fmt"
"github.com/anyproto/any-sync/app/logger"
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anyproto/any-sync/util/crypto"
"github.com/anytypeio/any-sync/app/logger"
aclrecordproto2 "github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anytypeio/any-sync/commonspace/object/keychain"
"github.com/anytypeio/any-sync/util/keys"
"github.com/anytypeio/any-sync/util/keys/asymmetric/encryptionkey"
"github.com/anytypeio/any-sync/util/keys/asymmetric/signingkey"
"github.com/anytypeio/any-sync/util/keys/symmetric"
"github.com/gogo/protobuf/proto"
"go.uber.org/zap"
"hash/fnv"
)
var log = logger.NewNamedSugared("common.commonspace.acllist")
var log = logger.NewNamed("acllist").Sugar()
var (
ErrNoSuchUser = errors.New("no such user")
@@ -29,75 +35,86 @@ var (
)
type UserPermissionPair struct {
Identity crypto.PubKey
Permission aclrecordproto.AclUserPermissions
Identity string
Permission aclrecordproto2.AclUserPermissions
}
type AclState struct {
id string
currentReadKeyId string
userReadKeys map[string]crypto.SymKey
userStates map[string]AclUserState
statesAtRecord map[string][]AclUserState
key crypto.PrivKey
pubKey crypto.PubKey
keyStore crypto.KeyStorage
totalReadKeys int
id string
currentReadKeyHash uint64
userReadKeys map[uint64]*symmetric.Key
userStates map[string]*aclrecordproto2.AclUserState
userInvites map[string]*aclrecordproto2.AclUserInvite
encryptionKey encryptionkey.PrivKey
signingKey signingkey.PrivKey
totalReadKeys int
lastRecordId string
identity string
permissionsAtRecord map[string][]UserPermissionPair
lastRecordId string
keychain *keychain.Keychain
}
func newAclStateWithKeys(
id string,
key crypto.PrivKey) (*AclState, error) {
signingKey signingkey.PrivKey,
encryptionKey encryptionkey.PrivKey) (*AclState, error) {
identity, err := signingKey.GetPublic().Raw()
if err != nil {
return nil, err
}
return &AclState{
id: id,
key: key,
pubKey: key.GetPublic(),
userReadKeys: make(map[string]crypto.SymKey),
userStates: make(map[string]AclUserState),
statesAtRecord: make(map[string][]AclUserState),
id: id,
identity: string(identity),
signingKey: signingKey,
encryptionKey: encryptionKey,
userReadKeys: make(map[uint64]*symmetric.Key),
userStates: make(map[string]*aclrecordproto2.AclUserState),
userInvites: make(map[string]*aclrecordproto2.AclUserInvite),
permissionsAtRecord: make(map[string][]UserPermissionPair),
}, nil
}
func newAclState(id string) *AclState {
return &AclState{
id: id,
userReadKeys: make(map[string]crypto.SymKey),
userStates: make(map[string]AclUserState),
statesAtRecord: make(map[string][]AclUserState),
id: id,
userReadKeys: make(map[uint64]*symmetric.Key),
userStates: make(map[string]*aclrecordproto2.AclUserState),
userInvites: make(map[string]*aclrecordproto2.AclUserInvite),
permissionsAtRecord: make(map[string][]UserPermissionPair),
}
}
func (st *AclState) CurrentReadKeyId() string {
return st.currentReadKeyId
func (st *AclState) CurrentReadKeyHash() uint64 {
return st.currentReadKeyHash
}
func (st *AclState) CurrentReadKey() (crypto.SymKey, error) {
key, exists := st.userReadKeys[st.currentReadKeyId]
func (st *AclState) CurrentReadKey() (*symmetric.Key, error) {
key, exists := st.userReadKeys[st.currentReadKeyHash]
if !exists {
return nil, ErrNoReadKey
}
return key, nil
}
func (st *AclState) UserReadKeys() map[string]crypto.SymKey {
func (st *AclState) UserReadKeys() map[uint64]*symmetric.Key {
return st.userReadKeys
}
func (st *AclState) StateAtRecord(id string, pubKey crypto.PubKey) (AclUserState, error) {
userState, ok := st.statesAtRecord[id]
func (st *AclState) PermissionsAtRecord(id string, identity string) (UserPermissionPair, error) {
permissions, ok := st.permissionsAtRecord[id]
if !ok {
log.Errorf("missing record at id %s", id)
return AclUserState{}, ErrNoSuchRecord
return UserPermissionPair{}, ErrNoSuchRecord
}
for _, perm := range userState {
if perm.PubKey.Equals(pubKey) {
for _, perm := range permissions {
if perm.Identity == identity {
return perm, nil
}
}
return AclUserState{}, ErrNoSuchUser
return UserPermissionPair{}, ErrNoSuchUser
}
func (st *AclState) applyRecord(record *AclRecord) (err error) {
@@ -111,18 +128,24 @@ func (st *AclState) applyRecord(record *AclRecord) (err error) {
return
}
if record.Id == st.id {
err = st.applyRoot(record)
root, ok := record.Model.(*aclrecordproto2.AclRoot)
if !ok {
return ErrIncorrectRoot
}
err = st.applyRoot(root)
if err != nil {
return
}
st.statesAtRecord[record.Id] = []AclUserState{
{PubKey: record.Identity, Permissions: aclrecordproto.AclUserPermissions_Admin},
st.permissionsAtRecord[record.Id] = []UserPermissionPair{
{Identity: string(root.Identity), Permission: aclrecordproto2.AclUserPermissions_Admin},
}
return
}
aclData := &aclrecordproto2.AclData{}
if record.Model == nil {
aclData := &aclrecordproto.AclData{}
if record.Model != nil {
aclData = record.Model.(*aclrecordproto2.AclData)
} else {
err = proto.Unmarshal(record.Data, aclData)
if err != nil {
return
@@ -130,90 +153,109 @@ func (st *AclState) applyRecord(record *AclRecord) (err error) {
record.Model = aclData
}
err = st.applyChangeData(record)
err = st.applyChangeData(aclData, record.CurrentReadKeyHash, record.Identity)
if err != nil {
return
}
// getting all states for users at record
var states []AclUserState
// getting all permissions for users at record
var permissions []UserPermissionPair
for _, state := range st.userStates {
states = append(states, state)
permission := UserPermissionPair{
Identity: string(state.Identity),
Permission: state.Permissions,
}
permissions = append(permissions, permission)
}
st.statesAtRecord[record.Id] = states
st.permissionsAtRecord[record.Id] = permissions
return
}
func (st *AclState) applyRoot(record *AclRecord) (err error) {
if st.key != nil && st.pubKey.Equals(record.Identity) {
err = st.saveReadKeyFromRoot(record)
func (st *AclState) applyRoot(root *aclrecordproto2.AclRoot) (err error) {
if st.signingKey != nil && st.encryptionKey != nil && st.identity == string(root.Identity) {
err = st.saveReadKeyFromRoot(root)
if err != nil {
return
}
}
// adding user to the list
userState := AclUserState{
PubKey: record.Identity,
Permissions: aclrecordproto.AclUserPermissions_Admin,
userState := &aclrecordproto2.AclUserState{
Identity: root.Identity,
EncryptionKey: root.EncryptionKey,
Permissions: aclrecordproto2.AclUserPermissions_Admin,
}
st.currentReadKeyId = record.ReadKeyId
st.userStates[mapKeyFromPubKey(record.Identity)] = userState
st.currentReadKeyHash = root.CurrentReadKeyHash
st.userStates[string(root.Identity)] = userState
st.totalReadKeys++
return
}
func (st *AclState) saveReadKeyFromRoot(record *AclRecord) (err error) {
var readKey crypto.SymKey
root, ok := record.Model.(*aclrecordproto.AclRoot)
if !ok {
return ErrIncorrectRoot
}
if root.EncryptedReadKey == nil {
readKey, err = st.deriveKey()
func (st *AclState) saveReadKeyFromRoot(root *aclrecordproto2.AclRoot) (err error) {
var readKey *symmetric.Key
if len(root.GetDerivationScheme()) != 0 {
var encPrivKey []byte
encPrivKey, err = st.encryptionKey.Raw()
if err != nil {
return
}
var signPrivKey []byte
signPrivKey, err = st.signingKey.Raw()
if err != nil {
return
}
readKey, err = aclrecordproto2.AclReadKeyDerive(signPrivKey, encPrivKey)
if err != nil {
return
}
} else {
readKey, err = st.decryptReadKey(root.EncryptedReadKey)
readKey, _, err = st.decryptReadKeyAndHash(root.EncryptedReadKey)
if err != nil {
return
}
}
st.userReadKeys[record.Id] = readKey
hasher := fnv.New64()
_, err = hasher.Write(readKey.Bytes())
if err != nil {
return
}
if hasher.Sum64() != root.CurrentReadKeyHash {
return ErrIncorrectRoot
}
st.userReadKeys[root.CurrentReadKeyHash] = readKey
return
}
func (st *AclState) applyChangeData(record *AclRecord) (err error) {
func (st *AclState) applyChangeData(changeData *aclrecordproto2.AclData, hash uint64, identity []byte) (err error) {
defer func() {
if err != nil {
return
}
if record.ReadKeyId != st.currentReadKeyId {
if hash != st.currentReadKeyHash {
st.totalReadKeys++
st.currentReadKeyId = record.ReadKeyId
st.currentReadKeyHash = hash
}
}()
model := record.Model.(*aclrecordproto.AclData)
if !st.isUserJoin(model) {
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[mapKeyFromPubKey(record.Identity)]; !exists {
if _, exists := st.userStates[string(identity)]; !exists {
err = ErrNoSuchUser
return
}
// only Admins can do non-user join changes
if !st.HasPermission(record.Identity, aclrecordproto.AclUserPermissions_Admin) {
// TODO: add string encoding
err = fmt.Errorf("user %s must have admin permissions", record.Identity.Account())
if !st.HasPermission(identity, aclrecordproto2.AclUserPermissions_Admin) {
err = fmt.Errorf("user %s must have admin permissions", identity)
return
}
}
for _, ch := range model.GetAclContent() {
if err = st.applyChangeContent(ch, record.Id); err != nil {
for _, ch := range changeData.GetAclContent() {
if err = st.applyChangeContent(ch); err != nil {
log.Info("error while applying changes: %v; ignore", zap.Error(err))
return err
}
@@ -222,29 +264,26 @@ func (st *AclState) applyChangeData(record *AclRecord) (err error) {
return nil
}
func (st *AclState) applyChangeContent(ch *aclrecordproto.AclContentValue, recordId string) error {
func (st *AclState) applyChangeContent(ch *aclrecordproto2.AclContentValue) error {
switch {
case ch.GetUserPermissionChange() != nil:
return st.applyUserPermissionChange(ch.GetUserPermissionChange(), recordId)
return st.applyUserPermissionChange(ch.GetUserPermissionChange())
case ch.GetUserAdd() != nil:
return st.applyUserAdd(ch.GetUserAdd(), recordId)
return st.applyUserAdd(ch.GetUserAdd())
case ch.GetUserRemove() != nil:
return st.applyUserRemove(ch.GetUserRemove(), recordId)
return st.applyUserRemove(ch.GetUserRemove())
case ch.GetUserInvite() != nil:
return st.applyUserInvite(ch.GetUserInvite(), recordId)
return st.applyUserInvite(ch.GetUserInvite())
case ch.GetUserJoin() != nil:
return st.applyUserJoin(ch.GetUserJoin(), recordId)
return st.applyUserJoin(ch.GetUserJoin())
default:
return fmt.Errorf("unexpected change type: %v", ch)
}
}
func (st *AclState) applyUserPermissionChange(ch *aclrecordproto.AclUserPermissionChange, recordId string) error {
chIdentity, err := st.keyStore.PubKeyFromProto(ch.Identity)
if err != nil {
return err
}
state, exists := st.userStates[mapKeyFromPubKey(chIdentity)]
func (st *AclState) applyUserPermissionChange(ch *aclrecordproto2.AclUserPermissionChange) error {
chIdentity := string(ch.Identity)
state, exists := st.userStates[chIdentity]
if !exists {
return ErrNoSuchUser
}
@@ -253,38 +292,131 @@ func (st *AclState) applyUserPermissionChange(ch *aclrecordproto.AclUserPermissi
return nil
}
func (st *AclState) applyUserInvite(ch *aclrecordproto.AclUserInvite, recordId string) error {
// TODO: check old code and bring it back :-)
func (st *AclState) applyUserInvite(ch *aclrecordproto2.AclUserInvite) error {
st.userInvites[string(ch.AcceptPublicKey)] = ch
return nil
}
func (st *AclState) applyUserJoin(ch *aclrecordproto.AclUserJoin, recordId string) error {
return nil
}
func (st *AclState) applyUserJoin(ch *aclrecordproto2.AclUserJoin) error {
invite, exists := st.userInvites[string(ch.AcceptPubKey)]
if !exists {
return fmt.Errorf("no such invite with such public key %s", keys.EncodeBytesToString(ch.AcceptPubKey))
}
chIdentity := string(ch.Identity)
func (st *AclState) applyUserAdd(ch *aclrecordproto.AclUserAdd, recordId string) error {
return nil
}
func (st *AclState) applyUserRemove(ch *aclrecordproto.AclUserRemove, recordId string) error {
return nil
}
func (st *AclState) decryptReadKey(msg []byte) (crypto.SymKey, error) {
decrypted, err := st.key.Decrypt(msg)
if err != nil {
return nil, ErrFailedToDecrypt
if _, exists = st.userStates[chIdentity]; exists {
return ErrUserAlreadyExists
}
key, err := crypto.UnmarshallAESKey(decrypted)
// validating signature
signature := ch.GetAcceptSignature()
verificationKey, err := signingkey.NewSigningEd25519PubKeyFromBytes(invite.AcceptPublicKey)
if err != nil {
return nil, ErrFailedToDecrypt
return fmt.Errorf("public key verifying invite accepts is given in incorrect format: %v", err)
}
return key, nil
res, err := verificationKey.Verify(ch.Identity, signature)
if err != nil {
return fmt.Errorf("verification returned error: %w", err)
}
if !res {
return ErrInvalidSignature
}
// if ourselves -> we need to decrypt the read keys
if st.identity == chIdentity {
for _, key := range ch.EncryptedReadKeys {
key, hash, err := st.decryptReadKeyAndHash(key)
if err != nil {
return ErrFailedToDecrypt
}
st.userReadKeys[hash] = key
}
}
// adding user to the list
userState := &aclrecordproto2.AclUserState{
Identity: ch.Identity,
EncryptionKey: ch.EncryptionKey,
Permissions: invite.Permissions,
}
st.userStates[chIdentity] = userState
return nil
}
func (st *AclState) HasPermission(identity crypto.PubKey, permission aclrecordproto.AclUserPermissions) bool {
state, exists := st.userStates[mapKeyFromPubKey(identity)]
func (st *AclState) applyUserAdd(ch *aclrecordproto2.AclUserAdd) error {
chIdentity := string(ch.Identity)
if _, exists := st.userStates[chIdentity]; exists {
return ErrUserAlreadyExists
}
st.userStates[chIdentity] = &aclrecordproto2.AclUserState{
Identity: ch.Identity,
EncryptionKey: ch.EncryptionKey,
Permissions: ch.Permissions,
}
if chIdentity == st.identity {
for _, key := range ch.EncryptedReadKeys {
key, hash, err := st.decryptReadKeyAndHash(key)
if err != nil {
return ErrFailedToDecrypt
}
st.userReadKeys[hash] = key
}
}
return nil
}
func (st *AclState) applyUserRemove(ch *aclrecordproto2.AclUserRemove) error {
chIdentity := string(ch.Identity)
if chIdentity == st.identity {
return ErrDocumentForbidden
}
if _, exists := st.userStates[chIdentity]; !exists {
return ErrNoSuchUser
}
delete(st.userStates, chIdentity)
for _, replace := range ch.ReadKeyReplaces {
repIdentity := string(replace.Identity)
// if this is our identity then we have to decrypt the key
if repIdentity == st.identity {
key, hash, err := st.decryptReadKeyAndHash(replace.EncryptedReadKey)
if err != nil {
return ErrFailedToDecrypt
}
st.userReadKeys[hash] = key
break
}
}
return nil
}
func (st *AclState) decryptReadKeyAndHash(msg []byte) (*symmetric.Key, uint64, error) {
decrypted, err := st.encryptionKey.Decrypt(msg)
if err != nil {
return nil, 0, ErrFailedToDecrypt
}
key, err := symmetric.FromBytes(decrypted)
if err != nil {
return nil, 0, ErrFailedToDecrypt
}
hasher := fnv.New64()
hasher.Write(decrypted)
return key, hasher.Sum64(), nil
}
func (st *AclState) HasPermission(identity []byte, permission aclrecordproto2.AclUserPermissions) bool {
state, exists := st.userStates[string(identity)]
if !exists {
return false
}
@@ -292,35 +424,41 @@ func (st *AclState) HasPermission(identity crypto.PubKey, permission aclrecordpr
return state.Permissions == permission
}
func (st *AclState) isUserJoin(data *aclrecordproto.AclData) bool {
func (st *AclState) isUserJoin(data *aclrecordproto2.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 *aclrecordproto.AclData, identity []byte) bool {
return false
func (st *AclState) isUserAdd(data *aclrecordproto2.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 && bytes.Compare(userAdd.GetIdentity(), identity) == 0
}
func (st *AclState) UserStates() map[string]AclUserState {
func (st *AclState) UserStates() map[string]*aclrecordproto2.AclUserState {
return st.userStates
}
func (st *AclState) Invite(acceptPubKey []byte) (invite *aclrecordproto.AclUserInvite, err error) {
func (st *AclState) Invite(acceptPubKey []byte) (invite *aclrecordproto2.AclUserInvite, err error) {
invite, exists := st.userInvites[string(acceptPubKey)]
if !exists {
err = ErrNoSuchInvite
return
}
if len(invite.EncryptedReadKeys) != st.totalReadKeys {
err = ErrOldInvite
}
return
}
func (st *AclState) UserKeys() (encKey encryptionkey.PrivKey, signKey signingkey.PrivKey) {
return st.encryptionKey, st.signingKey
}
func (st *AclState) Identity() []byte {
return []byte(st.identity)
}
func (st *AclState) LastRecordId() string {
return st.lastRecordId
}
func (st *AclState) deriveKey() (crypto.SymKey, error) {
keyBytes, err := st.key.Raw()
if err != nil {
return nil, err
}
return crypto.DeriveSymmetricKey(keyBytes, crypto.AnysyncSpacePath)
}
func mapKeyFromPubKey(pubKey crypto.PubKey) string {
return string(pubKey.Storage())
}
+11 -8
View File
@@ -1,18 +1,21 @@
package list
import (
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anyproto/any-sync/util/crypto"
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
"github.com/anytypeio/any-sync/util/keys/asymmetric/encryptionkey"
"github.com/anytypeio/any-sync/util/keys/asymmetric/signingkey"
)
type aclStateBuilder struct {
privKey crypto.PrivKey
id string
signPrivKey signingkey.PrivKey
encPrivKey encryptionkey.PrivKey
id string
}
func newAclStateBuilderWithIdentity(keys *accountdata.AccountKeys) *aclStateBuilder {
func newAclStateBuilderWithIdentity(accountData *accountdata.AccountData) *aclStateBuilder {
return &aclStateBuilder{
privKey: keys.SignKey,
signPrivKey: accountData.SignKey,
encPrivKey: accountData.EncKey,
}
}
@@ -25,8 +28,8 @@ func (sb *aclStateBuilder) Init(id string) {
}
func (sb *aclStateBuilder) Build(records []*AclRecord) (state *AclState, err error) {
if sb.privKey != nil {
state, err = newAclStateWithKeys(sb.id, sb.privKey)
if sb.encPrivKey != nil && sb.signPrivKey != nil {
state, err = newAclStateWithKeys(sb.id, sb.signPrivKey, sb.encPrivKey)
if err != nil {
return
}
+14 -21
View File
@@ -1,14 +1,14 @@
//go:generate mockgen -destination mock_list/mock_list.go github.com/anyproto/any-sync/commonspace/object/acl/list AclList
//go:generate mockgen -destination mock_list/mock_list.go github.com/anytypeio/any-sync/commonspace/object/acl/list AclList
package list
import (
"context"
"errors"
"fmt"
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
"github.com/anyproto/any-sync/util/crypto"
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anytypeio/any-sync/commonspace/object/acl/liststorage"
"github.com/anytypeio/any-sync/commonspace/object/keychain"
"sync"
)
@@ -33,7 +33,6 @@ type AclList interface {
Get(id string) (*AclRecord, error)
Iterate(iterFunc IterFunc)
IterateFrom(startId string, iterFunc IterFunc)
KeyStorage() crypto.KeyStorage
AddRawRecord(rawRec *aclrecordproto.RawAclRecordWithId) (added bool, err error)
@@ -48,25 +47,23 @@ type aclList struct {
stateBuilder *aclStateBuilder
recordBuilder AclRecordBuilder
keyStorage crypto.KeyStorage
aclState *AclState
keychain *keychain.Keychain
storage liststorage.ListStorage
sync.RWMutex
}
func BuildAclListWithIdentity(acc *accountdata.AccountKeys, storage liststorage.ListStorage) (AclList, error) {
func BuildAclListWithIdentity(acc *accountdata.AccountData, storage liststorage.ListStorage) (AclList, error) {
builder := newAclStateBuilderWithIdentity(acc)
keyStorage := crypto.NewKeyStorage()
return build(storage.Id(), keyStorage, builder, NewAclRecordBuilder(storage.Id(), keyStorage), storage)
return build(storage.Id(), builder, newAclRecordBuilder(storage.Id(), keychain.NewKeychain()), storage)
}
func BuildAclList(storage liststorage.ListStorage) (AclList, error) {
keyStorage := crypto.NewKeyStorage()
return build(storage.Id(), keyStorage, newAclStateBuilder(), NewAclRecordBuilder(storage.Id(), crypto.NewKeyStorage()), storage)
return build(storage.Id(), newAclStateBuilder(), newAclRecordBuilder(storage.Id(), keychain.NewKeychain()), storage)
}
func build(id string, keyStorage crypto.KeyStorage, stateBuilder *aclStateBuilder, recBuilder AclRecordBuilder, storage liststorage.ListStorage) (list AclList, err error) {
func build(id string, stateBuilder *aclStateBuilder, recBuilder AclRecordBuilder, storage liststorage.ListStorage) (list AclList, err error) {
head, err := storage.Head()
if err != nil {
return
@@ -77,7 +74,7 @@ func build(id string, keyStorage crypto.KeyStorage, stateBuilder *aclStateBuilde
return
}
record, err := recBuilder.Unmarshall(rawRecordWithId)
record, err := recBuilder.ConvertFromRaw(rawRecordWithId)
if err != nil {
return
}
@@ -89,7 +86,7 @@ func build(id string, keyStorage crypto.KeyStorage, stateBuilder *aclStateBuilde
return
}
record, err = recBuilder.Unmarshall(rawRecordWithId)
record, err = recBuilder.ConvertFromRaw(rawRecordWithId)
if err != nil {
return
}
@@ -140,7 +137,7 @@ func (a *aclList) AddRawRecord(rawRec *aclrecordproto.RawAclRecordWithId) (added
if _, ok := a.indexes[rawRec.Id]; ok {
return
}
record, err := a.recordBuilder.Unmarshall(rawRec)
record, err := a.recordBuilder.ConvertFromRaw(rawRec)
if err != nil {
return
}
@@ -159,7 +156,7 @@ func (a *aclList) AddRawRecord(rawRec *aclrecordproto.RawAclRecordWithId) (added
}
func (a *aclList) IsValidNext(rawRec *aclrecordproto.RawAclRecordWithId) (err error) {
_, err = a.recordBuilder.Unmarshall(rawRec)
_, err = a.recordBuilder.ConvertFromRaw(rawRec)
if err != nil {
return
}
@@ -179,10 +176,6 @@ func (a *aclList) AclState() *AclState {
return a.aclState
}
func (a *aclList) KeyStorage() crypto.KeyStorage {
return a.keyStorage
}
func (a *aclList) IsAfter(first string, second string) (bool, error) {
firstRec, okFirst := a.indexes[first]
secondRec, okSecond := a.indexes[second]
+83 -8
View File
@@ -1,16 +1,91 @@
package list
import (
"fmt"
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anytypeio/any-sync/commonspace/object/acl/testutils/acllistbuilder"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)
func TestAclList_BuildRoot(t *testing.T) {
randomKeys, err := accountdata.NewRandom()
require.NoError(t, err)
randomAcl, err := NewTestDerivedAcl("spaceId", randomKeys)
require.NoError(t, err)
fmt.Println(randomAcl.Id())
func TestAclList_AclState_UserInviteAndJoin(t *testing.T) {
st, err := acllistbuilder.NewListStorageWithTestName("userjoinexample.yml")
require.NoError(t, err, "building storage should not result in error")
keychain := st.(*acllistbuilder.AclListStorageBuilder).GetKeychain()
aclList, err := BuildAclList(st)
require.NoError(t, err, "building acl list should be without error")
idA := keychain.GetIdentity("A")
idB := keychain.GetIdentity("B")
idC := keychain.GetIdentity("C")
// checking final state
assert.Equal(t, aclrecordproto.AclUserPermissions_Admin, aclList.AclState().UserStates()[idA].Permissions)
assert.Equal(t, aclrecordproto.AclUserPermissions_Writer, aclList.AclState().UserStates()[idB].Permissions)
assert.Equal(t, aclrecordproto.AclUserPermissions_Reader, aclList.AclState().UserStates()[idC].Permissions)
assert.Equal(t, aclList.Head().CurrentReadKeyHash, aclList.AclState().CurrentReadKeyHash())
var records []*AclRecord
aclList.Iterate(func(record *AclRecord) (IsContinue bool) {
records = append(records, record)
return true
})
// checking permissions at specific records
assert.Equal(t, 3, len(records))
_, err = aclList.AclState().PermissionsAtRecord(records[1].Id, idB)
assert.Error(t, err, "B should have no permissions at record 1")
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: aclrecordproto.AclUserPermissions_Writer,
}, perm)
}
func TestAclList_AclState_UserJoinAndRemove(t *testing.T) {
st, err := acllistbuilder.NewListStorageWithTestName("userremoveexample.yml")
require.NoError(t, err, "building storage should not result in error")
keychain := st.(*acllistbuilder.AclListStorageBuilder).GetKeychain()
aclList, err := BuildAclList(st)
require.NoError(t, err, "building acl list should be without error")
idA := keychain.GetIdentity("A")
idB := keychain.GetIdentity("B")
idC := keychain.GetIdentity("C")
// checking final state
assert.Equal(t, aclrecordproto.AclUserPermissions_Admin, aclList.AclState().UserStates()[idA].Permissions)
assert.Equal(t, aclrecordproto.AclUserPermissions_Reader, aclList.AclState().UserStates()[idC].Permissions)
assert.Equal(t, aclList.Head().CurrentReadKeyHash, aclList.AclState().CurrentReadKeyHash())
_, exists := aclList.AclState().UserStates()[idB]
assert.Equal(t, false, exists)
var records []*AclRecord
aclList.Iterate(func(record *AclRecord) (IsContinue bool) {
records = append(records, record)
return true
})
// checking permissions at specific records
assert.Equal(t, 4, len(records))
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: aclrecordproto.AclUserPermissions_Writer,
}, perm)
_, err = aclList.AclState().PermissionsAtRecord(records[3].Id, idB)
assert.Error(t, err, "B should have no permissions at record 3, because user should be removed")
}
-31
View File
@@ -1,31 +0,0 @@
package list
import (
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
"github.com/anyproto/any-sync/util/crypto"
)
func NewTestDerivedAcl(spaceId string, keys *accountdata.AccountKeys) (AclList, error) {
builder := NewAclRecordBuilder("", crypto.NewKeyStorage())
masterKey, _, err := crypto.GenerateRandomEd25519KeyPair()
if err != nil {
return nil, err
}
root, err := builder.BuildRoot(RootContent{
PrivKey: keys.SignKey,
SpaceId: spaceId,
MasterKey: masterKey,
})
if err != nil {
return nil, err
}
st, err := liststorage.NewInMemoryAclListStorage(root.Id, []*aclrecordproto.RawAclRecordWithId{
root,
})
if err != nil {
return nil, err
}
return BuildAclListWithIdentity(keys, st)
}
@@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/anyproto/any-sync/commonspace/object/acl/list (interfaces: AclList)
// Source: github.com/anytypeio/any-sync/commonspace/object/acl/list (interfaces: AclList)
// Package mock_list is a generated GoMock package.
package mock_list
@@ -7,9 +7,8 @@ package mock_list
import (
reflect "reflect"
aclrecordproto "github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
list "github.com/anyproto/any-sync/commonspace/object/acl/list"
crypto "github.com/anyproto/any-sync/util/crypto"
aclrecordproto "github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
list "github.com/anytypeio/any-sync/commonspace/object/acl/list"
gomock "github.com/golang/mock/gomock"
)
@@ -161,20 +160,6 @@ func (mr *MockAclListMockRecorder) IterateFrom(arg0, arg1 interface{}) *gomock.C
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IterateFrom", reflect.TypeOf((*MockAclList)(nil).IterateFrom), arg0, arg1)
}
// KeyStorage mocks base method.
func (m *MockAclList) KeyStorage() crypto.KeyStorage {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "KeyStorage")
ret0, _ := ret[0].(crypto.KeyStorage)
return ret0
}
// KeyStorage indicates an expected call of KeyStorage.
func (mr *MockAclListMockRecorder) KeyStorage() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeyStorage", reflect.TypeOf((*MockAclList)(nil).KeyStorage))
}
// Lock mocks base method.
func (m *MockAclList) Lock() {
m.ctrl.T.Helper()
-22
View File
@@ -1,22 +0,0 @@
package list
import (
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anyproto/any-sync/util/crypto"
)
type AclRecord struct {
Id string
PrevId string
ReadKeyId string
Timestamp int64
Data []byte
Identity crypto.PubKey
Model interface{}
Signature []byte
}
type AclUserState struct {
PubKey crypto.PubKey
Permissions aclrecordproto.AclUserPermissions
}
+12
View File
@@ -0,0 +1,12 @@
package list
type AclRecord struct {
Id string
PrevId string
CurrentReadKeyHash uint64
Timestamp int64
Data []byte
Identity []byte
Model interface{}
Signature []byte
}
@@ -3,7 +3,7 @@ package liststorage
import (
"context"
"fmt"
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
"sync"
)
@@ -1,10 +1,10 @@
//go:generate mockgen -destination mock_liststorage/mock_liststorage.go github.com/anyproto/any-sync/commonspace/object/acl/liststorage ListStorage
//go:generate mockgen -destination mock_liststorage/mock_liststorage.go github.com/anytypeio/any-sync/commonspace/object/acl/liststorage ListStorage
package liststorage
import (
"context"
"errors"
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
)
var (
@@ -13,10 +13,6 @@ var (
ErrUnknownRecord = errors.New("record doesn't exist")
)
type Exporter interface {
ListStorage(root *aclrecordproto.RawAclRecordWithId) (ListStorage, error)
}
type ListStorage interface {
Id() string
Root() (*aclrecordproto.RawAclRecordWithId, error)
@@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/anyproto/any-sync/commonspace/object/acl/liststorage (interfaces: ListStorage)
// Source: github.com/anytypeio/any-sync/commonspace/object/acl/liststorage (interfaces: ListStorage)
// Package mock_liststorage is a generated GoMock package.
package mock_liststorage
@@ -8,7 +8,7 @@ import (
context "context"
reflect "reflect"
aclrecordproto "github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
aclrecordproto "github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
gomock "github.com/golang/mock/gomock"
)
+6 -6
View File
@@ -1,21 +1,21 @@
package syncacl
import (
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/any-sync/commonspace/objectsync"
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
"github.com/anytypeio/any-sync/commonspace/objectsync"
"github.com/anytypeio/any-sync/commonspace/objectsync/synchandler"
)
type SyncAcl struct {
list.AclList
synchandler.SyncHandler
messagePool objectsync.MessagePool
streamPool objectsync.StreamPool
}
func NewSyncAcl(aclList list.AclList, messagePool objectsync.MessagePool) *SyncAcl {
func NewSyncAcl(aclList list.AclList, streamPool objectsync.StreamPool) *SyncAcl {
return &SyncAcl{
AclList: aclList,
SyncHandler: nil,
messagePool: messagePool,
streamPool: streamPool,
}
}
@@ -3,9 +3,9 @@ package syncacl
import (
"context"
"fmt"
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
)
type syncAclHandler struct {
@@ -0,0 +1,194 @@
package acllistbuilder
import (
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anytypeio/any-sync/util/keys"
"github.com/anytypeio/any-sync/util/keys/asymmetric/encryptionkey"
"github.com/anytypeio/any-sync/util/keys/asymmetric/signingkey"
"github.com/anytypeio/any-sync/util/keys/symmetric"
"hash/fnv"
"strings"
)
type SymKey struct {
Hash uint64
Key *symmetric.Key
}
type YAMLKeychain struct {
SigningKeysByYAMLName map[string]signingkey.PrivKey
SigningKeysByRealIdentity map[string]signingkey.PrivKey
EncryptionKeysByYAMLName map[string]encryptionkey.PrivKey
ReadKeysByYAMLName map[string]*SymKey
ReadKeysByHash map[uint64]*SymKey
GeneratedIdentities map[string]string
}
func NewKeychain() *YAMLKeychain {
return &YAMLKeychain{
SigningKeysByYAMLName: map[string]signingkey.PrivKey{},
SigningKeysByRealIdentity: map[string]signingkey.PrivKey{},
EncryptionKeysByYAMLName: map[string]encryptionkey.PrivKey{},
GeneratedIdentities: map[string]string{},
ReadKeysByYAMLName: map[string]*SymKey{},
ReadKeysByHash: map[uint64]*SymKey{},
}
}
func (k *YAMLKeychain) ParseKeys(keys *Keys) {
for _, encKey := range keys.Enc {
k.AddEncryptionKey(encKey)
}
for _, signKey := range keys.Sign {
k.AddSigningKey(signKey)
}
for _, readKey := range keys.Read {
k.AddReadKey(readKey)
}
}
func (k *YAMLKeychain) AddEncryptionKey(key *Key) {
if _, exists := k.EncryptionKeysByYAMLName[key.Name]; exists {
return
}
var (
newPrivKey encryptionkey.PrivKey
err error
)
if key.Value == "generated" {
newPrivKey, _, err = encryptionkey.GenerateRandomRSAKeyPair(2048)
if err != nil {
panic(err)
}
} else {
newPrivKey, err = keys.DecodeKeyFromString(key.Value, encryptionkey.NewEncryptionRsaPrivKeyFromBytes, nil)
if err != nil {
panic(err)
}
}
k.EncryptionKeysByYAMLName[key.Name] = newPrivKey
}
func (k *YAMLKeychain) AddSigningKey(key *Key) {
if _, exists := k.SigningKeysByYAMLName[key.Name]; exists {
return
}
var (
newPrivKey signingkey.PrivKey
pubKey signingkey.PubKey
err error
)
if key.Value == "generated" {
newPrivKey, pubKey, err = signingkey.GenerateRandomEd25519KeyPair()
if err != nil {
panic(err)
}
} else {
newPrivKey, err = keys.DecodeKeyFromString(key.Value, signingkey.NewSigningEd25519PrivKeyFromBytes, nil)
if err != nil {
panic(err)
}
pubKey = newPrivKey.GetPublic()
}
k.SigningKeysByYAMLName[key.Name] = newPrivKey
rawPubKey, err := pubKey.Raw()
if err != nil {
panic(err)
}
encoded := string(rawPubKey)
k.SigningKeysByRealIdentity[encoded] = newPrivKey
k.GeneratedIdentities[key.Name] = encoded
}
func (k *YAMLKeychain) AddReadKey(key *Key) {
if _, exists := k.ReadKeysByYAMLName[key.Name]; exists {
return
}
var (
rkey *symmetric.Key
err error
)
if key.Value == "generated" {
rkey, err = symmetric.NewRandom()
if err != nil {
panic("should be able to generate symmetric key")
}
} else if key.Value == "derived" {
signKey, _ := k.SigningKeysByYAMLName[key.Name].Raw()
encKey, _ := k.EncryptionKeysByYAMLName[key.Name].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 {
panic("should be able to parse symmetric key")
}
}
hasher := fnv.New64()
hasher.Write(rkey.Bytes())
k.ReadKeysByYAMLName[key.Name] = &SymKey{
Hash: hasher.Sum64(),
Key: rkey,
}
k.ReadKeysByHash[hasher.Sum64()] = &SymKey{
Hash: hasher.Sum64(),
Key: rkey,
}
}
func (k *YAMLKeychain) AddKey(key *Key) {
parts := strings.Split(key.Name, ".")
if len(parts) != 3 {
panic("cannot parse a key")
}
switch parts[1] {
case "Signature":
k.AddSigningKey(key)
case "Enc":
k.AddEncryptionKey(key)
case "Read":
k.AddReadKey(key)
default:
panic("incorrect format")
}
}
func (k *YAMLKeychain) GetKey(key string) interface{} {
parts := strings.Split(key, ".")
if len(parts) != 3 {
panic("cannot parse a key")
}
name := parts[2]
switch parts[1] {
case "Sign":
if key, exists := k.SigningKeysByYAMLName[name]; exists {
return key
}
case "Enc":
if key, exists := k.EncryptionKeysByYAMLName[name]; exists {
return key
}
case "Read":
if key, exists := k.ReadKeysByYAMLName[name]; exists {
return key
}
default:
panic("incorrect format")
}
return nil
}
func (k *YAMLKeychain) GetIdentity(name string) string {
return k.GeneratedIdentities[name]
}
@@ -0,0 +1,295 @@
package acllistbuilder
import (
"context"
"fmt"
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anytypeio/any-sync/commonspace/object/acl/liststorage"
"github.com/anytypeio/any-sync/commonspace/object/acl/testutils/yamltests"
"github.com/anytypeio/any-sync/util/cidutil"
"github.com/anytypeio/any-sync/util/keys/asymmetric/encryptionkey"
"github.com/anytypeio/any-sync/util/keys/asymmetric/signingkey"
"github.com/anytypeio/any-sync/util/keys/symmetric"
"gopkg.in/yaml.v3"
"io/ioutil"
"path"
"time"
"github.com/gogo/protobuf/proto"
)
type AclListStorageBuilder struct {
liststorage.ListStorage
keychain *YAMLKeychain
}
func NewAclListStorageBuilder(keychain *YAMLKeychain) *AclListStorageBuilder {
return &AclListStorageBuilder{
keychain: keychain,
}
}
func NewListStorageWithTestName(name string) (liststorage.ListStorage, error) {
filePath := path.Join(yamltests.Path(), name)
return NewAclListStorageBuilderFromFile(filePath)
}
func NewAclListStorageBuilderFromFile(file string) (*AclListStorageBuilder, error) {
content, err := ioutil.ReadFile(file)
if err != nil {
return nil, err
}
ymlTree := YMLList{}
err = yaml.Unmarshal(content, &ymlTree)
if err != nil {
return nil, err
}
tb := NewAclListStorageBuilder(NewKeychain())
tb.Parse(&ymlTree)
return tb, nil
}
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.SigningKeysByRealIdentity[string(identity)].Sign(protoMarshalled)
if err != nil {
panic("should be able to sign final acl message!")
}
rawRec := &aclrecordproto.RawAclRecord{
Payload: protoMarshalled,
Signature: signature,
}
rawMarshalled, err := proto.Marshal(rawRec)
if err != nil {
panic(err)
}
id, _ := cidutil.NewCidFromBytes(rawMarshalled)
return &aclrecordproto.RawAclRecordWithId{
Payload: rawMarshalled,
Id: id,
}
}
func (t *AclListStorageBuilder) GetKeychain() *YAMLKeychain {
return t.keychain
}
func (t *AclListStorageBuilder) Parse(l *YMLList) {
// Just to clarify - we are generating new identities for the ones that
// are specified in the yml file, because our identities should be Ed25519
// the same thing is happening for the encryption keys
t.keychain.ParseKeys(&l.Keys)
rawRoot := t.parseRoot(l.Root)
var err error
t.ListStorage, err = liststorage.NewInMemoryAclListStorage(rawRoot.Id, []*aclrecordproto.RawAclRecordWithId{rawRoot})
if err != nil {
panic(err)
}
prevId := rawRoot.Id
for _, rec := range l.Records {
newRecord := t.parseRecord(rec, prevId)
rawRecord := t.createRaw(newRecord, newRecord.Identity)
err = t.AddRawRecord(context.Background(), rawRecord)
if err != nil {
panic(err)
}
prevId = rawRecord.Id
}
t.SetHead(prevId)
}
func (t *AclListStorageBuilder) parseRecord(rec *Record, prevId string) *aclrecordproto.AclRecord {
k := t.keychain.GetKey(rec.ReadKey).(*SymKey)
var aclChangeContents []*aclrecordproto.AclContentValue
for _, ch := range rec.AclChanges {
aclChangeContent := t.parseAclChange(ch)
aclChangeContents = append(aclChangeContents, aclChangeContent)
}
data := &aclrecordproto.AclData{
AclContent: aclChangeContents,
}
bytes, _ := data.Marshal()
return &aclrecordproto.AclRecord{
PrevId: prevId,
Identity: []byte(t.keychain.GetIdentity(rec.Identity)),
Data: bytes,
CurrentReadKeyHash: k.Hash,
Timestamp: time.Now().UnixNano(),
}
}
func (t *AclListStorageBuilder) parseAclChange(ch *AclChange) (convCh *aclrecordproto.AclContentValue) {
switch {
case ch.UserAdd != nil:
add := ch.UserAdd
encKey := t.keychain.GetKey(add.EncryptionKey).(encryptionkey.PrivKey)
rawKey, _ := encKey.GetPublic().Raw()
convCh = &aclrecordproto.AclContentValue{
Value: &aclrecordproto.AclContentValue_UserAdd{
UserAdd: &aclrecordproto.AclUserAdd{
Identity: []byte(t.keychain.GetIdentity(add.Identity)),
EncryptionKey: rawKey,
EncryptedReadKeys: t.encryptReadKeysWithPubKey(add.EncryptedReadKeys, encKey),
Permissions: t.convertPermission(add.Permission),
},
},
}
case ch.UserJoin != nil:
join := ch.UserJoin
encKey := t.keychain.GetKey(join.EncryptionKey).(encryptionkey.PrivKey)
rawKey, _ := encKey.GetPublic().Raw()
idKey, _ := t.keychain.SigningKeysByYAMLName[join.Identity].GetPublic().Raw()
signKey := t.keychain.GetKey(join.AcceptKey).(signingkey.PrivKey)
signature, err := signKey.Sign(idKey)
if err != nil {
panic(err)
}
acceptPubKey, _ := signKey.GetPublic().Raw()
convCh = &aclrecordproto.AclContentValue{
Value: &aclrecordproto.AclContentValue_UserJoin{
UserJoin: &aclrecordproto.AclUserJoin{
Identity: []byte(t.keychain.GetIdentity(join.Identity)),
EncryptionKey: rawKey,
AcceptSignature: signature,
AcceptPubKey: acceptPubKey,
EncryptedReadKeys: t.encryptReadKeysWithPubKey(join.EncryptedReadKeys, encKey),
},
},
}
case ch.UserInvite != nil:
invite := ch.UserInvite
rawAcceptKey, _ := t.keychain.GetKey(invite.AcceptKey).(signingkey.PrivKey).GetPublic().Raw()
hash := t.keychain.GetKey(invite.EncryptionKey).(*SymKey).Hash
encKey := t.keychain.ReadKeysByHash[hash]
convCh = &aclrecordproto.AclContentValue{
Value: &aclrecordproto.AclContentValue_UserInvite{
UserInvite: &aclrecordproto.AclUserInvite{
AcceptPublicKey: rawAcceptKey,
EncryptSymKeyHash: hash,
EncryptedReadKeys: t.encryptReadKeysWithSymKey(invite.EncryptedReadKeys, encKey.Key),
Permissions: t.convertPermission(invite.Permissions),
},
},
}
case ch.UserPermissionChange != nil:
permissionChange := ch.UserPermissionChange
convCh = &aclrecordproto.AclContentValue{
Value: &aclrecordproto.AclContentValue_UserPermissionChange{
UserPermissionChange: &aclrecordproto.AclUserPermissionChange{
Identity: []byte(t.keychain.GetIdentity(permissionChange.Identity)),
Permissions: t.convertPermission(permissionChange.Permission),
},
},
}
case ch.UserRemove != nil:
remove := ch.UserRemove
newReadKey := t.keychain.GetKey(remove.NewReadKey).(*SymKey)
var replaces []*aclrecordproto.AclReadKeyReplace
for _, id := range remove.IdentitiesLeft {
encKey := t.keychain.EncryptionKeysByYAMLName[id]
rawEncKey, _ := encKey.GetPublic().Raw()
encReadKey, err := encKey.GetPublic().Encrypt(newReadKey.Key.Bytes())
if err != nil {
panic(err)
}
replaces = append(replaces, &aclrecordproto.AclReadKeyReplace{
Identity: []byte(t.keychain.GetIdentity(id)),
EncryptionKey: rawEncKey,
EncryptedReadKey: encReadKey,
})
}
convCh = &aclrecordproto.AclContentValue{
Value: &aclrecordproto.AclContentValue_UserRemove{
UserRemove: &aclrecordproto.AclUserRemove{
Identity: []byte(t.keychain.GetIdentity(remove.RemovedIdentity)),
ReadKeyReplaces: replaces,
},
},
}
}
if convCh == nil {
panic("cannot have empty acl change")
}
return convCh
}
func (t *AclListStorageBuilder) encryptReadKeysWithPubKey(keys []string, encKey encryptionkey.PrivKey) (enc [][]byte) {
for _, k := range keys {
realKey := t.keychain.GetKey(k).(*SymKey).Key.Bytes()
res, err := encKey.GetPublic().Encrypt(realKey)
if err != nil {
panic(err)
}
enc = append(enc, res)
}
return
}
func (t *AclListStorageBuilder) encryptReadKeysWithSymKey(keys []string, key *symmetric.Key) (enc [][]byte) {
for _, k := range keys {
realKey := t.keychain.GetKey(k).(*SymKey).Key.Bytes()
res, err := key.Encrypt(realKey)
if err != nil {
panic(err)
}
enc = append(enc, res)
}
return
}
func (t *AclListStorageBuilder) convertPermission(perm string) aclrecordproto.AclUserPermissions {
switch perm {
case "admin":
return aclrecordproto.AclUserPermissions_Admin
case "writer":
return aclrecordproto.AclUserPermissions_Writer
case "reader":
return aclrecordproto.AclUserPermissions_Reader
default:
panic(fmt.Sprintf("incorrect permission: %s", perm))
}
}
func (t *AclListStorageBuilder) traverseFromHead(f func(rec *aclrecordproto.AclRecord, id string) error) (err error) {
panic("this was removed, add if needed")
}
func (t *AclListStorageBuilder) parseRoot(root *Root) (rawRoot *aclrecordproto.RawAclRecordWithId) {
rawSignKey, _ := t.keychain.SigningKeysByYAMLName[root.Identity].GetPublic().Raw()
rawEncKey, _ := t.keychain.EncryptionKeysByYAMLName[root.Identity].GetPublic().Raw()
readKey := t.keychain.ReadKeysByYAMLName[root.Identity]
aclRoot := &aclrecordproto.AclRoot{
Identity: rawSignKey,
EncryptionKey: rawEncKey,
SpaceId: root.SpaceId,
EncryptedReadKey: nil,
DerivationScheme: "scheme",
CurrentReadKeyHash: readKey.Hash,
}
return t.createRaw(aclRoot, rawSignKey)
}
@@ -0,0 +1,11 @@
//go:build ((!linux && !darwin) || android || ios || nographviz) && !amd64
// +build !linux,!darwin android ios nographviz
// +build !amd64
package acllistbuilder
import "fmt"
func (t *AclListStorageBuilder) Graph() (string, error) {
return "", fmt.Errorf("building graphs is not supported")
}
@@ -0,0 +1,120 @@
//go:build (linux || darwin) && !android && !ios && !nographviz && (amd64 || arm64)
// +build linux darwin
// +build !android
// +build !ios
// +build !nographviz
// +build amd64 arm64
package acllistbuilder
import (
"fmt"
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/gogo/protobuf/proto"
"strings"
"unicode"
"github.com/awalterschulze/gographviz"
)
// To quickly look at visualized string you can use https://dreampuf.github.io/GraphvizOnline
type EdgeParameters struct {
style string
color string
label string
}
func (t *AclListStorageBuilder) Graph() (string, error) {
// TODO: check updates on https://github.com/goccy/go-graphviz/issues/52 or make a fix yourself to use better library here
graph := gographviz.NewGraph()
graph.SetName("G")
graph.SetDir(true)
var nodes = make(map[string]struct{})
var addNodes = func(r *aclrecordproto.AclRecord, id string) error {
style := "solid"
var chSymbs []string
aclData := &aclrecordproto.AclData{}
err := proto.Unmarshal(r.GetData(), aclData)
if err != nil {
return err
}
for _, chc := range aclData.AclContent {
tp := fmt.Sprintf("%T", chc.Value)
tp = strings.Replace(tp, "AclChangeAclContentValueValueOf", "", 1)
res := ""
for _, ts := range tp {
if unicode.IsUpper(ts) {
res += string(ts)
}
}
chSymbs = append(chSymbs, res)
}
shortId := id
label := fmt.Sprintf("Id: %s\nChanges: %s\n",
shortId,
strings.Join(chSymbs, ","),
)
e := graph.AddNode("G", "\""+id+"\"", map[string]string{
"label": "\"" + label + "\"",
"style": "\"" + style + "\"",
})
if e != nil {
return e
}
nodes[id] = struct{}{}
return nil
}
var createEdge = func(firstId, secondId string, params EdgeParameters) error {
_, exists := nodes[firstId]
if !exists {
return fmt.Errorf("no such node")
}
_, exists = nodes[secondId]
if !exists {
return fmt.Errorf("no previous node")
}
err := graph.AddEdge("\""+firstId+"\"", "\""+secondId+"\"", true, map[string]string{
"color": params.color,
"style": params.style,
})
if err != nil {
return err
}
return nil
}
var addLinks = func(r *aclrecordproto.AclRecord, id string) error {
if r.PrevId == "" {
return nil
}
err := createEdge(id, r.PrevId, EdgeParameters{
style: "dashed",
color: "red",
})
if err != nil {
return err
}
return nil
}
err := t.traverseFromHead(addNodes)
if err != nil {
return "", err
}
err = t.traverseFromHead(addLinks)
if err != nil {
return "", err
}
return graph.String(), nil
}
@@ -0,0 +1,70 @@
package acllistbuilder
type Key struct {
Name string `yaml:"name"`
Value string `yaml:"value"`
}
type Keys struct {
Derived string `yaml:"Derived"`
Enc []*Key `yaml:"Enc"`
Sign []*Key `yaml:"Sign"`
Read []*Key `yaml:"Read"`
}
type AclChange struct {
UserAdd *struct {
Identity string `yaml:"identity"`
EncryptionKey string `yaml:"encryptionKey"`
EncryptedReadKeys []string `yaml:"encryptedReadKeys"`
Permission string `yaml:"permission"`
} `yaml:"userAdd"`
UserJoin *struct {
Identity string `yaml:"identity"`
EncryptionKey string `yaml:"encryptionKey"`
AcceptKey string `yaml:"acceptKey"`
EncryptedReadKeys []string `yaml:"encryptedReadKeys"`
} `yaml:"userJoin"`
UserInvite *struct {
AcceptKey string `yaml:"acceptKey"`
EncryptionKey string `yaml:"encryptionKey"`
EncryptedReadKeys []string `yaml:"encryptedReadKeys"`
Permissions string `yaml:"permissions"`
} `yaml:"userInvite"`
UserRemove *struct {
RemovedIdentity string `yaml:"removedIdentity"`
NewReadKey string `yaml:"newReadKey"`
IdentitiesLeft []string `yaml:"identitiesLeft"`
} `yaml:"userRemove"`
UserPermissionChange *struct {
Identity string `yaml:"identity"`
Permission string `yaml:"permission"`
}
}
type Record struct {
Identity string `yaml:"identity"`
AclChanges []*AclChange `yaml:"aclChanges"`
ReadKey string `yaml:"readKey"`
}
type Header struct {
FirstChangeId string `yaml:"firstChangeId"`
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"`
}
@@ -0,0 +1,15 @@
package yamltests
import (
"path/filepath"
"runtime"
)
var (
_, b, _, _ = runtime.Caller(0)
basepath = filepath.Dir(b)
)
func Path() string {
return basepath
}
@@ -0,0 +1,53 @@
root:
identity: A
spaceId: space
records:
- identity: A
aclChanges:
- userInvite:
acceptKey: key.Sign.Onetime1
encryptionKey: key.Read.EncKey
encryptedReadKeys: [key.Read.A]
permissions: writer
- userAdd:
identity: C
permission: reader
encryptionKey: key.Enc.C
encryptedReadKeys: [key.Read.A]
readKey: key.Read.A
- identity: B
aclChanges:
- userJoin:
identity: B
encryptionKey: key.Enc.B
acceptKey: key.Sign.Onetime1
encryptedReadKeys: [key.Read.A]
readKey: key.Read.A
keys:
Enc:
- name: A
value: generated
- name: B
value: generated
- name: C
value: generated
- name: D
value: generated
- name: Onetime1
value: generated
Sign:
- name: A
value: generated
- name: B
value: generated
- name: C
value: generated
- name: D
value: generated
- name: Onetime1
value: generated
Read:
- name: A
value: derived
- name: EncKey
value: generated
@@ -0,0 +1,58 @@
root:
identity: A
spaceId: space
records:
- identity: A
aclChanges:
- userInvite:
acceptKey: key.Sign.Onetime1
encryptionKey: key.Read.EncKey
encryptedReadKeys: [key.Read.A]
permissions: writer
- userAdd:
identity: C
permission: reader
encryptionKey: key.Enc.C
encryptedReadKeys: [key.Read.A]
readKey: key.Read.A
- identity: B
aclChanges:
- userJoin:
identity: B
encryptionKey: key.Enc.B
acceptKey: key.Sign.Onetime1
encryptedReadKeys: [key.Read.A]
readKey: key.Read.A
- identity: A
aclChanges:
- userRemove:
removedIdentity: B
newReadKey: key.Read.2
identitiesLeft: [A, C]
readKey: key.Read.2
keys:
Enc:
- name: A
value: generated
- name: B
value: generated
- name: C
value: generated
- name: Onetime1
value: generated
Sign:
- name: A
value: generated
- name: B
value: generated
- name: C
value: generated
- name: Onetime1
value: generated
Read:
- name: A
value: derived
- name: 2
value: generated
- name: EncKey
value: generated
+28
View File
@@ -0,0 +1,28 @@
package keychain
import (
"github.com/anytypeio/any-sync/util/keys/asymmetric/signingkey"
)
type Keychain struct {
keys map[string]signingkey.PubKey
}
func NewKeychain() *Keychain {
return &Keychain{
keys: make(map[string]signingkey.PubKey),
}
}
func (k *Keychain) GetOrAdd(identity string) (signingkey.PubKey, error) {
if key, exists := k.keys[identity]; exists {
return key, nil
}
res, err := signingkey.NewSigningEd25519PubKeyFromBytes([]byte(identity))
if err != nil {
return nil, err
}
k.keys[identity] = res.(signingkey.PubKey)
return res.(signingkey.PubKey), nil
}
@@ -2,11 +2,10 @@ package syncobjectgetter
import (
"context"
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
"github.com/anytypeio/any-sync/commonspace/objectsync/synchandler"
)
type SyncObject interface {
Id() string
synchandler.SyncHandler
}
@@ -1,80 +0,0 @@
package exporter
import (
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
"github.com/anyproto/any-sync/util/crypto"
)
type DataConverter interface {
Unmarshall(decrypted []byte) (any, error)
Marshall(model any) ([]byte, error)
}
type TreeExporterParams struct {
ListStorageExporter liststorage.Exporter
TreeStorageExporter treestorage.Exporter
DataConverter DataConverter
}
type TreeExporter interface {
ExportUnencrypted(tree objecttree.ReadableObjectTree) (err error)
}
type treeExporter struct {
listExporter liststorage.Exporter
treeExporter treestorage.Exporter
converter DataConverter
}
func NewTreeExporter(params TreeExporterParams) TreeExporter {
return &treeExporter{
listExporter: params.ListStorageExporter,
treeExporter: params.TreeStorageExporter,
converter: params.DataConverter,
}
}
func (t *treeExporter) ExportUnencrypted(tree objecttree.ReadableObjectTree) (err error) {
lst := tree.AclList()
// this exports root which should be enough before we implement acls
_, err = t.listExporter.ListStorage(lst.Root())
if err != nil {
return
}
treeStorage, err := t.treeExporter.TreeStorage(tree.Header())
if err != nil {
return
}
changeBuilder := objecttree.NewChangeBuilder(crypto.NewKeyStorage(), tree.Header())
putStorage := func(change *objecttree.Change) (err error) {
var raw *treechangeproto.RawTreeChangeWithId
raw, err = changeBuilder.Marshall(change)
if err != nil {
return
}
return treeStorage.AddRawChange(raw)
}
err = tree.IterateRoot(t.converter.Unmarshall, func(change *objecttree.Change) bool {
if change.Id == tree.Id() {
err = putStorage(change)
return err == nil
}
var data []byte
data, err = t.converter.Marshall(change.Model)
if err != nil {
return false
}
// that means that change is unencrypted
change.ReadKeyId = ""
change.Data = data
err = putStorage(change)
return err == nil
})
if err != nil {
return
}
return treeStorage.SetHeads(tree.Heads())
}
@@ -1,28 +0,0 @@
package exporter
import (
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
)
type TreeImportParams struct {
ListStorage liststorage.ListStorage
TreeStorage treestorage.TreeStorage
BeforeId string
IncludeBeforeId bool
}
func ImportHistoryTree(params TreeImportParams) (tree objecttree.ReadableObjectTree, err error) {
aclList, err := list.BuildAclList(params.ListStorage)
if err != nil {
return
}
return objecttree.BuildNonVerifiableHistoryTree(objecttree.HistoryTreeParams{
TreeStorage: params.TreeStorage,
AclList: aclList,
BeforeId: params.BeforeId,
IncludeBeforeId: params.IncludeBeforeId,
})
}
+11 -20
View File
@@ -2,9 +2,7 @@ package objecttree
import (
"errors"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/util/crypto"
"github.com/gogo/protobuf/proto"
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
)
var (
@@ -19,51 +17,44 @@ type Change struct {
AclHeadId string
Id string
SnapshotId string
IsSnapshot bool
Timestamp int64
ReadKeyId string
Identity crypto.PubKey
ReadKeyHash uint64
Identity string
Data []byte
Model interface{}
Signature []byte
// iterator helpers
visited bool
branchesFinished bool
IsSnapshot bool
Signature []byte
}
func NewChange(id string, identity crypto.PubKey, ch *treechangeproto.TreeChange, 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,
ReadKeyId: ch.ReadKeyId,
ReadKeyHash: ch.CurrentReadKeyHash,
Id: id,
Data: ch.ChangesData,
SnapshotId: ch.SnapshotBaseId,
IsSnapshot: ch.IsSnapshot,
Identity: identity,
Identity: string(ch.Identity),
Signature: signature,
}
}
func NewChangeFromRoot(id string, identity crypto.PubKey, ch *treechangeproto.RootChange, signature []byte) *Change {
changeInfo := &treechangeproto.TreeChangeInfo{
ChangeType: ch.ChangeType,
ChangePayload: ch.ChangePayload,
}
data, _ := proto.Marshal(changeInfo)
func NewChangeFromRoot(id string, ch *treechangeproto.RootChange, signature []byte) *Change {
return &Change{
Next: nil,
AclHeadId: ch.AclHeadId,
Id: id,
IsSnapshot: true,
Timestamp: ch.Timestamp,
Identity: identity,
Identity: string(ch.Identity),
Signature: signature,
Data: data,
Model: changeInfo,
}
}
@@ -2,76 +2,57 @@ package objecttree
import (
"errors"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/util/cidutil"
"github.com/anyproto/any-sync/util/crypto"
"github.com/anytypeio/any-sync/commonspace/object/keychain"
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anytypeio/any-sync/util/cidutil"
"github.com/anytypeio/any-sync/util/keys/asymmetric/signingkey"
"github.com/anytypeio/any-sync/util/keys/symmetric"
"github.com/gogo/protobuf/proto"
"time"
)
var ErrEmptyChange = errors.New("change payload should not be empty")
type BuilderContent struct {
TreeHeadIds []string
AclHeadId string
SnapshotBaseId string
ReadKeyId string
IsSnapshot bool
PrivKey crypto.PrivKey
ReadKey crypto.SymKey
Content []byte
Timestamp int64
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
PrivKey crypto.PrivKey
SpaceId string
Seed []byte
ChangeType string
ChangePayload []byte
Timestamp int64
}
type nonVerifiableChangeBuilder struct {
ChangeBuilder
}
func (c *nonVerifiableChangeBuilder) BuildRoot(payload InitialContent) (ch *Change, raw *treechangeproto.RawTreeChangeWithId, err error) {
return c.ChangeBuilder.BuildRoot(payload)
}
func (c *nonVerifiableChangeBuilder) Unmarshall(rawChange *treechangeproto.RawTreeChangeWithId, verify bool) (ch *Change, err error) {
return c.ChangeBuilder.Unmarshall(rawChange, false)
}
func (c *nonVerifiableChangeBuilder) Build(payload BuilderContent) (ch *Change, raw *treechangeproto.RawTreeChangeWithId, err error) {
return c.ChangeBuilder.Build(payload)
}
func (c *nonVerifiableChangeBuilder) Marshall(ch *Change) (raw *treechangeproto.RawTreeChangeWithId, err error) {
return c.ChangeBuilder.Marshall(ch)
AclHeadId string
Identity []byte
SigningKey signingkey.PrivKey
SpaceId string
Seed []byte
ChangeType string
Timestamp int64
}
type ChangeBuilder interface {
Unmarshall(rawIdChange *treechangeproto.RawTreeChangeWithId, verify bool) (ch *Change, err error)
Build(payload BuilderContent) (ch *Change, raw *treechangeproto.RawTreeChangeWithId, err error)
BuildRoot(payload InitialContent) (ch *Change, raw *treechangeproto.RawTreeChangeWithId, err error)
Marshall(ch *Change) (*treechangeproto.RawTreeChangeWithId, 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 newChangeFunc = func(id string, identity crypto.PubKey, ch *treechangeproto.TreeChange, signature []byte) *Change
type changeBuilder struct {
rootChange *treechangeproto.RawTreeChangeWithId
keys crypto.KeyStorage
newChange newChangeFunc
keys *keychain.Keychain
}
func NewChangeBuilder(keys crypto.KeyStorage, rootChange *treechangeproto.RawTreeChangeWithId) ChangeBuilder {
return &changeBuilder{keys: keys, rootChange: rootChange, newChange: NewChange}
func NewChangeBuilder(keys *keychain.Keychain, rootChange *treechangeproto.RawTreeChangeWithId) ChangeBuilder {
return &changeBuilder{keys: keys, rootChange: rootChange}
}
func (c *changeBuilder) Unmarshall(rawIdChange *treechangeproto.RawTreeChangeWithId, verify bool) (ch *Change, err error) {
func (c *changeBuilder) ConvertFromRaw(rawIdChange *treechangeproto.RawTreeChangeWithId, verify bool) (ch *Change, err error) {
if rawIdChange.GetRawChange() == nil {
err = ErrEmptyChange
return
@@ -96,9 +77,15 @@ func (c *changeBuilder) Unmarshall(rawIdChange *treechangeproto.RawTreeChangeWit
}
if verify {
var identityKey signingkey.PubKey
identityKey, err = c.keys.GetOrAdd(ch.Identity)
if err != nil {
return
}
// verifying signature
var res bool
res, err = ch.Identity.Verify(raw.Payload, raw.Signature)
res, err = identityKey.Verify(raw.Payload, raw.Signature)
if err != nil {
return
}
@@ -114,41 +101,43 @@ func (c *changeBuilder) SetRootRawChange(rawIdChange *treechangeproto.RawTreeCha
c.rootChange = rawIdChange
}
func (c *changeBuilder) BuildRoot(payload InitialContent) (ch *Change, rawIdChange *treechangeproto.RawTreeChangeWithId, err error) {
identity, err := payload.PrivKey.GetPublic().Marshall()
if err != nil {
return
}
func (c *changeBuilder) BuildInitialContent(payload InitialContent) (ch *Change, rawIdChange *treechangeproto.RawTreeChangeWithId, err error) {
change := &treechangeproto.RootChange{
AclHeadId: payload.AclHeadId,
Timestamp: payload.Timestamp,
Identity: identity,
ChangeType: payload.ChangeType,
ChangePayload: payload.ChangePayload,
SpaceId: payload.SpaceId,
Seed: payload.Seed,
AclHeadId: payload.AclHeadId,
Timestamp: payload.Timestamp,
Identity: payload.Identity,
ChangeType: payload.ChangeType,
SpaceId: payload.SpaceId,
Seed: payload.Seed,
}
marshalledChange, err := proto.Marshal(change)
if err != nil {
return
}
signature, err := payload.PrivKey.Sign(marshalledChange)
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 := cidutil.NewCidFromBytes(marshalledRawChange)
if err != nil {
return
}
ch = NewChangeFromRoot(id, payload.PrivKey.GetPublic(), change, signature)
ch = NewChangeFromRoot(id, change, signature)
rawIdChange = &treechangeproto.RawTreeChangeWithId{
RawChange: marshalledRawChange,
Id: id,
@@ -156,19 +145,15 @@ func (c *changeBuilder) BuildRoot(payload InitialContent) (ch *Change, rawIdChan
return
}
func (c *changeBuilder) Build(payload BuilderContent) (ch *Change, rawIdChange *treechangeproto.RawTreeChangeWithId, err error) {
identity, err := payload.PrivKey.GetPublic().Marshall()
if err != nil {
return
}
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,
ReadKeyId: payload.ReadKeyId,
Timestamp: payload.Timestamp,
Identity: identity,
IsSnapshot: payload.IsSnapshot,
TreeHeadIds: payload.TreeHeadIds,
AclHeadId: payload.AclHeadId,
SnapshotBaseId: payload.SnapshotBaseId,
CurrentReadKeyHash: payload.CurrentReadKeyHash,
Timestamp: int64(time.Now().Nanosecond()),
Identity: payload.Identity,
IsSnapshot: payload.IsSnapshot,
}
if payload.ReadKey != nil {
var encrypted []byte
@@ -180,27 +165,34 @@ func (c *changeBuilder) Build(payload BuilderContent) (ch *Change, rawIdChange *
} else {
change.ChangesData = payload.Content
}
marshalledChange, err := proto.Marshal(change)
if err != nil {
return
}
signature, err := payload.PrivKey.Sign(marshalledChange)
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 := cidutil.NewCidFromBytes(marshalledRawChange)
if err != nil {
return
}
ch = c.newChange(id, payload.PrivKey.GetPublic(), change, signature)
ch = NewChange(id, change, signature)
rawIdChange = &treechangeproto.RawTreeChangeWithId{
RawChange: marshalledRawChange,
Id: id,
@@ -208,23 +200,19 @@ func (c *changeBuilder) Build(payload BuilderContent) (ch *Change, rawIdChange *
return
}
func (c *changeBuilder) Marshall(ch *Change) (raw *treechangeproto.RawTreeChangeWithId, err error) {
if c.isRoot(ch.Id) {
func (c *changeBuilder) BuildRaw(ch *Change) (raw *treechangeproto.RawTreeChangeWithId, err error) {
if ch.Id == c.rootChange.Id {
return c.rootChange, nil
}
identity, err := ch.Identity.Marshall()
if err != nil {
return
}
treeChange := &treechangeproto.TreeChange{
TreeHeadIds: ch.PreviousIds,
AclHeadId: ch.AclHeadId,
SnapshotBaseId: ch.SnapshotId,
ChangesData: ch.Data,
ReadKeyId: ch.ReadKeyId,
Timestamp: ch.Timestamp,
Identity: identity,
IsSnapshot: ch.IsSnapshot,
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()
@@ -248,36 +236,22 @@ func (c *changeBuilder) Marshall(ch *Change) (raw *treechangeproto.RawTreeChange
}
func (c *changeBuilder) unmarshallRawChange(raw *treechangeproto.RawTreeChange, id string) (ch *Change, err error) {
var key crypto.PubKey
if c.isRoot(id) {
if c.rootChange.Id == id {
unmarshalled := &treechangeproto.RootChange{}
err = proto.Unmarshal(raw.Payload, unmarshalled)
if err != nil {
return
}
key, err = c.keys.PubKeyFromProto(unmarshalled.Identity)
if err != nil {
return
}
ch = NewChangeFromRoot(id, key, unmarshalled, raw.Signature)
ch = NewChangeFromRoot(id, unmarshalled, raw.Signature)
return
}
unmarshalled := &treechangeproto.TreeChange{}
err = proto.Unmarshal(raw.Payload, unmarshalled)
if err != nil {
return
}
key, err = c.keys.PubKeyFromProto(unmarshalled.Identity)
if err != nil {
return
}
ch = c.newChange(id, key, unmarshalled, raw.Signature)
ch = NewChange(id, unmarshalled, raw.Signature)
return
}
func (c *changeBuilder) isRoot(id string) bool {
if c.rootChange != nil {
return c.rootChange.Id == id
}
return false
}
@@ -1,12 +1,9 @@
package objecttree
import (
"context"
"fmt"
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
"github.com/anyproto/any-sync/util/slice"
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
)
type ObjectTreeValidator interface {
@@ -16,16 +13,6 @@ type ObjectTreeValidator interface {
ValidateNewChanges(tree *Tree, aclList list.AclList, newChanges []*Change) error
}
type noOpTreeValidator struct{}
func (n *noOpTreeValidator) ValidateFullTree(tree *Tree, aclList list.AclList) error {
return nil
}
func (n *noOpTreeValidator) ValidateNewChanges(tree *Tree, aclList list.AclList, newChanges []*Change) error {
return nil
}
type objectTreeValidator struct{}
func newTreeValidator() ObjectTreeValidator {
@@ -52,16 +39,16 @@ func (v *objectTreeValidator) ValidateNewChanges(tree *Tree, aclList list.AclLis
func (v *objectTreeValidator) validateChange(tree *Tree, aclList list.AclList, c *Change) (err error) {
var (
perm list.AclUserState
perm list.UserPermissionPair
state = aclList.AclState()
)
// checking if the user could write
perm, err = state.StateAtRecord(c.AclHeadId, c.Identity)
perm, err = state.PermissionsAtRecord(c.AclHeadId, c.Identity)
if err != nil {
return
}
if perm.Permissions != aclrecordproto.AclUserPermissions_Writer && perm.Permissions != aclrecordproto.AclUserPermissions_Admin {
if perm.Permission != aclrecordproto.AclUserPermissions_Writer && perm.Permission != aclrecordproto.AclUserPermissions_Admin {
err = list.ErrInsufficientPermissions
return
}
@@ -88,25 +75,3 @@ func (v *objectTreeValidator) validateChange(tree *Tree, aclList list.AclList, c
}
return
}
func ValidateRawTree(payload treestorage.TreeStorageCreatePayload, aclList list.AclList) (err error) {
treeStorage, err := treestorage.NewInMemoryTreeStorage(payload.RootRawChange, []string{payload.RootRawChange.Id}, nil)
if err != nil {
return
}
tree, err := BuildObjectTree(treeStorage, aclList)
if err != nil {
return
}
res, err := tree.AddRawChanges(context.Background(), RawChangesPayload{
NewHeads: payload.Heads,
RawChanges: payload.Changes,
})
if err != nil {
return
}
if !slice.UnsortedEquals(res.Heads, payload.Heads) {
return ErrHasInvalidChanges
}
return
}
@@ -1,60 +0,0 @@
package objecttree
import (
"errors"
)
var ErrLoadBeforeRoot = errors.New("can't load before root")
type HistoryTree interface {
ReadableObjectTree
}
type historyTree struct {
*objectTree
}
func (h *historyTree) rebuildFromStorage(params HistoryTreeParams) (err error) {
err = h.rebuild(params)
if err != nil {
return
}
h.aclList.RLock()
defer h.aclList.RUnlock()
state := h.aclList.AclState()
return h.readKeysFromAclState(state)
}
func (h *historyTree) rebuild(params HistoryTreeParams) (err error) {
var (
beforeId = params.BeforeId
include = params.IncludeBeforeId
full = params.BuildFullTree
)
h.treeBuilder.Reset()
if full {
h.tree, err = h.treeBuilder.BuildFull()
return
}
if beforeId == h.Id() && !include {
return ErrLoadBeforeRoot
}
heads := []string{beforeId}
if beforeId == "" {
heads, err = h.treeStorage.Heads()
if err != nil {
return
}
} else if !include {
beforeChange, err := h.treeBuilder.loadChange(beforeId)
if err != nil {
return err
}
heads = beforeChange.PreviousIds
}
h.tree, err = h.treeBuilder.build(heads, nil, nil)
return
}
@@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/anyproto/any-sync/commonspace/object/tree/objecttree (interfaces: ObjectTree)
// Source: github.com/anytypeio/any-sync/commonspace/object/tree/objecttree (interfaces: ObjectTree)
// Package mock_objecttree is a generated GoMock package.
package mock_objecttree
@@ -7,12 +7,10 @@ package mock_objecttree
import (
context "context"
reflect "reflect"
time "time"
list "github.com/anyproto/any-sync/commonspace/object/acl/list"
objecttree "github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
treechangeproto "github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
treestorage "github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
objecttree "github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
treechangeproto "github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
treestorage "github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
gomock "github.com/golang/mock/gomock"
)
@@ -39,20 +37,6 @@ func (m *MockObjectTree) EXPECT() *MockObjectTreeMockRecorder {
return m.recorder
}
// AclList mocks base method.
func (m *MockObjectTree) AclList() list.AclList {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AclList")
ret0, _ := ret[0].(list.AclList)
return ret0
}
// AclList indicates an expected call of AclList.
func (mr *MockObjectTreeMockRecorder) AclList() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AclList", reflect.TypeOf((*MockObjectTree)(nil).AclList))
}
// AddContent mocks base method.
func (m *MockObjectTree) AddContent(arg0 context.Context, arg1 objecttree.SignableChangeContent) (objecttree.AddResult, error) {
m.ctrl.T.Helper()
@@ -83,20 +67,6 @@ func (mr *MockObjectTreeMockRecorder) AddRawChanges(arg0, arg1 interface{}) *gom
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawChanges", reflect.TypeOf((*MockObjectTree)(nil).AddRawChanges), arg0, arg1)
}
// ChangeInfo mocks base method.
func (m *MockObjectTree) ChangeInfo() *treechangeproto.TreeChangeInfo {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ChangeInfo")
ret0, _ := ret[0].(*treechangeproto.TreeChangeInfo)
return ret0
}
// ChangeInfo indicates an expected call of ChangeInfo.
func (mr *MockObjectTreeMockRecorder) ChangeInfo() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChangeInfo", reflect.TypeOf((*MockObjectTree)(nil).ChangeInfo))
}
// ChangesAfterCommonSnapshot mocks base method.
func (m *MockObjectTree) ChangesAfterCommonSnapshot(arg0, arg1 []string) ([]*treechangeproto.RawTreeChangeWithId, error) {
m.ctrl.T.Helper()
@@ -126,19 +96,19 @@ func (mr *MockObjectTreeMockRecorder) Close() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockObjectTree)(nil).Close))
}
// Debug mocks base method.
func (m *MockObjectTree) Debug(arg0 objecttree.DescriptionParser) (objecttree.DebugInfo, error) {
// DebugDump mocks base method.
func (m *MockObjectTree) DebugDump(arg0 objecttree.DescriptionParser) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Debug", arg0)
ret0, _ := ret[0].(objecttree.DebugInfo)
ret := m.ctrl.Call(m, "DebugDump", arg0)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Debug indicates an expected call of Debug.
func (mr *MockObjectTreeMockRecorder) Debug(arg0 interface{}) *gomock.Call {
// DebugDump indicates an expected call of DebugDump.
func (mr *MockObjectTreeMockRecorder) DebugDump(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debug", reflect.TypeOf((*MockObjectTree)(nil).Debug), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DebugDump", reflect.TypeOf((*MockObjectTree)(nil).DebugDump), arg0)
}
// Delete mocks base method.
@@ -155,21 +125,6 @@ func (mr *MockObjectTreeMockRecorder) Delete() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockObjectTree)(nil).Delete))
}
// GetChange mocks base method.
func (m *MockObjectTree) GetChange(arg0 string) (*objecttree.Change, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetChange", arg0)
ret0, _ := ret[0].(*objecttree.Change)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetChange indicates an expected call of GetChange.
func (mr *MockObjectTreeMockRecorder) GetChange(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChange", reflect.TypeOf((*MockObjectTree)(nil).GetChange), arg0)
}
// HasChanges mocks base method.
func (m *MockObjectTree) HasChanges(arg0 ...string) bool {
m.ctrl.T.Helper()
@@ -258,20 +213,6 @@ func (mr *MockObjectTreeMockRecorder) IterateRoot(arg0, arg1 interface{}) *gomoc
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IterateRoot", reflect.TypeOf((*MockObjectTree)(nil).IterateRoot), arg0, arg1)
}
// Len mocks base method.
func (m *MockObjectTree) Len() int {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Len")
ret0, _ := ret[0].(int)
return ret0
}
// Len indicates an expected call of Len.
func (mr *MockObjectTreeMockRecorder) Len() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Len", reflect.TypeOf((*MockObjectTree)(nil).Len))
}
// Lock mocks base method.
func (m *MockObjectTree) Lock() {
m.ctrl.T.Helper()
@@ -284,21 +225,6 @@ func (mr *MockObjectTreeMockRecorder) Lock() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Lock", reflect.TypeOf((*MockObjectTree)(nil).Lock))
}
// PrepareChange mocks base method.
func (m *MockObjectTree) PrepareChange(arg0 objecttree.SignableChangeContent) (*treechangeproto.RawTreeChangeWithId, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "PrepareChange", arg0)
ret0, _ := ret[0].(*treechangeproto.RawTreeChangeWithId)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// PrepareChange indicates an expected call of PrepareChange.
func (mr *MockObjectTreeMockRecorder) PrepareChange(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrepareChange", reflect.TypeOf((*MockObjectTree)(nil).PrepareChange), arg0)
}
// RLock mocks base method.
func (m *MockObjectTree) RLock() {
m.ctrl.T.Helper()
@@ -365,49 +291,6 @@ func (mr *MockObjectTreeMockRecorder) Storage() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Storage", reflect.TypeOf((*MockObjectTree)(nil).Storage))
}
// TryClose mocks base method.
func (m *MockObjectTree) TryClose(arg0 time.Duration) (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "TryClose", arg0)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// TryClose indicates an expected call of TryClose.
func (mr *MockObjectTreeMockRecorder) TryClose(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TryClose", reflect.TypeOf((*MockObjectTree)(nil).TryClose), arg0)
}
// TryLock mocks base method.
func (m *MockObjectTree) TryLock() bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "TryLock")
ret0, _ := ret[0].(bool)
return ret0
}
// TryLock indicates an expected call of TryLock.
func (mr *MockObjectTreeMockRecorder) TryLock() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TryLock", reflect.TypeOf((*MockObjectTree)(nil).TryLock))
}
// TryRLock mocks base method.
func (m *MockObjectTree) TryRLock() bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "TryRLock")
ret0, _ := ret[0].(bool)
return ret0
}
// TryRLock indicates an expected call of TryRLock.
func (mr *MockObjectTreeMockRecorder) TryRLock() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TryRLock", reflect.TypeOf((*MockObjectTree)(nil).TryRLock))
}
// Unlock mocks base method.
func (m *MockObjectTree) Unlock() {
m.ctrl.T.Helper()
@@ -433,18 +316,3 @@ func (mr *MockObjectTreeMockRecorder) UnmarshalledHeader() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnmarshalledHeader", reflect.TypeOf((*MockObjectTree)(nil).UnmarshalledHeader))
}
// UnpackChange mocks base method.
func (m *MockObjectTree) UnpackChange(arg0 *treechangeproto.RawTreeChangeWithId) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UnpackChange", arg0)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UnpackChange indicates an expected call of UnpackChange.
func (mr *MockObjectTreeMockRecorder) UnpackChange(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnpackChange", reflect.TypeOf((*MockObjectTree)(nil).UnpackChange), arg0)
}
+130 -166
View File
@@ -1,33 +1,28 @@
//go:generate mockgen -destination mock_objecttree/mock_objecttree.go github.com/anyproto/any-sync/commonspace/object/tree/objecttree ObjectTree
//go:generate mockgen -destination mock_objecttree/mock_objecttree.go github.com/anytypeio/any-sync/commonspace/object/tree/objecttree ObjectTree
package objecttree
import (
"context"
"errors"
"github.com/anyproto/any-sync/util/crypto"
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
list2 "github.com/anytypeio/any-sync/commonspace/object/acl/list"
"github.com/anytypeio/any-sync/commonspace/object/keychain"
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
"github.com/anytypeio/any-sync/util/keys/symmetric"
"github.com/anytypeio/any-sync/util/slice"
"sync"
"time"
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
"github.com/anyproto/any-sync/util/slice"
)
type RWLocker interface {
sync.Locker
RLock()
RUnlock()
TryRLock() bool
TryLock() bool
}
var (
ErrHasInvalidChanges = errors.New("the change is invalid")
ErrNoCommonSnapshot = errors.New("trees doesn't have a common snapshot")
ErrNoChangeInTree = errors.New("no such change in tree")
ErrMissingKey = errors.New("missing current read key")
)
type AddResultSummary int
@@ -48,29 +43,19 @@ type RawChangesPayload struct {
type ChangeIterateFunc = func(change *Change) bool
type ChangeConvertFunc = func(decrypted []byte) (any, error)
type ReadableObjectTree interface {
type ObjectTree interface {
RWLocker
Id() string
Header() *treechangeproto.RawTreeChangeWithId
UnmarshalledHeader() *Change
ChangeInfo() *treechangeproto.TreeChangeInfo
Heads() []string
Root() *Change
Len() int
AclList() list.AclList
HasChanges(...string) bool
GetChange(string) (*Change, error)
DebugDump(parser DescriptionParser) (string, error)
Debug(parser DescriptionParser) (DebugInfo, error)
IterateRoot(convert ChangeConvertFunc, iterate ChangeIterateFunc) error
IterateFrom(id string, convert ChangeConvertFunc, iterate ChangeIterateFunc) error
}
type ObjectTree interface {
ReadableObjectTree
SnapshotPath() []string
ChangesAfterCommonSnapshot(snapshotPath, heads []string) ([]*treechangeproto.RawTreeChangeWithId, error)
@@ -80,12 +65,8 @@ type ObjectTree interface {
AddContent(ctx context.Context, content SignableChangeContent) (AddResult, error)
AddRawChanges(ctx context.Context, changes RawChangesPayload) (AddResult, error)
UnpackChange(raw *treechangeproto.RawTreeChangeWithId) (data []byte, err error)
PrepareChange(content SignableChangeContent) (res *treechangeproto.RawTreeChangeWithId, err error)
Delete() error
Close() error
TryClose(objectTTL time.Duration) (bool, error)
}
type objectTree struct {
@@ -94,15 +75,14 @@ type objectTree struct {
validator ObjectTreeValidator
rawChangeLoader *rawChangeLoader
treeBuilder *treeBuilder
aclList list.AclList
aclList list2.AclList
id string
rawRoot *treechangeproto.RawTreeChangeWithId
root *Change
tree *Tree
keys map[string]crypto.SymKey
currentReadKey crypto.SymKey
keys map[uint64]*symmetric.Key
// buffers
difSnapshotBuf []*treechangeproto.RawTreeChangeWithId
@@ -115,26 +95,41 @@ type objectTree struct {
sync.RWMutex
}
type objectTreeDeps struct {
changeBuilder ChangeBuilder
treeBuilder *treeBuilder
treeStorage treestorage.TreeStorage
validator ObjectTreeValidator
rawChangeLoader *rawChangeLoader
aclList list2.AclList
}
func defaultObjectTreeDeps(
rootChange *treechangeproto.RawTreeChangeWithId,
treeStorage treestorage.TreeStorage,
aclList list2.AclList) objectTreeDeps {
keychain := keychain.NewKeychain()
changeBuilder := NewChangeBuilder(keychain, rootChange)
treeBuilder := newTreeBuilder(treeStorage, changeBuilder)
return objectTreeDeps{
changeBuilder: changeBuilder,
treeBuilder: treeBuilder,
treeStorage: treeStorage,
validator: newTreeValidator(),
rawChangeLoader: newRawChangeLoader(treeStorage, changeBuilder),
aclList: aclList,
}
}
func (ot *objectTree) rebuildFromStorage(theirHeads []string, newChanges []*Change) (err error) {
oldTree := ot.tree
ot.treeBuilder.Reset()
ot.tree, err = ot.treeBuilder.Build(theirHeads, newChanges)
if err != nil {
return
}
// in case there are new heads
if theirHeads != nil && oldTree != nil {
// checking that old root is still in tree
rootCh, rootExists := ot.tree.attached[oldTree.RootId()]
// checking the case where theirHeads were actually below prevHeads
// so if we did load some extra data in the tree, let's reduce it to old root
if slice.UnsortedEquals(oldTree.headIds, ot.tree.headIds) && rootExists && ot.tree.RootId() != oldTree.RootId() {
ot.tree.makeRootAndRemove(rootCh)
}
}
// during building the tree we may have marked some changes as possible roots,
// but obviously they are not roots, because of the way how we construct the tree
ot.tree.clearPossibleRoots()
@@ -148,14 +143,6 @@ func (ot *objectTree) Id() string {
return ot.id
}
func (ot *objectTree) Len() int {
return ot.tree.Len()
}
func (ot *objectTree) AclList() list.AclList {
return ot.aclList
}
func (ot *objectTree) Header() *treechangeproto.RawTreeChangeWithId {
return ot.rawRoot
}
@@ -164,21 +151,10 @@ func (ot *objectTree) UnmarshalledHeader() *Change {
return ot.root
}
func (ot *objectTree) ChangeInfo() *treechangeproto.TreeChangeInfo {
return ot.root.Model.(*treechangeproto.TreeChangeInfo)
}
func (ot *objectTree) Storage() treestorage.TreeStorage {
return ot.treeStorage
}
func (ot *objectTree) GetChange(id string) (*Change, error) {
if ch, ok := ot.tree.attached[id]; ok {
return ch, nil
}
return nil, ErrNoChangeInTree
}
func (ot *objectTree) AddContent(ctx context.Context, content SignableChangeContent) (res AddResult, err error) {
payload, err := ot.prepareBuilderContent(content)
if err != nil {
@@ -189,7 +165,7 @@ func (ot *objectTree) AddContent(ctx context.Context, content SignableChangeCont
oldHeads := make([]string, 0, len(ot.tree.Heads()))
oldHeads = append(oldHeads, ot.tree.Heads()...)
objChange, rawChange, err := ot.changeBuilder.Build(payload)
objChange, rawChange, err := ot.changeBuilder.BuildContent(payload)
if content.IsSnapshot {
// clearing tree, because we already saved everything in the last snapshot
ot.tree = &Tree{}
@@ -199,90 +175,65 @@ func (ot *objectTree) AddContent(ctx context.Context, content SignableChangeCont
panic(err)
}
err = ot.treeStorage.AddRawChangesSetHeads([]*treechangeproto.RawTreeChangeWithId{rawChange}, []string{objChange.Id})
err = ot.treeStorage.AddRawChange(rawChange)
if err != nil {
return
}
mode := Append
if content.IsSnapshot {
mode = Rebuild
err = ot.treeStorage.SetHeads([]string{objChange.Id})
if err != nil {
return
}
res = AddResult{
OldHeads: oldHeads,
Heads: []string{objChange.Id},
Added: []*treechangeproto.RawTreeChangeWithId{rawChange},
Mode: mode,
Mode: Append,
}
log.With("treeId", ot.id).With("head", objChange.Id).
Debug("finished adding content")
return
}
func (ot *objectTree) UnpackChange(raw *treechangeproto.RawTreeChangeWithId) (data []byte, err error) {
unmarshalled, err := ot.changeBuilder.Unmarshall(raw, true)
if err != nil {
return
}
data = unmarshalled.Data
return
}
func (ot *objectTree) PrepareChange(content SignableChangeContent) (res *treechangeproto.RawTreeChangeWithId, err error) {
payload, err := ot.prepareBuilderContent(content)
if err != nil {
return
}
_, res, err = ot.changeBuilder.Build(payload)
return
}
func (ot *objectTree) prepareBuilderContent(content SignableChangeContent) (cnt BuilderContent, err error) {
ot.aclList.RLock()
defer ot.aclList.RUnlock()
var (
state = ot.aclList.AclState() // special method for own keys
readKey crypto.SymKey
pubKey = content.Key.GetPublic()
readKeyId string
state = ot.aclList.AclState() // special method for own keys
readKey *symmetric.Key
readKeyHash uint64
)
canWrite := state.HasPermission(pubKey, aclrecordproto.AclUserPermissions_Writer) ||
state.HasPermission(pubKey, aclrecordproto.AclUserPermissions_Admin)
canWrite := state.HasPermission(content.Identity, aclrecordproto.AclUserPermissions_Writer) ||
state.HasPermission(content.Identity, aclrecordproto.AclUserPermissions_Admin)
if !canWrite {
err = list.ErrInsufficientPermissions
err = list2.ErrInsufficientPermissions
return
}
if content.IsEncrypted {
readKeyId = state.CurrentReadKeyId()
if ot.currentReadKey == nil {
err = ErrMissingKey
readKeyHash = state.CurrentReadKeyHash()
readKey, err = state.CurrentReadKey()
if err != nil {
return
}
readKey = ot.currentReadKey
}
timestamp := content.Timestamp
if timestamp <= 0 {
timestamp = time.Now().Unix()
}
cnt = BuilderContent{
TreeHeadIds: ot.tree.Heads(),
AclHeadId: ot.aclList.Head().Id,
SnapshotBaseId: ot.tree.RootId(),
ReadKeyId: readKeyId,
IsSnapshot: content.IsSnapshot,
PrivKey: content.Key,
ReadKey: readKey,
Content: content.Data,
Timestamp: timestamp,
TreeHeadIds: ot.tree.Heads(),
AclHeadId: ot.aclList.Head().Id,
SnapshotBaseId: ot.tree.RootId(),
CurrentReadKeyHash: readKeyHash,
Identity: content.Identity,
IsSnapshot: content.IsSnapshot,
SigningKey: content.Key,
ReadKey: readKey,
Content: content.Data,
}
return
}
func (ot *objectTree) AddRawChanges(ctx context.Context, changesPayload RawChangesPayload) (addResult AddResult, err error) {
lastHeadId := ot.tree.lastIteratedHeadId
addResult, err = ot.addRawChanges(ctx, changesPayload)
if err != nil {
return
@@ -291,16 +242,16 @@ func (ot *objectTree) AddRawChanges(ctx context.Context, changesPayload RawChang
// reducing tree if we have new roots
ot.tree.reduceTree()
// that means that we removed the ids while reducing
if _, exists := ot.tree.attached[lastHeadId]; !exists {
addResult.Mode = Rebuild
// adding to database all the added changes only after they are good
for _, ch := range addResult.Added {
err = ot.treeStorage.AddRawChange(ch)
if err != nil {
return
}
}
err = ot.treeStorage.AddRawChangesSetHeads(addResult.Added, addResult.Heads)
if err != nil {
// rolling back all changes made to inmemory state
ot.rebuildFromStorage(nil, nil)
}
// setting heads
err = ot.treeStorage.SetHeads(ot.tree.Heads())
return
}
@@ -331,7 +282,7 @@ func (ot *objectTree) addRawChanges(ctx context.Context, changesPayload RawChang
if unAttached, exists := ot.tree.unAttached[ch.Id]; exists {
change = unAttached
} else {
change, err = ot.changeBuilder.Unmarshall(ch, true)
change, err = ot.changeBuilder.ConvertFromRaw(ch, true)
if err != nil {
return
}
@@ -363,7 +314,7 @@ func (ot *objectTree) addRawChanges(ctx context.Context, changesPayload RawChang
}
// checks if we need to go to database
snapshotNotInTree := func(ch *Change) bool {
isOldSnapshot := func(ch *Change) bool {
if ch.SnapshotId == ot.tree.RootId() {
return false
}
@@ -378,12 +329,26 @@ func (ot *objectTree) addRawChanges(ctx context.Context, changesPayload RawChang
shouldRebuildFromStorage := false
// checking if we have some changes with different snapshot and then rebuilding
for _, ch := range ot.newChangesBuf {
if snapshotNotInTree(ch) {
for idx, ch := range ot.newChangesBuf {
if isOldSnapshot(ch) {
var exists bool
// checking if it exists in the storage, if yes, then at some point it was added to the tree
// thus we don't need to look at this change
exists, err = ot.treeStorage.HasChange(ctx, ch.Id)
if err != nil {
return
}
if exists {
// marking as nil to delete after
ot.newChangesBuf[idx] = nil
continue
}
// we haven't seen the change, and it refers to old snapshot, so we should rebuild
shouldRebuildFromStorage = true
break
}
}
// discarding all previously seen changes
ot.newChangesBuf = slice.DiscardFromSlice(ot.newChangesBuf, func(ch *Change) bool { return ch == nil })
if shouldRebuildFromStorage {
err = ot.rebuildFromStorage(changesPayload.NewHeads, ot.newChangesBuf)
@@ -468,7 +433,7 @@ func (ot *objectTree) createAddResult(oldHeads []string, mode Mode, treeChangesA
// if we got some changes that we need to convert to raw
if _, exists := alreadyConverted[ch]; !exists {
var raw *treechangeproto.RawTreeChangeWithId
raw, err = ot.changeBuilder.Marshall(ch)
raw, err = ot.changeBuilder.BuildRaw(ch)
if err != nil {
return
}
@@ -480,11 +445,6 @@ func (ot *objectTree) createAddResult(oldHeads []string, mode Mode, treeChangesA
var added []*treechangeproto.RawTreeChangeWithId
added, err = getAddedChanges(treeChangesAdded)
if !ot.treeBuilder.keepInMemoryData {
for _, ch := range treeChangesAdded {
ch.Data = nil
}
}
if err != nil {
return
}
@@ -508,13 +468,13 @@ func (ot *objectTree) IterateFrom(id string, convert ChangeConvertFunc, iterate
}
decrypt := func(c *Change) (decrypted []byte, err error) {
// the change is not encrypted
if c.ReadKeyId == "" {
if c.ReadKeyHash == 0 {
decrypted = c.Data
return
}
readKey, exists := ot.keys[c.ReadKeyId]
readKey, exists := ot.keys[c.ReadKeyHash]
if !exists {
err = list.ErrNoReadKey
err = list2.ErrNoReadKey
return
}
@@ -551,8 +511,22 @@ func (ot *objectTree) IterateFrom(id string, convert ChangeConvertFunc, iterate
}
func (ot *objectTree) HasChanges(chs ...string) bool {
hasChange := func(s string) bool {
_, attachedExists := ot.tree.attached[s]
if attachedExists {
return attachedExists
}
has, err := ot.treeStorage.HasChange(context.Background(), s)
if err != nil {
return false
}
return has
}
for _, ch := range chs {
if _, attachedExists := ot.tree.attached[ch]; !attachedExists {
if !hasChange(ch) {
return false
}
}
@@ -568,10 +542,6 @@ func (ot *objectTree) Root() *Change {
return ot.tree.Root()
}
func (ot *objectTree) TryClose(objectTTL time.Duration) (bool, error) {
return true, ot.Close()
}
func (ot *objectTree) Close() error {
return nil
}
@@ -619,7 +589,19 @@ func (ot *objectTree) ChangesAfterCommonSnapshot(theirPath, theirHeads []string)
}
}
return ot.rawChangeLoader.Load(commonSnapshot, ot.tree, theirHeads)
if commonSnapshot == ot.tree.RootId() {
return ot.getChangesFromTree(theirHeads)
} else {
return ot.getChangesFromDB(commonSnapshot, theirHeads)
}
}
func (ot *objectTree) getChangesFromTree(theirHeads []string) (rawChanges []*treechangeproto.RawTreeChangeWithId, err error) {
return ot.rawChangeLoader.LoadFromTree(ot.tree, theirHeads)
}
func (ot *objectTree) getChangesFromDB(commonSnapshot string, theirHeads []string) (rawChanges []*treechangeproto.RawTreeChangeWithId, err error) {
return ot.rawChangeLoader.LoadFromStorage(commonSnapshot, ot.tree.headIds, theirHeads)
}
func (ot *objectTree) snapshotPathIsActual() bool {
@@ -631,9 +613,11 @@ func (ot *objectTree) validateTree(newChanges []*Change) error {
defer ot.aclList.RUnlock()
state := ot.aclList.AclState()
err := ot.readKeysFromAclState(state)
if err != nil {
return err
// just not to take lock many times, updating the key map from aclList
if len(ot.keys) != len(state.UserReadKeys()) {
for key, value := range state.UserReadKeys() {
ot.keys[key] = value
}
}
if len(newChanges) == 0 {
return ot.validator.ValidateFullTree(ot.tree, ot.aclList)
@@ -642,26 +626,6 @@ func (ot *objectTree) validateTree(newChanges []*Change) error {
return ot.validator.ValidateNewChanges(ot.tree, ot.aclList, newChanges)
}
func (ot *objectTree) readKeysFromAclState(state *list.AclState) (err error) {
// just not to take lock many times, updating the key map from aclList
if len(ot.keys) == len(state.UserReadKeys()) {
return nil
}
for key, value := range state.UserReadKeys() {
treeKey, err := deriveTreeKey(value, ot.id)
if err != nil {
return err
}
ot.keys[key] = treeKey
}
curKey, err := state.CurrentReadKey()
if err != nil {
return err
}
ot.currentReadKey, err = deriveTreeKey(curKey, ot.id)
return err
}
func (ot *objectTree) Debug(parser DescriptionParser) (DebugInfo, error) {
return objectTreeDebug{}.debugInfo(ot, parser)
func (ot *objectTree) DebugDump(parser DescriptionParser) (string, error) {
return ot.tree.Graph(parser)
}
@@ -2,73 +2,133 @@ package objecttree
import (
"context"
"fmt"
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
"github.com/gogo/protobuf/proto"
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
"github.com/anytypeio/any-sync/commonspace/object/acl/testutils/acllistbuilder"
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
"time"
)
type mockChangeCreator struct{}
func (c *mockChangeCreator) createRoot(id, aclId string) *treechangeproto.RawTreeChangeWithId {
aclChange := &treechangeproto.RootChange{
AclHeadId: aclId,
}
res, _ := aclChange.Marshal()
raw := &treechangeproto.RawTreeChange{
Payload: res,
Signature: nil,
}
rawMarshalled, _ := raw.Marshal()
return &treechangeproto.RawTreeChangeWithId{
RawChange: rawMarshalled,
Id: id,
}
}
func (c *mockChangeCreator) createRaw(id, aclId, snapshotId string, isSnapshot bool, prevIds ...string) *treechangeproto.RawTreeChangeWithId {
aclChange := &treechangeproto.TreeChange{
TreeHeadIds: prevIds,
AclHeadId: aclId,
SnapshotBaseId: snapshotId,
ChangesData: nil,
IsSnapshot: isSnapshot,
}
res, _ := aclChange.Marshal()
raw := &treechangeproto.RawTreeChange{
Payload: res,
Signature: nil,
}
rawMarshalled, _ := raw.Marshal()
return &treechangeproto.RawTreeChangeWithId{
RawChange: rawMarshalled,
Id: id,
}
}
func (c *mockChangeCreator) createNewTreeStorage(treeId, aclHeadId string) treestorage.TreeStorage {
root := c.createRoot(treeId, aclHeadId)
treeStorage, _ := treestorage.NewInMemoryTreeStorage(root, []string{root.Id}, []*treechangeproto.RawTreeChangeWithId{root})
return treeStorage
}
type mockChangeBuilder struct {
originalBuilder ChangeBuilder
}
func (c *mockChangeBuilder) BuildInitialContent(payload InitialContent) (ch *Change, raw *treechangeproto.RawTreeChangeWithId, err error) {
panic("implement me")
}
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)
}
type mockChangeValidator struct{}
func (m *mockChangeValidator) ValidateNewChanges(tree *Tree, aclList list.AclList, newChanges []*Change) error {
return nil
}
func (m *mockChangeValidator) ValidateFullTree(tree *Tree, aclList list.AclList) error {
return nil
}
type testTreeContext struct {
aclList list.AclList
treeStorage treestorage.TreeStorage
changeCreator *MockChangeCreator
changeBuilder *mockChangeBuilder
changeCreator *mockChangeCreator
objTree ObjectTree
}
func prepareAclList(t *testing.T) (list.AclList, *accountdata.AccountKeys) {
randKeys, err := accountdata.NewRandom()
require.NoError(t, err)
aclList, err := list.NewTestDerivedAcl("spaceId", randKeys)
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(st)
require.NoError(t, err, "building acl list should be without error")
return aclList, randKeys
}
func prepareHistoryTreeDeps(aclList list.AclList) (*MockChangeCreator, objectTreeDeps) {
changeCreator := NewMockChangeCreator()
treeStorage := changeCreator.CreateNewTreeStorage("0", aclList.Head().Id)
root, _ := treeStorage.Root()
changeBuilder := &nonVerifiableChangeBuilder{
ChangeBuilder: NewChangeBuilder(newMockKeyStorage(), root),
}
deps := objectTreeDeps{
changeBuilder: changeBuilder,
treeBuilder: newTreeBuilder(true, treeStorage, changeBuilder),
treeStorage: treeStorage,
rawChangeLoader: newRawChangeLoader(treeStorage, changeBuilder),
validator: &noOpTreeValidator{},
aclList: aclList,
}
return changeCreator, deps
return aclList
}
func prepareTreeContext(t *testing.T, aclList list.AclList) testTreeContext {
return prepareContext(t, aclList, BuildTestableTree, nil)
}
func prepareEmptyDataTreeContext(t *testing.T, aclList list.AclList, additionalChanges func(changeCreator *MockChangeCreator) RawChangesPayload) testTreeContext {
return prepareContext(t, aclList, BuildEmptyDataTestableTree, additionalChanges)
}
func prepareContext(
t *testing.T,
aclList list.AclList,
objTreeBuilder BuildObjectTreeFunc,
additionalChanges func(changeCreator *MockChangeCreator) RawChangesPayload) testTreeContext {
changeCreator := NewMockChangeCreator()
treeStorage := changeCreator.CreateNewTreeStorage("0", aclList.Head().Id)
if additionalChanges != nil {
payload := additionalChanges(changeCreator)
err := treeStorage.AddRawChangesSetHeads(payload.RawChanges, payload.NewHeads)
require.NoError(t, err)
changeCreator := &mockChangeCreator{}
treeStorage := changeCreator.createNewTreeStorage("0", aclList.Head().Id)
root, _ := treeStorage.Root()
changeBuilder := &mockChangeBuilder{
originalBuilder: NewChangeBuilder(nil, root),
}
objTree, err := objTreeBuilder(treeStorage, aclList)
deps := objectTreeDeps{
changeBuilder: changeBuilder,
treeBuilder: newTreeBuilder(treeStorage, changeBuilder),
treeStorage: treeStorage,
rawChangeLoader: newRawChangeLoader(treeStorage, changeBuilder),
validator: &mockChangeValidator{},
aclList: aclList,
}
// check build
objTree, err := buildObjectTree(deps)
require.NoError(t, err, "building tree should be without error")
// check tree iterate
@@ -78,69 +138,18 @@ func prepareContext(
return true
})
require.NoError(t, err, "iterate should be without error")
if additionalChanges == nil {
assert.Equal(t, []string{"0"}, iterChangesId)
}
assert.Equal(t, []string{"0"}, iterChangesId)
return testTreeContext{
aclList: aclList,
treeStorage: treeStorage,
changeBuilder: changeBuilder,
changeCreator: changeCreator,
objTree: objTree,
}
}
func TestObjectTree(t *testing.T) {
aclList, keys := prepareAclList(t)
ctx := context.Background()
t.Run("add content", func(t *testing.T) {
root, err := CreateObjectTreeRoot(ObjectTreeCreatePayload{
PrivKey: keys.SignKey,
ChangeType: "changeType",
ChangePayload: nil,
SpaceId: "spaceId",
IsEncrypted: true,
}, aclList)
require.NoError(t, err)
store, _ := treestorage.NewInMemoryTreeStorage(root, []string{root.Id}, []*treechangeproto.RawTreeChangeWithId{root})
oTree, err := BuildObjectTree(store, aclList)
require.NoError(t, err)
t.Run("0 timestamp is changed to current", func(t *testing.T) {
start := time.Now()
res, err := oTree.AddContent(ctx, SignableChangeContent{
Data: []byte("some"),
Key: keys.SignKey,
IsSnapshot: false,
IsEncrypted: true,
Timestamp: 0,
})
end := time.Now()
require.NoError(t, err)
require.Len(t, oTree.Heads(), 1)
require.Equal(t, res.Added[0].Id, oTree.Heads()[0])
ch, err := oTree.(*objectTree).changeBuilder.Unmarshall(res.Added[0], true)
require.NoError(t, err)
require.GreaterOrEqual(t, start.Unix(), ch.Timestamp)
require.LessOrEqual(t, end.Unix(), ch.Timestamp)
})
t.Run("timestamp is set correctly", func(t *testing.T) {
someTs := time.Now().Add(time.Hour).Unix()
res, err := oTree.AddContent(ctx, SignableChangeContent{
Data: []byte("some"),
Key: keys.SignKey,
IsSnapshot: false,
IsEncrypted: true,
Timestamp: someTs,
})
require.NoError(t, err)
require.Len(t, oTree.Heads(), 1)
require.Equal(t, res.Added[0].Id, oTree.Heads()[0])
ch, err := oTree.(*objectTree).changeBuilder.Unmarshall(res.Added[0], true)
require.NoError(t, err)
require.Equal(t, ch.Timestamp, someTs)
})
})
aclList := prepareAclList(t)
t.Run("add simple", func(t *testing.T) {
ctx := prepareTreeContext(t, aclList)
@@ -149,8 +158,8 @@ func TestObjectTree(t *testing.T) {
objTree := ctx.objTree
rawChanges := []*treechangeproto.RawTreeChangeWithId{
changeCreator.CreateRaw("1", aclList.Head().Id, "0", false, "0"),
changeCreator.CreateRaw("2", aclList.Head().Id, "0", false, "1"),
changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"),
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
}
payload := RawChangesPayload{
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
@@ -194,7 +203,7 @@ func TestObjectTree(t *testing.T) {
objTree := ctx.objTree
rawChanges := []*treechangeproto.RawTreeChangeWithId{
changeCreator.CreateRaw("0", aclList.Head().Id, "", true, ""),
changeCreator.createRaw("0", aclList.Head().Id, "", true, ""),
}
payload := RawChangesPayload{
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
@@ -218,33 +227,7 @@ func TestObjectTree(t *testing.T) {
objTree := ctx.objTree
rawChanges := []*treechangeproto.RawTreeChangeWithId{
changeCreator.CreateRaw("2", aclList.Head().Id, "0", false, "1"),
}
payload := RawChangesPayload{
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
RawChanges: rawChanges,
}
res, err := objTree.AddRawChanges(context.Background(), payload)
require.NoError(t, err, "adding changes should be without error")
// check result
assert.Equal(t, []string{"0"}, res.OldHeads)
assert.Equal(t, []string{"0"}, res.Heads)
assert.Equal(t, 0, len(res.Added))
assert.Equal(t, Nothing, res.Mode)
// check tree heads
assert.Equal(t, []string{"0"}, objTree.Heads())
})
t.Run("add not connected changes", func(t *testing.T) {
ctx := prepareTreeContext(t, aclList)
changeCreator := ctx.changeCreator
objTree := ctx.objTree
// this change could in theory replace current snapshot, we should prevent that
rawChanges := []*treechangeproto.RawTreeChangeWithId{
changeCreator.CreateRaw("2", aclList.Head().Id, "0", true, "1"),
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
}
payload := RawChangesPayload{
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
@@ -270,10 +253,10 @@ func TestObjectTree(t *testing.T) {
objTree := ctx.objTree
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"),
changeCreator.CreateRaw("4", aclList.Head().Id, "3", false, "3"),
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"),
changeCreator.createRaw("4", aclList.Head().Id, "3", false, "3"),
}
payload := RawChangesPayload{
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
@@ -287,8 +270,7 @@ func TestObjectTree(t *testing.T) {
assert.Equal(t, []string{"0"}, res.OldHeads)
assert.Equal(t, []string{"4"}, res.Heads)
assert.Equal(t, len(rawChanges), len(res.Added))
// here we have rebuild, because we reduced tree to new snapshot
assert.Equal(t, Rebuild, res.Mode)
assert.Equal(t, Append, res.Mode)
// check tree heads
assert.Equal(t, []string{"4"}, objTree.Heads())
@@ -320,9 +302,9 @@ func TestObjectTree(t *testing.T) {
objTree := ctx.objTree
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"),
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"),
}
payload := RawChangesPayload{
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
@@ -338,195 +320,18 @@ func TestObjectTree(t *testing.T) {
assert.Equal(t, true, objTree.(*objectTree).snapshotPathIsActual())
})
t.Run("test empty data tree", func(t *testing.T) {
t.Run("empty tree add", func(t *testing.T) {
ctx := prepareEmptyDataTreeContext(t, aclList, nil)
changeCreator := ctx.changeCreator
objTree := ctx.objTree
rawChangesFirst := []*treechangeproto.RawTreeChangeWithId{
changeCreator.CreateRawWithData("1", aclList.Head().Id, "0", false, []byte("1"), "0"),
changeCreator.CreateRawWithData("2", aclList.Head().Id, "0", false, []byte("2"), "1"),
changeCreator.CreateRawWithData("3", aclList.Head().Id, "0", false, []byte("3"), "2"),
}
rawChangesSecond := []*treechangeproto.RawTreeChangeWithId{
changeCreator.CreateRawWithData("4", aclList.Head().Id, "0", false, []byte("4"), "2"),
changeCreator.CreateRawWithData("5", aclList.Head().Id, "0", false, []byte("5"), "1"),
changeCreator.CreateRawWithData("6", aclList.Head().Id, "0", false, []byte("6"), "3", "4", "5"),
}
// making them to be saved in unattached
_, err := objTree.AddRawChanges(context.Background(), RawChangesPayload{
NewHeads: []string{"6"},
RawChanges: rawChangesSecond,
})
require.NoError(t, err, "adding changes should be without error")
// attaching them
res, err := objTree.AddRawChanges(context.Background(), RawChangesPayload{
NewHeads: []string{"3"},
RawChanges: rawChangesFirst,
})
require.NoError(t, err, "adding changes should be without error")
require.Equal(t, "0", objTree.Root().Id)
require.Equal(t, []string{"6"}, objTree.Heads())
require.Equal(t, 6, len(res.Added))
// checking that added changes still have data
for _, ch := range res.Added {
unmarshallRaw := &treechangeproto.RawTreeChange{}
proto.Unmarshal(ch.RawChange, unmarshallRaw)
treeCh := &treechangeproto.TreeChange{}
proto.Unmarshal(unmarshallRaw.Payload, treeCh)
require.Equal(t, ch.Id, string(treeCh.ChangesData))
}
// checking that the tree doesn't have data in memory
err = objTree.IterateRoot(nil, func(change *Change) bool {
if change.Id == "0" {
return true
}
require.Nil(t, change.Data)
return true
})
})
t.Run("empty tree load", func(t *testing.T) {
ctx := prepareEmptyDataTreeContext(t, aclList, func(changeCreator *MockChangeCreator) RawChangesPayload {
rawChanges := []*treechangeproto.RawTreeChangeWithId{
changeCreator.CreateRawWithData("1", aclList.Head().Id, "0", false, []byte("1"), "0"),
changeCreator.CreateRawWithData("2", aclList.Head().Id, "0", false, []byte("2"), "1"),
changeCreator.CreateRawWithData("3", aclList.Head().Id, "0", false, []byte("3"), "2"),
changeCreator.CreateRawWithData("4", aclList.Head().Id, "0", false, []byte("4"), "2"),
changeCreator.CreateRawWithData("5", aclList.Head().Id, "0", false, []byte("5"), "1"),
changeCreator.CreateRawWithData("6", aclList.Head().Id, "0", false, []byte("6"), "3", "4", "5"),
}
return RawChangesPayload{NewHeads: []string{"6"}, RawChanges: rawChanges}
})
ctx.objTree.IterateRoot(nil, func(change *Change) bool {
if change.Id == "0" {
return true
}
require.Nil(t, change.Data)
return true
})
rawChanges, err := ctx.objTree.ChangesAfterCommonSnapshot([]string{"0"}, []string{"6"})
require.NoError(t, err)
for _, ch := range rawChanges {
unmarshallRaw := &treechangeproto.RawTreeChange{}
proto.Unmarshal(ch.RawChange, unmarshallRaw)
treeCh := &treechangeproto.TreeChange{}
proto.Unmarshal(unmarshallRaw.Payload, treeCh)
require.Equal(t, ch.Id, string(treeCh.ChangesData))
}
})
})
t.Run("rollback when add to storage returns error", func(t *testing.T) {
ctx := prepareTreeContext(t, aclList)
changeCreator := ctx.changeCreator
objTree := ctx.objTree
store := ctx.treeStorage.(*treestorage.InMemoryTreeStorage)
addErr := fmt.Errorf("error saving")
store.SetReturnErrorOnAdd(addErr)
rawChanges := []*treechangeproto.RawTreeChangeWithId{
changeCreator.CreateRaw("1", aclList.Head().Id, "0", true, "0"),
}
payload := RawChangesPayload{
NewHeads: []string{"1"},
RawChanges: rawChanges,
}
_, err := objTree.AddRawChanges(context.Background(), payload)
require.Error(t, err, addErr)
require.Equal(t, "0", objTree.Root().Id)
})
t.Run("their heads before common snapshot", func(t *testing.T) {
// checking that adding old changes did not affect the tree
ctx := prepareTreeContext(t, aclList)
changeCreator := ctx.changeCreator
objTree := ctx.objTree
rawChanges := []*treechangeproto.RawTreeChangeWithId{
changeCreator.CreateRaw("1", aclList.Head().Id, "0", true, "0"),
changeCreator.CreateRaw("2", aclList.Head().Id, "1", false, "1"),
changeCreator.CreateRaw("3", aclList.Head().Id, "1", true, "2"),
changeCreator.CreateRaw("4", aclList.Head().Id, "1", false, "2"),
changeCreator.CreateRaw("5", aclList.Head().Id, "1", false, "1"),
changeCreator.CreateRaw("6", aclList.Head().Id, "1", true, "3", "4", "5"),
}
payload := RawChangesPayload{
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
RawChanges: rawChanges,
}
_, err := objTree.AddRawChanges(context.Background(), payload)
require.NoError(t, err, "adding changes should be without error")
require.Equal(t, "6", objTree.Root().Id)
rawChangesPrevious := []*treechangeproto.RawTreeChangeWithId{
changeCreator.CreateRaw("1", aclList.Head().Id, "0", true, "0"),
}
payload = RawChangesPayload{
NewHeads: []string{"1"},
RawChanges: rawChangesPrevious,
}
_, err = objTree.AddRawChanges(context.Background(), payload)
require.NoError(t, err, "adding changes should be without error")
require.Equal(t, "6", objTree.Root().Id)
})
t.Run("stored changes will not break the pipeline if heads were not updated", func(t *testing.T) {
ctx := prepareTreeContext(t, aclList)
changeCreator := ctx.changeCreator
objTree := ctx.objTree
store := ctx.treeStorage.(*treestorage.InMemoryTreeStorage)
rawChanges := []*treechangeproto.RawTreeChangeWithId{
changeCreator.CreateRaw("1", aclList.Head().Id, "0", true, "0"),
}
payload := RawChangesPayload{
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
RawChanges: rawChanges,
}
_, err := objTree.AddRawChanges(context.Background(), payload)
require.NoError(t, err, "adding changes should be without error")
require.Equal(t, "1", objTree.Root().Id)
// creating changes to save in the storage
// to imitate the condition where all changes are in the storage
// but the head was not updated
storageChanges := []*treechangeproto.RawTreeChangeWithId{
changeCreator.CreateRaw("2", aclList.Head().Id, "1", false, "1"),
changeCreator.CreateRaw("3", aclList.Head().Id, "1", true, "2"),
changeCreator.CreateRaw("4", aclList.Head().Id, "1", false, "2"),
changeCreator.CreateRaw("5", aclList.Head().Id, "1", false, "1"),
changeCreator.CreateRaw("6", aclList.Head().Id, "1", true, "3", "4", "5"),
}
store.AddRawChangesSetHeads(storageChanges, []string{"1"})
// updating with subset of those changes to see that everything will still work
payload = RawChangesPayload{
NewHeads: []string{"6"},
RawChanges: storageChanges,
}
_, err = objTree.AddRawChanges(context.Background(), payload)
require.NoError(t, err, "adding changes should be without error")
require.Equal(t, "6", objTree.Root().Id)
})
t.Run("changes from tree after common snapshot complex", func(t *testing.T) {
ctx := prepareTreeContext(t, aclList)
changeCreator := ctx.changeCreator
objTree := ctx.objTree
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"),
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"),
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"),
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"),
}
payload := RawChangesPayload{
@@ -600,13 +405,13 @@ func TestObjectTree(t *testing.T) {
objTree := ctx.objTree
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"),
changeCreator.CreateRaw("4", aclList.Head().Id, "0", false, "2"),
changeCreator.CreateRaw("5", aclList.Head().Id, "0", false, "1"),
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"),
changeCreator.createRaw("4", aclList.Head().Id, "0", false, "2"),
changeCreator.createRaw("5", aclList.Head().Id, "0", false, "1"),
// main difference from tree example
changeCreator.CreateRaw("6", aclList.Head().Id, "0", true, "3", "4", "5"),
changeCreator.createRaw("6", aclList.Head().Id, "0", true, "3", "4", "5"),
}
payload := RawChangesPayload{
@@ -681,9 +486,9 @@ func TestObjectTree(t *testing.T) {
objTree := ctx.objTree
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"),
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"),
}
payload := RawChangesPayload{
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
@@ -695,9 +500,9 @@ func TestObjectTree(t *testing.T) {
require.Equal(t, "3", objTree.Root().Id)
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"),
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"),
}
payload = RawChangesPayload{
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
@@ -736,154 +541,4 @@ func TestObjectTree(t *testing.T) {
assert.Equal(t, ch, raw, "the changes in the storage should be the same")
}
})
t.Run("test history tree not include", func(t *testing.T) {
changeCreator, deps := prepareHistoryTreeDeps(aclList)
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"),
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"),
}
deps.treeStorage.AddRawChangesSetHeads(rawChanges, []string{"6"})
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
BeforeId: "6",
IncludeBeforeId: false,
})
require.NoError(t, err)
// check tree heads
assert.Equal(t, []string{"3", "4", "5"}, hTree.Heads())
// check tree iterate
var iterChangesId []string
err = hTree.IterateFrom(hTree.Root().Id, nil, func(change *Change) bool {
iterChangesId = append(iterChangesId, change.Id)
return true
})
require.NoError(t, err, "iterate should be without error")
assert.Equal(t, []string{"0", "1", "2", "3", "4", "5"}, iterChangesId)
assert.Equal(t, "0", hTree.Root().Id)
})
t.Run("test history tree build full", func(t *testing.T) {
changeCreator, deps := prepareHistoryTreeDeps(aclList)
// sequence of snapshots: 5->1->0
rawChanges := []*treechangeproto.RawTreeChangeWithId{
changeCreator.CreateRaw("1", aclList.Head().Id, "0", true, "0"),
changeCreator.CreateRaw("2", aclList.Head().Id, "1", false, "1"),
changeCreator.CreateRaw("3", aclList.Head().Id, "1", true, "2"),
changeCreator.CreateRaw("4", aclList.Head().Id, "1", false, "2"),
changeCreator.CreateRaw("5", aclList.Head().Id, "1", true, "3", "4"),
changeCreator.CreateRaw("6", aclList.Head().Id, "5", false, "5"),
}
deps.treeStorage.AddRawChangesSetHeads(rawChanges, []string{"6"})
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
BuildFullTree: true,
})
require.NoError(t, err)
// check tree heads
assert.Equal(t, []string{"6"}, hTree.Heads())
// check tree iterate
var iterChangesId []string
err = hTree.IterateFrom(hTree.Root().Id, nil, func(change *Change) bool {
iterChangesId = append(iterChangesId, change.Id)
return true
})
require.NoError(t, err, "iterate should be without error")
assert.Equal(t, []string{"0", "1", "2", "3", "4", "5", "6"}, iterChangesId)
assert.Equal(t, "0", hTree.Root().Id)
})
t.Run("test history tree include", func(t *testing.T) {
changeCreator, deps := prepareHistoryTreeDeps(aclList)
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"),
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"),
}
deps.treeStorage.AddRawChangesSetHeads(rawChanges, []string{"6"})
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
BeforeId: "6",
IncludeBeforeId: true,
})
require.NoError(t, err)
// check tree heads
assert.Equal(t, []string{"6"}, hTree.Heads())
// check tree iterate
var iterChangesId []string
err = hTree.IterateFrom(hTree.Root().Id, nil, func(change *Change) bool {
iterChangesId = append(iterChangesId, change.Id)
return true
})
require.NoError(t, err, "iterate should be without error")
assert.Equal(t, []string{"0", "1", "2", "3", "4", "5", "6"}, iterChangesId)
assert.Equal(t, "0", hTree.Root().Id)
})
t.Run("test history tree root", func(t *testing.T) {
_, deps := prepareHistoryTreeDeps(aclList)
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
BeforeId: "0",
IncludeBeforeId: true,
})
require.NoError(t, err)
// check tree heads
assert.Equal(t, []string{"0"}, hTree.Heads())
// check tree iterate
var iterChangesId []string
err = hTree.IterateFrom(hTree.Root().Id, nil, func(change *Change) bool {
iterChangesId = append(iterChangesId, change.Id)
return true
})
require.NoError(t, err, "iterate should be without error")
assert.Equal(t, []string{"0"}, iterChangesId)
assert.Equal(t, "0", hTree.Root().Id)
})
t.Run("validate correct tree", func(t *testing.T) {
ctx := prepareTreeContext(t, aclList)
changeCreator := ctx.changeCreator
rawChanges := []*treechangeproto.RawTreeChangeWithId{
ctx.objTree.Header(),
changeCreator.CreateRaw("1", aclList.Head().Id, "0", false, "0"),
changeCreator.CreateRaw("2", aclList.Head().Id, "0", false, "1"),
changeCreator.CreateRaw("3", aclList.Head().Id, "0", true, "2"),
}
defaultObjectTreeDeps = nonVerifiableTreeDeps
err := ValidateRawTree(treestorage.TreeStorageCreatePayload{
RootRawChange: ctx.objTree.Header(),
Heads: []string{"3"},
Changes: rawChanges,
}, ctx.aclList)
require.NoError(t, err)
})
t.Run("fail to validate not connected tree", func(t *testing.T) {
ctx := prepareTreeContext(t, aclList)
changeCreator := ctx.changeCreator
rawChanges := []*treechangeproto.RawTreeChangeWithId{
ctx.objTree.Header(),
changeCreator.CreateRaw("3", aclList.Head().Id, "0", true, "2"),
}
defaultObjectTreeDeps = nonVerifiableTreeDeps
err := ValidateRawTree(treestorage.TreeStorageCreatePayload{
RootRawChange: ctx.objTree.Header(),
Heads: []string{"3"},
Changes: rawChanges,
}, ctx.aclList)
require.Equal(t, ErrHasInvalidChanges, err)
})
}
@@ -1,25 +0,0 @@
package objecttree
type objectTreeDebug struct {
}
type DebugInfo struct {
TreeLen int
TreeString string
Graphviz string
Heads []string
SnapshotPath []string
}
func (o objectTreeDebug) debugInfo(ot *objectTree, parser DescriptionParser) (di DebugInfo, err error) {
di = DebugInfo{}
di.Graphviz, err = ot.tree.Graph(parser)
if err != nil {
return
}
di.TreeString = ot.tree.String()
di.TreeLen = ot.tree.Len()
di.Heads = ot.Heads()
di.SnapshotPath = ot.SnapshotPath()
return
}
@@ -1,132 +1,37 @@
package objecttree
import (
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
"github.com/anyproto/any-sync/util/crypto"
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
"github.com/anytypeio/any-sync/commonspace/object/keychain"
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
"github.com/anytypeio/any-sync/util/keys/asymmetric/signingkey"
"github.com/anytypeio/any-sync/util/keys/symmetric"
"github.com/anytypeio/any-sync/util/slice"
"go.uber.org/zap"
"math/rand"
"time"
)
type ObjectTreeCreatePayload struct {
PrivKey crypto.PrivKey
ChangeType string
ChangePayload []byte
SpaceId string
IsEncrypted bool
Seed []byte
Timestamp int64
SignKey signingkey.PrivKey
ChangeType string
SpaceId string
Identity []byte
IsEncrypted bool
}
type HistoryTreeParams struct {
TreeStorage treestorage.TreeStorage
AclList list.AclList
BeforeId string
IncludeBeforeId bool
BuildFullTree bool
}
type objectTreeDeps struct {
changeBuilder ChangeBuilder
treeBuilder *treeBuilder
treeStorage treestorage.TreeStorage
validator ObjectTreeValidator
rawChangeLoader *rawChangeLoader
aclList list.AclList
}
type BuildObjectTreeFunc = func(treeStorage treestorage.TreeStorage, aclList list.AclList) (ObjectTree, error)
var defaultObjectTreeDeps = verifiableTreeDeps
func verifiableTreeDeps(
rootChange *treechangeproto.RawTreeChangeWithId,
treeStorage treestorage.TreeStorage,
aclList list.AclList) objectTreeDeps {
changeBuilder := NewChangeBuilder(crypto.NewKeyStorage(), rootChange)
treeBuilder := newTreeBuilder(true, treeStorage, changeBuilder)
return objectTreeDeps{
changeBuilder: changeBuilder,
treeBuilder: treeBuilder,
treeStorage: treeStorage,
validator: newTreeValidator(),
rawChangeLoader: newRawChangeLoader(treeStorage, changeBuilder),
aclList: aclList,
}
}
func emptyDataTreeDeps(
rootChange *treechangeproto.RawTreeChangeWithId,
treeStorage treestorage.TreeStorage,
aclList list.AclList) objectTreeDeps {
changeBuilder := NewChangeBuilder(crypto.NewKeyStorage(), rootChange)
treeBuilder := newTreeBuilder(false, treeStorage, changeBuilder)
return objectTreeDeps{
changeBuilder: changeBuilder,
treeBuilder: treeBuilder,
treeStorage: treeStorage,
validator: newTreeValidator(),
rawChangeLoader: newStorageLoader(treeStorage, changeBuilder),
aclList: aclList,
}
}
func nonVerifiableTreeDeps(
rootChange *treechangeproto.RawTreeChangeWithId,
treeStorage treestorage.TreeStorage,
aclList list.AclList) objectTreeDeps {
changeBuilder := &nonVerifiableChangeBuilder{NewChangeBuilder(newMockKeyStorage(), rootChange)}
treeBuilder := newTreeBuilder(true, treeStorage, changeBuilder)
return objectTreeDeps{
changeBuilder: changeBuilder,
treeBuilder: treeBuilder,
treeStorage: treeStorage,
validator: &noOpTreeValidator{},
rawChangeLoader: newRawChangeLoader(treeStorage, changeBuilder),
aclList: aclList,
}
}
func BuildEmptyDataObjectTree(treeStorage treestorage.TreeStorage, aclList list.AclList) (ObjectTree, error) {
rootChange, err := treeStorage.Root()
func CreateObjectTreeRoot(payload ObjectTreeCreatePayload, aclList list.AclList) (root *treechangeproto.RawTreeChangeWithId, err error) {
bytes := make([]byte, 32)
_, err = rand.Read(bytes)
if err != nil {
return nil, err
return
}
deps := emptyDataTreeDeps(rootChange, treeStorage, aclList)
return buildObjectTree(deps)
return createObjectTreeRoot(payload, time.Now().UnixNano(), bytes, aclList)
}
func BuildTestableTree(treeStorage treestorage.TreeStorage, aclList list.AclList) (ObjectTree, error) {
root, _ := treeStorage.Root()
changeBuilder := &nonVerifiableChangeBuilder{
ChangeBuilder: NewChangeBuilder(newMockKeyStorage(), root),
}
deps := objectTreeDeps{
changeBuilder: changeBuilder,
treeBuilder: newTreeBuilder(true, treeStorage, changeBuilder),
treeStorage: treeStorage,
rawChangeLoader: newRawChangeLoader(treeStorage, changeBuilder),
validator: &noOpTreeValidator{},
aclList: aclList,
}
return buildObjectTree(deps)
}
func BuildEmptyDataTestableTree(treeStorage treestorage.TreeStorage, aclList list.AclList) (ObjectTree, error) {
root, _ := treeStorage.Root()
changeBuilder := &nonVerifiableChangeBuilder{
ChangeBuilder: NewChangeBuilder(newMockKeyStorage(), root),
}
deps := objectTreeDeps{
changeBuilder: changeBuilder,
treeBuilder: newTreeBuilder(false, treeStorage, changeBuilder),
treeStorage: treeStorage,
rawChangeLoader: newStorageLoader(treeStorage, changeBuilder),
validator: &noOpTreeValidator{},
aclList: aclList,
}
return buildObjectTree(deps)
func DeriveObjectTreeRoot(payload ObjectTreeCreatePayload, aclList list.AclList) (root *treechangeproto.RawTreeChangeWithId, err error) {
return createObjectTreeRoot(payload, 0, nil, aclList)
}
func BuildObjectTree(treeStorage treestorage.TreeStorage, aclList list.AclList) (ObjectTree, error) {
@@ -138,25 +43,54 @@ func BuildObjectTree(treeStorage treestorage.TreeStorage, aclList list.AclList)
return buildObjectTree(deps)
}
func BuildNonVerifiableHistoryTree(params HistoryTreeParams) (HistoryTree, error) {
rootChange, err := params.TreeStorage.Root()
if err != nil {
return nil, err
}
deps := nonVerifiableTreeDeps(rootChange, params.TreeStorage, params.AclList)
return buildHistoryTree(deps, params)
func CreateDerivedObjectTree(
payload ObjectTreeCreatePayload,
aclList list.AclList,
createStorage treestorage.TreeStorageCreatorFunc) (objTree ObjectTree, err error) {
return createObjectTree(payload, 0, nil, aclList, createStorage)
}
func BuildHistoryTree(params HistoryTreeParams) (HistoryTree, error) {
rootChange, err := params.TreeStorage.Root()
func CreateObjectTree(
payload ObjectTreeCreatePayload,
aclList list.AclList,
createStorage treestorage.TreeStorageCreatorFunc) (objTree ObjectTree, err error) {
bytes := make([]byte, 32)
_, err = rand.Read(bytes)
if err != nil {
return nil, err
return
}
deps := defaultObjectTreeDeps(rootChange, params.TreeStorage, params.AclList)
return buildHistoryTree(deps, params)
return createObjectTree(payload, time.Now().UnixNano(), bytes, aclList, createStorage)
}
func CreateObjectTreeRoot(payload ObjectTreeCreatePayload, aclList list.AclList) (root *treechangeproto.RawTreeChangeWithId, err error) {
func createObjectTree(
payload ObjectTreeCreatePayload,
timestamp int64,
seed []byte,
aclList list.AclList,
createStorage treestorage.TreeStorageCreatorFunc) (objTree ObjectTree, err error) {
raw, err := createObjectTreeRoot(payload, timestamp, seed, aclList)
if err != nil {
return
}
// create storage
st, err := createStorage(treestorage.TreeStorageCreatePayload{
RootRawChange: raw,
Changes: []*treechangeproto.RawTreeChangeWithId{raw},
Heads: []string{raw.Id},
})
if err != nil {
return
}
return BuildObjectTree(st, aclList)
}
func createObjectTreeRoot(
payload ObjectTreeCreatePayload,
timestamp int64,
seed []byte,
aclList list.AclList) (root *treechangeproto.RawTreeChangeWithId, err error) {
aclList.RLock()
aclHeadId := aclList.Head().Id
aclList.RUnlock()
@@ -165,29 +99,29 @@ func CreateObjectTreeRoot(payload ObjectTreeCreatePayload, aclList list.AclList)
return
}
cnt := InitialContent{
AclHeadId: aclHeadId,
PrivKey: payload.PrivKey,
SpaceId: payload.SpaceId,
ChangeType: payload.ChangeType,
ChangePayload: payload.ChangePayload,
Timestamp: payload.Timestamp,
Seed: payload.Seed,
AclHeadId: aclHeadId,
Identity: payload.Identity,
SigningKey: payload.SignKey,
SpaceId: payload.SpaceId,
ChangeType: payload.ChangeType,
Timestamp: timestamp,
Seed: seed,
}
_, root, err = NewChangeBuilder(crypto.NewKeyStorage(), nil).BuildRoot(cnt)
_, root, err = NewChangeBuilder(keychain.NewKeychain(), nil).BuildInitialContent(cnt)
return
}
func buildObjectTree(deps objectTreeDeps) (ObjectTree, error) {
objTree := &objectTree{
id: deps.treeStorage.Id(),
treeStorage: deps.treeStorage,
treeBuilder: deps.treeBuilder,
validator: deps.validator,
aclList: deps.aclList,
changeBuilder: deps.changeBuilder,
rawChangeLoader: deps.rawChangeLoader,
keys: make(map[string]crypto.SymKey),
tree: nil,
keys: make(map[uint64]*symmetric.Key),
newChangesBuf: make([]*Change, 0, 10),
difSnapshotBuf: make([]*treechangeproto.RawTreeChangeWithId, 0, 10),
notSeenIdxBuf: make([]int, 0, 10),
@@ -198,14 +132,31 @@ func buildObjectTree(deps objectTreeDeps) (ObjectTree, error) {
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 = objTree.treeStorage.Id()
objTree.rawRoot, err = objTree.treeStorage.Root()
if err != nil {
return nil, err
}
// verifying root
header, err := objTree.changeBuilder.Unmarshall(objTree.rawRoot, true)
header, err := objTree.changeBuilder.ConvertFromRaw(objTree.rawRoot, true)
if err != nil {
return nil, err
}
@@ -213,38 +164,3 @@ func buildObjectTree(deps objectTreeDeps) (ObjectTree, error) {
return objTree, nil
}
func buildHistoryTree(deps objectTreeDeps, params HistoryTreeParams) (ht HistoryTree, err error) {
objTree := &objectTree{
id: deps.treeStorage.Id(),
treeStorage: deps.treeStorage,
treeBuilder: deps.treeBuilder,
validator: deps.validator,
aclList: deps.aclList,
changeBuilder: deps.changeBuilder,
rawChangeLoader: deps.rawChangeLoader,
keys: make(map[string]crypto.SymKey),
newChangesBuf: make([]*Change, 0, 10),
difSnapshotBuf: make([]*treechangeproto.RawTreeChangeWithId, 0, 10),
notSeenIdxBuf: make([]int, 0, 10),
newSnapshotsBuf: make([]*Change, 0, 10),
}
hTree := &historyTree{objectTree: objTree}
err = hTree.rebuildFromStorage(params)
if err != nil {
return nil, err
}
objTree.id = objTree.treeStorage.Id()
objTree.rawRoot, err = objTree.treeStorage.Root()
if err != nil {
return nil, err
}
header, err := objTree.changeBuilder.Unmarshall(objTree.rawRoot, false)
if err != nil {
return nil, err
}
objTree.root = header
return hTree, nil
}
@@ -2,16 +2,15 @@ package objecttree
import (
"context"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
"github.com/anyproto/any-sync/util/slice"
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
"github.com/anytypeio/any-sync/util/slice"
"time"
)
type rawChangeLoader struct {
treeStorage treestorage.TreeStorage
changeBuilder ChangeBuilder
alwaysFromStorage bool
treeStorage treestorage.TreeStorage
changeBuilder ChangeBuilder
// buffers
idStack []string
@@ -24,12 +23,6 @@ type rawCacheEntry struct {
position int
}
func newStorageLoader(treeStorage treestorage.TreeStorage, changeBuilder ChangeBuilder) *rawChangeLoader {
loader := newRawChangeLoader(treeStorage, changeBuilder)
loader.alwaysFromStorage = true
return loader
}
func newRawChangeLoader(treeStorage treestorage.TreeStorage, changeBuilder ChangeBuilder) *rawChangeLoader {
return &rawChangeLoader{
treeStorage: treeStorage,
@@ -37,15 +30,7 @@ func newRawChangeLoader(treeStorage treestorage.TreeStorage, changeBuilder Chang
}
}
func (r *rawChangeLoader) Load(commonSnapshot string, t *Tree, breakpoints []string) ([]*treechangeproto.RawTreeChangeWithId, error) {
if commonSnapshot == t.root.Id && !r.alwaysFromStorage {
return r.loadFromTree(t, breakpoints)
} else {
return r.loadFromStorage(commonSnapshot, t.Heads(), breakpoints)
}
}
func (r *rawChangeLoader) loadFromTree(t *Tree, breakpoints []string) ([]*treechangeproto.RawTreeChangeWithId, error) {
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])
@@ -54,7 +39,7 @@ func (r *rawChangeLoader) loadFromTree(t *Tree, breakpoints []string) ([]*treech
convert := func(chs []*Change) (rawChanges []*treechangeproto.RawTreeChangeWithId, err error) {
for _, ch := range chs {
var raw *treechangeproto.RawTreeChangeWithId
raw, err = r.changeBuilder.Marshall(ch)
raw, err = r.changeBuilder.BuildRaw(ch)
if err != nil {
return
}
@@ -113,7 +98,7 @@ func (r *rawChangeLoader) loadFromTree(t *Tree, breakpoints []string) ([]*treech
return convert(results)
}
func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoints []string) ([]*treechangeproto.RawTreeChangeWithId, error) {
func (r *rawChangeLoader) LoadFromStorage(commonSnapshot string, heads, breakpoints []string) ([]*treechangeproto.RawTreeChangeWithId, error) {
// resetting cache
r.cache = make(map[string]rawCacheEntry)
defer func() {
@@ -241,7 +226,7 @@ func (r *rawChangeLoader) loadEntry(id string) (entry rawCacheEntry, err error)
return
}
change, err := r.changeBuilder.Unmarshall(rawChange, false)
change, err := r.changeBuilder.ConvertFromRaw(rawChange, false)
if err != nil {
return
}
@@ -0,0 +1,16 @@
package objecttree
import (
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
)
func ValidateRawTree(payload treestorage.TreeStorageCreatePayload, aclList list.AclList) (err error) {
treeStorage, err := treestorage.NewInMemoryTreeStorage(payload.RootRawChange, payload.Heads, payload.Changes)
if err != nil {
return
}
_, err = BuildObjectTree(treeStorage, aclList)
return
}
@@ -1,19 +1,13 @@
package objecttree
import (
"github.com/anyproto/any-sync/util/crypto"
"github.com/anytypeio/any-sync/util/keys/asymmetric/signingkey"
)
// SignableChangeContent is a payload to be passed when we are creating change
type SignableChangeContent struct {
// Data is a data provided by the client
Data []byte
// Key is the key which will be used to sign the change
Key crypto.PrivKey
// IsSnapshot tells if the change has snapshot of all previous data
IsSnapshot bool
// IsEncrypted tells if we encrypt the data with the relevant symmetric key
Data []byte
Key signingkey.PrivKey
Identity []byte
IsSnapshot bool
IsEncrypted bool
// Timestamp is a timestamp of change, if it is <= 0, then we use current timestamp
Timestamp int64
}
@@ -1,121 +0,0 @@
package objecttree
import (
"fmt"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
"github.com/anyproto/any-sync/util/crypto"
libcrypto "github.com/libp2p/go-libp2p/core/crypto"
)
type mockPubKey struct {
}
const mockKeyValue = "mockKey"
func (m mockPubKey) Equals(key crypto.Key) bool {
return true
}
func (m mockPubKey) Raw() ([]byte, error) {
return []byte(mockKeyValue), nil
}
func (m mockPubKey) Encrypt(message []byte) ([]byte, error) {
return message, nil
}
func (m mockPubKey) Verify(data []byte, sig []byte) (bool, error) {
return true, nil
}
func (m mockPubKey) Marshall() ([]byte, error) {
return []byte(mockKeyValue), nil
}
func (m mockPubKey) Storage() []byte {
return []byte(mockKeyValue)
}
func (m mockPubKey) Account() string {
return mockKeyValue
}
func (m mockPubKey) Network() string {
return mockKeyValue
}
func (m mockPubKey) PeerId() string {
return mockKeyValue
}
func (m mockPubKey) LibP2P() (libcrypto.PubKey, error) {
return nil, fmt.Errorf("can't be converted in libp2p")
}
type mockKeyStorage struct {
}
func newMockKeyStorage() mockKeyStorage {
return mockKeyStorage{}
}
func (m mockKeyStorage) PubKeyFromProto(protoBytes []byte) (crypto.PubKey, error) {
return mockPubKey{}, nil
}
type MockChangeCreator struct{}
func NewMockChangeCreator() *MockChangeCreator {
return &MockChangeCreator{}
}
func (c *MockChangeCreator) CreateRoot(id, aclId string) *treechangeproto.RawTreeChangeWithId {
aclChange := &treechangeproto.RootChange{
AclHeadId: aclId,
}
res, _ := aclChange.Marshal()
raw := &treechangeproto.RawTreeChange{
Payload: res,
Signature: nil,
}
rawMarshalled, _ := raw.Marshal()
return &treechangeproto.RawTreeChangeWithId{
RawChange: rawMarshalled,
Id: id,
}
}
func (c *MockChangeCreator) CreateRaw(id, aclId, snapshotId string, isSnapshot bool, prevIds ...string) *treechangeproto.RawTreeChangeWithId {
return c.CreateRawWithData(id, aclId, snapshotId, isSnapshot, nil, prevIds...)
}
func (c *MockChangeCreator) CreateRawWithData(id, aclId, snapshotId string, isSnapshot bool, data []byte, prevIds ...string) *treechangeproto.RawTreeChangeWithId {
aclChange := &treechangeproto.TreeChange{
TreeHeadIds: prevIds,
AclHeadId: aclId,
SnapshotBaseId: snapshotId,
ChangesData: data,
IsSnapshot: isSnapshot,
}
res, _ := aclChange.Marshal()
raw := &treechangeproto.RawTreeChange{
Payload: res,
Signature: nil,
}
rawMarshalled, _ := raw.Marshal()
return &treechangeproto.RawTreeChangeWithId{
RawChange: rawMarshalled,
Id: id,
}
}
func (c *MockChangeCreator) CreateNewTreeStorage(treeId, aclHeadId string) treestorage.TreeStorage {
root := c.CreateRoot(treeId, aclHeadId)
treeStorage, _ := treestorage.NewInMemoryTreeStorage(root, []string{root.Id}, []*treechangeproto.RawTreeChangeWithId{root})
return treeStorage
}
@@ -125,7 +125,7 @@ func TestTree_Hash(t *testing.T) {
}
func TestTree_AddFuzzy(t *testing.T) {
rand.Seed(time.Now().Unix())
rand.Seed(time.Now().UnixNano())
getChanges := func() []*Change {
changes := []*Change{
newChange("1", "root", "root"),
@@ -4,16 +4,15 @@ import (
"context"
"errors"
"fmt"
"time"
"github.com/anyproto/any-sync/app/logger"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
"github.com/anyproto/any-sync/util/slice"
"github.com/anytypeio/any-sync/app/logger"
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
"github.com/anytypeio/any-sync/util/slice"
"go.uber.org/zap"
"time"
)
var (
log = logger.NewNamedSugared("common.commonspace.objecttree")
log = logger.NewNamed("acltree").Sugar()
ErrEmpty = errors.New("logs empty")
)
@@ -21,20 +20,18 @@ type treeBuilder struct {
treeStorage treestorage.TreeStorage
builder ChangeBuilder
cache map[string]*Change
tree *Tree
keepInMemoryData bool
cache map[string]*Change
tree *Tree
// buffers
idStack []string
loadBuffer []*Change
}
func newTreeBuilder(keepData bool, storage treestorage.TreeStorage, builder ChangeBuilder) *treeBuilder {
func newTreeBuilder(storage treestorage.TreeStorage, builder ChangeBuilder) *treeBuilder {
return &treeBuilder{
treeStorage: storage,
builder: builder,
keepInMemoryData: keepData,
treeStorage: storage,
builder: builder,
}
}
@@ -44,40 +41,16 @@ func (tb *treeBuilder) Reset() {
}
func (tb *treeBuilder) Build(theirHeads []string, newChanges []*Change) (*Tree, error) {
heads, err := tb.treeStorage.Heads()
if err != nil {
return nil, err
}
return tb.build(heads, theirHeads, newChanges)
}
func (tb *treeBuilder) BuildFull() (*Tree, error) {
defer func() {
tb.cache = make(map[string]*Change)
}()
tb.cache = make(map[string]*Change)
heads, err := tb.treeStorage.Heads()
if err != nil {
return nil, err
}
err = tb.buildTree(heads, tb.treeStorage.Id())
if err != nil {
return nil, err
}
return tb.tree, nil
}
func (tb *treeBuilder) build(heads []string, theirHeads []string, newChanges []*Change) (*Tree, error) {
defer func() {
tb.cache = make(map[string]*Change)
}()
var proposedHeads []string
tb.cache = make(map[string]*Change)
heads, err := tb.treeStorage.Heads()
if err != nil {
return nil, err
}
// TODO: we can actually get this from tree (though not sure, that there would always be
// an invariant where the tree has the closest common snapshot of heads)
// so if optimization is critical we can change this to inject from tree directly,
// so if optimization is critical we can change this to inject from tree directly
// but then we have to be sure that invariant stays true
oldBreakpoint, err := tb.findBreakpoint(heads, true)
if err != nil {
@@ -108,7 +81,7 @@ func (tb *treeBuilder) build(heads []string, theirHeads []string, newChanges []*
}
proposedHeads = append(proposedHeads, heads...)
log.With(zap.Strings("heads", proposedHeads), zap.String("id", tb.treeStorage.Id())).Debug("building tree")
log.With(zap.Strings("heads", proposedHeads)).Debug("building tree")
if err = tb.buildTree(proposedHeads, breakpoint); err != nil {
return nil, fmt.Errorf("buildTree error: %v", err)
}
@@ -121,8 +94,8 @@ func (tb *treeBuilder) buildTree(heads []string, breakpoint string) (err error)
if err != nil {
return
}
changes := tb.dfs(heads, breakpoint)
tb.tree.AddFast(ch)
changes := tb.dfs(heads, breakpoint)
tb.tree.AddFast(changes...)
return
}
@@ -177,13 +150,10 @@ func (tb *treeBuilder) loadChange(id string) (ch *Change, err error) {
return nil, err
}
ch, err = tb.builder.Unmarshall(change, true)
ch, err = tb.builder.ConvertFromRaw(change, true)
if err != nil {
return nil, err
}
if !tb.keepInMemoryData {
ch.Data = nil
}
tb.cache[id] = ch
return ch, nil
@@ -1,5 +1,6 @@
//go:build (!linux && !darwin) || android || ios || nographviz || !cgo || windows
// +build !linux,!darwin android ios nographviz !cgo windows
//go:build ((!linux && !darwin) || android || ios || nographviz) && !amd64
// +build !linux,!darwin android ios nographviz
// +build !amd64
package objecttree
@@ -1,9 +1,8 @@
//go:build (linux || darwin) && !android && !ios && !nographviz && cgo && (amd64 || arm64)
//go:build (linux || darwin) && !android && !ios && !nographviz && (amd64 || arm64)
// +build linux darwin
// +build !android
// +build !ios
// +build !nographviz
// +build cgo
// +build amd64 arm64
package objecttree
@@ -11,11 +10,10 @@ package objecttree
import (
"bytes"
"fmt"
"strings"
"time"
"github.com/goccy/go-graphviz"
"github.com/goccy/go-graphviz/cgraph"
"strings"
"time"
)
func (t *Tree) Graph(parser DescriptionParser) (data string, err error) {
@@ -1,11 +1,5 @@
package objecttree
import (
"fmt"
"github.com/anyproto/any-sync/util/crypto"
"math/rand"
)
func commonSnapshotForTwoPaths(ourPath []string, theirPath []string) (string, error) {
var i int
var j int
@@ -33,24 +27,3 @@ OuterLoop:
}
return ourPath[i+1], nil
}
func deriveTreeKey(key crypto.SymKey, cid string) (crypto.SymKey, error) {
raw, err := key.Raw()
if err != nil {
return nil, err
}
return crypto.DeriveSymmetricKey(raw, fmt.Sprintf(crypto.AnysyncTreePath, cid))
}
func DoSnapshot(treeLen int) bool {
if treeLen <= 100 {
return false
}
var (
delta = treeLen/50 + 1
midPoint = 1000
val = rand.Intn(midPoint * 2)
)
return midPoint-delta <= val && val <= midPoint+delta
}
@@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/anyproto/any-sync/commonspace/object/tree/synctree (interfaces: SyncTree,ReceiveQueue,HeadNotifiable)
// Source: github.com/anytypeio/any-sync/commonspace/object/tree/synctree (interfaces: SyncClient,SyncTree,ReceiveQueue,HeadNotifiable)
// Package mock_synctree is a generated GoMock package.
package mock_synctree
@@ -7,17 +7,137 @@ package mock_synctree
import (
context "context"
reflect "reflect"
time "time"
list "github.com/anyproto/any-sync/commonspace/object/acl/list"
objecttree "github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
updatelistener "github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
treechangeproto "github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
treestorage "github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
spacesyncproto "github.com/anyproto/any-sync/commonspace/spacesyncproto"
objecttree "github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
treechangeproto "github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
treestorage "github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
spacesyncproto "github.com/anytypeio/any-sync/commonspace/spacesyncproto"
gomock "github.com/golang/mock/gomock"
)
// MockSyncClient is a mock of SyncClient interface.
type MockSyncClient struct {
ctrl *gomock.Controller
recorder *MockSyncClientMockRecorder
}
// MockSyncClientMockRecorder is the mock recorder for MockSyncClient.
type MockSyncClientMockRecorder struct {
mock *MockSyncClient
}
// NewMockSyncClient creates a new mock instance.
func NewMockSyncClient(ctrl *gomock.Controller) *MockSyncClient {
mock := &MockSyncClient{ctrl: ctrl}
mock.recorder = &MockSyncClientMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockSyncClient) EXPECT() *MockSyncClientMockRecorder {
return m.recorder
}
// BroadcastAsync mocks base method.
func (m *MockSyncClient) BroadcastAsync(arg0 *treechangeproto.TreeSyncMessage) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "BroadcastAsync", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// BroadcastAsync indicates an expected call of BroadcastAsync.
func (mr *MockSyncClientMockRecorder) BroadcastAsync(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BroadcastAsync", reflect.TypeOf((*MockSyncClient)(nil).BroadcastAsync), arg0)
}
// BroadcastAsyncOrSendResponsible mocks base method.
func (m *MockSyncClient) BroadcastAsyncOrSendResponsible(arg0 *treechangeproto.TreeSyncMessage) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "BroadcastAsyncOrSendResponsible", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// BroadcastAsyncOrSendResponsible indicates an expected call of BroadcastAsyncOrSendResponsible.
func (mr *MockSyncClientMockRecorder) BroadcastAsyncOrSendResponsible(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BroadcastAsyncOrSendResponsible", reflect.TypeOf((*MockSyncClient)(nil).BroadcastAsyncOrSendResponsible), arg0)
}
// CreateFullSyncRequest mocks base method.
func (m *MockSyncClient) CreateFullSyncRequest(arg0 objecttree.ObjectTree, arg1, arg2 []string) (*treechangeproto.TreeSyncMessage, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateFullSyncRequest", arg0, arg1, arg2)
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateFullSyncRequest indicates an expected call of CreateFullSyncRequest.
func (mr *MockSyncClientMockRecorder) CreateFullSyncRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncRequest", reflect.TypeOf((*MockSyncClient)(nil).CreateFullSyncRequest), arg0, arg1, arg2)
}
// CreateFullSyncResponse mocks base method.
func (m *MockSyncClient) CreateFullSyncResponse(arg0 objecttree.ObjectTree, arg1, arg2 []string) (*treechangeproto.TreeSyncMessage, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateFullSyncResponse", arg0, arg1, arg2)
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateFullSyncResponse indicates an expected call of CreateFullSyncResponse.
func (mr *MockSyncClientMockRecorder) CreateFullSyncResponse(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncResponse", reflect.TypeOf((*MockSyncClient)(nil).CreateFullSyncResponse), arg0, arg1, arg2)
}
// CreateHeadUpdate mocks base method.
func (m *MockSyncClient) CreateHeadUpdate(arg0 objecttree.ObjectTree, arg1 []*treechangeproto.RawTreeChangeWithId) *treechangeproto.TreeSyncMessage {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateHeadUpdate", arg0, arg1)
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
return ret0
}
// CreateHeadUpdate indicates an expected call of CreateHeadUpdate.
func (mr *MockSyncClientMockRecorder) CreateHeadUpdate(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateHeadUpdate", reflect.TypeOf((*MockSyncClient)(nil).CreateHeadUpdate), arg0, arg1)
}
// CreateNewTreeRequest mocks base method.
func (m *MockSyncClient) CreateNewTreeRequest() *treechangeproto.TreeSyncMessage {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateNewTreeRequest")
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
return ret0
}
// CreateNewTreeRequest indicates an expected call of CreateNewTreeRequest.
func (mr *MockSyncClientMockRecorder) CreateNewTreeRequest() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNewTreeRequest", reflect.TypeOf((*MockSyncClient)(nil).CreateNewTreeRequest))
}
// SendAsync mocks base method.
func (m *MockSyncClient) SendAsync(arg0 string, arg1 *treechangeproto.TreeSyncMessage, arg2 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SendAsync", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// SendAsync indicates an expected call of SendAsync.
func (mr *MockSyncClientMockRecorder) SendAsync(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendAsync", reflect.TypeOf((*MockSyncClient)(nil).SendAsync), arg0, arg1, arg2)
}
// MockSyncTree is a mock of SyncTree interface.
type MockSyncTree struct {
ctrl *gomock.Controller
@@ -41,20 +161,6 @@ func (m *MockSyncTree) EXPECT() *MockSyncTreeMockRecorder {
return m.recorder
}
// AclList mocks base method.
func (m *MockSyncTree) AclList() list.AclList {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AclList")
ret0, _ := ret[0].(list.AclList)
return ret0
}
// AclList indicates an expected call of AclList.
func (mr *MockSyncTreeMockRecorder) AclList() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AclList", reflect.TypeOf((*MockSyncTree)(nil).AclList))
}
// AddContent mocks base method.
func (m *MockSyncTree) AddContent(arg0 context.Context, arg1 objecttree.SignableChangeContent) (objecttree.AddResult, error) {
m.ctrl.T.Helper()
@@ -85,20 +191,6 @@ func (mr *MockSyncTreeMockRecorder) AddRawChanges(arg0, arg1 interface{}) *gomoc
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawChanges", reflect.TypeOf((*MockSyncTree)(nil).AddRawChanges), arg0, arg1)
}
// ChangeInfo mocks base method.
func (m *MockSyncTree) ChangeInfo() *treechangeproto.TreeChangeInfo {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ChangeInfo")
ret0, _ := ret[0].(*treechangeproto.TreeChangeInfo)
return ret0
}
// ChangeInfo indicates an expected call of ChangeInfo.
func (mr *MockSyncTreeMockRecorder) ChangeInfo() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChangeInfo", reflect.TypeOf((*MockSyncTree)(nil).ChangeInfo))
}
// ChangesAfterCommonSnapshot mocks base method.
func (m *MockSyncTree) ChangesAfterCommonSnapshot(arg0, arg1 []string) ([]*treechangeproto.RawTreeChangeWithId, error) {
m.ctrl.T.Helper()
@@ -128,19 +220,19 @@ func (mr *MockSyncTreeMockRecorder) Close() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockSyncTree)(nil).Close))
}
// Debug mocks base method.
func (m *MockSyncTree) Debug(arg0 objecttree.DescriptionParser) (objecttree.DebugInfo, error) {
// DebugDump mocks base method.
func (m *MockSyncTree) DebugDump(arg0 objecttree.DescriptionParser) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Debug", arg0)
ret0, _ := ret[0].(objecttree.DebugInfo)
ret := m.ctrl.Call(m, "DebugDump", arg0)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Debug indicates an expected call of Debug.
func (mr *MockSyncTreeMockRecorder) Debug(arg0 interface{}) *gomock.Call {
// DebugDump indicates an expected call of DebugDump.
func (mr *MockSyncTreeMockRecorder) DebugDump(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debug", reflect.TypeOf((*MockSyncTree)(nil).Debug), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DebugDump", reflect.TypeOf((*MockSyncTree)(nil).DebugDump), arg0)
}
// Delete mocks base method.
@@ -157,21 +249,6 @@ func (mr *MockSyncTreeMockRecorder) Delete() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockSyncTree)(nil).Delete))
}
// GetChange mocks base method.
func (m *MockSyncTree) GetChange(arg0 string) (*objecttree.Change, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetChange", arg0)
ret0, _ := ret[0].(*objecttree.Change)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetChange indicates an expected call of GetChange.
func (mr *MockSyncTreeMockRecorder) GetChange(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChange", reflect.TypeOf((*MockSyncTree)(nil).GetChange), arg0)
}
// HandleMessage mocks base method.
func (m *MockSyncTree) HandleMessage(arg0 context.Context, arg1 string, arg2 *spacesyncproto.ObjectSyncMessage) error {
m.ctrl.T.Helper()
@@ -274,20 +351,6 @@ func (mr *MockSyncTreeMockRecorder) IterateRoot(arg0, arg1 interface{}) *gomock.
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IterateRoot", reflect.TypeOf((*MockSyncTree)(nil).IterateRoot), arg0, arg1)
}
// Len mocks base method.
func (m *MockSyncTree) Len() int {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Len")
ret0, _ := ret[0].(int)
return ret0
}
// Len indicates an expected call of Len.
func (mr *MockSyncTreeMockRecorder) Len() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Len", reflect.TypeOf((*MockSyncTree)(nil).Len))
}
// Lock mocks base method.
func (m *MockSyncTree) Lock() {
m.ctrl.T.Helper()
@@ -300,19 +363,18 @@ func (mr *MockSyncTreeMockRecorder) Lock() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Lock", reflect.TypeOf((*MockSyncTree)(nil).Lock))
}
// PrepareChange mocks base method.
func (m *MockSyncTree) PrepareChange(arg0 objecttree.SignableChangeContent) (*treechangeproto.RawTreeChangeWithId, error) {
// Ping mocks base method.
func (m *MockSyncTree) Ping() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "PrepareChange", arg0)
ret0, _ := ret[0].(*treechangeproto.RawTreeChangeWithId)
ret1, _ := ret[1].(error)
return ret0, ret1
ret := m.ctrl.Call(m, "Ping")
ret0, _ := ret[0].(error)
return ret0
}
// PrepareChange indicates an expected call of PrepareChange.
func (mr *MockSyncTreeMockRecorder) PrepareChange(arg0 interface{}) *gomock.Call {
// Ping indicates an expected call of Ping.
func (mr *MockSyncTreeMockRecorder) Ping() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrepareChange", reflect.TypeOf((*MockSyncTree)(nil).PrepareChange), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ping", reflect.TypeOf((*MockSyncTree)(nil).Ping))
}
// RLock mocks base method.
@@ -353,18 +415,6 @@ func (mr *MockSyncTreeMockRecorder) Root() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Root", reflect.TypeOf((*MockSyncTree)(nil).Root))
}
// SetListener mocks base method.
func (m *MockSyncTree) SetListener(arg0 updatelistener.UpdateListener) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetListener", arg0)
}
// SetListener indicates an expected call of SetListener.
func (mr *MockSyncTreeMockRecorder) SetListener(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetListener", reflect.TypeOf((*MockSyncTree)(nil).SetListener), arg0)
}
// SnapshotPath mocks base method.
func (m *MockSyncTree) SnapshotPath() []string {
m.ctrl.T.Helper()
@@ -393,63 +443,6 @@ func (mr *MockSyncTreeMockRecorder) Storage() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Storage", reflect.TypeOf((*MockSyncTree)(nil).Storage))
}
// SyncWithPeer mocks base method.
func (m *MockSyncTree) SyncWithPeer(arg0 context.Context, arg1 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SyncWithPeer", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// SyncWithPeer indicates an expected call of SyncWithPeer.
func (mr *MockSyncTreeMockRecorder) SyncWithPeer(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncWithPeer", reflect.TypeOf((*MockSyncTree)(nil).SyncWithPeer), arg0, arg1)
}
// TryClose mocks base method.
func (m *MockSyncTree) TryClose(arg0 time.Duration) (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "TryClose", arg0)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// TryClose indicates an expected call of TryClose.
func (mr *MockSyncTreeMockRecorder) TryClose(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TryClose", reflect.TypeOf((*MockSyncTree)(nil).TryClose), arg0)
}
// TryLock mocks base method.
func (m *MockSyncTree) TryLock() bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "TryLock")
ret0, _ := ret[0].(bool)
return ret0
}
// TryLock indicates an expected call of TryLock.
func (mr *MockSyncTreeMockRecorder) TryLock() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TryLock", reflect.TypeOf((*MockSyncTree)(nil).TryLock))
}
// TryRLock mocks base method.
func (m *MockSyncTree) TryRLock() bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "TryRLock")
ret0, _ := ret[0].(bool)
return ret0
}
// TryRLock indicates an expected call of TryRLock.
func (mr *MockSyncTreeMockRecorder) TryRLock() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TryRLock", reflect.TypeOf((*MockSyncTree)(nil).TryRLock))
}
// Unlock mocks base method.
func (m *MockSyncTree) Unlock() {
m.ctrl.T.Helper()
@@ -476,21 +469,6 @@ func (mr *MockSyncTreeMockRecorder) UnmarshalledHeader() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnmarshalledHeader", reflect.TypeOf((*MockSyncTree)(nil).UnmarshalledHeader))
}
// UnpackChange mocks base method.
func (m *MockSyncTree) UnpackChange(arg0 *treechangeproto.RawTreeChangeWithId) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UnpackChange", arg0)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UnpackChange indicates an expected call of UnpackChange.
func (mr *MockSyncTreeMockRecorder) UnpackChange(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnpackChange", reflect.TypeOf((*MockSyncTree)(nil).UnpackChange), arg0)
}
// MockReceiveQueue is a mock of ReceiveQueue interface.
type MockReceiveQueue struct {
ctrl *gomock.Controller
@@ -0,0 +1,36 @@
package synctree
import (
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anytypeio/any-sync/commonspace/objectsync"
)
type queuedClient struct {
SyncClient
queue objectsync.ActionQueue
}
func newQueuedClient(client SyncClient, queue objectsync.ActionQueue) SyncClient {
return &queuedClient{
SyncClient: client,
queue: queue,
}
}
func (q *queuedClient) BroadcastAsync(message *treechangeproto.TreeSyncMessage) (err error) {
return q.queue.Send(func() error {
return q.SyncClient.BroadcastAsync(message)
})
}
func (q *queuedClient) SendAsync(peerId string, message *treechangeproto.TreeSyncMessage, replyId string) (err error) {
return q.queue.Send(func() error {
return q.SyncClient.SendAsync(peerId, message, replyId)
})
}
func (q *queuedClient) BroadcastAsyncOrSendResponsible(message *treechangeproto.TreeSyncMessage) (err error) {
return q.queue.Send(func() error {
return q.SyncClient.BroadcastAsyncOrSendResponsible(message)
})
}
@@ -2,7 +2,7 @@ package synctree
import (
"errors"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
"sync"
)
@@ -1,10 +1,10 @@
package objectsync
package synctree
import (
"fmt"
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/util/slice"
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anytypeio/any-sync/util/slice"
)
type RequestFactory interface {
@@ -14,8 +14,10 @@ type RequestFactory interface {
CreateFullSyncResponse(t objecttree.ObjectTree, theirHeads, theirSnapshotPath []string) (*treechangeproto.TreeSyncMessage, error)
}
func NewRequestFactory() RequestFactory {
return &requestFactory{}
var sharedFactory = &requestFactory{}
func GetRequestFactory() RequestFactory {
return sharedFactory
}
type requestFactory struct{}
@@ -0,0 +1,90 @@
//go:generate mockgen -destination mock_synctree/mock_synctree.go github.com/anytypeio/any-sync/commonspace/object/tree/synctree SyncClient,SyncTree,ReceiveQueue,HeadNotifiable
package synctree
import (
"github.com/anytypeio/any-sync/commonspace/confconnector"
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anytypeio/any-sync/commonspace/objectsync"
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
"github.com/anytypeio/any-sync/nodeconf"
)
type SyncClient interface {
RequestFactory
BroadcastAsync(message *treechangeproto.TreeSyncMessage) (err error)
BroadcastAsyncOrSendResponsible(message *treechangeproto.TreeSyncMessage) (err error)
SendAsync(peerId string, message *treechangeproto.TreeSyncMessage, replyId string) (err error)
}
type syncClient struct {
objectsync.StreamPool
RequestFactory
spaceId string
connector confconnector.ConfConnector
configuration nodeconf.Configuration
checker objectsync.StreamChecker
}
func newSyncClient(
spaceId string,
pool objectsync.StreamPool,
factory RequestFactory,
configuration nodeconf.Configuration,
checker objectsync.StreamChecker) SyncClient {
return &syncClient{
StreamPool: pool,
RequestFactory: factory,
configuration: configuration,
checker: checker,
spaceId: spaceId,
}
}
func (s *syncClient) BroadcastAsync(message *treechangeproto.TreeSyncMessage) (err error) {
objMsg, err := marshallTreeMessage(message, message.RootChange.Id, "")
if err != nil {
return
}
s.checker.CheckResponsiblePeers()
return s.StreamPool.BroadcastAsync(objMsg)
}
func (s *syncClient) SendAsync(peerId string, message *treechangeproto.TreeSyncMessage, replyId string) (err error) {
err = s.checker.CheckPeerConnection(peerId)
if err != nil {
return
}
objMsg, err := marshallTreeMessage(message, message.RootChange.Id, replyId)
if err != nil {
return
}
return s.StreamPool.SendAsync([]string{peerId}, objMsg)
}
func (s *syncClient) BroadcastAsyncOrSendResponsible(message *treechangeproto.TreeSyncMessage) (err error) {
objMsg, err := marshallTreeMessage(message, message.RootChange.Id, "")
if err != nil {
return
}
if s.configuration.IsResponsible(s.spaceId) {
s.checker.CheckResponsiblePeers()
return s.StreamPool.SendAsync(s.configuration.NodeIds(s.spaceId), objMsg)
}
return s.BroadcastAsync(message)
}
func marshallTreeMessage(message *treechangeproto.TreeSyncMessage, id, replyId string) (objMsg *spacesyncproto.ObjectSyncMessage, err error) {
payload, err := message.Marshal()
if err != nil {
return
}
objMsg = &spacesyncproto.ObjectSyncMessage{
ReplyId: replyId,
Payload: payload,
ObjectId: id,
}
return
}
@@ -1,361 +0,0 @@
package synctree
import (
"context"
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
"github.com/anyproto/any-sync/util/slice"
"github.com/gogo/protobuf/proto"
"github.com/stretchr/testify/require"
"math/rand"
"testing"
"time"
)
func TestEmptyClientGetsFullHistory(t *testing.T) {
treeId := "treeId"
spaceId := "spaceId"
keys, err := accountdata.NewRandom()
require.NoError(t, err)
aclList, err := list.NewTestDerivedAcl(spaceId, keys)
require.NoError(t, err)
storage := createStorage(treeId, aclList)
changeCreator := objecttree.NewMockChangeCreator()
deps := fixtureDeps{
aclList: aclList,
initStorage: storage.(*treestorage.InMemoryTreeStorage),
connectionMap: map[string][]string{
"peer1": []string{"peer2"},
"peer2": []string{"peer1"},
},
emptyTrees: []string{"peer2"},
treeBuilder: objecttree.BuildTestableTree,
}
fx := newProtocolFixture(t, spaceId, deps)
fx.run(t)
fx.handlers["peer1"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
NewHeads: nil,
RawChanges: []*treechangeproto.RawTreeChangeWithId{
changeCreator.CreateRaw("1", aclList.Id(), treeId, true, treeId),
},
})
time.Sleep(100 * time.Millisecond)
fx.stop()
firstHeads := fx.handlers["peer1"].tree().Heads()
secondHeads := fx.handlers["peer2"].tree().Heads()
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
require.Equal(t, []string{"1"}, firstHeads)
logMsgs := fx.log.batcher.GetAll()
var fullResponseMsg msgDescription
for _, msg := range logMsgs {
descr := msg.description()
if descr.name == "FullSyncResponse" {
fullResponseMsg = descr
}
}
// that means that we got not only the last snapshot, but also the first one
require.Len(t, fullResponseMsg.changes, 2)
}
func TestTreeFuzzyMerge(t *testing.T) {
testTreeFuzzyMerge(t, true)
testTreeFuzzyMerge(t, false)
}
func testTreeFuzzyMerge(t *testing.T, withData bool) {
var (
rnd = rand.New(rand.NewSource(time.Now().Unix()))
levels = 20
perLevel = 20
rounds = 10
)
for i := 0; i < rounds; i++ {
testTreeMerge(t, levels, perLevel, withData, func() bool {
return true
})
testTreeMerge(t, levels, perLevel, withData, func() bool {
return false
})
testTreeMerge(t, levels, perLevel, withData, func() bool {
return rnd.Intn(10) > 8
})
levels += 2
}
}
func testTreeMerge(t *testing.T, levels, perLevel int, hasData bool, isSnapshot func() bool) {
treeId := "treeId"
spaceId := "spaceId"
keys, err := accountdata.NewRandom()
require.NoError(t, err)
aclList, err := list.NewTestDerivedAcl(spaceId, keys)
storage := createStorage(treeId, aclList)
changeCreator := objecttree.NewMockChangeCreator()
builder := objecttree.BuildTestableTree
if hasData {
builder = objecttree.BuildEmptyDataTestableTree
}
params := genParams{
prefix: "peer1",
aclId: aclList.Id(),
startIdx: 0,
levels: levels,
perLevel: perLevel,
snapshotId: treeId,
prevHeads: []string{treeId},
isSnapshot: isSnapshot,
hasData: hasData,
}
// generating initial tree
initialRes := genChanges(changeCreator, params)
err = storage.AddRawChangesSetHeads(initialRes.changes, initialRes.heads)
require.NoError(t, err)
deps := fixtureDeps{
aclList: aclList,
initStorage: storage.(*treestorage.InMemoryTreeStorage),
connectionMap: map[string][]string{
"peer1": []string{"node1"},
"peer2": []string{"node1"},
"node1": []string{"peer1", "peer2"},
},
emptyTrees: []string{"peer2", "node1"},
treeBuilder: builder,
}
fx := newProtocolFixture(t, spaceId, deps)
fx.run(t)
fx.handlers["peer1"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
NewHeads: initialRes.heads,
RawChanges: initialRes.changes,
})
time.Sleep(50 * time.Millisecond)
firstHeads := fx.handlers["peer1"].tree().Heads()
secondHeads := fx.handlers["peer2"].tree().Heads()
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
params = genParams{
prefix: "peer1",
aclId: aclList.Id(),
startIdx: levels,
levels: levels,
perLevel: perLevel,
snapshotId: initialRes.snapshotId,
prevHeads: initialRes.heads,
isSnapshot: isSnapshot,
hasData: hasData,
}
// generating different additions to the tree for different peers
peer1Res := genChanges(changeCreator, params)
params.prefix = "peer2"
peer2Res := genChanges(changeCreator, params)
fx.handlers["peer1"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
NewHeads: peer1Res.heads,
RawChanges: peer1Res.changes,
})
fx.handlers["peer2"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
NewHeads: peer2Res.heads,
RawChanges: peer2Res.changes,
})
time.Sleep(50 * time.Millisecond)
fx.stop()
firstTree := fx.handlers["peer1"].tree()
secondTree := fx.handlers["peer2"].tree()
firstHeads = firstTree.Heads()
secondHeads = secondTree.Heads()
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
require.True(t, slice.UnsortedEquals(firstHeads, append(peer1Res.heads, peer2Res.heads...)))
firstStorage := firstTree.Storage().(*treestorage.InMemoryTreeStorage)
secondStorage := secondTree.Storage().(*treestorage.InMemoryTreeStorage)
require.True(t, firstStorage.Equal(secondStorage))
if hasData {
for _, ch := range secondStorage.Changes {
if ch.Id == treeId {
continue
}
unmarshallRaw := &treechangeproto.RawTreeChange{}
proto.Unmarshal(ch.RawChange, unmarshallRaw)
treeCh := &treechangeproto.TreeChange{}
proto.Unmarshal(unmarshallRaw.Payload, treeCh)
require.Equal(t, ch.Id, string(treeCh.ChangesData))
}
}
}
func TestTreeStorageHasExtraChanges(t *testing.T) {
var (
rnd = rand.New(rand.NewSource(time.Now().Unix()))
levels = 20
perLevel = 40
)
// simulating cases where one peer has some extra changes saved in storage
// and checking that this will not break the sync
t.Run("tree storage has extra simple", func(t *testing.T) {
testTreeStorageHasExtra(t, levels, perLevel, false, func() bool {
return false
})
testTreeStorageHasExtra(t, levels, perLevel, false, func() bool {
return rnd.Intn(10) > 5
})
testTreeStorageHasExtra(t, levels, perLevel, true, func() bool {
return false
})
testTreeStorageHasExtra(t, levels, perLevel, true, func() bool {
return rnd.Intn(10) > 5
})
})
t.Run("tree storage has extra three parts", func(t *testing.T) {
testTreeStorageHasExtraThreeParts(t, levels, perLevel, false, func() bool {
return false
})
testTreeStorageHasExtraThreeParts(t, levels, perLevel, false, func() bool {
return rnd.Intn(10) > 5
})
testTreeStorageHasExtraThreeParts(t, levels, perLevel, true, func() bool {
return false
})
testTreeStorageHasExtraThreeParts(t, levels, perLevel, true, func() bool {
return rnd.Intn(10) > 5
})
})
}
func testTreeStorageHasExtra(t *testing.T, levels, perLevel int, hasData bool, isSnapshot func() bool) {
treeId := "treeId"
spaceId := "spaceId"
keys, err := accountdata.NewRandom()
require.NoError(t, err)
aclList, err := list.NewTestDerivedAcl(spaceId, keys)
storage := createStorage(treeId, aclList)
changeCreator := objecttree.NewMockChangeCreator()
builder := objecttree.BuildTestableTree
if hasData {
builder = objecttree.BuildEmptyDataTestableTree
}
params := genParams{
prefix: "peer1",
aclId: aclList.Id(),
startIdx: 0,
levels: levels,
perLevel: perLevel,
snapshotId: treeId,
prevHeads: []string{treeId},
isSnapshot: isSnapshot,
hasData: hasData,
}
deps := fixtureDeps{
aclList: aclList,
initStorage: storage.(*treestorage.InMemoryTreeStorage),
connectionMap: map[string][]string{
"peer1": []string{"peer2"},
"peer2": []string{"peer1"},
},
treeBuilder: builder,
}
fx := newProtocolFixture(t, spaceId, deps)
// generating initial tree
initialRes := genChanges(changeCreator, params)
fx.run(t)
// adding some changes to store, but without updating heads
store := fx.handlers["peer1"].tree().Storage().(*treestorage.InMemoryTreeStorage)
oldHeads, _ := store.Heads()
store.AddRawChangesSetHeads(initialRes.changes, oldHeads)
// sending those changes to other peer
fx.handlers["peer2"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
NewHeads: initialRes.heads,
RawChanges: initialRes.changes,
})
time.Sleep(50 * time.Millisecond)
// here we want that the saved changes in storage should not affect the sync protocol
firstHeads := fx.handlers["peer1"].tree().Heads()
secondHeads := fx.handlers["peer2"].tree().Heads()
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
require.True(t, slice.UnsortedEquals(initialRes.heads, firstHeads))
}
func testTreeStorageHasExtraThreeParts(t *testing.T, levels, perLevel int, hasData bool, isSnapshot func() bool) {
treeId := "treeId"
spaceId := "spaceId"
keys, err := accountdata.NewRandom()
require.NoError(t, err)
aclList, err := list.NewTestDerivedAcl(spaceId, keys)
storage := createStorage(treeId, aclList)
changeCreator := objecttree.NewMockChangeCreator()
builder := objecttree.BuildTestableTree
if hasData {
builder = objecttree.BuildEmptyDataTestableTree
}
params := genParams{
prefix: "peer1",
aclId: aclList.Id(),
startIdx: 0,
levels: levels,
perLevel: perLevel,
snapshotId: treeId,
prevHeads: []string{treeId},
isSnapshot: isSnapshot,
hasData: hasData,
}
deps := fixtureDeps{
aclList: aclList,
initStorage: storage.(*treestorage.InMemoryTreeStorage),
connectionMap: map[string][]string{
"peer1": []string{"peer2"},
"peer2": []string{"peer1"},
},
treeBuilder: builder,
}
fx := newProtocolFixture(t, spaceId, deps)
// generating parts
firstPart := genChanges(changeCreator, params)
params.startIdx = levels
params.snapshotId = firstPart.snapshotId
params.prevHeads = firstPart.heads
secondPart := genChanges(changeCreator, params)
params.startIdx = levels * 2
params.snapshotId = secondPart.snapshotId
params.prevHeads = secondPart.heads
thirdPart := genChanges(changeCreator, params)
// adding part1 to first peer and saving part2 and part3 in its storage
res, _ := fx.handlers["peer1"].tree().AddRawChanges(context.Background(), objecttree.RawChangesPayload{
NewHeads: firstPart.heads,
RawChanges: firstPart.changes,
})
require.True(t, slice.UnsortedEquals(res.Heads, firstPart.heads))
store := fx.handlers["peer1"].tree().Storage().(*treestorage.InMemoryTreeStorage)
oldHeads, _ := store.Heads()
store.AddRawChangesSetHeads(secondPart.changes, oldHeads)
store.AddRawChangesSetHeads(thirdPart.changes, oldHeads)
var peer2Initial []*treechangeproto.RawTreeChangeWithId
peer2Initial = append(peer2Initial, firstPart.changes...)
peer2Initial = append(peer2Initial, secondPart.changes...)
// adding part1 and part2 to second peer
res, _ = fx.handlers["peer2"].tree().AddRawChanges(context.Background(), objecttree.RawChangesPayload{
NewHeads: secondPart.heads,
RawChanges: peer2Initial,
})
require.True(t, slice.UnsortedEquals(res.Heads, secondPart.heads))
fx.run(t)
// sending part3 changes to other peer
fx.handlers["peer2"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
NewHeads: thirdPart.heads,
RawChanges: thirdPart.changes,
})
time.Sleep(50 * time.Millisecond)
firstHeads := fx.handlers["peer1"].tree().Heads()
secondHeads := fx.handlers["peer2"].tree().Heads()
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
require.True(t, slice.UnsortedEquals(thirdPart.heads, firstHeads))
}
+146 -66
View File
@@ -1,23 +1,24 @@
//go:generate mockgen -destination mock_synctree/mock_synctree.go github.com/anyproto/any-sync/commonspace/object/tree/synctree SyncTree,ReceiveQueue,HeadNotifiable
package synctree
import (
"context"
"errors"
"time"
"github.com/anyproto/any-sync/app/logger"
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
"github.com/anyproto/any-sync/commonspace/objectsync"
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
"github.com/anyproto/any-sync/commonspace/spacestorage"
"github.com/anyproto/any-sync/commonspace/syncstatus"
"github.com/anyproto/any-sync/net/peer"
"github.com/anyproto/any-sync/nodeconf"
"fmt"
"github.com/anytypeio/any-sync/app/logger"
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
"github.com/anytypeio/any-sync/commonspace/object/tree/synctree/updatelistener"
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
"github.com/anytypeio/any-sync/commonspace/objectsync"
"github.com/anytypeio/any-sync/commonspace/objectsync/synchandler"
spacestorage "github.com/anytypeio/any-sync/commonspace/spacestorage"
"github.com/anytypeio/any-sync/commonspace/syncstatus"
"github.com/anytypeio/any-sync/net/peer"
"github.com/anytypeio/any-sync/nodeconf"
"github.com/gogo/protobuf/proto"
"go.uber.org/zap"
"sync/atomic"
)
var (
@@ -29,62 +30,151 @@ type HeadNotifiable interface {
UpdateHeads(id string, heads []string)
}
type ListenerSetter interface {
SetListener(listener updatelistener.UpdateListener)
}
type SyncTree interface {
objecttree.ObjectTree
synchandler.SyncHandler
ListenerSetter
SyncWithPeer(ctx context.Context, peerId string) (err error)
Ping() (err error)
}
// SyncTree sends head updates to sync service and also sends new changes to update listener
type syncTree struct {
objecttree.ObjectTree
synchandler.SyncHandler
syncClient objectsync.SyncClient
syncClient SyncClient
syncStatus syncstatus.StatusUpdater
notifiable HeadNotifiable
listener updatelistener.UpdateListener
onClose func(id string)
treeUsage *atomic.Int32
isClosed bool
isDeleted bool
}
var log = logger.NewNamed("common.commonspace.synctree")
var log = logger.NewNamed("commonspace.synctree").Sugar()
type ResponsiblePeersGetter interface {
GetResponsiblePeers(ctx context.Context) (peers []peer.Peer, err error)
}
var buildObjectTree = objecttree.BuildObjectTree
var createSyncClient = newWrappedSyncClient
type BuildDeps struct {
SpaceId string
SyncClient objectsync.SyncClient
Configuration nodeconf.NodeConf
ObjectSync objectsync.ObjectSync
Configuration nodeconf.Configuration
HeadNotifiable HeadNotifiable
Listener updatelistener.UpdateListener
AclList list.AclList
SpaceStorage spacestorage.SpaceStorage
TreeStorage treestorage.TreeStorage
OnClose func(id string)
TreeUsage *atomic.Int32
SyncStatus syncstatus.StatusUpdater
PeerGetter ResponsiblePeersGetter
BuildObjectTree objecttree.BuildObjectTreeFunc
WaitTreeRemoteSync bool
}
func newWrappedSyncClient(
spaceId string,
factory RequestFactory,
objectSync objectsync.ObjectSync,
configuration nodeconf.Configuration) SyncClient {
syncClient := newSyncClient(spaceId, objectSync.StreamPool(), factory, configuration, objectSync.StreamChecker())
return newQueuedClient(syncClient, objectSync.ActionQueue())
}
func BuildSyncTreeOrGetRemote(ctx context.Context, id string, deps BuildDeps) (t SyncTree, err error) {
var (
remoteGetter = treeRemoteGetter{treeId: id, deps: deps}
isRemote bool
)
deps.TreeStorage, isRemote, err = remoteGetter.getTree(ctx)
getTreeRemote := func() (msg *treechangeproto.TreeSyncMessage, err error) {
streamChecker := deps.ObjectSync.StreamChecker()
peerId, err := peer.CtxPeerId(ctx)
if err != nil {
streamChecker.CheckResponsiblePeers()
peerId, err = streamChecker.FirstResponsiblePeer()
if err != nil {
return
}
}
newTreeRequest := GetRequestFactory().CreateNewTreeRequest()
objMsg, err := marshallTreeMessage(newTreeRequest, id, "")
if err != nil {
return
}
err = deps.ObjectSync.StreamChecker().CheckPeerConnection(peerId)
if err != nil {
return
}
resp, err := deps.ObjectSync.StreamPool().SendSync(peerId, objMsg)
if err != nil {
return
}
msg = &treechangeproto.TreeSyncMessage{}
err = proto.Unmarshal(resp.Payload, msg)
return
}
waitTree := func(wait bool) (msg *treechangeproto.TreeSyncMessage, err error) {
if !wait {
return getTreeRemote()
}
for {
msg, err = getTreeRemote()
if err == nil {
return
}
select {
case <-ctx.Done():
err = fmt.Errorf("waiting for object %s interrupted, context closed", id)
return
default:
break
}
}
}
deps.TreeStorage, err = deps.SpaceStorage.TreeStorage(id)
if err == nil {
return buildSyncTree(ctx, false, deps)
}
if err != nil && err != treestorage.ErrUnknownTreeId {
return
}
status, err := deps.SpaceStorage.TreeDeletedStatus(id)
if err != nil {
return
}
return buildSyncTree(ctx, isRemote, deps)
if status != "" {
err = spacestorage.ErrTreeStorageAlreadyDeleted
return
}
resp, err := waitTree(deps.WaitTreeRemoteSync)
if err != nil {
return
}
if resp.GetContent().GetFullSyncResponse() == nil {
err = fmt.Errorf("expected to get full sync response, but got something else")
return
}
fullSyncResp := resp.GetContent().GetFullSyncResponse()
payload := treestorage.TreeStorageCreatePayload{
RootRawChange: resp.RootChange,
Changes: fullSyncResp.Changes,
Heads: fullSyncResp.Heads,
}
// basically building tree with in-memory storage and validating that it was without errors
log.With(zap.String("id", id)).Debug("validating tree")
err = objecttree.ValidateRawTree(payload, deps.AclList)
if err != nil {
return
}
// now we are sure that we can save it to the storage
deps.TreeStorage, err = deps.SpaceStorage.CreateTreeStorage(payload)
if err != nil {
return
}
return buildSyncTree(ctx, true, deps)
}
func PutSyncTree(ctx context.Context, payload treestorage.TreeStorageCreatePayload, deps BuildDeps) (t SyncTree, err error) {
@@ -95,40 +185,39 @@ func PutSyncTree(ctx context.Context, payload treestorage.TreeStorageCreatePaylo
return buildSyncTree(ctx, true, deps)
}
func buildSyncTree(ctx context.Context, sendUpdate bool, deps BuildDeps) (t SyncTree, err error) {
objTree, err := deps.BuildObjectTree(deps.TreeStorage, deps.AclList)
func buildSyncTree(ctx context.Context, isFirstBuild bool, deps BuildDeps) (t SyncTree, err error) {
objTree, err := buildObjectTree(deps.TreeStorage, deps.AclList)
if err != nil {
return
}
syncClient := deps.SyncClient
syncClient := createSyncClient(
deps.SpaceId,
sharedFactory,
deps.ObjectSync,
deps.Configuration)
syncTree := &syncTree{
ObjectTree: objTree,
syncClient: syncClient,
notifiable: deps.HeadNotifiable,
onClose: deps.OnClose,
treeUsage: deps.TreeUsage,
listener: deps.Listener,
syncStatus: deps.SyncStatus,
}
syncHandler := newSyncTreeHandler(deps.SpaceId, syncTree, syncClient, deps.SyncStatus)
syncHandler := newSyncTreeHandler(syncTree, syncClient, deps.SyncStatus)
syncTree.SyncHandler = syncHandler
t = syncTree
syncTree.Lock()
syncTree.afterBuild()
syncTree.Unlock()
if sendUpdate {
if isFirstBuild {
headUpdate := syncTree.syncClient.CreateHeadUpdate(t, nil)
// send to everybody, because everybody should know that the node or client got new tree
syncTree.syncClient.Broadcast(ctx, headUpdate)
syncTree.syncClient.BroadcastAsync(headUpdate)
}
return
}
func (s *syncTree) SetListener(listener updatelistener.UpdateListener) {
// this should be called under lock
s.listener = listener
}
func (s *syncTree) IterateFrom(id string, convert objecttree.ChangeConvertFunc, iterate objecttree.ChangeIterateFunc) (err error) {
if err = s.checkAlive(); err != nil {
return
@@ -156,7 +245,7 @@ func (s *syncTree) AddContent(ctx context.Context, content objecttree.SignableCh
}
s.syncStatus.HeadsChange(s.Id(), res.Heads)
headUpdate := s.syncClient.CreateHeadUpdate(s, res.Added)
s.syncClient.Broadcast(ctx, headUpdate)
err = s.syncClient.BroadcastAsync(headUpdate)
return
}
@@ -183,16 +272,13 @@ func (s *syncTree) AddRawChanges(ctx context.Context, changesPayload objecttree.
s.notifiable.UpdateHeads(s.Id(), res.Heads)
}
headUpdate := s.syncClient.CreateHeadUpdate(s, res.Added)
s.syncClient.Broadcast(ctx, headUpdate)
err = s.syncClient.BroadcastAsync(headUpdate)
}
return
}
func (s *syncTree) Delete() (err error) {
log.Debug("deleting sync tree", zap.String("id", s.Id()))
defer func() {
log.Debug("deleted sync tree", zap.Error(err), zap.String("id", s.Id()))
}()
log.With("id", s.Id()).Debug("deleting sync tree")
s.Lock()
defer s.Unlock()
if err = s.checkAlive(); err != nil {
@@ -206,21 +292,14 @@ func (s *syncTree) Delete() (err error) {
return
}
func (s *syncTree) TryClose(objectTTL time.Duration) (bool, error) {
return true, s.Close()
}
func (s *syncTree) Close() (err error) {
log.Debug("closing sync tree", zap.String("id", s.Id()))
defer func() {
log.Debug("closed sync tree", zap.Error(err), zap.String("id", s.Id()))
}()
log.With("id", s.Id()).Debug("closing sync tree")
s.Lock()
defer s.Unlock()
if s.isClosed {
return ErrSyncTreeClosed
}
s.onClose(s.Id())
s.treeUsage.Add(-1)
s.isClosed = true
return
}
@@ -235,17 +314,18 @@ func (s *syncTree) checkAlive() (err error) {
return
}
func (s *syncTree) SyncWithPeer(ctx context.Context, peerId string) (err error) {
func (s *syncTree) Ping() (err error) {
s.Lock()
defer s.Unlock()
headUpdate := s.syncClient.CreateHeadUpdate(s, nil)
return s.syncClient.SendWithReply(ctx, peerId, headUpdate.RootChange.Id, headUpdate, "")
return s.syncClient.BroadcastAsyncOrSendResponsible(headUpdate)
}
func (s *syncTree) afterBuild() {
if s.listener != nil {
s.listener.Rebuild(s)
}
s.treeUsage.Add(1)
if s.notifiable != nil {
s.notifiable.UpdateHeads(s.Id(), s.Heads())
}
@@ -2,15 +2,15 @@ package synctree
import (
"context"
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener/mock_updatelistener"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/commonspace/objectsync"
"github.com/anyproto/any-sync/commonspace/objectsync/mock_objectsync"
"github.com/anyproto/any-sync/commonspace/syncstatus"
"github.com/anyproto/any-sync/nodeconf"
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
"github.com/anytypeio/any-sync/commonspace/object/tree/synctree/mock_synctree"
"github.com/anytypeio/any-sync/commonspace/object/tree/synctree/updatelistener"
"github.com/anytypeio/any-sync/commonspace/object/tree/synctree/updatelistener/mock_updatelistener"
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anytypeio/any-sync/commonspace/objectsync"
"github.com/anytypeio/any-sync/commonspace/syncstatus"
"github.com/anytypeio/any-sync/nodeconf"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"
"testing"
@@ -18,7 +18,7 @@ import (
type syncTreeMatcher struct {
objTree objecttree.ObjectTree
client objectsync.SyncClient
client SyncClient
listener updatelistener.UpdateListener
}
@@ -34,8 +34,8 @@ func (s syncTreeMatcher) String() string {
return ""
}
func syncClientFuncCreator(client objectsync.SyncClient) func(spaceId string, factory objectsync.RequestFactory, objectSync objectsync.ObjectSync, configuration nodeconf.NodeConf) objectsync.SyncClient {
return func(spaceId string, factory objectsync.RequestFactory, objectSync objectsync.ObjectSync, configuration nodeconf.NodeConf) objectsync.SyncClient {
func syncClientFuncCreator(client SyncClient) func(spaceId string, factory RequestFactory, objectSync objectsync.ObjectSync, configuration nodeconf.Configuration) SyncClient {
return func(spaceId string, factory RequestFactory, objectSync objectsync.ObjectSync, configuration nodeconf.Configuration) SyncClient {
return client
}
}
@@ -46,7 +46,7 @@ func Test_BuildSyncTree(t *testing.T) {
defer ctrl.Finish()
updateListenerMock := mock_updatelistener.NewMockUpdateListener(ctrl)
syncClientMock := mock_objectsync.NewMockSyncClient(ctrl)
syncClientMock := mock_synctree.NewMockSyncClient(ctrl)
objTreeMock := newTestObjMock(mock_objecttree.NewMockObjectTree(ctrl))
tr := &syncTree{
ObjectTree: objTreeMock,
@@ -73,7 +73,7 @@ func Test_BuildSyncTree(t *testing.T) {
updateListenerMock.EXPECT().Update(tr)
syncClientMock.EXPECT().CreateHeadUpdate(gomock.Eq(tr), gomock.Eq(changes)).Return(headUpdate)
syncClientMock.EXPECT().Broadcast(gomock.Any(), gomock.Eq(headUpdate))
syncClientMock.EXPECT().BroadcastAsync(gomock.Eq(headUpdate)).Return(nil)
res, err := tr.AddRawChanges(ctx, payload)
require.NoError(t, err)
require.Equal(t, expectedRes, res)
@@ -95,7 +95,7 @@ func Test_BuildSyncTree(t *testing.T) {
updateListenerMock.EXPECT().Rebuild(tr)
syncClientMock.EXPECT().CreateHeadUpdate(gomock.Eq(tr), gomock.Eq(changes)).Return(headUpdate)
syncClientMock.EXPECT().Broadcast(gomock.Any(), gomock.Eq(headUpdate))
syncClientMock.EXPECT().BroadcastAsync(gomock.Eq(headUpdate)).Return(nil)
res, err := tr.AddRawChanges(ctx, payload)
require.NoError(t, err)
require.Equal(t, expectedRes, res)
@@ -133,7 +133,7 @@ func Test_BuildSyncTree(t *testing.T) {
Return(expectedRes, nil)
syncClientMock.EXPECT().CreateHeadUpdate(gomock.Eq(tr), gomock.Eq(changes)).Return(headUpdate)
syncClientMock.EXPECT().Broadcast(gomock.Any(), gomock.Eq(headUpdate))
syncClientMock.EXPECT().BroadcastAsync(gomock.Eq(headUpdate)).Return(nil)
res, err := tr.AddContent(ctx, content)
require.NoError(t, err)
require.Equal(t, expectedRes, res)
@@ -2,13 +2,12 @@ package synctree
import (
"context"
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/commonspace/objectsync"
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
"github.com/anyproto/any-sync/commonspace/syncstatus"
"github.com/anyproto/any-sync/util/slice"
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anytypeio/any-sync/commonspace/objectsync/synchandler"
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
"github.com/anytypeio/any-sync/commonspace/syncstatus"
"github.com/anytypeio/any-sync/util/slice"
"github.com/gogo/protobuf/proto"
"go.uber.org/zap"
"sync"
@@ -16,21 +15,19 @@ import (
type syncTreeHandler struct {
objTree objecttree.ObjectTree
syncClient objectsync.SyncClient
syncClient SyncClient
syncStatus syncstatus.StatusUpdater
handlerLock sync.Mutex
spaceId string
queue ReceiveQueue
}
const maxQueueSize = 5
func newSyncTreeHandler(spaceId string, objTree objecttree.ObjectTree, syncClient objectsync.SyncClient, syncStatus syncstatus.StatusUpdater) synchandler.SyncHandler {
func newSyncTreeHandler(objTree objecttree.ObjectTree, syncClient SyncClient, syncStatus syncstatus.StatusUpdater) synchandler.SyncHandler {
return &syncTreeHandler{
objTree: objTree,
syncClient: syncClient,
syncStatus: syncStatus,
spaceId: spaceId,
queue: newReceiveQueue(maxQueueSize),
}
}
@@ -41,9 +38,10 @@ func (s *syncTreeHandler) HandleMessage(ctx context.Context, senderId string, ms
if err != nil {
return
}
s.syncStatus.HeadsReceive(senderId, msg.ObjectId, treechangeproto.GetHeads(unmarshalled))
queueFull := s.queue.AddMessage(senderId, unmarshalled, msg.RequestId)
queueFull := s.queue.AddMessage(senderId, unmarshalled, msg.ReplyId)
if queueFull {
return
}
@@ -82,44 +80,38 @@ func (s *syncTreeHandler) handleHeadUpdate(
fullRequest *treechangeproto.TreeSyncMessage
isEmptyUpdate = len(update.Changes) == 0
objTree = s.objTree
treeId = objTree.Id()
)
log := log.With(
zap.Strings("update heads", update.Heads),
zap.String("treeId", treeId),
zap.String("spaceId", s.spaceId),
zap.Int("len(update changes)", len(update.Changes)))
log.DebugCtx(ctx, "received head update message")
log := log.With("senderId", senderId).
With("heads", objTree.Heads()).
With("treeId", objTree.Id())
log.Debug("received head update message")
defer func() {
if err != nil {
log.ErrorCtx(ctx, "head update finished with error", zap.Error(err))
log.With(zap.Error(err)).Debug("head update finished with error")
} else if fullRequest != nil {
cnt := fullRequest.Content.GetFullSyncRequest()
log = log.With(zap.Strings("request heads", cnt.Heads), zap.Int("len(request changes)", len(cnt.Changes)))
log.DebugCtx(ctx, "sending full sync request")
log.Debug("sending full sync request")
} else {
if !isEmptyUpdate {
log.DebugCtx(ctx, "head update finished correctly")
log.Debug("head update finished correctly")
}
}
}()
// isEmptyUpdate is sent when the tree is brought up from cache
if isEmptyUpdate {
headEquals := slice.UnsortedEquals(objTree.Heads(), update.Heads)
log.DebugCtx(ctx, "is empty update", zap.String("treeId", objTree.Id()), zap.Bool("headEquals", headEquals))
if headEquals {
log.With("treeId", objTree.Id()).Debug("is empty update")
if slice.UnsortedEquals(objTree.Heads(), update.Heads) {
return
}
// we need to sync in any case
fullRequest, err = s.syncClient.CreateFullSyncRequest(objTree, update.Heads, update.SnapshotPath)
if err != nil {
return
}
return s.syncClient.SendWithReply(ctx, senderId, treeId, fullRequest, replyId)
return s.syncClient.SendAsync(senderId, fullRequest, replyId)
}
if s.alreadyHasHeads(objTree, update.Heads) {
@@ -143,7 +135,7 @@ func (s *syncTreeHandler) handleHeadUpdate(
return
}
return s.syncClient.SendWithReply(ctx, senderId, treeId, fullRequest, replyId)
return s.syncClient.SendAsync(senderId, fullRequest, replyId)
}
func (s *syncTreeHandler) handleFullSyncRequest(
@@ -155,26 +147,22 @@ func (s *syncTreeHandler) handleFullSyncRequest(
fullResponse *treechangeproto.TreeSyncMessage
header = s.objTree.Header()
objTree = s.objTree
treeId = s.objTree.Id()
)
log := log.With(zap.String("senderId", senderId),
zap.Strings("request heads", request.Heads),
zap.String("treeId", treeId),
zap.String("replyId", replyId),
zap.String("spaceId", s.spaceId),
zap.Int("len(request changes)", len(request.Changes)))
log.DebugCtx(ctx, "received full sync request message")
log := log.With("senderId", senderId).
With("heads", request.Heads).
With("treeId", s.objTree.Id()).
With("replyId", replyId)
log.Debug("received full sync request message")
defer func() {
if err != nil {
log.ErrorCtx(ctx, "full sync request finished with error", zap.Error(err))
s.syncClient.SendWithReply(ctx, senderId, treeId, treechangeproto.WrapError(treechangeproto.ErrFullSync, header), replyId)
log.With(zap.Error(err)).Debug("full sync request finished with error")
s.syncClient.SendAsync(senderId, treechangeproto.WrapError(err, header), replyId)
return
} else if fullResponse != nil {
cnt := fullResponse.Content.GetFullSyncResponse()
log = log.With(zap.Strings("response heads", cnt.Heads), zap.Int("len(response changes)", len(cnt.Changes)))
log.DebugCtx(ctx, "full sync response sent")
log.Debug("full sync response sent")
}
}()
@@ -192,7 +180,7 @@ func (s *syncTreeHandler) handleFullSyncRequest(
return
}
return s.syncClient.SendWithReply(ctx, senderId, treeId, fullResponse, replyId)
return s.syncClient.SendAsync(senderId, fullResponse, replyId)
}
func (s *syncTreeHandler) handleFullSyncResponse(
@@ -201,20 +189,17 @@ func (s *syncTreeHandler) handleFullSyncResponse(
response *treechangeproto.TreeFullSyncResponse) (err error) {
var (
objTree = s.objTree
treeId = s.objTree.Id()
)
log := log.With(
zap.Strings("heads", response.Heads),
zap.String("treeId", treeId),
zap.String("spaceId", s.spaceId),
zap.Int("len(changes)", len(response.Changes)))
log.DebugCtx(ctx, "received full sync response message")
log := log.With("senderId", senderId).
With("heads", response.Heads).
With("treeId", s.objTree.Id())
log.Debug("received full sync response message")
defer func() {
if err != nil {
log.ErrorCtx(ctx, "full sync response failed", zap.Error(err))
log.With(zap.Error(err)).Debug("full sync response failed")
} else {
log.DebugCtx(ctx, "full sync response succeeded")
log.Debug("full sync response succeeded")
}
}()
Loaded 100 of 260 files, more files were not shown because too many files have changed in this diff. Show more