Compare commits

..
1119 Commits
Author SHA1 Message Date
Sergey Cherepanov ebf4034ec7 consensus: fix race 2023-07-11 12:02:15 +02:00
Sergey Cherepanov 1a23081336 merge 2023-07-10 19:12:58 +02:00
Sergey Cherepanov ef128dd33f switch to uber/gomock 2023-07-10 15:41:22 +02:00
Sergey Cherepanov 648aa15b55 consensus: err invalid payload 2023-07-07 11:22:12 +02:00
Sergey Cherepanov cd3c6c736a consensus: remove log payload 2023-07-05 18:50:14 +02:00
Mikhail Rakhmanov 79cc89bec2 Merge pull request #43 from anyproto/remove-object-load-deadline 2023-07-04 16:55:12 +02:00
mcrakhman fe31afc337 Remove cancel with deadline 2023-07-04 16:51:14 +02:00
Mikhail Rakhmanov ac8c8e4a31 Merge pull request #42 from anyproto/fix-peer-connection 2023-07-04 11:12:38 +02:00
mcrakhman ab34ff4bc9 Fix not sending correct connection and incoming count 2023-07-04 08:07:55 +02:00
Sergey Cherepanov 0c1d752acf consensus: err forbidden 2023-07-03 17:36:31 +02:00
Sergey Cherepanov b12a056dd9 consensus: use strings for ids 2023-07-03 16:19:24 +02:00
Mikhail Rakhmanov b10d72a092 Merge pull request #40 from anyproto/acl-change 2023-07-02 15:54:07 +02:00
mcrakhman 822e7f374d Change to consensus proto 2023-07-01 13:17:18 +02:00
mcrakhman e094743fbc Merge remote-tracking branch 'origin/consensus-client' into acl-change 2023-07-01 12:51:05 +02:00
Sergey Cherepanov 92cbfb1cb3 change consensus proto and client 2023-06-30 19:42:07 +02:00
Sergey Cherepanov 59cf8b46fd consensus: change err offset 2023-06-29 14:52:47 +02:00
Sergey Cherepanov 50f94e7518 consensus: change err offset 2023-06-29 14:51:42 +02:00
Sergey Cherepanov cbdbe0c34b Merge branch 'main' of github.com:anyproto/any-sync into consensus-client 2023-06-29 14:40:07 +02:00
Sergey Cherepanov 02dd4783bc fix mock 2023-06-29 14:37:52 +02:00
mcrakhman 5ffc175f4f Remove time from test 2023-06-29 01:05:43 +02:00
mcrakhman 68cda47ede Update list mock 2023-06-29 01:00:52 +02:00
mcrakhman f4cbbfa374 Update tests 2023-06-29 00:57:24 +02:00
mcrakhman 53e9c4ab02 Merge branch 'main' into acl-change
# Conflicts:
#	net/peer/peer.go
2023-06-28 23:12:04 +02:00
Mikhail Rakhmanov 8dc0ead8f3 Merge pull request #39 from anyproto/fix-last-iterated-id
Add lastIteratedId when setting merged heads
2023-06-28 22:33:21 +02:00
mcrakhman 02b326cc90 Add lastIteratedId when setting merged heads 2023-06-28 21:34:50 +02:00
mcrakhman e5b4f62e48 fix nodes online 2023-06-28 17:35:45 +02:00
mcrakhman 3f08fcb555 Add account remove test 2023-06-28 15:43:35 +02:00
mcrakhman 39f41c52d1 Add invite test 2023-06-28 14:55:17 +02:00
mcrakhman ffd613a5fc Fix requestmanager test 2023-06-28 11:59:13 +02:00
Sergey Cherepanov ea6ca799e7 Merge pull request #38 from anyproto/fix-handshake-race
fix race in proto handshake
2023-06-28 11:00:39 +02:00
Sergey Cherepanov 6057fc2c7c Merge pull request #37 from anyproto/dependabot/go_modules/github.com/libp2p/go-libp2p-0.28.1
Bump github.com/libp2p/go-libp2p from 0.28.0 to 0.28.1
2023-06-28 10:27:34 +02:00
Sergey Cherepanov 8770da4abf fix race in proto handshake 2023-06-28 10:11:21 +02:00
Sergey Cherepanov a092f7b4a1 consensus client 2023-06-27 22:09:52 +02:00
mcrakhman 0ffbb6fa5a Rework ACL structures 2023-06-27 19:44:44 +02:00
mcrakhman 061522eec2 Update protocol 2023-06-26 19:38:54 +02:00
dependabot[bot] b768dedd56 Bump github.com/libp2p/go-libp2p from 0.28.0 to 0.28.1
Bumps [github.com/libp2p/go-libp2p](https://github.com/libp2p/go-libp2p) from 0.28.0 to 0.28.1.
- [Release notes](https://github.com/libp2p/go-libp2p/releases)
- [Changelog](https://github.com/libp2p/go-libp2p/blob/master/CHANGELOG.md)
- [Commits](https://github.com/libp2p/go-libp2p/compare/v0.28.0...v0.28.1)

---
updated-dependencies:
- dependency-name: github.com/libp2p/go-libp2p
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-26 16:03:40 +00:00
Sergey Cherepanov 5a02d1c338 Merge pull request #36 from anyproto/subconn-limit
peer sub connections limit/throttling
2023-06-26 13:48:50 +02:00
mcrakhman 62f23b7229 Update record builder to build new payloads 2023-06-26 11:43:17 +02:00
mcrakhman 81aadfde7e Add validate method in list 2023-06-26 10:10:14 +02:00
Sergey Cherepanov f943991bc0 yamux bench test: open sub conn 2023-06-23 18:32:13 +02:00
Sergey Cherepanov f0ffc9b7bf descrease net.pool ttl 2023-06-23 18:27:59 +02:00
Sergey Cherepanov 291b8daf5f fix blinking test 2023-06-23 17:47:29 +02:00
Sergey Cherepanov e929d5431d correct throttle counting 2023-06-23 17:39:13 +02:00
Sergey Cherepanov 49c3178f65 cleanup 2023-06-23 16:57:51 +02:00
Sergey Cherepanov 894f4db1ff peer sub connections throtling + fixes 2023-06-23 16:54:55 +02:00
mcrakhman 7577c14d5f Add state apply changes 2023-06-23 16:16:26 +02:00
mcrakhman f9bab4d51d Add content validator 2023-06-23 14:50:09 +02:00
mcrakhman 1fada6f336 Merge branch 'main' into acl-change 2023-06-23 13:44:38 +02:00
Mikhail Rakhmanov 0095a34167 Merge pull request #35 from anyproto/fix-raw-loader 2023-06-22 19:31:02 +02:00
mcrakhman 2c573138e6 Correctly removing changes which we don't need to send 2023-06-22 19:22:56 +02:00
Sergey Cherepanov 78a3bc6aeb Merge pull request #30 from anyproto/dependabot/go_modules/github.com/prometheus/client_golang-1.16.0
Bump github.com/prometheus/client_golang from 1.15.1 to 1.16.0
2023-06-22 15:30:14 +02:00
Sergey Cherepanov 89d32044e3 Merge pull request #29 from anyproto/dependabot/go_modules/golang.org/x/net-0.11.0
Bump golang.org/x/net from 0.10.0 to 0.11.0
2023-06-22 15:30:02 +02:00
Sergey Cherepanov d1be3c8a43 Merge pull request #28 from anyproto/dependabot/go_modules/github.com/libp2p/go-libp2p-0.28.0
Bump github.com/libp2p/go-libp2p from 0.27.5 to 0.28.0
2023-06-22 15:29:51 +02:00
mcrakhman 718a5b04dc Update proto 2023-06-22 13:42:38 +02:00
Sergey 9d2691ddfd Merge pull request #33 from anyproto/add-iterate-components-method
Add app.IterateComponents method
2023-06-21 12:56:56 +00:00
Sergey 4eb2245669 IterateComponents: Add test and comment 2023-06-21 17:54:38 +05:00
Sergey fa178d7c26 Add app.IterateComponents method. This method helps to create debugging HTTP handlers in Heart 2023-06-21 13:50:50 +05:00
Mikhail Rakhmanov 1b47a54f87 Merge pull request #32 from anyproto/sync-improvements 2023-06-20 13:24:47 +02:00
mcrakhman 8e7df9eae5 Sync updates 2023-06-20 12:02:05 +02:00
dependabot[bot] d3636604d7 Bump github.com/prometheus/client_golang from 1.15.1 to 1.16.0
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.15.1 to 1.16.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.15.1...v1.16.0)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-19 16:05:27 +00:00
dependabot[bot] 1d4447f126 Bump golang.org/x/net from 0.10.0 to 0.11.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.10.0 to 0.11.0.
- [Commits](https://github.com/golang/net/compare/v0.10.0...v0.11.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-19 16:05:09 +00:00
dependabot[bot] e9668c73a8 Bump github.com/libp2p/go-libp2p from 0.27.5 to 0.28.0
Bumps [github.com/libp2p/go-libp2p](https://github.com/libp2p/go-libp2p) from 0.27.5 to 0.28.0.
- [Release notes](https://github.com/libp2p/go-libp2p/releases)
- [Changelog](https://github.com/libp2p/go-libp2p/blob/master/CHANGELOG.md)
- [Commits](https://github.com/libp2p/go-libp2p/compare/v0.27.5...v0.28.0)

---
updated-dependencies:
- dependency-name: github.com/libp2p/go-libp2p
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-19 16:04:53 +00:00
Mikhail Rakhmanov fe7b97bee9 Merge pull request #27 from anyproto/change-add-to-tryadd-streampool 2023-06-19 11:14:52 +02:00
mcrakhman a9889a6245 TryAdd correctly 2023-06-19 11:10:56 +02:00
mcrakhman 16be33fc96 Change Add to TryAdd 2023-06-19 10:41:16 +02:00
Mikhail Rakhmanov 2ea446184d Merge pull request #26 from anyproto/cache-cancel-load-close
Cancel on cache close
2023-06-15 17:38:14 +02:00
mcrakhman 18716eebb4 Cancel on cache close 2023-06-15 13:08:03 +02:00
Sergey Cherepanov 53cbfca3ca Merge pull request #25 from anyproto/fix-metric-panic
validate rpc method name
2023-06-14 15:27:46 +02:00
Sergey Cherepanov 646f7fbedc remove mockgen from deps 2023-06-14 12:01:52 +02:00
Sergey Cherepanov d35ac55ee1 validate rpc method name 2023-06-14 11:55:59 +02:00
Sergey Cherepanov ff3fc68451 Merge pull request #24 from anyproto/fix-metrics
Peer addr in ctx + fix any-sync version
2023-06-13 19:09:58 +02:00
Mikhail Rakhmanov 8f984443b9 Merge pull request #23 from anyproto/no-retry-on-not-found 2023-06-13 19:09:31 +02:00
Sergey Cherepanov 31f0014783 add peer addr to cctx 2023-06-13 19:06:10 +02:00
Sergey Cherepanov 69f2cb8b1d app.VersionName fix any-sync version 2023-06-13 19:02:50 +02:00
mcrakhman cc3da7e66b Simplify tree remote getter 2023-06-13 19:00:01 +02:00
mcrakhman 060c6d1231 Retry fail on treechangeproto.ErrGetTree 2023-06-13 18:14:13 +02:00
Mikhail Rakhmanov 759c48c6b7 Merge pull request #21 from anyproto/add-treegetter-get-timeout 2023-06-13 15:51:32 +02:00
Sergey Cherepanov d551201021 Merge pull request #22 from anyproto/GO-1466-client-version
Go 1466 client version
2023-06-13 15:49:00 +02:00
Sergey Cherepanov be58956bec provide client version in secureservice 2023-06-13 15:39:22 +02:00
mcrakhman 0ca2fe5e7e Change naming 2023-06-13 15:36:23 +02:00
Sergey Cherepanov 38090ee68f provide client version to rpc log 2023-06-13 15:32:46 +02:00
Sergey Cherepanov c753da8def client version to handshake 2023-06-13 15:30:14 +02:00
mcrakhman 6eda884686 Remove goto 2023-06-13 15:26:42 +02:00
mcrakhman 5c884afcf1 Make error typed 2023-06-13 15:22:46 +02:00
mcrakhman 2aaa8f4a0c Change retry logic and add tests 2023-06-13 15:21:11 +02:00
Sergey Cherepanov ba7cffb51a Merge branch 'main' of github.com:anyproto/any-sync into GO-1466-client-version 2023-06-13 14:49:14 +02:00
Sergey Cherepanov 3cfa70c291 Merge pull request #20 from anyproto/net-fixes
Net fixes
2023-06-13 14:17:10 +02:00
Sergey Cherepanov 531f61d9d6 Merge pull request #19 from anyproto/dependabot/go_modules/github.com/multiformats/go-multihash-0.2.3
Bump github.com/multiformats/go-multihash from 0.2.2 to 0.2.3
2023-06-13 14:03:01 +02:00
dependabot[bot] c3ebc8981c Bump github.com/multiformats/go-multihash from 0.2.2 to 0.2.3
Bumps [github.com/multiformats/go-multihash](https://github.com/multiformats/go-multihash) from 0.2.2 to 0.2.3.
- [Release notes](https://github.com/multiformats/go-multihash/releases)
- [Commits](https://github.com/multiformats/go-multihash/compare/v0.2.2...v0.2.3)

---
updated-dependencies:
- dependency-name: github.com/multiformats/go-multihash
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-13 11:49:19 +00:00
Sergey Cherepanov 1bfb7ff64f Merge pull request #18 from anyproto/dependabot/go_modules/github.com/ipfs/go-merkledag-0.11.0
Bump github.com/ipfs/go-merkledag from 0.10.0 to 0.11.0
2023-06-13 13:47:43 +02:00
Sergey Cherepanov 733027d798 remove multiconn.LastUsage + fix peer gc 2023-06-13 13:40:13 +02:00
Sergey Cherepanov 05b479e5fa yamux keep-alive config + remove write deadline for sub conns 2023-06-13 13:28:19 +02:00
Sergey Cherepanov 767f868a36 remove test test 2023-06-12 18:55:40 +02:00
Sergey Cherepanov ab6ecaa462 write deadline + check conn for close 2023-06-12 18:42:30 +02:00
dependabot[bot] d6df4b7001 Bump github.com/ipfs/go-merkledag from 0.10.0 to 0.11.0
Bumps [github.com/ipfs/go-merkledag](https://github.com/ipfs/go-merkledag) from 0.10.0 to 0.11.0.
- [Release notes](https://github.com/ipfs/go-merkledag/releases)
- [Commits](https://github.com/ipfs/go-merkledag/compare/v0.10.0...v0.11.0)

---
updated-dependencies:
- dependency-name: github.com/ipfs/go-merkledag
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-12 16:05:59 +00:00
Sergey Cherepanov cb0396c40f comments 2023-06-12 16:01:17 +02:00
Sergey Cherepanov 40cd112a2a app.VersionName 2023-06-12 15:58:23 +02:00
Sergey Cherepanov fb1df54941 Merge pull request #16 from anyproto/peer-active-subconn-gc
peer: gc active sub conn
2023-06-09 19:38:36 +02:00
Sergey Cherepanov 89afc03218 fix HandshakeIncompatibleVersion test 2023-06-09 19:28:09 +02:00
Sergey Cherepanov e97e6b68c6 peer: gc active sub conn 2023-06-09 19:14:23 +02:00
mcrakhman b0c0e4b26e Fix proto version 2023-06-09 17:10:25 +02:00
Mikhail Rakhmanov 90c5ef3311 Merge pull request #15 from anyproto/new-sync-protocol 2023-06-09 11:44:35 +02:00
mcrakhman 35c29a4842 Add WithServer for TestPool 2023-06-08 21:07:03 +02:00
Sergey Cherepanov 15326da736 yamux: move listCtx to Init 2023-06-08 14:37:54 +02:00
Sergey Cherepanov 24ce490524 yamux: AddListener method 2023-06-08 14:36:14 +02:00
Sergey Cherepanov 85cf6b8332 debug server fixes 2023-06-08 13:38:00 +02:00
Sergey Cherepanov f9cb0c2dbb debug server 2023-06-08 13:27:01 +02:00
Sergey Cherepanov 33cbdd06a6 drpc conn config 2023-06-08 13:10:30 +02:00
Sergey Cherepanov c7828d0671 move server config to drpc config 2023-06-08 12:35:29 +02:00
mcrakhman dad76def2f Merge branch 'new-sync-protocol-tests' into new-sync-protocol 2023-06-07 22:05:43 +02:00
mcrakhman 318a49c526 Change objectsync injection 2023-06-07 22:05:05 +02:00
mcrakhman c8c0839a57 Add request manager tests 2023-06-07 21:48:13 +02:00
Sergey Cherepanov 5a6661eab1 cleanup net config 2023-06-07 20:52:09 +02:00
Sergey Cherepanov 065ff11983 bump proto version as 1 2023-06-07 20:50:26 +02:00
Sergey Cherepanov 6c9d1b0e84 rpctest pool 2023-06-07 20:45:32 +02:00
mcrakhman 4d1494a17a Add mocks and some requestmanager tests 2023-06-07 19:31:15 +02:00
mcrakhman 51eb5b1a42 Fix settings and deletion tests 2023-06-07 18:05:13 +02:00
Sergey Cherepanov 485c9dd768 yamux default timeouts 2023-06-07 14:49:44 +02:00
mcrakhman 564c636391 Fix diffsyncer tests 2023-06-07 14:09:29 +02:00
Sergey Cherepanov 5a8c69e557 Merge branch 'yamux' into new-sync-protocol 2023-06-07 13:34:31 +02:00
Sergey Cherepanov e7a61fb06b mod tidy 2023-06-07 13:32:14 +02:00
Sergey Cherepanov 46a3666198 Merge branch 'main' of github.com:anyproto/any-sync into yamux 2023-06-07 13:31:13 +02:00
mcrakhman 4ef617b1f2 More headsync tests 2023-06-07 13:06:37 +02:00
mcrakhman 2f5e0dd6c8 WIP headsync tests revive 2023-06-07 11:30:27 +02:00
mcrakhman 8310cb3c05 Fix sync protocol integration tests 2023-06-06 22:08:06 +02:00
mcrakhman 100e7e04c3 SyncTreeHandler tests 2023-06-06 21:49:23 +02:00
mcrakhman b18bb02176 TreeSyncProtocol tests 2023-06-06 20:50:53 +02:00
Sergey Cherepanov ddc900a93c yamux fixes 2023-06-06 20:45:18 +02:00
mcrakhman 3a2f9fe6f5 WIP synctree tests rewrite 2023-06-06 20:10:44 +02:00
Sergey Cherepanov 2ec42e5e1f yamux fix test race 2023-06-06 18:50:20 +02:00
Sergey Cherepanov 5090006e2d fix handshake race 2023-06-06 18:43:46 +02:00
Sergey Cherepanov 156f3387eb ref: use peerId instead libp2p sec connection + quic ready interfaces 2023-06-06 18:36:09 +02:00
mcrakhman 67d535362f Different fixes 2023-06-06 17:18:59 +02:00
Sergey Cherepanov 2d851d4cb9 Merge pull request #13 from anyproto/dependabot/go_modules/github.com/libp2p/go-libp2p-0.27.5
Bump github.com/libp2p/go-libp2p from 0.27.3 to 0.27.5
2023-06-06 12:21:56 +02:00
mcrakhman 4c45ad3e67 Merge remote-tracking branch 'origin/yamux' into new-sync-protocol 2023-06-06 10:43:52 +02:00
mcrakhman e96524f702 Yamux setaccepter 2023-06-06 10:43:43 +02:00
Sergey Cherepanov 8fdb98f14d Merge pull request #14 from anyproto/dependabot/go_modules/github.com/stretchr/testify-1.8.4
Bump github.com/stretchr/testify from 1.8.3 to 1.8.4
2023-06-05 21:35:40 +02:00
Sergey Cherepanov 0b4f08fbef remove stream multiqueue 2023-06-05 21:34:23 +02:00
Sergey Cherepanov af9d71d16e peer subConn gc 2023-06-05 21:23:41 +02:00
mcrakhman 66775873c7 Add syncstatusprovider 2023-06-05 20:44:27 +02:00
Sergey Cherepanov 9d4945c733 fix 2023-06-05 20:39:18 +02:00
Sergey Cherepanov 0c0a501aad peer outgoing proto handshake + test multiconn + streampool tests 2023-06-05 20:39:09 +02:00
dependabot[bot] 62daeaa5ea Bump github.com/stretchr/testify from 1.8.3 to 1.8.4
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.3 to 1.8.4.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.3...v1.8.4)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-05 16:04:08 +00:00
dependabot[bot] 6b63e9020c Bump github.com/libp2p/go-libp2p from 0.27.3 to 0.27.5
Bumps [github.com/libp2p/go-libp2p](https://github.com/libp2p/go-libp2p) from 0.27.3 to 0.27.5.
- [Release notes](https://github.com/libp2p/go-libp2p/releases)
- [Changelog](https://github.com/libp2p/go-libp2p/blob/master/CHANGELOG.md)
- [Commits](https://github.com/libp2p/go-libp2p/compare/v0.27.3...v0.27.5)

---
updated-dependencies:
- dependency-name: github.com/libp2p/go-libp2p
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-05 16:03:56 +00:00
mcrakhman 69e607eddb Expose more methods 2023-06-05 15:16:38 +02:00
mcrakhman 85a093dd4a Change space methods (handle requests) 2023-06-05 15:09:17 +02:00
mcrakhman aff2061bd1 WIP request manager 2023-06-04 19:01:33 +02:00
mcrakhman b85f545fa3 Update connections on space level 2023-06-04 11:17:56 +02:00
mcrakhman 248205cddd Merge remote-tracking branch 'origin/yamux' into new-sync-protocol 2023-06-04 10:43:11 +02:00
mcrakhman ce63951ae6 Update proto files 2023-06-03 22:48:16 +02:00
mcrakhman 990cbc58b6 Add sync requests handling 2023-06-03 22:41:03 +02:00
mcrakhman 748681d765 WIP rearrange components 2023-06-03 15:57:55 +02:00
Sergey Cherepanov 96768adaae switch coordinator to new peer api 2023-06-02 10:39:48 +02:00
Sergey Cherepanov e9f23e2dd9 peer.DoDrpc method 2023-06-02 10:39:24 +02:00
Sergey Cherepanov 1206071dd6 fix pool test 2023-06-02 10:34:49 +02:00
mcrakhman a89a325d6c Fix sync client 2023-06-02 01:08:56 +02:00
mcrakhman 815bc7927d Wire up the stuff 2023-06-02 00:59:33 +02:00
mcrakhman 796b66478b Further components rearrange 2023-06-01 22:55:37 +02:00
mcrakhman eeb87dd144 WIP further space refactoring 2023-06-01 14:24:58 +02:00
mcrakhman b0fa43fb14 WIP work on components 2023-06-01 10:28:32 +02:00
Sergey Cherepanov 41a85f7987 merge 2023-05-31 20:27:38 +02:00
Sergey Cherepanov fb007211f0 simplify drpc server + peer accept loop 2023-05-31 20:24:07 +02:00
Sergey Cherepanov 00c582e157 use net.Conn for a proto handshake 2023-05-31 19:24:23 +02:00
Sergey Cherepanov c43ac9eb84 handshake proto + common handshake fixes 2023-05-31 16:22:49 +02:00
Sergey Cherepanov 467536ac97 Merge pull request #11 from anyproto/dependabot/go_modules/github.com/ipfs/go-ipfs-chunker-0.0.6
Bump github.com/ipfs/go-ipfs-chunker from 0.0.5 to 0.0.6
2023-05-30 13:59:26 +02:00
dependabot[bot] 9175fc9827 Bump github.com/ipfs/go-ipfs-chunker from 0.0.5 to 0.0.6
Bumps [github.com/ipfs/go-ipfs-chunker](https://github.com/ipfs/go-ipfs-chunker) from 0.0.5 to 0.0.6.
- [Release notes](https://github.com/ipfs/go-ipfs-chunker/releases)
- [Commits](https://github.com/ipfs/go-ipfs-chunker/compare/v0.0.5...v0.0.6)

---
updated-dependencies:
- dependency-name: github.com/ipfs/go-ipfs-chunker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-30 11:59:19 +00:00
Sergey Cherepanov 92f6a07d85 Merge pull request #12 from anyproto/dependabot/go_modules/github.com/ipfs/go-unixfs-0.4.6
Bump github.com/ipfs/go-unixfs from 0.4.5 to 0.4.6
2023-05-30 13:57:27 +02:00
Sergey Cherepanov fc53eaf9e5 Merge pull request #10 from anyproto/dependabot/go_modules/github.com/ipfs/go-ipfs-blockstore-1.3.1
Bump github.com/ipfs/go-ipfs-blockstore from 1.3.0 to 1.3.1
2023-05-30 13:57:08 +02:00
Sergey Cherepanov 1109ce8da1 Merge pull request #9 from anyproto/dependabot/go_modules/github.com/ipfs/go-blockservice-0.5.2
Bump github.com/ipfs/go-blockservice from 0.5.1 to 0.5.2
2023-05-30 13:56:58 +02:00
Sergey Cherepanov 24b0cafb7f Merge pull request #8 from anyproto/dependabot/go_modules/github.com/multiformats/go-multihash-0.2.2
Bump github.com/multiformats/go-multihash from 0.2.1 to 0.2.2
2023-05-30 13:56:48 +02:00
dependabot[bot] 1c0d7166c9 Bump github.com/ipfs/go-unixfs from 0.4.5 to 0.4.6
Bumps [github.com/ipfs/go-unixfs](https://github.com/ipfs/go-unixfs) from 0.4.5 to 0.4.6.
- [Release notes](https://github.com/ipfs/go-unixfs/releases)
- [Commits](https://github.com/ipfs/go-unixfs/compare/v0.4.5...v0.4.6)

---
updated-dependencies:
- dependency-name: github.com/ipfs/go-unixfs
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-29 16:07:32 +00:00
dependabot[bot] b1129e898e Bump github.com/ipfs/go-ipfs-blockstore from 1.3.0 to 1.3.1
Bumps [github.com/ipfs/go-ipfs-blockstore](https://github.com/ipfs/go-ipfs-blockstore) from 1.3.0 to 1.3.1.
- [Release notes](https://github.com/ipfs/go-ipfs-blockstore/releases)
- [Commits](https://github.com/ipfs/go-ipfs-blockstore/compare/v1.3.0...v1.3.1)

---
updated-dependencies:
- dependency-name: github.com/ipfs/go-ipfs-blockstore
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-29 16:06:57 +00:00
dependabot[bot] 2b5159e86a Bump github.com/ipfs/go-blockservice from 0.5.1 to 0.5.2
Bumps [github.com/ipfs/go-blockservice](https://github.com/ipfs/go-blockservice) from 0.5.1 to 0.5.2.
- [Release notes](https://github.com/ipfs/go-blockservice/releases)
- [Commits](https://github.com/ipfs/go-blockservice/compare/v0.5.1...v0.5.2)

---
updated-dependencies:
- dependency-name: github.com/ipfs/go-blockservice
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-29 16:06:38 +00:00
dependabot[bot] 616159b144 Bump github.com/multiformats/go-multihash from 0.2.1 to 0.2.2
Bumps [github.com/multiformats/go-multihash](https://github.com/multiformats/go-multihash) from 0.2.1 to 0.2.2.
- [Release notes](https://github.com/multiformats/go-multihash/releases)
- [Commits](https://github.com/multiformats/go-multihash/compare/v0.2.1...v0.2.2)

---
updated-dependencies:
- dependency-name: github.com/multiformats/go-multihash
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-29 16:06:21 +00:00
Sergey Cherepanov 553ed3a64b peer drpc conn pool 2023-05-29 17:56:44 +02:00
Mikhail Rakhmanov 85ec3e2313 Merge pull request #7 from anyproto/update-dial-timeout
Update dial timeout
2023-05-29 16:34:36 +02:00
Sergey Cherepanov a898c6fc9c pool.AddPeer close previous peer 2023-05-29 16:33:33 +02:00
mcrakhman 973d55c12b Update dial timeout 2023-05-29 16:28:28 +02:00
Mikhail Rakhmanov e148577aed Merge pull request #6 from anyproto/space-lifetime-load 2023-05-29 16:11:54 +02:00
mcrakhman 1e1f71d700 Don't close when tree is loading, fix peer conditions 2023-05-29 16:02:59 +02:00
Sergey Cherepanov 12a7dfe05a Merge branch 'main' of github.com:anyproto/any-sync into yamux 2023-05-26 20:42:06 +02:00
Sergey Cherepanov fa02cfd830 mod tidy 2023-05-26 20:40:50 +02:00
Mikhail Rakhmanov f442b21969 Merge pull request #4 from anyproto/update-tree-syncer 2023-05-26 20:35:05 +02:00
mcrakhman e9ea4e1d3e Provide treemanager from outside for settings id 2023-05-26 20:21:07 +02:00
Sergey Cherepanov 7958b43da6 peer service 2023-05-26 20:06:22 +02:00
Sergey Cherepanov bc2049ccbc wip: yamux transport + pool rewrite 2023-05-26 19:18:51 +02:00
Mikhail Rakhmanov 7a45acb497 Merge pull request #3 from anyproto/start-headsync 2023-05-26 19:05:21 +02:00
mcrakhman 556f03ed7f Expose more methods in exec pool 2023-05-26 11:29:21 +02:00
mcrakhman f0a3edd798 Add tree syncer 2023-05-26 09:49:09 +02:00
mcrakhman 5b553f1a8d Change headsync logic 2023-05-25 20:23:19 +02:00
mcrakhman a5e2bea04c Update message pool to include optional context deadline 2023-05-24 21:47:59 +02:00
mcrakhman c485baaadf Simplify interface 2023-05-24 15:05:21 +02:00
mcrakhman 2ab43e2b69 Space headsync delayed start 2023-05-24 14:54:10 +02:00
Sergey Cherepanov f8c79c33bc update go.mod 2023-05-23 18:00:04 +02:00
Sergey Cherepanov 453aa7b769 update go mod 2023-05-23 15:43:33 +02:00
Mikhail Iudin dbae377351 fix imports 2023-05-23 14:47:24 +02:00
Mikhail Iudin da6c99c949 fix rebase changes 2023-05-23 14:38:34 +02:00
dependabot[bot] 71c0580bae Bump github.com/stretchr/testify from 1.8.2 to 1.8.3
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.2 to 1.8.3.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.2...v1.8.3)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-22 18:03:40 +00:00
mcrakhman f005c82bf5 Fix mock 2023-05-23 12:15:54 +02:00
Sergey Cherepanov 24395ec7bf refactor handshake err 2023-05-22 19:17:20 +02:00
Sergey Cherepanov d8ed9f9307 fix HandshakeError 2023-05-22 18:00:30 +02:00
Sergey Cherepanov d04e55bc9c propagate handshake error + NetworkCompatibilityStatus method 2023-05-22 17:57:00 +02:00
Sergey Cherepanov 698507d363 make credentials with version, fix test 2023-05-22 15:31:55 +02:00
Sergey Cherepanov f5bb73a3e7 handshake proto version 2023-05-22 13:51:13 +02:00
Sergey Cherepanov a0633c39ec fix test 2023-05-19 22:21:12 +02:00
Sergey Cherepanov 3d2005833b remove debug 2023-05-19 22:18:39 +02:00
Sergey Cherepanov 8c84ab9b31 write app version to rpc log 2023-05-18 13:35:49 +02:00
Sergey Cherepanov b1ad0be3b4 app.AnySyncVersion 2023-05-18 13:31:53 +02:00
Sergey Cherepanov 0d72b3b671 app.AppName + prometheus versions metric 2023-05-18 13:21:01 +02:00
mcrakhman b141dcdb47 Remove extra comment 2023-05-23 11:51:42 +02:00
mcrakhman 1343dad443 Improve tests to include check for new snapshot 2023-05-23 11:50:15 +02:00
mcrakhman db7a95514d Add mark deleted logic 2023-05-23 11:34:24 +02:00
mcrakhman da9bbba79b Update state builder to use map and fix tests 2023-05-23 10:49:28 +02:00
mcrakhman c348ee2a49 Add tests 2023-05-23 08:48:15 +02:00
mcrakhman 9cd73af53c Implement test utils for space 2023-05-22 15:37:48 +02:00
mcrakhman 5532c2c482 Fix settings state rebuild 2023-05-21 23:50:14 +02:00
mcrakhman 6e48b2dcff Fix send error problem 2023-05-17 13:13:23 +02:00
dependabot[bot] 743a7b8d2f Bump github.com/libp2p/go-libp2p from 0.27.2 to 0.27.3
Bumps [github.com/libp2p/go-libp2p](https://github.com/libp2p/go-libp2p) from 0.27.2 to 0.27.3.
- [Release notes](https://github.com/libp2p/go-libp2p/releases)
- [Changelog](https://github.com/libp2p/go-libp2p/blob/master/CHANGELOG.md)
- [Commits](https://github.com/libp2p/go-libp2p/compare/v0.27.2...v0.27.3)

---
updated-dependencies:
- dependency-name: github.com/libp2p/go-libp2p
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-15 18:02:23 +00:00
dependabot[bot] ca82644ece Bump golang.org/x/crypto from 0.8.0 to 0.9.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.8.0 to 0.9.0.
- [Commits](https://github.com/golang/crypto/compare/v0.8.0...v0.9.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-15 18:02:39 +00:00
Sergey Cherepanov 5e1ac84d37 increase sendSync timeout 2023-05-15 20:30:32 +02:00
Sergey Cherepanov b8cb74d2ff QueueId wrapper + test 2023-05-15 20:01:52 +02:00
Sergey Cherepanov 3d4ac4d130 use multiqueue for stream + MessageQueueId interface 2023-05-15 19:52:19 +02:00
Sergey Cherepanov 34c6ce261e streampool: send queue per peer + metrics 2023-05-15 15:50:27 +02:00
mcrakhman 7ed44c27b9 Fix logs in diffsyncer (thanks Roma) 2023-05-11 18:48:38 +02:00
mcrakhman 86ac7223db Fix ctx and change add method name in storage 2023-05-11 18:31:59 +02:00
mcrakhman 7feb6b0d2c Send update only on remote tree or put tree when building 2023-05-11 13:40:24 +02:00
mcrakhman a232ed76ce Update syncprotocol test 2023-05-11 09:51:24 +02:00
mcrakhman 2c832d2518 Update sync protocol to remove transaction add 2023-05-10 23:51:03 +02:00
mcrakhman a6d66c15a0 Add build full tree for history tree 2023-05-07 20:27:46 +02:00
mcrakhman 527236b17b Fix unmarshallSendError bug 2023-05-07 20:27:30 +02:00
dependabot[bot] a1a08ae73b Bump golang.org/x/net from 0.9.0 to 0.10.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.9.0 to 0.10.0.
- [Commits](https://github.com/golang/net/compare/v0.9.0...v0.10.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-08 18:05:01 +00:00
dependabot[bot] 298f3e0ac2 Bump storj.io/drpc from 0.0.32 to 0.0.33
Bumps [storj.io/drpc](https://github.com/storj/drpc) from 0.0.32 to 0.0.33.
- [Release notes](https://github.com/storj/drpc/releases)
- [Commits](https://github.com/storj/drpc/compare/v0.0.32...v0.0.33)

---
updated-dependencies:
- dependency-name: storj.io/drpc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-08 18:04:52 +00:00
dependabot[bot] 9612635602 Bump github.com/libp2p/go-libp2p from 0.27.1 to 0.27.2
Bumps [github.com/libp2p/go-libp2p](https://github.com/libp2p/go-libp2p) from 0.27.1 to 0.27.2.
- [Release notes](https://github.com/libp2p/go-libp2p/releases)
- [Changelog](https://github.com/libp2p/go-libp2p/blob/master/CHANGELOG.md)
- [Commits](https://github.com/libp2p/go-libp2p/compare/v0.27.1...v0.27.2)

---
updated-dependencies:
- dependency-name: github.com/libp2p/go-libp2p
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-08 18:04:21 +00:00
dependabot[bot] 58968f1e29 Bump github.com/prometheus/client_golang from 1.15.0 to 1.15.1
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.15.0 to 1.15.1.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.15.0...v1.15.1)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-08 18:03:33 +00:00
Sergey Cherepanov b54e141a13 remove peer addr from log 2023-05-05 16:30:22 +02:00
Sergey Cherepanov 41709e2ee5 remove peer addr from log 2023-05-05 16:28:31 +02:00
Sergey Cherepanov 81f2b5fd15 check that metric is using 2023-05-04 19:35:07 +02:00
Sergey Cherepanov 1e53e7f33b allow nil metric 2023-05-04 19:30:46 +02:00
Sergey Cherepanov 183edb517c fix import 2023-05-04 19:04:41 +02:00
Sergey Cherepanov 9e074d7a5a file fields 2023-05-04 17:49:24 +02:00
Sergey Cherepanov 0664c310fa request log 2023-05-04 17:39:23 +02:00
Sergey Cherepanov 39a8c59f83 stream ctx + peer addr 2023-05-04 17:38:34 +02:00
Sergey Cherepanov ed0a751d15 wip: drpc access log 2023-04-24 14:59:15 +02:00
Sergey Cherepanov 6ed21a94d3 move rpc prometheus config to metric service 2023-04-21 17:00:35 +02:00
mcrakhman 01abaf6f81 Add comment 2023-05-05 18:51:30 +02:00
mcrakhman 6aefb07782 Fix space settings 2023-05-05 18:49:51 +02:00
mcrakhman 4ed3ddeac1 Update dialer to have peer addrs map 2023-05-05 16:18:46 +02:00
mcrakhman f1e8f5d193 Improve timestamp test 2023-05-04 16:25:41 +02:00
mcrakhman 2489e19bab Change timestamp condition 2023-05-04 14:58:17 +02:00
mcrakhman 2958e32636 Send timestamp to addcontent 2023-05-04 14:12:29 +02:00
mcrakhman 21bde9743d Add timestamp externally in tree 2023-05-04 11:10:08 +02:00
mcrakhman 8f144f20a6 Keep inmemory data on load 2023-04-27 19:11:05 +02:00
mcrakhman 61454b7405 Add sync protocol tests for empty data tree 2023-04-27 18:43:04 +02:00
mcrakhman 95cb364316 Fix empty data tree 2023-04-27 18:24:45 +02:00
mcrakhman a6524c1ee7 Remove error in broadcast 2023-04-26 18:12:03 +02:00
mcrakhman ebf984a595 Add reference to linear task in periodicsync todo 2023-04-25 21:41:18 +02:00
mcrakhman 4e7b96b942 Check peerId when dialling 2023-04-25 20:55:00 +02:00
mcrakhman 55a885f326 Update space receipt to include network id 2023-04-25 20:49:31 +02:00
mcrakhman ac79c0c15c More info in receipt errors and change tests 2023-04-25 14:25:08 +02:00
mcrakhman ab74809973 Nodeconf error and periodicsync partial rename 2023-04-25 14:00:38 +02:00
Sergey Cherepanov fabaf676b8 nodeconf update interval config 2023-04-24 15:04:11 +02:00
mcrakhman 2ec5960b2b Remove commonspace checks 2023-04-25 10:38:22 +02:00
mcrakhman 92ed77ab5a Use same format for identity 2023-04-25 10:04:42 +02:00
mcrakhman 447272a1f1 Move receipt logic to any-sync, write tests 2023-04-25 09:54:12 +02:00
mcrakhman 0e00cf4ebb Add receipt check 2023-04-24 20:33:35 +02:00
mcrakhman 8a414929b0 Add errors to proto 2023-04-24 14:12:24 +02:00
mcrakhman ba39057a23 Change identity error 2023-04-21 23:29:29 +02:00
mcrakhman e34d298b8e Fix test 2023-04-21 23:25:54 +02:00
mcrakhman 5709bc99e3 Add todos for test helpers 2023-04-21 23:23:07 +02:00
mcrakhman 5da565f68c Improve payload validation 2023-04-21 23:21:49 +02:00
mcrakhman 0281f006e6 Move validation to any-sync 2023-04-21 20:12:47 +02:00
Sergey Cherepanov 7f9afe9ad3 update libp2p 2023-04-21 17:02:51 +02:00
Sergey Cherepanov 0d0381cc42 fix panic for empty node addresses 2023-04-21 16:04:30 +02:00
mcrakhman de0cb12354 Change inmemory parameter naming 2023-04-20 17:12:39 +02:00
mcrakhman d60a07f58c Change config parameter name 2023-04-20 12:52:25 +02:00
mcrakhman 321f843e8e Change test name for fuzzy merge 2023-04-20 12:48:25 +02:00
mcrakhman 245188760a Change error to typed in treeremotegetter 2023-04-20 12:46:36 +02:00
mcrakhman 00902c5740 Fix full sync request condition 2023-04-20 11:46:19 +02:00
mcrakhman 077cdd6278 Refactor genchanges 2023-04-20 11:26:53 +02:00
mcrakhman 4a22eabebd Fix proto 2023-04-20 00:04:16 +02:00
mcrakhman 0d514560a4 Simplify request factory 2023-04-19 22:52:06 +02:00
mcrakhman fed0eee243 Change sync client message pool interactions and request-reply 2023-04-19 22:43:46 +02:00
mcrakhman 52816f0f44 Fix sending reply with empty rootchange 2023-04-19 21:16:32 +02:00
mcrakhman 3b3a0199bd Update object sync error handling logic 2023-04-19 20:42:47 +02:00
mcrakhman 533806893f Add logs if failed to get object 2023-04-19 19:34:31 +02:00
mcrakhman bfb9ed1d83 Further test comments and changes 2023-04-19 11:52:11 +02:00
mcrakhman bb47f0aa21 Add comments to test 2023-04-19 11:49:04 +02:00
Dmitry Bilienko a2e6fccac6 ACL Payload validation fix 2023-04-18 11:37:46 +05:00
Dmitry Bilienko 4773eb1d06 Validation issues fix 2023-04-17 17:15:48 +05:00
Dmitry Bilienko d144336aa2 Additional validation of identifiers and new unit tests. 2023-04-13 15:19:58 +05:00
Dmitry Bilienko 4a0f5cd9a6 Check all payload validations on nodes 2023-04-12 13:23:33 +05:00
Roman Khafizianov 23d0cae7b1 add test 2023-04-07 16:47:39 +02:00
Roman Khafizianov 3a4ab634cf logger: make the log named logger list order determined 2023-04-07 16:29:35 +02:00
dependabot[bot] 6572c99042 Bump github.com/prometheus/client_golang from 1.14.0 to 1.15.0
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.14.0 to 1.15.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.14.0...v1.15.0)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-17 18:04:58 +00:00
Sergey Cherepanov 133d6db83c use periodicSync 2023-04-17 20:08:07 +02:00
Sergey Cherepanov db47f14923 fix decode node types 2023-04-17 17:16:26 +02:00
Sergey Cherepanov 6f5b0b04c2 nodeConfStore: create dir on start 2023-04-17 17:10:15 +02:00
Sergey Cherepanov 1616766b4b update conf on start 2023-04-17 17:00:35 +02:00
Sergey Cherepanov 122df895d1 log dial error 2023-04-17 16:08:19 +02:00
Sergey Cherepanov d819f7773e nodeconf: stop update loop on close 2023-04-17 15:59:43 +02:00
Sergey Cherepanov b7eb185463 update coordinatorclient mock 2023-04-17 14:33:59 +02:00
Sergey Cherepanov a790c7f5f8 nodeconf source constructor 2023-04-17 13:47:04 +02:00
Sergey Cherepanov 03e400a4b2 fix 2023-04-13 20:47:53 +02:00
Sergey Cherepanov 81cf289a85 fix 2023-04-13 20:46:56 +02:00
Sergey Cherepanov 4bff7f503d fix 2023-04-13 20:46:01 +02:00
Sergey Cherepanov 2dad7f5c62 nodeconf logs 2023-04-13 20:44:50 +02:00
Sergey Cherepanov 3fcbeb2020 fix 2023-04-13 19:06:16 +02:00
Sergey Cherepanov 2a40bcce85 fix test 2023-04-12 15:09:04 +02:00
Sergey Cherepanov 745f3d56fb nodeconf store 2023-04-12 14:50:35 +02:00
Sergey Cherepanov ed004a2ae0 fix handshake test 2023-04-12 14:22:33 +02:00
Sergey Cherepanov b722e7301d fix config 2023-04-12 14:22:20 +02:00
Sergey Cherepanov 93cd6897d3 coord nodeconf source, nodeconf service updates 2023-04-08 17:01:40 +02:00
Sergey Cherepanov 18db36a70a nodeconf interfaces 2023-04-06 19:02:18 +02:00
Sergey Cherepanov 4ea2953045 remove unused 2023-04-02 11:58:42 +02:00
Sergey Cherepanov 0ebf93e383 strkey: newtwork id encoding 2023-04-02 14:34:03 +02:00
Sergey Cherepanov f4f01b23f9 mod tidy 2023-04-02 14:33:33 +02:00
Sergey Cherepanov d7aaa95e57 fix test 2023-04-02 14:33:07 +02:00
Sergey Cherepanov f651813f0b update nodeconf service 2023-03-27 17:55:27 +02:00
Sergey Cherepanov 16daf32edb expose nodeconf config getter 2023-03-26 16:22:18 +02:00
Sergey Cherepanov f9b306abc7 node configuration command proto 2023-03-26 14:54:27 +02:00
mcrakhman dc910d51c5 Fix objectsync etc 2023-04-18 22:51:12 +02:00
mcrakhman c64921a249 Send error response on sync (wip) 2023-04-18 12:09:25 +02:00
mcrakhman 07b16554cd Change treegetter to treemanager and return BuildObjectTreeFunc 2023-04-18 00:34:14 +02:00
mcrakhman 520c9717ca Add tests and don't load data into changes 2023-04-17 23:51:17 +02:00
mcrakhman 767f3aac96 Add test with random trees 2023-04-17 21:49:51 +02:00
mcrakhman 977308edcc Improve validator logic and add tests 2023-04-13 23:55:28 +02:00
mcrakhman 4a1a95a7b9 Add empty client logic 2023-04-13 21:32:39 +02:00
mcrakhman b82c7da159 Change add to tryadd in dial 2023-04-12 19:16:48 +02:00
dependabot[bot] 2ee3e26dd6 Bump github.com/ipfs/go-blockservice from 0.5.0 to 0.5.1
Bumps [github.com/ipfs/go-blockservice](https://github.com/ipfs/go-blockservice) from 0.5.0 to 0.5.1.
- [Release notes](https://github.com/ipfs/go-blockservice/releases)
- [Commits](https://github.com/ipfs/go-blockservice/compare/v0.5.0...v0.5.1)

---
updated-dependencies:
- dependency-name: github.com/ipfs/go-blockservice
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-11 09:07:04 +00:00
mcrakhman aea13e5c14 Update chash 2023-04-11 18:43:20 +02:00
dependabot[bot] 5c6b920fa5 Bump github.com/ipfs/go-cid from 0.4.0 to 0.4.1
Bumps [github.com/ipfs/go-cid](https://github.com/ipfs/go-cid) from 0.4.0 to 0.4.1.
- [Release notes](https://github.com/ipfs/go-cid/releases)
- [Commits](https://github.com/ipfs/go-cid/compare/v0.4.0...v0.4.1)

---
updated-dependencies:
- dependency-name: github.com/ipfs/go-cid
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-10 18:03:50 +00:00
dependabot[bot] 244bde3017 Bump golang.org/x/crypto from 0.7.0 to 0.8.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.7.0 to 0.8.0.
- [Release notes](https://github.com/golang/crypto/releases)
- [Commits](https://github.com/golang/crypto/compare/v0.7.0...v0.8.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-10 18:03:19 +00:00
Dmitry Bilienko 3563206ef1 Cache | Reduce RAM 2023-03-28 16:25:36 +08:00
mcrakhman 896559df39 Fix mocks 2023-04-04 11:22:48 +02:00
mcrakhman 9c7041dcc1 Remove master key from account 2023-04-03 13:19:12 +02:00
mcrakhman b2e0b86584 Update derivation path comment 2023-04-03 13:01:58 +02:00
mcrakhman 299362793c Update key path 2023-04-03 10:21:28 +02:00
mcrakhman 7013c79d09 Add old account to coordinator proto 2023-04-02 18:53:02 +02:00
mcrakhman f21a259ddc Update cryptography and add master key 2023-04-02 18:13:10 +02:00
mcrakhman 73845d8842 Change doSnapshot method call 2023-03-29 23:43:04 +02:00
mcrakhman 4da3fcbd4a Add settings changefactory tests 2023-03-29 23:40:49 +02:00
mcrakhman 8729d069dc Add settings snapshot logic 2023-03-29 23:06:51 +02:00
dependabot[bot] 16a5c2efe9 Bump github.com/goccy/go-graphviz from 0.1.0 to 0.1.1
Bumps [github.com/goccy/go-graphviz](https://github.com/goccy/go-graphviz) from 0.1.0 to 0.1.1.
- [Release notes](https://github.com/goccy/go-graphviz/releases)
- [Commits](https://github.com/goccy/go-graphviz/compare/v0.1.0...v0.1.1)

---
updated-dependencies:
- dependency-name: github.com/goccy/go-graphviz
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-29 15:57:04 +00:00
dependabot[bot] 15616b0297 Bump github.com/ipfs/go-unixfs from 0.4.4 to 0.4.5
Bumps [github.com/ipfs/go-unixfs](https://github.com/ipfs/go-unixfs) from 0.4.4 to 0.4.5.
- [Release notes](https://github.com/ipfs/go-unixfs/releases)
- [Commits](https://github.com/ipfs/go-unixfs/compare/v0.4.4...v0.4.5)

---
updated-dependencies:
- dependency-name: github.com/ipfs/go-unixfs
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-29 15:56:32 +00:00
mcrakhman 120306a1df Add some tests 2023-03-28 21:29:12 +02:00
mcrakhman 7246126f84 Update space header validation 2023-03-28 00:11:09 +02:00
mcrakhman 61819f04a2 Change sym encryption logic in objecttree 2023-03-27 23:03:02 +02:00
mcrakhman 8a4ed528b8 Fix private key marshal 2023-03-27 22:28:49 +02:00
mcrakhman f6c4d0a0fd Fix mnemonic 2023-03-27 22:08:07 +02:00
mcrakhman 456fbe1f24 Move peer util package into crypto 2023-03-27 21:22:32 +02:00
mcrakhman 06222e33ff Add peer id string for keys 2023-03-27 17:04:59 +02:00
mcrakhman c3e6af0032 Add accountdata constructor 2023-03-27 16:32:56 +02:00
mcrakhman 79419b3f71 Add marshalling of private key 2023-03-27 16:03:58 +02:00
mcrakhman 07f658a96f Add individual key derivation for trees 2023-03-27 13:01:35 +02:00
mcrakhman 72a64afbec Remove pointer methods 2023-03-27 01:57:49 +02:00
mcrakhman b3a4272188 Add decoders 2023-03-27 01:55:33 +02:00
mcrakhman 407c9d316c Add mnemonic 2023-03-27 01:34:51 +02:00
mcrakhman 7e51eca1a6 Fix tests 2023-03-26 18:47:35 +02:00
mcrakhman 21a3ae0768 Fix objecttree tests 2023-03-26 18:36:07 +02:00
mcrakhman dca0103899 Remove old acl methods and tests, acl builder 2023-03-26 17:53:11 +02:00
mcrakhman 4efe189eec Fix handshake tests 2023-03-26 13:45:24 +02:00
mcrakhman 52f462ff2c Add KeyStorage 2023-03-25 14:06:20 +01:00
mcrakhman 3778465e3b More key changes 2023-03-25 13:15:19 +01:00
mcrakhman c00ea331dc ACLState refactoring 2023-03-24 22:43:25 +01:00
mcrakhman 9f0828a56a Update aclrecordproto 2023-03-22 18:51:01 +01:00
Sergey Cherepanov 374bb40894 make chunk size public 2023-03-21 18:28:45 +01:00
Sergey Cherepanov f225c395ad increase ipfs chunk size 2023-03-21 18:24:29 +01:00
Roman Khafizianov 0c6aff5af7 app long start/stop logger 2023-03-21 15:27:15 +01:00
Sergey Cherepanov 53ed41bb55 update fileproto 2023-03-21 11:23:52 +01:00
Sergey Cherepanov 178d4e166c update fileproto 2023-03-21 11:20:16 +01:00
Sergey Cherepanov 481ec10ecd update fileproto 2023-03-21 11:10:50 +01:00
dependabot[bot] f8d342c535 Bump github.com/multiformats/go-multibase from 0.1.1 to 0.2.0
Bumps [github.com/multiformats/go-multibase](https://github.com/multiformats/go-multibase) from 0.1.1 to 0.2.0.
- [Release notes](https://github.com/multiformats/go-multibase/releases)
- [Commits](https://github.com/multiformats/go-multibase/compare/v0.1.1...v0.2.0)

---
updated-dependencies:
- dependency-name: github.com/multiformats/go-multibase
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-20 18:03:47 +00:00
dependabot[bot] d275333bc7 Bump github.com/ipfs/go-cid from 0.3.2 to 0.4.0
Bumps [github.com/ipfs/go-cid](https://github.com/ipfs/go-cid) from 0.3.2 to 0.4.0.
- [Release notes](https://github.com/ipfs/go-cid/releases)
- [Commits](https://github.com/ipfs/go-cid/compare/v0.3.2...v0.4.0)

---
updated-dependencies:
- dependency-name: github.com/ipfs/go-cid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-20 18:04:01 +00:00
dependabot[bot] 62c7033588 Bump github.com/ipfs/go-ipfs-blockstore from 1.2.0 to 1.3.0
Bumps [github.com/ipfs/go-ipfs-blockstore](https://github.com/ipfs/go-ipfs-blockstore) from 1.2.0 to 1.3.0.
- [Release notes](https://github.com/ipfs/go-ipfs-blockstore/releases)
- [Commits](https://github.com/ipfs/go-ipfs-blockstore/compare/v1.2.0...v1.3.0)

---
updated-dependencies:
- dependency-name: github.com/ipfs/go-ipfs-blockstore
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-13 18:08:51 +00:00
mcrakhman 3bdf79c133 Update derive id method name 2023-03-16 19:01:05 +01:00
mcrakhman aa4817e1e6 Add error wrapping in app 2023-03-16 18:46:48 +01:00
mcrakhman 6d6fd1e849 Update rpc err in pull space 2023-03-16 18:27:19 +01:00
mcrakhman efc65a7843 Derive space id as a separate method 2023-03-16 17:57:10 +01:00
Sergey Cherepanov 6ea6d20a60 fileproto forbidden error 2023-03-19 18:40:28 +03:00
Sergey Cherepanov 89dd5dada2 fileId ctx funcs 2023-03-06 12:22:29 +03:00
Sergey Cherepanov 3dc92b9a67 fileproto: FileInfo 2023-03-03 13:22:37 +03:00
Sergey Cherepanov 415b537c36 fix fileproto 2023-03-03 13:09:55 +03:00
Sergey Cherepanov a03150703d fix fileproto 2023-03-03 13:08:38 +03:00
Sergey Cherepanov 1f3c9326fe fileproto: SpaceInfo method 2023-03-02 19:11:55 +03:00
Sergey Cherepanov 0fc2aadb7b update fileproto 2023-03-01 13:44:15 +03:00
Sergey Cherepanov 41e827a2ee fix fileproto 2023-02-28 12:26:19 +03:00
Sergey Cherepanov 625bbf5c95 extending file protocol 2023-02-28 12:04:05 +03:00
mcrakhman 52df5d8ae1 Update derivation 2023-03-24 14:56:39 +01:00
mcrakhman 7c4abdbb8c Add sym key 2023-03-24 13:01:53 +01:00
mcrakhman 3a07939b01 WIP refactor crypto proto 2023-03-24 11:45:39 +01:00
mcrakhman 2072bbb998 Add second test and description 2023-03-20 16:24:07 +01:00
mcrakhman 4d8d1f1457 Add simple test 2023-03-20 16:00:44 +01:00
mcrakhman b27ca28bb3 Create fixture draft 2023-03-14 11:41:29 +01:00
mcrakhman 24e400ad70 Add more components for async communication 2023-03-14 10:33:59 +01:00
mcrakhman ab3a8e2bcd Remove old connection from pool on close (GetOneOf) 2023-03-13 17:18:21 +01:00
mcrakhman df867395ae Add test component structure 2023-03-13 22:03:38 +01:00
mcrakhman b333d16b6f Start sync tests 2023-03-13 21:47:07 +01:00
Roman Khafizianov 7cd737a08a change logger name 2023-03-13 14:14:55 +01:00
Roman Khafizianov 96c4ea3a45 cleanup logger names 2023-03-10 19:32:20 +01:00
Roman Khafizianov 92f08eaa79 fix replacing existing logger 2023-03-10 19:18:15 +01:00
mcrakhman 7137732a2a Add counter for put sync tree 2023-03-13 13:46:36 +01:00
mcrakhman 1bf928daa5 Update counters 2023-03-13 13:04:23 +01:00
mcrakhman 0537dec817 Add increment/decrement logs 2023-03-13 12:58:37 +01:00
mcrakhman 65e1577bfd Ocache remove with ctx 2023-03-11 13:41:07 +01:00
mcrakhman 96b43de5f0 Change timeestamps 2023-03-11 12:52:42 +01:00
mcrakhman d4fdc63a40 Fix test 2023-03-10 18:22:08 +01:00
mcrakhman 307b741dc7 Update tryclose with ttl 2023-03-10 10:17:57 +01:00
mcrakhman 82828cba6f Add TryClose to space interface 2023-03-09 23:11:20 +01:00
mcrakhman 520f2cf470 Update objecttree with tryclose 2023-03-09 23:09:26 +01:00
mcrakhman afd134c7a1 Remove called records in fuzzy test 2023-03-09 22:58:36 +01:00
mcrakhman 1f1295e92b Lower timeouts 2023-03-09 22:56:59 +01:00
mcrakhman e1c4c472dd Add tryclose everywhere 2023-03-09 22:52:31 +01:00
mcrakhman 866ea0977f Add some fuzzy tests 2023-03-09 22:03:45 +01:00
mcrakhman 7feadbfb80 Update cache logic to remove from entries on close 2023-03-09 20:25:01 +01:00
mcrakhman 53b3e6e706 Readability improvements 2023-03-09 09:38:45 +01:00
mcrakhman 86b66121f8 More tests and split entry 2023-03-09 09:06:02 +01:00
mcrakhman ec7c33d820 Test returning to cache after tryclose 2023-03-08 22:52:43 +01:00
mcrakhman 8b5a7de4ef Add ocache tryClose 2023-03-08 22:07:44 +01:00
mcrakhman 1ffb1b60df Add newline to treechangeproto 2023-03-10 16:52:30 +01:00
mcrakhman 831ac5150a Update state builder to fix root change 2023-03-10 16:33:13 +01:00
mcrakhman 3f17e594ec Update space reserved constant 2023-03-10 15:07:45 +01:00
mcrakhman 6a3606f92c Add change type and payload 2023-03-10 14:37:03 +01:00
Roman Khafizianov a79b578e5f fix default logger encoding config 2023-03-07 19:37:21 +01:00
Roman Khafizianov 766fbd2fb6 logger: add Format in config
support json, color, plaintext
2023-03-07 18:52:28 +01:00
Roman Khafizianov da73ebf9ab rename some loggers to the same convention 2023-03-07 10:42:36 +01:00
Roman Khafizianov 4d6e15e03b rename logger common.commonspace.settings -> commonspace.settings 2023-03-06 22:26:32 +01:00
Roman Khafizianov 77d8c739f2 add an ability to set levels separately for named loggers
with glob support
2023-03-06 22:25:43 +01:00
dependabot[bot] 5e6199d106 Bump github.com/ipfs/go-unixfs from 0.4.3 to 0.4.4
Bumps [github.com/ipfs/go-unixfs](https://github.com/ipfs/go-unixfs) from 0.4.3 to 0.4.4.
- [Release notes](https://github.com/ipfs/go-unixfs/releases)
- [Commits](https://github.com/ipfs/go-unixfs/compare/v0.4.3...v0.4.4)

---
updated-dependencies:
- dependency-name: github.com/ipfs/go-unixfs
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-06 18:09:49 +00:00
dependabot[bot] bcf04d3fae Bump golang.org/x/net from 0.7.0 to 0.8.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.7.0 to 0.8.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/compare/v0.7.0...v0.8.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-06 18:09:36 +00:00
mcrakhman f4b904b47b Add strkey for string encoding 2023-03-06 19:08:16 +01:00
mcrakhman b56603ca83 Add todo 2023-03-06 14:29:07 +01:00
mcrakhman a124dbc7ea Update diffsyncer to directly sync settings tree 2023-03-06 11:08:27 +01:00
mcrakhman dea506aa42 Add error unwrapping 2023-03-06 09:41:45 +01:00
mcrakhman 1210b9d21a Add safe access in case no delete change provided 2023-03-06 09:31:27 +01:00
mcrakhman 93822ae556 Add errors to coordinator proto 2023-03-04 12:10:32 +01:00
mcrakhman ddb60e498d Update status method 2023-03-03 23:10:05 +01:00
mcrakhman df772ed629 Fix spacestorage 2023-03-02 23:28:46 +01:00
mcrakhman d0117b5d6e Simplify cred provider logic 2023-03-02 22:40:56 +01:00
mcrakhman dc72150c1a Add credential provider 2023-03-02 22:04:37 +01:00
mcrakhman 76a3c2af9b Fix change builder with no root 2023-03-02 21:08:14 +01:00
mcrakhman 3e391079f3 Update metrics to convert dots 2023-03-02 20:54:52 +01:00
mcrakhman c219bb58be Update coordinator client 2023-03-02 19:55:27 +01:00
mcrakhman d668449e63 Update coordinator proto 2023-03-02 14:19:32 +01:00
mcrakhman 15e6c52049 Update space header validation 2023-03-02 13:27:08 +01:00
mcrakhman 3c04d1e914 Add space header validation 2023-03-02 13:23:43 +01:00
mcrakhman 2b3ba949d3 Add header to coordinator proto 2023-03-02 12:18:12 +01:00
mcrakhman 8feb5cd180 Change verify export 2023-03-01 22:12:07 +01:00
mcrakhman bb100994ec Export VerifyDeleteChange 2023-03-01 22:06:37 +01:00
Mikhail Iudin d3adb45fae change license org 2023-03-01 03:01:13 +02:00
mcrakhman cb9ae34498 Update coordinator proto 2023-02-27 22:50:11 +01:00
Roman Khafizianov b2b45e0f2f exclude graphviz when CGO_ENABLED=0 2023-02-28 17:03:19 +01:00
dependabot[bot] cd92818e56 Bump github.com/stretchr/testify from 1.8.1 to 1.8.2
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.1 to 1.8.2.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.1...v1.8.2)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-27 18:15:28 +00:00
mcrakhman 3caa36d3a5 Change deleter peer logic 2023-02-27 14:24:28 +01:00
mcrakhman fa52eb413d Any-sync remove printlns and fix condition 2023-02-26 18:47:43 +01:00
mcrakhman b7c4bf8f1e Add deletionmanager tests 2023-02-26 00:05:28 +01:00
mcrakhman ad639f66be Add some basic tests for settings 2023-02-25 22:41:12 +01:00
mcrakhman e02b2e6964 Test settings state builder 2023-02-25 21:41:35 +01:00
mcrakhman ac22c3014c Add deps 2023-02-24 23:51:45 +01:00
mcrakhman 94248dfd59 Update interfaces 2023-02-24 23:42:05 +01:00
mcrakhman 51736bc535 Add logs and fix building settings state 2023-02-24 23:25:11 +01:00
mcrakhman b2f8f83518 Stop syncing with clients after deletion 2023-02-24 00:10:40 +01:00
mcrakhman 05d76811b6 Remove unnecessary call 2023-02-24 00:04:11 +01:00
mcrakhman a0aa02bb8a Change deletion logic in headsync and objectsync 2023-02-23 23:58:09 +01:00
mcrakhman 73b27c7dac Update any-sync new deletion logic 2023-02-23 21:08:31 +01:00
mcrakhman 8a38d54192 Add space deleted in storage 2023-02-22 10:05:17 +01:00
mcrakhman 913c6925bf Add comments 2023-02-21 14:44:12 +01:00
mcrakhman 89fa4d71a3 Create methods for deleting space 2023-02-20 23:47:32 +01:00
mcrakhman 7e946d4313 Add spaceIsDeleted checks 2023-02-20 22:51:00 +01:00
mcrakhman 09387888de Add some deletion logic 2023-02-20 21:43:14 +01:00
Sergey Cherepanov 04512cee0d mock coordinatorclient 2023-02-27 17:19:23 +03:00
Sergey Cherepanov 2c369a1fe4 coordinator client 2023-02-27 16:40:27 +03:00
Sergey Cherepanov 92fb54a3e4 remove priv keys from nodes conf 2023-02-23 16:55:10 +03:00
Sergey Cherepanov 6eb84043f0 add peerId to space receipt 2023-02-23 14:13:11 +03:00
dependabot[bot] 33e1065e4d Bump github.com/ipfs/go-merkledag from 0.9.0 to 0.10.0
Bumps [github.com/ipfs/go-merkledag](https://github.com/ipfs/go-merkledag) from 0.9.0 to 0.10.0.
- [Release notes](https://github.com/ipfs/go-merkledag/releases)
- [Commits](https://github.com/ipfs/go-merkledag/compare/v0.9.0...v0.10.0)

---
updated-dependencies:
- dependency-name: github.com/ipfs/go-merkledag
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-20 18:08:42 +00:00
dependabot[bot] 5f03a5e76b Bump golang.org/x/net from 0.6.0 to 0.7.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.6.0 to 0.7.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/compare/v0.6.0...v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-20 18:08:07 +00:00
mcrakhman 8abf60d5d7 Add more logs for space 2023-02-21 16:46:40 +01:00
mcrakhman aa6c2e7ce4 Fix putTree close and handshake params and add logs for trees used debug 2023-02-21 16:36:58 +01:00
Sergey Cherepanov d6737cc547 coordinator node type 2023-02-23 13:38:58 +03:00
Sergey Cherepanov aa5566c010 coordinator proto 2023-02-22 16:35:29 +03:00
mcrakhman c95059b4c6 Add subscribe after push 2023-02-17 22:27:10 +01:00
Sergey Cherepanov 370683ff70 fix listener 2023-02-17 15:38:42 +03:00
Sergey Cherepanov 68e122cf72 remove unused param 2023-02-17 00:24:52 +03:00
Sergey Cherepanov 3a43f603fb dial timeout 2023-02-17 00:19:05 +03:00
Sergey Cherepanov 1a588d0a72 remove tls listener, move handshake to serve goroutine 2023-02-17 00:09:52 +03:00
mcrakhman f796cc6c6d Add listen addrs 2023-02-14 23:09:03 +01:00
Sergey Cherepanov ba1cc69c01 secure service test 2023-02-15 22:09:09 +03:00
Sergey Cherepanov e93812cdcc handshake with ctx 2023-02-15 21:29:37 +03:00
Sergey Cherepanov 862d5fe693 increase dial timeout + debug logs 2023-02-15 14:58:23 +03:00
Sergey Cherepanov 95bcbcd9a5 cleanup thread on error 2023-02-14 21:18:25 +03:00
Sergey Cherepanov 5fb7bcca22 dial timeout 2023-02-14 19:39:57 +03:00
Sergey Cherepanov 956d644bab fix 2023-02-14 19:00:27 +03:00
Sergey Cherepanov e05dc905e5 handleQueue thread close / onObjectClose method 2023-02-14 16:53:43 +03:00
Sergey Cherepanov c262be5f58 timeoutconn: remove debug 2023-02-13 22:13:40 +03:00
Sergey Cherepanov de47b5aaf5 default periodic sync timeout 2023-02-13 22:09:06 +03:00
Sergey Cherepanov 7409171af3 commongetter: check for space close 2023-02-13 21:53:27 +03:00
Sergey Cherepanov f858d39534 listen ctx for closing object 2023-02-13 21:49:56 +03:00
dependabot[bot] 703bf4fda6 Bump golang.org/x/net from 0.5.0 to 0.6.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.5.0 to 0.6.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/compare/v0.5.0...v0.6.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-13 18:12:15 +00:00
dependabot[bot] 0938f812c2 Bump github.com/ipfs/go-unixfs from 0.4.2 to 0.4.3
Bumps [github.com/ipfs/go-unixfs](https://github.com/ipfs/go-unixfs) from 0.4.2 to 0.4.3.
- [Release notes](https://github.com/ipfs/go-unixfs/releases)
- [Commits](https://github.com/ipfs/go-unixfs/compare/v0.4.2...v0.4.3)

---
updated-dependencies:
- dependency-name: github.com/ipfs/go-unixfs
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-13 18:12:32 +00:00
Roman Khafizianov 3ed9d6180a fix tests 2023-02-10 14:40:45 +01:00
Roman Khafizianov f81196eb98 RWLocker iface: add TryLock 2023-02-10 14:23:45 +01:00
Sergey Cherepanov d30d79a110 wip: secureservice with verifiers 2023-02-13 21:48:41 +03:00
Sergey Cherepanov d91d0941f6 handshake: return identity 2023-02-13 13:45:07 +03:00
Sergey Cherepanov d07ff2ff8e handshake proto and implementation 2023-02-11 22:52:20 +03:00
dependabot[bot] 38bd4eb36e Bump github.com/ipfs/go-unixfs from 0.4.1 to 0.4.2
Bumps [github.com/ipfs/go-unixfs](https://github.com/ipfs/go-unixfs) from 0.4.1 to 0.4.2.
- [Release notes](https://github.com/ipfs/go-unixfs/releases)
- [Commits](https://github.com/ipfs/go-unixfs/compare/v0.4.1...v0.4.2)

---
updated-dependencies:
- dependency-name: github.com/ipfs/go-unixfs
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-07 12:34:43 +00:00
dependabot[bot] 1ad2a45c86 Bump github.com/goccy/go-graphviz from 0.0.9 to 0.1.0
Bumps [github.com/goccy/go-graphviz](https://github.com/goccy/go-graphviz) from 0.0.9 to 0.1.0.
- [Release notes](https://github.com/goccy/go-graphviz/releases)
- [Commits](https://github.com/goccy/go-graphviz/compare/v0.0.9...v0.1.0)

---
updated-dependencies:
- dependency-name: github.com/goccy/go-graphviz
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-07 12:34:24 +00:00
dependabot[bot] 261645a780 Bump golang.org/x/net from 0.3.0 to 0.5.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.3.0 to 0.5.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/compare/v0.3.0...v0.5.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-04 15:06:55 +00:00
dependabot[bot] 522fd57e4e Bump github.com/ipfs/go-merkledag from 0.8.1 to 0.9.0
Bumps [github.com/ipfs/go-merkledag](https://github.com/ipfs/go-merkledag) from 0.8.1 to 0.9.0.
- [Release notes](https://github.com/ipfs/go-merkledag/releases)
- [Commits](https://github.com/ipfs/go-merkledag/compare/v0.8.1...v0.9.0)

---
updated-dependencies:
- dependency-name: github.com/ipfs/go-merkledag
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-04 15:07:10 +00:00
Sergey Cherepanov 1ae94266d2 fix win build 2023-02-05 17:29:55 +03:00
mcrakhman 2f899fe95c Move remote getter to separate entity 2023-02-05 11:31:14 +01:00
mcrakhman 2185f18d70 Update batcher to 3.0.1 2023-02-04 17:41:20 +01:00
mcrakhman d1fe82b086 Rename sync tree 2023-02-04 17:38:27 +01:00
mcrakhman b97df8d5f6 Remove SendResponsible 2023-02-03 20:15:55 +01:00
mcrakhman 5db8fd8f0b Simplify ping tree 2023-02-03 20:12:02 +01:00
mcrakhman 7541375e43 Add wait for available peers to appear 2023-02-03 15:36:12 +01:00
mcrakhman 47297d8988 Update responsible peers 2023-02-03 14:55:30 +01:00
mcrakhman 086b99ede2 Switch peers when getting tree 2023-02-03 14:49:05 +01:00
mcrakhman bc08acd36c Fix peer getting in sync tree 2023-02-03 12:55:44 +01:00
mcrakhman 8097445e6e Helper methods for local streams etc 2023-02-01 23:21:02 +01:00
dependabot[bot] 56e6cba575 Bump github.com/ipfs/go-block-format from 0.0.3 to 0.1.1
Bumps [github.com/ipfs/go-block-format](https://github.com/ipfs/go-block-format) from 0.0.3 to 0.1.1.
- [Release notes](https://github.com/ipfs/go-block-format/releases)
- [Commits](https://github.com/ipfs/go-block-format/compare/v0.0.3...v0.1.1)

---
updated-dependencies:
- dependency-name: github.com/ipfs/go-block-format
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-04 15:06:40 +00:00
Sergey Cherepanov a0e2f62d7e expose chash params 2023-02-04 19:45:11 +03:00
Sergey Cherepanov 8a259941a7 testrpc server implement app.Component 2023-02-04 15:20:34 +03:00
Sergey Cherepanov 45885a59b3 fix 2023-02-02 21:21:25 +03:00
Sergey Cherepanov 9a043e39a0 fix 2023-02-02 21:16:59 +03:00
Sergey Cherepanov 984dc91f14 fix ldiff mock 2023-02-02 13:19:20 +03:00
Sergey Cherepanov 64c596de8a testPool: add peers 2023-02-02 13:18:30 +03:00
Sergey Cherepanov c51e3e2e05 testPool: implement pool.Service 2023-02-02 13:12:52 +03:00
Sergey Cherepanov f6e2cc3890 ldiff: Len 2023-02-01 21:38:47 +03:00
Sergey Cherepanov 887354642c ldiff: Element by id 2023-02-01 20:58:20 +03:00
Sergey Cherepanov 94696cc44e Update dependabot.yml 2023-02-04 18:19:18 +03:00
Sergey Cherepanov 4a9565add1 Create dependabot.yml 2023-02-04 17:28:23 +03:00
Sergey Cherepanov bac139c93f fix the test blinking 2023-01-31 19:34:23 +03:00
Sergey Cherepanov 820d5af672 fileproto: allowWrite flag 2023-01-31 15:48:53 +03:00
Sergey Cherepanov 7288d0b28f update fileproto: BlocksGet -> BlockGet 2023-01-31 14:27:41 +03:00
Sergey Cherepanov 10d721b970 update fileproto: unary method instead stream for blocksGet 2023-01-31 14:24:39 +03:00
Sergey Cherepanov 748994be2e rename proto pkg 2023-01-30 20:19:52 +03:00
Sergey Cherepanov 2ccc150b38 update fileproto 2023-01-30 20:17:41 +03:00
Sergey Cherepanov 6cb9fc5092 streammanger -> peermanager, remove confconnector 2023-01-30 19:18:53 +03:00
Sergey Cherepanov 854d832c81 streampool: close dial queue 2023-01-30 18:40:25 +03:00
Sergey Cherepanov cf15052b76 stream config 2023-01-30 17:03:31 +03:00
Sergey Cherepanov f6d5ea6495 dial pool 2023-01-30 16:43:52 +03:00
Sergey Cherepanov b155031cb1 async dial 2023-01-30 13:16:07 +03:00
mcrakhman 8b67c188ba Add dialer to inner pool 2023-01-28 23:58:47 +01:00
Sergey Cherepanov b1a1ff1a72 fix streampool tags 2023-01-27 20:34:48 +03:00
Sergey Cherepanov 0e7450fd52 streampool fixes 2023-01-27 16:14:34 +03:00
Sergey Cherepanov bf3b80825a fix test 2023-01-27 14:52:08 +03:00
Sergey Cherepanov f95123fc43 add requestId to separate request/reply flows 2023-01-27 14:37:06 +03:00
Sergey Cherepanov a2765a5233 handle replies in separate thread 2023-01-27 13:58:28 +03:00
Sergey Cherepanov 80c8da8cac space milti queue 2023-01-27 13:50:36 +03:00
Sergey Cherepanov eb1049b160 debug/fixes 2023-01-25 23:35:20 +03:00
Sergey Cherepanov 81bdd3fefb ctx logger + streapool add/removeTags + space subscribe message 2023-01-24 22:10:16 +03:00
Sergey Cherepanov cc89d5919e debug 2023-01-24 16:23:35 +03:00
Sergey Cherepanov 2d2f24b3e3 peer and space last usage 2023-01-20 19:32:38 +03:00
Sergey Cherepanov 5fb6ee5a7b sync fixes + fix tests 2023-01-20 14:55:45 +03:00
Sergey Cherepanov 34848254be commonspace with new streampool 2023-01-19 15:17:04 +03:00
Sergey Cherepanov e4d8b4578d common streampool pkg 2023-01-18 13:46:49 +03:00
Sergey Cherepanov d256bfbc4c PoolService still keeps the default Pool but with ability to create new pools 2023-01-18 13:44:50 +03:00
mcrakhman b37a7480a3 Set heads in tree storage 2023-01-26 12:55:34 +01:00
mcrakhman 9aea231fd8 Set object tree id at the start 2023-01-26 10:06:06 +01:00
mcrakhman 0d516874e2 Add tree exporter 2023-01-25 21:20:23 +01:00
mcrakhman 75d94db5bb Change debug api for tree 2023-01-25 11:26:37 +01:00
mcrakhman 5a6af6dd2c Change interfaces for import tree 2023-01-25 10:13:01 +01:00
mcrakhman 4e76451bf8 Change dataconverter interface 2023-01-24 17:04:38 +01:00
mcrakhman 88c4bc28f6 Add exporter/viewer interfaces and empty methods, refactoring 2023-01-22 22:16:14 +01:00
mcrakhman 13bd358bce Fix timestamps 2023-01-18 10:06:01 +01:00
mcrakhman bb665828bc Few fixes re history 2023-01-18 09:16:38 +01:00
mcrakhman 4f27e8e48d Optimize interfaces 2023-01-18 08:36:54 +01:00
mcrakhman e12edef524 Expose header in history 2023-01-18 08:24:33 +01:00
mcrakhman d15a02571f Add GetChange method to tree 2023-01-18 08:07:47 +01:00
mcrakhman 4a0ec1f793 WIP history tree 2023-01-17 23:07:15 +01:00
mcrakhman e0db25e2b3 Update synctree to be able to provide listener 2023-01-17 15:16:04 +01:00
mcrakhman bf76836a21 Update delete loop 2023-01-17 00:04:14 +01:00
mcrakhman b0f684769a Refactor commongetter to hide reserved objects 2023-01-16 20:34:26 +01:00
mcrakhman 15e950ed44 Save tls listeners 2023-01-15 23:41:54 +01:00
mcrakhman 58646d8b0c Transactional add in object tree 2023-01-15 15:49:09 +01:00
Sergey Cherepanov 7c017f6249 pool.Get instead pool.Dial for pullSpace 2023-01-15 22:45:43 +03:00
Sergey Cherepanov 9828b1ea04 addr in dial logs 2023-01-15 22:44:09 +03:00
Sergey Cherepanov 5855742497 fix nil ctx 2023-01-15 18:23:46 +03:00
Sergey Cherepanov 61b9c03aad log connection error 2023-01-15 17:13:17 +03:00
mcrakhman d7cae8e32e Make settings iterate from root, hide settings from user 2023-01-14 22:24:57 +01:00
mcrakhman 7a57d7b0c4 Change objecttree to set mode Rebuild if we update the snapshot after current heads 2023-01-14 20:59:54 +01:00
Sergey Cherepanov 11d50d087a write space hash on open 2023-01-13 19:51:29 +03:00
Sergey Cherepanov ccfcc2f399 save space hash 2023-01-13 19:13:26 +03:00
Sergey Cherepanov 7794373b3c StateHash method for space 2023-01-13 17:02:25 +03:00
Sergey Cherepanov 96f7c6073d nodeconf Partition + ReplKey + configuration tests 2023-01-13 15:41:08 +03:00
Sergey Cherepanov 42b0a2a38b shorter repl key 2023-01-13 15:40:20 +03:00
mcrakhman f95ee94f62 Change PutTree 2023-01-13 12:59:13 +01:00
mcrakhman b2da6ff300 Update synctree to connect with responsible node 2023-01-13 12:55:28 +01:00
Sergey Cherepanov 50b51b1246 remove consensus dep 2023-01-05 15:39:08 +03:00
mcrakhman 3cbecca696 Update any-sync to wait sync for docs 2023-01-13 11:42:44 +01:00
Sergey Cherepanov c27e8a9773 test account improve 2023-01-13 12:11:20 +03:00
Sergey Cherepanov 907de1ecd2 update chash 2023-01-13 10:40:34 +03:00
Sergey Cherepanov aa9d84a86b nodeconf: expose CHash 2023-01-13 10:14:50 +03:00
mcrakhman 957ed31ded Add handling for root ids 2023-01-11 19:18:04 +01:00
Sergey Cherepanov 0317a4fbe2 net pool metrics 2023-01-12 13:53:32 +03:00
mcrakhman 332d8bb373 Change object tree debug dump 2023-01-11 19:04:47 +01:00
Grigory Efimov 3c03ee80da .github/workflows/coverage.yml update actions 2023-01-11 20:32:06 +03:00
Grigory Efimov 101ee0e300 fix set-output 2023-01-11 20:25:15 +03:00
Grigory Efimov dcc7ff372d .github/workflows/coverage.yml add go-cache-paths 2023-01-11 20:20:28 +03:00
Grigory Efimov 5b7cf0b902 .github/workflows/coverage.yml add actions/cach 2023-01-11 20:09:54 +03:00
Grigory Efimov 3bf29d8352 refact 2023-01-11 19:59:54 +03:00
Grigory Efimov 6ba6356777 fix go version 2023-01-11 19:48:47 +03:00
Grigory Efimov 1119d89ba7 fix .github/workflows/coverage.yml 2023-01-11 19:43:52 +03:00
Grigory Efimov 612a78cee0 testbytest 2023-01-11 19:39:50 +03:00
Grigory Efimov 0f4dd039cd testbytest 2023-01-11 19:38:44 +03:00
Grigory Efimov c719bcfd0c testbytest 2023-01-11 19:36:30 +03:00
Grigory Efimov f297ac8a4f testbytest 2023-01-11 18:52:12 +03:00
Grigory Efimov a625d7ab41 testbytest 2023-01-11 18:51:30 +03:00
Grigory Efimov 561c504f7b testbytest 2023-01-11 18:37:56 +03:00
Grigory Efimov 7a63b533fe testbytest 2023-01-11 18:34:28 +03:00
Grigory Efimov 35d492e63b testbytest 2023-01-11 18:33:17 +03:00
Grigory Efimov 502695bb37 testbytest 2023-01-11 18:06:13 +03:00
Grigory Efimov ae4e6f9701 testbytest 2023-01-11 18:04:16 +03:00
Grigory Efimov cd29d45b27 testbytest 2023-01-11 17:49:00 +03:00
Grigory Efimov f4a52215d5 testbytest 2023-01-11 17:17:19 +03:00
Grigory Efimov 5736e7e35a testbytest 2023-01-11 17:11:52 +03:00
Grigory Efimov 0728a4ead1 .github/workflows/coverage.yml add test go install 2023-01-11 17:09:12 +03:00
Grigory Efimov 75c739ce4f Makefile: revert tests 2023-01-11 17:08:43 +03:00
Grigory Efimov 19c035f4f3 fix .github/workflows/coverage.yml 2023-01-11 16:29:21 +03:00
Grigory Efimov 195ac70726 fix .github/workflows/coverage.yml 2023-01-11 16:25:52 +03:00
Grigory Efimov 5b08cba028 fix .github/workflows/coverage.yml 2023-01-11 15:53:34 +03:00
Grigory Efimov 9d35ef2e57 fix .github/workflows/coverage.yml 2023-01-11 15:43:55 +03:00
Grigory Efimov 20c038ede8 fix .github/workflows/coverage.yml 2023-01-11 15:38:53 +03:00
Grigory Efimov 2447926f03 fix .github/workflows/coverage.yml 2023-01-11 15:34:49 +03:00
Sergey Cherepanov a20f0771f1 fileservice.HasCid 2023-01-11 15:39:32 +03:00
Grigory Efimov 302a65bedb fix GOPRIVATE 2023-01-11 15:22:43 +03:00
Grigory Efimov 1aa867ecfc add export GIT_TERMINAL_PROMPT=1 2023-01-11 15:20:13 +03:00
Grigory Efimov 5c5c763d24 fix .github/workflows/coverage.yml 2023-01-11 15:18:40 +03:00
Grigory Efimov 6ad23dd128 coverage.yml add GOPRIVATE 2023-01-11 15:16:18 +03:00
Grigory Efimov 6534b8c60c fix 2023-01-11 15:06:17 +03:00
Grigory Efimov 9956bedd78 coverage.yml test 2023-01-11 15:03:48 +03:00
Grigory Efimov 1103a5b284 .github/workflows/coverage.yml add git config 2023-01-11 14:53:55 +03:00
Sergey Cherepanov f13d7e634a update chash 2023-01-11 15:30:31 +03:00
Sergey Cherepanov 4b649f5996 files: export DAGService 2023-01-11 14:50:20 +03:00
Sergey Cherepanov 36071b3a79 build deps locally 2023-01-11 14:15:20 +03:00
mcrakhman 4db7d11f1f Fix acl derivation 2023-01-10 17:54:04 +01:00
mcrakhman 2edd059846 Fix incorrect Graph method 2023-01-07 12:41:13 +01:00
Sergey Cherepanov da4a43d5a0 go vet 2023-01-07 13:35:12 +03:00
Sergey Cherepanov 3c611239b9 fix CI 2023-01-07 13:26:46 +03:00
mcrakhman d12455bc31 Update libp2p ver 2023-01-07 00:11:27 +01:00
mcrakhman 3ec34e84bc Use account service in secureservice instead of config 2023-01-06 20:55:10 +01:00
Sergey Cherepanov 257a4d8e13 fix CI 2023-01-06 21:44:28 +03:00
Sergey Cherepanov 41fa304f70 crypro: DeriveRSA 2023-01-06 21:39:36 +03:00
Sergey Cherepanov c3230d5430 CI coverage 2023-01-06 21:38:48 +03:00
Sergey Cherepanov c74c41deaa Revert "remove key.Key.Equals for more compatibility"
This reverts commit 7f5df570631033b017e2d724f7cd118445834ec9.
2023-01-05 18:24:48 +03:00
Sergey Cherepanov 770b649936 remove key.Key.Equals for more compatibility 2023-01-05 17:53:24 +03:00
Sergey Cherepanov 7eb1539bdf remove consensus dep 2023-01-05 15:39:08 +03:00
Sergey Cherepanov 596a315c5f make common module as any-sync repo 2023-01-05 15:34:09 +03:00
mcrakhman aac5b3e83c Change object creation logic 2023-01-05 12:45:50 +01:00
Sergey Cherepanov 7f19df6f6b go.mod: update mb/v3 2023-01-05 15:02:08 +03:00
mcrakhman 8073ad00c3 Update space service to return id in case the space storage is already created 2023-01-04 15:07:56 +01:00
mcrakhman 67b49cd33e Rename ObjectTree.Iterate to IterateRoot 2023-01-04 14:39:26 +01:00
mcrakhman 15667b7ad8 Add easy access to unmarshalled header 2023-01-03 18:55:43 +01:00
mcrakhman df57a0230f Refactor space storage and add tree root 2023-01-03 00:02:33 +01:00
mcrakhman ce1273a32c Make tree creation logic more transparent 2023-01-02 23:30:58 +01:00
mcrakhman bb398cdb2a Update ocache ObjectLocker 2023-01-02 14:27:24 +01:00
mcrakhman 54813ccdec Add generic mustcomponent as in middleware 2023-01-02 13:16:49 +01:00
Sergey Cherepanov 9c1f0acf8b files: rpcstore.AddAsync + lazy connect in clientmgr 2023-01-03 15:08:07 +03:00
mcrakhman 4294c7d2f9 Remove unnecessary context cleanup 2023-01-02 11:31:43 +01:00
mcrakhman 43d63c3e01 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 9ba3c1b34a Change headsync logic (ensure space closing on nodes) 2023-01-02 10:54:17 +01:00
mcrakhman 0f257cbc0c Change object creation to do it through cache 2023-01-01 22:05:29 +01:00
Sergey Cherepanov fd690d5758 files:partial upload 2022-12-27 13:55:51 +03:00
mcrakhman 63007032e4 Remove unused function in confconnector 2022-12-29 18:27:27 +01:00
mcrakhman 5f62fc7e88 Add queue test 2022-12-29 15:16:30 +01:00
mcrakhman 021342bfe9 Fix waiters in streampool 2022-12-27 19:59:27 +01:00
Dmitry 270a6bdcee Nodes descriptions added 2022-12-27 17:57:17 +08:00
Dmitry aa029ce71b First integrational test! 2022-12-23 20:43:06 +08:00
mcrakhman 8afb4b5c1f Update action queue 2022-12-29 13:45:55 +01:00
mcrakhman bf146e6676 Some streampool fixes 2022-12-28 17:52:10 +01:00
mcrakhman 5794ea1650 Change solution to use queue for better performance 2022-12-27 22:26:07 +01:00
mcrakhman e18f71111a Fix streampool waiters bug 2022-12-27 19:57:01 +01:00
Sergey Cherepanov 17e37fb8fb refactor configs 2022-12-27 16:50:20 +03:00
mcrakhman 523aedaf3e Change port number for file node in deploy 2022-12-26 18:05:33 +01:00
mcrakhman 21e5cb36c5 Change ID to Id 2022-12-26 18:01:18 +01:00
mcrakhman 27e0cbf1ea Change ACL to Acl 2022-12-26 17:52:39 +01:00
mcrakhman a32a3674b1 Add comments and change cnames 2022-12-26 17:36:42 +01:00
mcrakhman 7046601ef8 Add generation for textchanges in test client 2022-12-25 21:20:41 +01:00
mcrakhman 98323b0063 Refactor spacesyncproto 2022-12-25 21:12:17 +01:00
mcrakhman a66a356947 Moving around files and refactoring of common 2022-12-25 20:27:37 +01:00
mcrakhman 53e945b956 WIP refactoring syncstatus headsync 2022-12-25 15:08:26 +01:00
Sergey Cherepanov 934f413807 files: rpcstore context listen 2022-12-26 18:59:33 +03:00
Sergey Cherepanov f3daed1144 move fileserver to filenode, filenode in debugapi 2022-12-23 21:23:44 +03:00
Sergey Cherepanov 73774d99c2 files: fix syncer loop, check ctx.Done 2022-12-22 14:50:20 +03:00
Sergey Cherepanov 69e5ea08e0 go mod fixes 2022-12-22 14:08:15 +03:00
mcrakhman c81724c36f Fix sync bugs (connection with different nodes on stream and diff) 2022-12-21 21:24:17 +01:00
mcrakhman 86359af3be Send head notify message on object create 2022-12-21 19:50:46 +01:00
mcrakhman 56f35c1a73 Change some parameter names 2022-12-21 10:48:32 +01:00
mcrakhman 6861aa2c51 Change naming, add debug mode and remove sh script 2022-12-21 00:23:09 +01:00
mcrakhman 7a08bd8fb3 More cmd fixes 2022-12-20 23:49:36 +01:00
mcrakhman 861e126ab4 Change command a bit, change debug command parameter 2022-12-20 23:30:32 +01:00
mcrakhman c13a6e1d5f Add preliminary deploy script 2022-12-20 18:26:47 +01:00
mcrakhman 40760d5c19 Few more fixes in tests 2022-12-19 18:51:31 +01:00
mcrakhman b7e1e1502f Statusservice refactoring 2022-12-19 17:54:06 +01:00
mcrakhman 81f24d4cb0 Update status service 2022-12-19 16:59:43 +01:00
mcrakhman bc857efc6c Add some comments and checks 2022-12-18 13:43:05 +01:00
mcrakhman fb6476939e Change sync status update logic 2022-12-18 13:16:37 +01:00
mcrakhman b59e2c2593 Added simple watcher and statusservice upgrades 2022-12-17 21:48:48 +01:00
mcrakhman 1af3e764ec Add status service implementation 2022-12-17 17:19:19 +01:00
mcrakhman 85321db4d7 Add preliminary interfaces for syncstatus 2022-12-17 16:55:15 +01:00
mcrakhman a510fa21b7 Fix tests 2022-12-17 14:00:39 +01:00
mcrakhman 791ecc56b0 Fixing tests wip 2022-12-16 23:39:00 +01:00
mcrakhman 3eade57cba Fix objecttree tests 2022-12-16 21:16:30 +01:00
mcrakhman fcbc6af479 Add comments and fix streampool test 2022-12-16 21:02:34 +01:00
mcrakhman 6f13df03a6 Add queue to sync client 2022-12-16 16:40:23 +01:00
mcrakhman 7183127189 Move queue to separate object 2022-12-16 13:09:15 +01:00
mcrakhman 2fa94cfe7b More fixes to synctree and objecttree 2022-12-15 21:53:58 +01:00
mcrakhman fd7c0010d8 Add queue to handle quickly updating records 2022-12-15 17:20:11 +01:00
mcrakhman ff12e4ad1e Removed sending under a lock in sync tree handler 2022-12-14 21:37:36 +01:00
mcrakhman 5e8bd53fb2 Add connection with timeout, message limits more logs etc 2022-12-14 14:27:43 +01:00
mcrakhman fa733293cb Fix treebuilder errors 2022-12-13 13:57:23 +01:00
mcrakhman f2f2be36e9 More raw loader fixes 2022-12-12 21:22:35 +01:00
mcrakhman d8461fb856 Fix not taking lock 2022-12-12 17:30:21 +01:00
mcrakhman f8509bc85a Change obj tree logic to include their heads on add raw changes 2022-12-12 17:17:09 +01:00
mcrakhman c31ebe6794 Remove sugared logger 2022-12-09 22:11:22 +01:00
mcrakhman ed3d7a7720 Few fixes to syncservice and streamchecker 2022-12-09 21:53:24 +01:00
mcrakhman 30a8194638 Fix stream checker and tests 2022-12-09 19:55:57 +01:00
mcrakhman 8a4b58969b Add stream checker 2022-12-09 17:16:43 +01:00
Sergey Cherepanov 8210ea2c76 gitignore debug.json 2022-12-22 13:46:22 +03:00
Sergey Cherepanov 41e2b26116 files: ref, proxyStore, index 2022-12-21 21:23:47 +03:00
Sergey Cherepanov 185f4aeb59 file to client 2022-12-14 19:14:47 +03:00
Sergey Cherepanov 0ed7b8162a file-node 2022-12-14 16:37:59 +03:00
mcrakhman f4809905b9 Change sync period 2022-12-06 21:02:47 +01:00
mcrakhman 084dfd0ede Add config gen to Makefile 2022-12-06 21:02:37 +01:00
mcrakhman 96edcc1dc9 Add debug client to script and change commands 2022-12-06 18:15:26 +01:00
mcrakhman 36b940e9ae Add build helpers for local testing 2022-12-06 16:36:17 +01:00
mcrakhman 9d9867a632 Change tree creation logic 2022-12-06 10:19:53 +01:00
mcrakhman 86ba8e33d0 More fixes to settingsdocument 2022-12-05 23:04:56 +01:00
mcrakhman 6bf8864f5a Fix incorrect args in debug 2022-12-05 20:02:26 +01:00
mcrakhman a67aebf526 Remove duplicated code 2022-12-05 19:11:16 +01:00
mcrakhman 8c60e77b6f Update synctree ping logic 2022-12-05 18:51:59 +01:00
mcrakhman 00b1b8e4a5 Fix notifiable when creating sync tree 2022-12-05 17:21:14 +01:00
mcrakhman c5b766345c Add scripts 2022-12-05 16:35:14 +01:00
mcrakhman 4eca88b0bc Use cobra command line 2022-12-05 12:43:36 +01:00
mcrakhman 2b3ae0fb94 Add two client script and fix common snapshot bug 2022-12-04 15:02:50 +01:00
mcrakhman 82eb76c1ea Fix syncservice loop 2022-12-03 17:54:10 +01:00
mcrakhman 2ba8108292 Fix dfsprev 2022-12-03 15:59:45 +01:00
mcrakhman 8735700514 Add tree params and debugging 2022-12-03 15:06:43 +01:00
mcrakhman e6f4ff2f20 WIP add snapshot methods to debug api 2022-12-03 14:43:18 +01:00
mcrakhman 78f67d221c Check space dir existence before creating new 2022-12-03 13:23:14 +01:00
mcrakhman 5098daf152 WIP node api 2022-12-03 13:09:15 +01:00
mcrakhman 75706111ee WIP node api 2022-12-03 12:44:02 +01:00
mcrakhman 0d0bd82dc7 Simplify space locked solution 2022-12-03 11:48:18 +01:00
mcrakhman af5077d1c2 Stopped space from unloading if trees are still there 2022-12-02 19:45:43 +01:00
mcrakhman c85ae1a335 Move notifiable logic to synctree instead of syncclient 2022-12-02 14:14:02 +01:00
mcrakhman 3dec56141f Add dump heads and new head when adding text 2022-12-02 12:18:38 +01:00
mcrakhman c5a958df12 Add load space methods and fix storage errors 2022-12-02 09:52:04 +01:00
mcrakhman bd7e38d1af Add scripts 2022-12-01 22:08:01 +01:00
mcrakhman 896b2d982a Redirect logs and add more commands 2022-12-01 21:28:36 +01:00
mcrakhman 17d24547aa WIP debug client 2022-12-01 16:30:41 +01:00
mcrakhman 8b630dc7be WIP debug client 2022-12-01 11:15:17 +01:00
mcrakhman 7f67f46b30 Client api wip 2022-11-30 23:42:26 +01:00
mcrakhman 220b2608b1 Add api grpc server to client 2022-11-30 22:50:43 +01:00
mcrakhman d02169c585 Abstract drpc server and add client drpc api 2022-11-30 22:09:18 +01:00
mcrakhman a1e4e4f714 Add idprovider tests 2022-11-29 20:54:44 +01:00
mcrakhman ef20f0c44e Add deleter tests 2022-11-29 19:45:51 +01:00
mcrakhman e9bee38b36 Add deletionstate tests 2022-11-29 19:37:18 +01:00
mcrakhman df804c198d Add settingsdocument tests 2022-11-29 18:53:04 +01:00
mcrakhman 694973a4e3 Tests and refactoring 2022-11-29 16:54:04 +01:00
mcrakhman b24f79645d Improve interfaces for synctree, generate mocks 2022-11-28 16:54:31 +01:00
mcrakhman 8483ca1c87 Changes for tests and database fixes 2022-11-28 16:15:25 +01:00
mcrakhman 91427be6f7 Update diffsyncer 2022-11-25 15:24:25 +01:00
mcrakhman 907cbdea09 Fix diffservice tests 2022-11-25 15:09:44 +01:00
mcrakhman 54277d77c2 Re-generate mocks and fix sync tree tests 2022-11-25 14:37:00 +01:00
mcrakhman 0f6122c09b Fix more bugs 2022-11-25 11:40:36 +01:00
mcrakhman f5a2a7a959 Add sync listener nil check 2022-11-25 09:46:01 +01:00
mcrakhman d23f36d364 Fix settings document 2022-11-24 22:41:34 +01:00
mcrakhman f988ba9206 Fix tests 2022-11-24 22:20:01 +01:00
mcrakhman 4e6b7cfdda Add deletion test 2022-11-24 21:03:43 +01:00
mcrakhman 5715450aff Change push space and some tree deletion logic 2022-11-24 20:24:59 +01:00
mcrakhman bba4efaa50 Simplify deletion pipeline and change other stuff 2022-11-24 01:17:56 +01:00
mcrakhman 87997b6b4b Add remove notifial 2022-11-13 14:16:58 +01:00
mcrakhman cb2f64f0d5 Change deletion logic 2022-11-13 09:35:47 +01:00
mcrakhman 035a2fc1e6 Fix some document deletion bugs 2022-11-12 22:55:12 +01:00
mcrakhman a6507db992 Add sync loop to settings document 2022-11-12 18:17:01 +01:00
mcrakhman a5fe46e50e WIP more deletion checks 2022-11-12 17:49:25 +01:00
mcrakhman 676976e7d3 Diff service notify logic 2022-11-12 16:24:06 +01:00
mcrakhman 696cc37055 Add non-encrypted changes and settings document logic 2022-11-12 13:29:00 +01:00
mcrakhman d09a802487 WIP document deletion 2022-11-11 12:37:45 +01:00
mcrakhman 8805ba78ef Change pullspace and pushspace methods and handlers 2022-11-10 20:10:57 +01:00
mcrakhman bd3aff19e7 Create space with settings 2022-11-10 19:44:33 +01:00
Sergey Cherepanov 5d3e79e506 commonfile 2022-12-12 20:49:43 +03:00
Sergey Cherepanov a894f42e05 ipfs store + cache store impl 2022-11-12 15:22:33 +03:00
mcrakhman 7167b71a90 Add client space 2022-11-07 15:53:27 +01:00
mcrakhman 4881c052aa Fix tests and change peer id context logic 2022-11-07 13:36:10 +01:00
Sergey Cherepanov b0b4e5b721 acl sync handler [wip] 2022-11-07 11:22:48 +03:00
Sergey Cherepanov 8d7eaf665c node acl service 2022-11-02 13:54:57 +03:00
Sergey Cherepanov cb8a288773 acl service wip 2022-10-31 11:25:58 +03:00
Sergey Cherepanov 41a3f0502e node acl service (wip), fixes 2022-10-26 17:00:43 +03:00
Sergey Cherepanov 348acd6d84 remove debug 2022-10-24 13:05:44 +03:00
mcrakhman 6faa5d6dc6 Fix bugs in syncer etc, add peerkey for clients 2022-11-06 22:43:28 +01:00
mcrakhman be1c6e89b9 Add load space command 2022-11-06 18:36:25 +01:00
mcrakhman e1987a36bb Add pull space method to proto and provide get space 2022-11-06 14:25:27 +01:00
mcrakhman 54d2e6fc27 Fix building list 2022-10-28 13:24:05 +02:00
mcrakhman 3046a44d12 Add test for user join build and fix bugs 2022-10-28 13:01:20 +02:00
mcrakhman 2946c050b9 Add raw record to list 2022-10-28 12:04:23 +02:00
mcrakhman 5f6e51c15a Change lisstoragebuilder to use inmemorystorage 2022-10-28 11:26:39 +02:00
mcrakhman 481e2c52af Change invite logic 2022-10-27 17:09:07 +02:00
mcrakhman 1e73844ac0 Further test refactoring 2022-10-27 16:57:01 +02:00
mcrakhman 457f90f17f Refactor tests 2022-10-27 16:43:10 +02:00
mcrakhman 1fab89e154 Fix mocks and formatting 2022-10-26 22:05:55 +02:00
mcrakhman c53ede6913 Update tests 2022-10-26 22:03:02 +02:00
mcrakhman 177ac62057 Add space storage test 2022-10-26 21:48:59 +02:00
mcrakhman 9b85cf2373 Add badger list storage and tree storage test 2022-10-26 21:26:36 +02:00
mcrakhman b977ac0e14 Finish pogreb tests and fix some issues 2022-10-26 21:00:15 +02:00
mcrakhman 7ddd6825ba Add list storage 2022-10-26 14:18:44 +02:00
mcrakhman ca6964702f Add more tree tests 2022-10-26 13:27:57 +02:00
mcrakhman f2790cb574 Change tree methods and start adding tree tests 2022-10-26 12:55:47 +02:00
mcrakhman 5ecef4d6f5 Fix ocache closing 2022-10-25 12:16:04 +02:00
mcrakhman ddae2be2a1 Test stream pool 2022-10-24 21:57:19 +02:00
mcrakhman 70f3929f44 Remove lastusage from syncclient 2022-10-24 14:46:24 +02:00
mcrakhman dad0926ede Remove fleet artefacts 2022-10-24 14:44:15 +02:00
Sergey Cherepanov 77a289d2d0 fix tests + remove consensus testclient 2022-10-20 20:20:03 +03:00
Sergey Cherepanov 9fbe1aefd2 reconnection for consensus client + fixes 2022-10-20 20:14:27 +03:00
Sergey Cherepanov 2dcacdfbb6 consensus stream watch error 2022-10-20 14:04:59 +03:00
Sergey Cherepanov add04d3e88 WIP: acl service 2022-10-20 14:04:26 +03:00
mcrakhman 921e4410b8 Revert "Push main go remove replace"
This reverts commit 440d63ade0.
2022-10-18 11:17:57 +02:00
mcrakhman 349cf58ccf Push main go remove replace 2022-10-18 11:17:15 +02:00
mcrakhman 34f9c55458 Better logs in space storage 2022-10-24 12:09:59 +02:00
mcrakhman 9642c11eea Change space payload 2022-10-24 11:59:37 +02:00
mcrakhman 453967fad8 Stream pool and synctree fixes 2022-10-24 10:31:29 +02:00
mcrakhman 500725ffee Add tests for sync handler 2022-10-23 22:24:13 +02:00
mcrakhman 3f90011c10 Test sync tree 2022-10-23 18:19:15 +02:00
mcrakhman e1784aa2fb Refactor synctree 2022-10-22 16:19:47 +02:00
mcrakhman 6518a0f971 Change proto and generalise logic 2022-10-22 16:05:06 +02:00
mcrakhman 69211dfe66 Move concrete logic to concrete packages 2022-10-22 14:23:42 +02:00
mcrakhman e289fd4f89 WIP sync refactoring 2022-10-22 13:04:58 +02:00
mcrakhman cec48d1c44 Add timeout when waiting in streampool 2022-10-21 23:50:14 +02:00
mcrakhman a21e27cce5 Don't broadcast when no content added 2022-10-21 21:32:37 +02:00
mcrakhman 3de5265fbc Storage close and diff fixes and logs 2022-10-21 20:34:37 +02:00
mcrakhman 8c5ac1337a Ocache fix tests and return deadline back 2022-10-21 11:21:20 +02:00
mcrakhman 27ff00eb48 Fix ranges deadlock 2022-10-21 11:00:38 +02:00
mcrakhman e0e0ed08ae Fix bug with nested read locks in diff 2022-10-20 21:48:31 +02:00
mcrakhman f920200be9 Raw loader and key fixes 2022-10-20 20:12:00 +02:00
mcrakhman 3eeb3ec887 Fix listener sync tree and diff iter bug 2022-10-20 19:39:45 +02:00
mcrakhman 40d5d832fb Fix add text bug 2022-10-20 19:08:43 +02:00
mcrakhman f213ec7efe Fix bug with head update not being sent on first receive 2022-10-20 18:56:09 +02:00
mcrakhman 1af3f1b290 Fix bugs with storage 2022-10-20 16:02:16 +02:00
mcrakhman df4f1d709d Create waiters map 2022-10-19 22:57:34 +02:00
mcrakhman bb156e1bd0 Fix ocache and streampool bugs, fix push space 2022-10-19 22:21:44 +02:00
mcrakhman 524cf07d56 Fix bugs appearing while creating tree 2022-10-19 20:21:58 +02:00
mcrakhman 282ef17df3 Document creation fixes 2022-10-19 17:59:12 +02:00
mcrakhman 071d0800b7 Fix client imports 2022-10-19 15:07:29 +02:00
mcrakhman 267c96785f Add client configs to nodegen 2022-10-19 14:03:40 +02:00
mcrakhman 9d4f0395b9 Add API service 2022-10-19 13:38:20 +02:00
mcrakhman aa0d6ea155 Add controller which calls different api methods 2022-10-19 12:48:55 +02:00
mcrakhman 9692ebe887 Add space and tree close logic and fix some related tests 2022-10-19 12:20:24 +02:00
mcrakhman a3f2fb5eae Add client cmd 2022-10-18 15:16:06 +02:00
mcrakhman 42e0c50540 Add space and document creation logic for client 2022-10-18 14:17:49 +02:00
Sergey Cherepanov 2c9f54464a go mod fixes 2022-10-17 18:11:00 +03:00
Sergey Cherepanov 547582e3ed mod replace 2022-10-17 17:05:42 +03:00
Sergey Cherepanov 2c3ebf9ce1 go workspaces 2022-10-17 15:31:09 +03:00
Sergey Cherepanov daa9bb1df0 move pkgs 2022-10-17 14:11:55 +03:00
Sergey Cherepanov 5689863046 prometheus handle metrics 2022-10-13 17:00:51 +03:00
Sergey Cherepanov 76bf8d14bc fix 2022-10-11 12:36:05 +03:00
Sergey Cherepanov 4cbede57b1 drpc metrics to quantile 2022-10-11 12:13:31 +03:00
Sergey Cherepanov ad8e2c5ac3 log config 2022-10-10 13:48:56 +03:00
Sergey Cherepanov 273a98d985 fix 2022-10-10 11:38:25 +03:00
Sergey Cherepanov 7ee5ccd513 metric service, ocache and drpc handlers 2022-10-08 20:16:20 +03:00
mcrakhman b07b50a042 Change space service methods and change list storage 2022-10-17 12:40:55 +02:00
mcrakhman 3752fde5e1 Add preliminary API and fix space service methods 2022-10-16 17:59:16 +02:00
mcrakhman 8b45d698c2 Further wip badger 2022-10-16 16:41:01 +02:00
mcrakhman 8a5b489c55 Add transaction benchmarks for badger 2022-10-16 10:13:17 +02:00
mcrakhman bc9f0c19b7 Client badger WIP 2022-10-15 21:43:01 +02:00
mcrakhman 998e00d245 Change tree keys logic 2022-10-15 17:09:25 +02:00
mcrakhman ba7beb4d5a Change pogreb implementation 2022-10-15 16:26:03 +02:00
mcrakhman ff2baaeb79 Fix keys and remove close waiter 2022-10-14 13:32:40 +02:00
mcrakhman e0b01e2672 Add tests for ocache 2022-10-14 12:37:45 +02:00
mcrakhman 00251a7439 Change ocache to include wait for closing 2022-10-13 13:27:18 +02:00
mcrakhman 003a8688d8 Add close waiter and close logic for db 2022-10-12 21:36:16 +02:00
Sergey Cherepanov 933b1e3a4b change copaction interval 2022-10-12 15:14:19 +03:00
mcrakhman 541e9ee48a Naming fixes 2022-10-11 23:11:17 +02:00
mcrakhman 993efa29b2 Add checks for bench 2022-10-11 23:01:41 +02:00
mcrakhman 13a0879c1d Add wip benchmarks 2022-10-11 21:30:06 +02:00
mcrakhman 1b5b461c9b Change mutex logic 2022-10-11 11:15:46 +02:00
mcrakhman 2e20380ecd Wire up node 2022-10-11 10:25:29 +02:00
mcrakhman 18c3a14eb8 Fix incorrect storage key 2022-10-11 09:56:51 +02:00
mcrakhman 25260793c6 WIP storage 2022-10-10 23:08:44 +02:00
mcrakhman 8ba457bd12 Add some raw prototypes for storage 2022-10-10 20:10:47 +02:00
mcrakhman 2aaac963ce SyncTree tests 2022-10-10 09:10:47 +02:00
mcrakhman 80fb78ad36 Add conf connector and change configuration logic 2022-10-09 21:54:34 +02:00
mcrakhman 0da185508b Add sync handler tests 2022-10-03 23:35:25 +02:00
mcrakhman 9c41f8d51f Add head update tests 2022-10-02 22:40:13 +02:00
mcrakhman 84356efa86 Further updates to sync logic 2022-10-02 16:11:47 +02:00
mcrakhman 9e95f21cbd Update sync handler 2022-10-02 11:54:34 +02:00
mcrakhman 79cce17c2e Few tree sync fixes and additions 2022-10-01 21:04:31 +02:00
mcrakhman c9faaf4e01 Add diffservice tests 2022-09-30 16:55:34 +02:00
mcrakhman 9cbf3a81bc Add periodicsync test 2022-09-30 16:12:41 +02:00
mcrakhman 3780d31a21 Add more syncer tests 2022-09-30 16:02:47 +02:00
mcrakhman 38a1a22f0c Move mocks to separate packages 2022-09-30 15:53:33 +02:00
mcrakhman 897b4900c2 Require no error in tests 2022-09-30 14:43:11 +02:00
mcrakhman 93674aeabc Add push space custom matchers 2022-09-30 14:41:36 +02:00
mcrakhman 8c97888410 Make sync test 2022-09-30 14:21:48 +02:00
mcrakhman 379a750f4e Add mock to write diffservice tests 2022-09-30 13:49:31 +02:00
mcrakhman c234046f91 Prepare components for testing 2022-09-30 09:28:13 +02:00
mcrakhman a775006e2b Separate diff service into components 2022-09-29 16:51:05 +02:00
Sergey Cherepanov 77aa5a65d9 cleanup and comments 2022-10-07 15:06:54 +03:00
Sergey Cherepanov 7bb3d5e144 rewrite for single stream 2022-10-07 11:04:48 +03:00
Sergey Cherepanov 978a320b43 pool.Dial* methods + consesus test client 2022-10-05 11:41:48 +03:00
Sergey Cherepanov 5200b54857 consensus client 2022-10-04 17:55:30 +03:00
Sergey Cherepanov ea95c1a3c2 ignore consensus nodes in configuratrion 2022-10-04 16:40:40 +03:00
Sergey Cherepanov ed54533a20 nodesgen consensus configs 2022-10-04 16:36:11 +03:00
Sergey Cherepanov 7f0960937e consensus node 2022-10-04 15:39:29 +03:00
Sergey Cherepanov 0d2de36ac0 consistent node db component 2022-09-30 19:16:44 +03:00
mcrakhman 0ef1df6dfb Fix circular deps etc 2022-09-29 16:22:56 +02:00
mcrakhman b59edc4b6b Change cache logic 2022-09-28 20:56:11 +02:00
mcrakhman 2347591c69 Add better error handling 2022-09-28 19:55:47 +02:00
mcrakhman b8bdbacbce Add push space logic and change storage interface 2022-09-28 09:18:04 +02:00
Sergey Cherepanov 8cfe3df997 rpc errors 2022-09-27 19:43:39 +03:00
mcrakhman f7b330985d Add space/tree creation and derivation and push space protocol changes 2022-09-27 21:06:49 +02:00
mcrakhman 830fe45487 Gen new configs 2022-09-27 14:05:21 +02:00
mcrakhman a377b56fe4 Change account logic and add space objects 2022-09-27 13:31:50 +02:00
mcrakhman 60d8ad9879 Fix keys 2022-09-26 22:26:42 +02:00
mcrakhman e01d1438b5 Remove unneeded files 2022-09-26 22:20:08 +02:00
mcrakhman 9b752d0e96 Few more fixes, remove node service 2022-09-26 22:15:40 +02:00
mcrakhman 9b196a6521 Remove decoders and fix sync tree 2022-09-26 20:32:59 +02:00
mcrakhman efd7cafd59 More test fixes 2022-09-26 14:41:50 +02:00
mcrakhman a01d14f541 Make acl list etc compile 2022-09-26 13:47:34 +02:00
mcrakhman 69b21cd810 Fixes to acls 2022-09-26 12:03:20 +02:00
mcrakhman d41045aa63 WIP root change refactoring 2022-09-25 13:50:35 +02:00
mcrakhman 41709d7dd3 Update gitignore to include vendor 2022-09-21 09:26:33 +02:00
mcrakhman 4270bd25b5 Some renamings and fixes 2022-09-18 11:23:01 +02:00
mcrakhman f3a2944d60 Add treecache to node 2022-09-17 22:18:11 +02:00
mcrakhman 212553d63d Remove responsibility of objecttree to notify and move to synctree 2022-09-17 21:39:17 +02:00
mcrakhman 4754c9704b Move diff logic to separate service, simplify send sync 2022-09-17 20:54:23 +02:00
mcrakhman ca1d0a43b1 Update stream configuration and sync service 2022-09-16 11:20:43 +02:00
mcrakhman 0e95dbfecf Update sync handler 2022-09-16 10:17:54 +02:00
mcrakhman ce2c2eaecc Add tree validation before saving 2022-09-16 07:47:36 +02:00
mcrakhman 592a802d5d Add build tree sync request 2022-09-16 00:22:22 +02:00
mcrakhman b17c33256a Update peer reading logic 2022-09-15 14:11:49 +02:00
mcrakhman 6d38f381ce Update tree sync logic 2022-09-15 13:42:51 +02:00
mcrakhman 404f128310 Add sync tree and remove head send from sync handler 2022-09-15 12:34:28 +02:00
mcrakhman bd0f419fab Add more logic re sync service 2022-09-15 11:35:16 +02:00
mcrakhman b2fd9d2211 WIP sync 2022-09-14 18:54:10 +02:00
mcrakhman 3f41c60f2b Add sync handler 2022-09-13 22:26:51 +02:00
mcrakhman 5a606b8854 Add .idea to gitignore 2022-09-13 16:34:26 +02:00
mcrakhman 5e87a66472 Generate drpc and some fixes 2022-09-13 15:52:47 +02:00
mcrakhman d41f736234 Revert "Use string instead of bytes, to simplify code"
This reverts commit a907cde399.
2022-09-13 12:26:37 +02:00
mcrakhman 53f0c04f95 Use string instead of bytes, to simplify code 2022-09-13 10:44:02 +02:00
mcrakhman fa123f4ab0 Fix pkg tests 2022-09-13 09:04:47 +02:00
mcrakhman c6acf63977 Fix list package and some storage stuff 2022-09-12 21:25:20 +02:00
mcrakhman e743e34849 WIP proto refactoring 2022-09-12 21:06:20 +02:00
mcrakhman d5244cf6e3 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
Sergey Cherepanov fe6b71059d package moving + commonaspace and nidespace services 2022-09-08 21:56:32 +03:00
Sergey Cherepanov 7b60bc49ac rename protos-go -> protos 2022-09-08 12:15:54 +03:00
Sergey Cherepanov 54bff9012e space service periodic sync 2022-09-08 12:05:30 +03:00
Sergey Cherepanov 1a9f48de92 remotediff test 2022-09-08 00:24:19 +03:00
Sergey Cherepanov d08510b8d1 drpc space service, new pool, new peer 2022-09-08 00:00:46 +03:00
Sergey Cherepanov 4f3c8334be change proto generator to std gogo: fix tests 2022-09-07 19:49:54 +03:00
Sergey Cherepanov d157c561b7 change proto generator to std gogo 2022-09-07 19:43:01 +03:00
mcrakhman 2d00719a05 Few more changes and tree fixes 2022-09-09 09:45:25 +02:00
mcrakhman b1c6c7e44e Change tree Rebuild checks 2022-09-09 09:01:50 +02:00
mcrakhman e384d549a2 Refactor ACLState a little bit 2022-09-08 23:20:11 +02:00
mcrakhman bf4e4de38c Fix tests 2022-09-08 08:08:33 +02:00
mcrakhman 89715bdca7 Make stuff compile 2022-09-08 07:50:52 +02:00
mcrakhman 2ea1ddddb8 Add load from database tests and fixes 2022-09-07 23:23:59 +02:00
mcrakhman 77e4f9b6f9 More tests and fixes for reading from tree 2022-09-07 22:30:29 +02:00
mcrakhman cd0f601afa Change sync logic 2022-09-07 22:01:11 +02:00
mcrakhman 246e0aa674 WIP algorithm 2022-09-07 21:02:42 +02:00
mcrakhman dec7cc6ee3 Fix addcontent acl list locks 2022-09-07 11:31:16 +02:00
mcrakhman d175be5728 More test cases 2022-09-07 01:02:53 +02:00
mcrakhman 78537ce028 Add more tests and fix bugs 2022-09-07 00:12:11 +02:00
mcrakhman e4686d93e5 Add first test for object tree 2022-09-06 20:58:41 +02:00
mcrakhman b294d13254 Change storages and add iterate 2022-09-06 20:23:57 +02:00
mcrakhman b0f2f875aa Add mocks for tree 2022-09-06 19:25:06 +02:00
mcrakhman 266fd9436c Make better dependencies for object tree 2022-09-06 18:08:24 +02:00
mcrakhman d3e62b418a Rename to object tree 2022-09-05 13:08:49 +02:00
mcrakhman 257b9cba8e Fixing several copy issues and other stuff in doc tree 2022-09-05 12:35:54 +02:00
mcrakhman b026d228df Further changes to treebuilder etc 2022-09-05 11:48:50 +02:00
mcrakhman 9882ebc267 Fix algorithms and add tests 2022-09-04 17:05:00 +02:00
mcrakhman d52d539e9d Add tree reduce 2022-09-04 16:05:38 +02:00
mcrakhman c099e04434 Change tree iterate 2022-09-04 14:34:37 +02:00
mcrakhman cf63d29b42 Fix change validator 2022-08-23 00:21:46 +02:00
mcrakhman 07b17491cb Add ACL exchange logic 2022-08-22 23:53:14 +02:00
mcrakhman 28f4f03dff Add generated read keys 2022-08-21 17:43:22 +02:00
mcrakhman 1ab430d088 Import acl document from the start 2022-08-21 16:44:05 +02:00
mcrakhman 0efa617075 Add test for user remove 2022-08-21 15:50:06 +02:00
mcrakhman 0dcb95fbcd Add first ACL list test and other refactoring 2022-08-21 15:23:47 +02:00
mcrakhman a17b102756 Few tree fixes 2022-08-20 23:55:18 +02:00
mcrakhman fb64d4d840 Add tree tests and benchmarks 2022-08-20 23:22:01 +02:00
mcrakhman 0d6bbd9258 Optimize and simplify tree algorithm 2022-08-20 16:57:32 +02:00
mcrakhman 86118686ad Update code to work with acl lists and new models 2022-08-20 14:28:06 +02:00
mcrakhman 48a64edce7 Add some logic in validator 2022-08-19 22:31:13 +02:00
mcrakhman 8868bd92fe Add ACLList struct 2022-08-19 21:44:38 +02:00
mcrakhman a140834288 Change treeheader to header 2022-08-19 20:34:09 +02:00
mcrakhman 550d1826a9 Change list builder 2022-08-19 18:17:02 +02:00
mcrakhman 70184e1554 Create ACL list and update tree package 2022-08-18 23:03:27 +02:00
mcrakhman 38209fc1fb Add rollback after adding invalid changes 2022-08-16 00:57:38 +02:00
mcrakhman 1ea9f03f57 Refactoring doc tree 2022-08-15 23:31:00 +02:00
mcrakhman a957bd3864 Fix update document 2022-08-12 10:01:01 +02:00
mcrakhman 6986fb1633 Fix bugs related to docTree and aclTree 2022-08-11 19:04:29 +02:00
mcrakhman 19e1b6f22b Move treeheader to upper level in sync 2022-08-11 16:31:09 +02:00
mcrakhman 45a0e43fe4 Add storage and change API to include both tree types 2022-08-11 15:54:27 +02:00
mcrakhman 02ae77645d Update sync layer to work with different trees 2022-08-11 15:33:39 +02:00
mcrakhman f50c876ec8 Rewrite document service methods 2022-08-11 11:04:03 +02:00
mcrakhman e709febfb2 Start tree cache and document rewrite 2022-08-10 22:16:16 +02:00
mcrakhman e4ad5b6485 Add doc tree 2022-08-10 18:10:28 +02:00
mcrakhman 660f8d839b Further work acltree, aclstate etc 2022-08-10 14:48:17 +02:00
mcrakhman 3eedbf4188 Add read and write methods for treecache 2022-08-10 12:53:15 +02:00
mcrakhman 588bab92a0 Add tree removal for state build 2022-08-10 12:00:22 +02:00
mcrakhman 110ec01b0c Add acltree in new package 2022-08-10 11:52:45 +02:00
mcrakhman 307f517ee5 Start refactoring tree package 2022-08-09 21:48:14 +02:00
Sergey Cherepanov dfa19d2aaf WIP: files 2022-09-07 13:01:15 +03:00
Sergey Cherepanov 3d195f4b6b file bench service 2022-08-31 14:49:35 +03:00
Sergey Cherepanov f41a7f8c6a configuration to space 2022-08-24 13:53:32 +03:00
Sergey Cherepanov fe2c8bdade configuration from config 2022-08-24 00:40:14 +03:00
Sergey Cherepanov 8a058b8b23 configuration service 2022-08-23 21:32:04 +03:00
Sergey Cherepanov 44bfe5cdbe remove example service 2022-08-15 20:06:23 +03:00
Sergey Cherepanov b30038011f diff container and mod tidy 2022-08-15 20:03:30 +03:00
mcrakhman 84d1f0df06 Fix sync logic, move acks after handling finished, made message service async 2022-08-06 10:22:09 +02:00
mcrakhman 1a892b05cf Fix replyId update and add goroutine in handleMessage 2022-08-05 21:05:47 +02:00
mcrakhman 1c81926f35 Fix sync bugs and add logs 2022-08-05 18:14:35 +02:00
mcrakhman 203719b999 Add contexts and fix bugs 2022-08-05 17:09:40 +02:00
mcrakhman 1d1045ec03 Change dialling logic 2022-08-05 16:33:37 +02:00
mcrakhman 3f4c50bce4 Fix compile errors and add dependencies 2022-08-05 14:19:03 +02:00
mcrakhman 26704a72e9 Add message to syncproto, fix request handler 2022-08-05 13:44:23 +02:00
Sergey Cherepanov ebfdbdd508 net utils wip 2022-08-05 12:07:34 +03:00
mcrakhman 2588697197 Add indication in tree graph for non-acl changes 2022-08-03 14:47:31 +02:00
mcrakhman 634ab66aa4 Change config generation logic 2022-08-03 14:20:20 +02:00
mcrakhman 85cdb1c189 Add logs for receiving a message, descriptive messages for api 2022-08-02 20:50:35 +02:00
mcrakhman 0e9f5423d3 Fix server starting, add some logs 2022-08-02 20:28:33 +02:00
mcrakhman d5c2ad63fc Change bootstrap, remove cycles 2022-08-02 20:08:07 +02:00
mcrakhman b8b52462e1 Add http server for debugging 2022-08-02 19:28:32 +02:00
mcrakhman 95a269c8d2 Add component registration on start 2022-08-02 17:36:37 +02:00
mcrakhman a379a56cd1 Add nodes generation 2022-08-02 17:30:59 +02:00
mcrakhman 35528f6024 Add document service 2022-07-21 11:54:19 +02:00
mcrakhman 57c09e263d Update tree cache for document creation 2022-07-21 11:03:39 +02:00
mcrakhman b8016c1d44 WIP sync logic prototype with server 2022-07-19 10:40:31 +02:00
mcrakhman 2ec62c6d77 WIP stream logic 2022-07-19 01:43:21 +02:00
mcrakhman 394fef40ca Add message service to handle streams 2022-07-19 00:47:22 +02:00
mcrakhman e42e0843e7 Use account key instead of generating new 2022-07-18 22:27:27 +02:00
Sergey Cherepanov 5ecff24268 drpc server, libp2p tls connections 2022-07-17 09:44:16 +03:00
Sergey Cherepanov c56af9f87c remove drpc 2022-07-15 12:35:54 +03:00
Sergey Cherepanov 15af9eec36 drpc experiment 2022-07-14 16:59:01 +03:00
Sergey Cherepanov cff04397d1 grpc server and test client implementation 2022-07-14 00:22:28 +03:00
mcrakhman 55d07ca757 Add oneof to sync proto 2022-07-18 10:45:05 +02:00
mcrakhman 65114e2ea9 Create new tree logic 2022-07-17 13:34:07 +02:00
mcrakhman 9d106fee5b WIP new document logic 2022-07-16 14:24:27 +02:00
mcrakhman 4d935a0a3b Move logic to different components 2022-07-16 13:21:46 +02:00
mcrakhman 46cee8c9ed Add getting common snapshot logic 2022-07-16 12:24:49 +02:00
mcrakhman 24c59e40ee Provide for getting new tree 2022-07-16 00:50:08 +02:00
mcrakhman 0474bd37a1 Fix request handler heads update logic 2022-07-15 18:10:31 +02:00
mcrakhman 5b821c0d2a Start using raw changes + wip request handler 2022-07-15 16:27:10 +02:00
mcrakhman e7469c848c WIP handling sync requests 2022-07-15 10:14:39 +02:00
mcrakhman 9247866bb6 Use pb rawchanges instead of treestorage.RawChange 2022-07-15 09:39:16 +02:00
mcrakhman 9b15cbaaca WIP sync proto 2022-07-14 23:52:37 +02:00
mcrakhman 5395188ccf Simplify code in decoders 2022-07-14 21:29:10 +02:00
mcrakhman d0dfb9d8be Add tree cache service 2022-07-14 21:23:40 +02:00
mcrakhman d2f2cdb40c Add account reading from file logic 2022-07-14 21:07:34 +02:00
mcrakhman 1ee6a4c7cb WIP pubsub and sync logic 2022-07-14 14:28:37 +02:00
mcrakhman bc1a0cf678 Rename pb packages to something more meaningful 2022-07-14 13:16:12 +02:00
mcrakhman 49a9edfe6c Add context to acl tree 2022-07-14 11:21:52 +02:00
mcrakhman 883e51c84d Rename keys 2022-07-13 22:57:12 +02:00
mcrakhman d5a801cff5 Remove go-threads 2022-07-13 22:54:47 +02:00
mcrakhman 20df88bbde Change decoder logic 2022-07-13 22:36:56 +02:00
mcrakhman 90b884534c Add error in all methods of tree storage 2022-07-13 22:25:32 +02:00
mcrakhman acff22a19e Remove last thread ids 2022-07-13 22:15:07 +02:00
mcrakhman 59977218e2 Change thread to tree storage and other changes with protos 2022-07-13 22:11:38 +02:00
mcrakhman 2ac69e480e Update keys 2022-07-13 20:52:56 +02:00
Sergey Cherepanov 8265774754 add pkg/ocache 2022-07-13 21:10:48 +03:00
Sergey Cherepanov a23d8933b7 gitignore for bin 2022-07-13 20:13:13 +03:00
Sergey Cherepanov 231c571787 project structure proposal 2022-07-13 20:09:19 +03:00
Sergey Cherepanov a06924829e fix go.sum 2022-07-13 19:23:16 +03:00
Sergey Cherepanov 7323df8ca9 inital structure: app, config, node binary 2022-07-13 19:18:43 +03:00
Sergey Cherepanov d2c9fd4500 move acl tree files to acl folder 2022-07-13 19:17:50 +03:00
mcrakhman de775e95b0 Remove unneded plaintextdocument file 2022-07-13 09:00:10 +02:00
mcrakhman 3d95401a98 Improve test 2022-07-12 23:13:51 +02:00
mcrakhman c5fbe7e56b Add document tests 2022-07-12 23:07:05 +02:00
mcrakhman acc2699b0e Add test for thread utility 2022-07-12 21:27:27 +02:00
mcrakhman dbfb8cbabc Change InviteId logic and add UserAdd to change builder 2022-07-12 20:25:13 +02:00
mcrakhman df6f3659fd Add test for rebuild useCase 2022-07-12 17:46:08 +02:00
mcrakhman 8c9a501bc8 Add first update test 2022-07-12 16:57:02 +02:00
mcrakhman 029fead117 Change updates format in yaml 2022-07-12 16:33:22 +02:00
mcrakhman 6490f09662 Fix header parse and add todos 2022-07-12 13:26:06 +02:00
mcrakhman d11af440a4 Simplify paths to yaml tests 2022-07-12 10:55:09 +02:00
mcrakhman 97829406fe Change first thread entry handling 2022-07-12 10:27:02 +02:00
mcrakhman af653405d0 Add synchronization to inmemory store 2022-07-12 09:01:06 +02:00
mcrakhman 67dba37ea3 WIP plaintextdocument 2022-07-12 08:55:30 +02:00
mcrakhman 02d2b948a3 WIP plain text document 2022-07-12 08:45:13 +02:00
mcrakhman 0a118c4891 Add inmemory thread and build logic 2022-07-12 00:30:24 +02:00
mcrakhman 3424f15a02 Update changebuilder to include CID creation 2022-07-11 23:56:13 +02:00
mcrakhman de1e6af58f Add thread header and make snapshot 2022-07-11 23:40:44 +02:00
mcrakhman b411c3501e Add tree updater to ACL tree 2022-07-11 11:28:58 +02:00
mcrakhman b07a137fb9 WIP ACLTree tests 2022-07-10 17:33:27 +02:00
mcrakhman 8372abb4e1 Change ACLTree methods to include change builder 2022-07-10 17:14:15 +02:00
mcrakhman 43797b1b84 Add logic update and create logic 2022-07-10 15:53:50 +02:00
mcrakhman e6534e134b WIP Change interface rework 2022-07-10 11:44:02 +02:00
mcrakhman 8570c4e318 WIP ACLTree 2022-07-08 15:13:01 +02:00
mcrakhman ed06aadd21 WIP ACLTree 2022-07-08 12:31:06 +02:00
mcrakhman 45024837ff WIP ACLTree and moving files to packages 2022-07-08 11:33:32 +02:00
mcrakhman 3df678bc80 Continue adjusting folder structure 2022-07-08 10:52:06 +02:00
mcrakhman 2a1e3241dd WIP change package structure 2022-07-08 10:19:43 +02:00
mcrakhman 8f704e6de0 WIP create change continue 2022-07-07 11:32:02 +02:00
mcrakhman 2f66bd3e9c WIP change creation logic 2022-07-07 11:11:34 +02:00
mcrakhman eea64a3014 Add simple update test 2022-07-07 09:18:55 +02:00
mcrakhman c2bd392c84 Add document build test 2022-07-07 08:53:47 +02:00
mcrakhman 475904a266 Add updated changes to thread builder 2022-07-07 08:47:11 +02:00
mcrakhman f15c806415 Fix some bugs 2022-07-06 23:35:43 +02:00
mcrakhman 2940519b5f Add new heads logic 2022-07-06 23:24:11 +02:00
mcrakhman 0e62047498 WIP document update 2022-07-06 21:51:00 +02:00
mcrakhman 2bd5bf742d Add Build method to document 2022-07-05 18:09:45 +02:00
mcrakhman 1ed6a6f7da Change init logic and fix tests 2022-07-05 17:21:45 +02:00
mcrakhman 092e9096b8 WIP change init logic 2022-07-05 15:58:11 +02:00
mcrakhman 6f53ceb768 Unify changes loading in change loader 2022-07-05 15:42:17 +02:00
mcrakhman f64db4b867 WIP work on document structure 2022-07-05 11:59:01 +02:00
mcrakhman cfa735604d Add some comments in tests 2022-07-04 13:42:29 +02:00
mcrakhman 11cd75fe17 Further fixes including tests 2022-07-04 13:28:16 +02:00
mcrakhman 64a78a96cb Fix tree builder bugs related to dfs 2022-07-04 11:22:37 +02:00
mcrakhman 70b2068f3f Fix first ACL test and threadbuilder 2022-07-03 18:15:33 +02:00
mcrakhman 43dbdd8d68 Add plain text document state 2022-07-03 17:51:14 +02:00
mcrakhman fefbdbd764 Fix threadbuilder and change examples 2022-07-03 17:35:21 +02:00
mcrakhman 1a1883d79a Make doc state building more abstract 2022-07-03 16:38:48 +02:00
mcrakhman ec05045246 Some more fixes for trees and stuff 2022-07-02 22:34:50 +02:00
mcrakhman 9278cd14d7 Further refactoring of ACL logic 2022-07-01 11:39:42 +02:00
mcrakhman 697fed7f84 Add some deps for threadbuilder 2022-07-01 10:54:47 +02:00
mcrakhman 4ac1f633d4 Rework threadbuilder for new change format 2022-06-30 18:52:20 +02:00
mcrakhman 53a9bb5fc5 Add makefile and proto generating 2022-06-29 18:30:44 +02:00
mcrakhman 210718c79b Start adding ACLs 2022-06-28 15:37:09 +02:00
Mikhail Rakhmanov 0fceefbc10 Initial commit 2022-06-28 13:37:24 +02:00
Mikhail Iudin 24b7495571 first commit 2022-06-21 00:07:29 +02:00
289 changed files with 34048 additions and 12456 deletions

No files matched your search

+1 -1
View File
@@ -10,5 +10,5 @@ updates:
schedule:
interval: "weekly"
ignore:
- dependency-name: "github.com/anytypeio/go-chash"
- 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/anytypeio
GOPRIVATE: github.com/anyproto
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
+21
View File
@@ -0,0 +1,21 @@
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.
+8 -3
View File
@@ -1,5 +1,5 @@
.PHONY: proto test deps
export GOPRIVATE=github.com/anytypeio
export GOPRIVATE=github.com/anyproto
export PATH:=deps:$(PATH)
proto:
@@ -7,15 +7,20 @@ proto:
@$(eval P_ACL_RECORDS_PATH_PB := commonspace/object/acl/aclrecordproto)
@$(eval P_TREE_CHANGES_PATH_PB := commonspace/object/tree/treechangeproto)
@$(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))
@$(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))
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
protoc --gogofaster_out=:. --go-drpc_out=protolib=github.com/gogo/protobuf:. consensus/consensusproto/protos/*.proto
deps:
go mod download
+7 -8
View File
@@ -1,23 +1,22 @@
//go:generate mockgen -destination mock_accountservice/mock_accountservice.go github.com/anytypeio/any-sync/accountservice Service
//go:generate mockgen -destination mock_accountservice/mock_accountservice.go github.com/anyproto/any-sync/accountservice Service
package accountservice
import (
"github.com/anytypeio/any-sync/app"
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
"github.com/anyproto/any-sync/app"
"github.com/anyproto/any-sync/commonspace/object/accountdata"
)
const CName = "common.accountservice"
type Service interface {
app.Component
Account() *accountdata.AccountData
Account() *accountdata.AccountKeys
}
type Config struct {
PeerId string `yaml:"peerId"`
PeerKey string `yaml:"peerKey"`
SigningKey string `yaml:"signingKey"`
EncryptionKey string `yaml:"encryptionKey"`
PeerId string `yaml:"peerId"`
PeerKey string `yaml:"peerKey"`
SigningKey string `yaml:"signingKey"`
}
type ConfigGetter interface {
+4 -4
View File
@@ -1,12 +1,12 @@
package mock_accountservice
import (
"github.com/anytypeio/any-sync/accountservice"
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
"github.com/golang/mock/gomock"
"github.com/anyproto/any-sync/accountservice"
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"go.uber.org/mock/gomock"
)
func NewAccountServiceWithAccount(ctrl *gomock.Controller, acc *accountdata.AccountData) *MockService {
func NewAccountServiceWithAccount(ctrl *gomock.Controller, acc *accountdata.AccountKeys) *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/anytypeio/any-sync/accountservice (interfaces: Service)
// Source: github.com/anyproto/any-sync/accountservice (interfaces: Service)
// Package mock_accountservice is a generated GoMock package.
package mock_accountservice
@@ -7,9 +7,9 @@ package mock_accountservice
import (
reflect "reflect"
app "github.com/anytypeio/any-sync/app"
accountdata "github.com/anytypeio/any-sync/commonspace/object/accountdata"
gomock "github.com/golang/mock/gomock"
app "github.com/anyproto/any-sync/app"
accountdata "github.com/anyproto/any-sync/commonspace/object/accountdata"
gomock "go.uber.org/mock/gomock"
)
// MockService is a mock of Service interface.
@@ -36,10 +36,10 @@ func (m *MockService) EXPECT() *MockServiceMockRecorder {
}
// Account mocks base method.
func (m *MockService) Account() *accountdata.AccountData {
func (m *MockService) Account() *accountdata.AccountKeys {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Account")
ret0, _ := ret[0].(*accountdata.AccountData)
ret0, _ := ret[0].(*accountdata.AccountKeys)
return ret0
}
+156 -26
View File
@@ -4,10 +4,11 @@ import (
"context"
"errors"
"fmt"
"github.com/anytypeio/any-sync/app/logger"
"github.com/anyproto/any-sync/app/logger"
"go.uber.org/zap"
"os"
"runtime"
"runtime/debug"
"strings"
"sync"
"time"
@@ -15,12 +16,15 @@ import (
var (
// values of this vars will be defined while compilation
GitCommit, GitBranch, GitState, GitSummary, BuildDate string
name string
AppName, GitCommit, GitBranch, GitState, GitSummary, BuildDate string
name string
)
var (
log = logger.NewNamed("app")
log = logger.NewNamed("app")
StopDeadline = time.Minute
StopWarningAfter = time.Second * 10
StartWarningAfter = time.Second * 10
)
// Component is a minimal interface for a common app.Component
@@ -51,10 +55,14 @@ 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 StartStat
deviceState int
parent *App
components []Component
mu sync.RWMutex
startStat Stat
stopStat Stat
deviceState int
versionName string
anySyncVersion string
}
// Name returns app name
@@ -62,23 +70,47 @@ 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 StartStat struct {
// SetVersionName sets the custom application version
func (app *App) SetVersionName(v string) {
app.versionName = v
}
// VersionName returns a string with the settled app version or auto-generated version if it didn't set
func (app *App) VersionName() string {
if app.versionName != "" {
return app.versionName
}
return AppName + ":" + GitSummary + "/any-sync:" + app.AnySyncVersion()
}
type Stat struct {
SpentMsPerComp map[string]int64
SpentMsTotal int64
}
// StartStat returns total time spent per comp
func (app *App) StartStat() StartStat {
// StartStat returns total time spent per comp for the last Start
func (app *App) StartStat() Stat {
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()
@@ -92,6 +124,16 @@ func VersionDescription() string {
return fmt.Sprintf("build on %s from %s at #%s(%s)", BuildDate, GitBranch, GitCommit, GitState)
}
// ChildApp creates a child container which has access to parent's components
// It doesn't call Start on any of the parent's components
func (app *App) ChildApp() *App {
return &App{
parent: app,
deviceState: app.deviceState,
anySyncVersion: app.AnySyncVersion(),
}
}
// Register adds service to registry
// All components will be started in the order they were registered
func (app *App) Register(s Component) *App {
@@ -111,10 +153,14 @@ func (app *App) Register(s Component) *App {
func (app *App) Component(name string) Component {
app.mu.RLock()
defer app.mu.RUnlock()
for _, s := range app.components {
if s.Name() == name {
return s
current := app
for current != nil {
for _, s := range current.components {
if s.Name() == name {
return s
}
}
current = current.parent
}
return nil
}
@@ -132,10 +178,14 @@ func (app *App) MustComponent(name string) Component {
func MustComponent[i any](app *App) i {
app.mu.RLock()
defer app.mu.RUnlock()
for _, s := range app.components {
if v, ok := s.(i); ok {
return v
current := app
for current != nil {
for _, s := range current.components {
if v, ok := s.(i); ok {
return v
}
}
current = current.parent
}
empty := new(i)
panic(fmt.Errorf("component with interface %T is not found", empty))
@@ -145,9 +195,13 @@ func MustComponent[i any](app *App) i {
func (app *App) ComponentNames() (names []string) {
app.mu.RLock()
defer app.mu.RUnlock()
names = make([]string, len(app.components))
for i, c := range app.components {
names[i] = c.Name()
names = make([]string, 0, len(app.components))
current := app
for current != nil {
for _, c := range current.components {
names = append(names, c.Name())
}
current = current.parent
}
return
}
@@ -158,7 +212,17 @@ 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 {
@@ -172,7 +236,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': %v", s.Name(), err)
return fmt.Errorf("can't init service '%s': %w", s.Name(), err)
}
}
@@ -181,17 +245,32 @@ 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': %v", serviceRun.Name(), err)
return fmt.Errorf("can't run service '%s': %w", serviceRun.Name(), err)
}
spent := time.Since(start).Milliseconds()
app.startStat.SpentMsTotal += spent
app.startStat.SpentMsPerComp[s.Name()] = spent
}
}
log.Debug("all components started")
close(done)
l := statLogger(app.stopStat, log)
if app.startStat.SpentMsTotal > StartWarningAfter.Milliseconds() {
l.Warn("all components started")
}
l.Debug("all components started")
return
}
// IterateComponents iterates over all registered components. It's safe for concurrent use.
func (app *App) IterateComponents(fn func(Component)) {
app.mu.RLock()
defer app.mu.RUnlock()
for _, s := range app.components {
fn(s)
}
}
func stackAllGoroutines() []byte {
buf := make([]byte, 1024)
for {
@@ -203,18 +282,41 @@ 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(time.Minute):
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):
_, _ = os.Stderr.Write([]byte("app.Close timeout\n"))
_, _ = os.Stderr.Write(stackAllGoroutines())
panic("app.Close timeout")
@@ -224,16 +326,27 @@ 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"))
}
log.Debug("all components have been closed")
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")
return nil
}
@@ -250,3 +363,20 @@ func (app *App) SetDeviceState(state int) {
}
}
}
var onceVersion sync.Once
func (app *App) AnySyncVersion() string {
onceVersion.Do(func() {
info, ok := debug.ReadBuildInfo()
if ok {
for _, mod := range info.Deps {
if mod.Path == "github.com/anyproto/any-sync" {
app.anySyncVersion = mod.Version
break
}
}
}
})
return app.anySyncVersion
}
+34
View File
@@ -34,6 +34,40 @@ func TestAppServiceRegistry(t *testing.T) {
names := app.ComponentNames()
assert.Equal(t, names, []string{"c1", "r1", "s1"})
})
t.Run("Child MustComponent", func(t *testing.T) {
app := app.ChildApp()
app.Register(newTestService(testTypeComponent, "x1", nil, nil))
for _, name := range []string{"c1", "r1", "s1", "x1"} {
assert.NotPanics(t, func() { app.MustComponent(name) }, name)
}
assert.Panics(t, func() { app.MustComponent("not-registered") })
})
t.Run("Child ComponentNames", func(t *testing.T) {
app := app.ChildApp()
app.Register(newTestService(testTypeComponent, "x1", nil, nil))
names := app.ComponentNames()
assert.Equal(t, names, []string{"x1", "c1", "r1", "s1"})
})
t.Run("Child override", func(t *testing.T) {
app := app.ChildApp()
app.Register(newTestService(testTypeRunnable, "s1", nil, nil))
_ = app.MustComponent("s1").(*testRunnable)
})
}
func TestApp_IterateComponents(t *testing.T) {
app := new(App)
app.Register(newTestService(testTypeRunnable, "c1", nil, nil))
app.Register(newTestService(testTypeRunnable, "r1", nil, nil))
app.Register(newTestService(testTypeComponent, "s1", nil, nil))
var got []string
app.IterateComponents(func(s Component) {
got = append(got, s.Name())
})
assert.ElementsMatch(t, []string{"c1", "r1", "s1"}, got)
}
func TestAppStart(t *testing.T) {
+1 -1
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/anytypeio/any-sync/app/ldiff Diff,Remote
//go:generate mockgen -destination mock_ldiff/mock_ldiff.go github.com/anyproto/any-sync/app/ldiff Diff,Remote
package ldiff
import (
+5 -5
View File
@@ -3,9 +3,9 @@ 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 +44,7 @@ func TestDiff_Diff(t *testing.T) {
d2 := New(16, 16)
for i := 0; i < 1000; i++ {
id := fmt.Sprint(i)
head := bson.NewObjectId().Hex()
head := uuid.NewString()
d1.Set(Element{
Id: id,
Head: head,
@@ -92,7 +92,7 @@ func TestDiff_Diff(t *testing.T) {
for i := 0; i < 10; i++ {
d2.Set(Element{
Id: fmt.Sprint(i),
Head: bson.NewObjectId().Hex(),
Head: uuid.NewString(),
})
}
@@ -108,7 +108,7 @@ func TestDiff_Diff(t *testing.T) {
for i := 0; i < 10; i++ {
d2.Set(Element{
Id: fmt.Sprint(i),
Head: bson.NewObjectId().Hex(),
Head: uuid.NewString(),
})
}
var cancel func()
@@ -123,7 +123,7 @@ func BenchmarkDiff_Ranges(b *testing.B) {
d := New(16, 16)
for i := 0; i < 10000; i++ {
id := fmt.Sprint(i)
head := bson.NewObjectId().Hex()
head := uuid.NewString()
d.Set(Element{
Id: id,
Head: head,
+3 -3
View File
@@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/anytypeio/any-sync/app/ldiff (interfaces: Diff,Remote)
// Source: github.com/anyproto/any-sync/app/ldiff (interfaces: Diff,Remote)
// Package mock_ldiff is a generated GoMock package.
package mock_ldiff
@@ -8,8 +8,8 @@ import (
context "context"
reflect "reflect"
ldiff "github.com/anytypeio/any-sync/app/ldiff"
gomock "github.com/golang/mock/gomock"
ldiff "github.com/anyproto/any-sync/app/ldiff"
gomock "go.uber.org/mock/gomock"
)
// MockDiff is a mock of Diff interface.
+109 -18
View File
@@ -1,33 +1,124 @@
package logger
import "go.uber.org/zap"
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"`
}
type Config struct {
Production bool `yaml:"production"`
DefaultLevel string `yaml:"defaultLevel"`
NamedLevels map[string]string `yaml:"namedLevels"`
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
}
func (l Config) ApplyGlobal() {
var conf zap.Config
if l.Production {
conf = zap.NewProductionConfig()
if l.ZapConfig != nil {
conf = *l.ZapConfig
} else {
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
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
}
}
defaultLogger, err := conf.Build()
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()
if err != nil {
Default().Fatal("can't build logger", zap.Error(err))
}
SetDefault(defaultLogger)
SetNamedLevels(levels)
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
}
+6 -1
View File
@@ -32,6 +32,7 @@ func CtxGetFields(ctx context.Context) (fields []zap.Field) {
type CtxLogger struct {
*zap.Logger
name string
}
func (cl CtxLogger) DebugCtx(ctx context.Context, msg string, fields ...zap.Field) {
@@ -51,5 +52,9 @@ func (cl CtxLogger) ErrorCtx(ctx context.Context, msg string, fields ...zap.Fiel
}
func (cl CtxLogger) With(fields ...zap.Field) CtxLogger {
return CtxLogger{cl.Logger.With(fields...)}
return CtxLogger{cl.Logger.With(fields...), cl.name}
}
func (cl CtxLogger) Sugar() *zap.SugaredLogger {
return NewNamedSugared(cl.name)
}
+106 -20
View File
@@ -1,51 +1,137 @@
package logger
import (
"go.uber.org/zap"
"sync"
"github.com/gobwas/glob"
"go.uber.org/zap"
)
var (
mu sync.Mutex
defaultLogger *zap.Logger
levels = make(map[string]zap.AtomicLevel)
loggers = make(map[string]CtxLogger)
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)
)
func init() {
defaultLogger, _ = zap.NewDevelopment()
zap.NewProduction()
type namedLevel struct {
name string
level zap.AtomicLevel
}
func init() {
loggerConfig = zap.NewDevelopmentConfig()
logger, _ = loggerConfig.Build()
}
// 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()
*defaultLogger = *l
for name, l := range loggers {
*l.Logger = *defaultLogger.Named(name)
}
*logger = *l
}
func SetNamedLevels(l map[string]zap.AtomicLevel) {
// 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) {
mu.Lock()
defer mu.Unlock()
levels = l
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
}
}
func Default() *zap.Logger {
mu.Lock()
defer mu.Unlock()
return defaultLogger
return logger
}
// getLevel returns the level for the given name
// it return the first matching name or glob pattern whatever comes first
func getLevel(name string) zap.AtomicLevel {
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 {
mu.Lock()
defer mu.Unlock()
l := defaultLogger.Named(name)
if len(fields) > 0 {
l = l.With(fields...)
if l, nameExists := namedLoggers[name]; nameExists {
return l
}
ctxL := CtxLogger{l}
loggers[name] = ctxL
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
return l
}
+150
View File
@@ -0,0 +1,150 @@
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)
}
})
}
}
+136
View File
@@ -0,0 +1,136 @@
package ocache
import (
"context"
"sync"
"time"
"go.uber.org/zap"
)
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
cancel context.CancelFunc
}
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) setCancel(cancel context.CancelFunc) {
e.mx.Lock()
defer e.mx.Unlock()
e.cancel = cancel
}
func (e *entry) cancelLoad() {
e.mx.Lock()
defer e.mx.Unlock()
if e.cancel != nil {
e.cancel()
}
}
func (e *entry) waitLoad(ctx context.Context, id string) (value Object, err error) {
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
}
+12 -4
View File
@@ -1,14 +1,22 @@
package ocache
import "github.com/prometheus/client_golang/prometheus"
import (
"github.com/prometheus/client_golang/prometheus"
"strings"
)
func WithPrometheus(reg *prometheus.Registry, namespace, subsystem string) Option {
if subsystem == "" {
subsystem = "cache"
}
if reg == nil {
return nil
}
if subsystem == "" {
subsystem = "cache"
}
nameSplit := strings.Split(namespace, ".")
subSplit := strings.Split(subsystem, ".")
namespace = strings.Join(nameSplit, "_")
subsystem = strings.Join(subSplit, "_")
return func(cache *oCache) {
cache.metrics = &metrics{
hit: prometheus.NewCounter(prometheus.CounterOpts{
+19
View File
@@ -0,0 +1,19 @@
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"))
}
+95 -184
View File
@@ -3,10 +3,11 @@ package ocache
import (
"context"
"errors"
"github.com/anytypeio/any-sync/app/logger"
"go.uber.org/zap"
"sync"
"time"
"github.com/anyproto/any-sync/app/logger"
"go.uber.org/zap"
)
var (
@@ -44,12 +45,6 @@ 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),
@@ -73,33 +68,7 @@ func New(loadFunc LoadFunc, opts ...Option) OCache {
type Object interface {
Close() (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
TryClose(objectTTL time.Duration) (res bool, err error)
}
type OCache interface {
@@ -116,12 +85,8 @@ 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(id string) (ok bool, err error)
Remove(ctx context.Context, 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
@@ -134,17 +99,16 @@ type OCache interface {
}
type oCache struct {
mu sync.Mutex
data map[string]*entry
loadFunc LoadFunc
timeNow func() time.Time
ttl time.Duration
gc time.Duration
closed bool
closeCh chan struct{}
log *zap.SugaredLogger
metrics *metrics
refCounter bool
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
}
func (c *oCache) Get(ctx context.Context, id string) (value Object, err error) {
@@ -160,72 +124,44 @@ 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
}
closing := e.isClosing
if !e.isClosing {
e.lastUsage = c.timeNow()
if c.refCounter {
e.refCount++
}
}
e.lastUsage = time.Now()
c.mu.Unlock()
if closing {
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-e.close:
goto Load
}
reload, err := e.waitClose(ctx, id)
if err != nil {
return nil, err
}
if reload {
goto Load
}
if load {
go c.load(ctx, id, e)
}
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
c.metricsGet(!load)
return e.waitLoad(ctx, id)
}
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()
if c.metrics != nil {
c.metrics.hit.Inc()
}
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-val.load:
return val.value, val.loadErr
}
c.metricsGet(true)
return val.waitLoad(ctx, id)
}
func (c *oCache) load(ctx context.Context, id string, e *entry) {
defer close(e.load)
ctx, cancel := context.WithCancel(ctx)
e.setCancel(cancel)
value, err := c.loadFunc(ctx, id)
cancel()
c.mu.Lock()
defer c.mu.Unlock()
@@ -234,63 +170,39 @@ 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) 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) {
func (c *oCache) Remove(ctx context.Context, id string) (ok bool, err error) {
c.mu.Lock()
if c.closed {
c.mu.Unlock()
err = ErrClosed
return
}
var e *entry
e, ok = c.data[id]
if !ok || e.isClosing {
e, ok := c.data[id]
if !ok {
c.mu.Unlock()
return
return false, ErrNotExists
}
e.isClosing = true
e.close = make(chan struct{})
c.mu.Unlock()
return c.remove(ctx, e)
}
<-e.load
if e.value != nil {
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
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
}
@@ -312,13 +224,7 @@ func (c *oCache) Add(id string, value Object) (err error) {
if _, ok := c.data[id]; ok {
return ErrExists
}
e := &entry{
id: id,
lastUsage: time.Now(),
refCount: 0,
load: make(chan struct{}),
value: value,
}
e := newEntry(id, value, entryStateActive)
close(e.load)
c.data[id] = e
return
@@ -330,7 +236,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:
@@ -366,40 +272,35 @@ func (c *oCache) GC() {
deadline := c.timeNow().Add(-c.ttl)
var toClose []*entry
for _, e := range c.data {
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
if e.isActive() && e.lastUsage.Before(deadline) {
e.close = make(chan struct{})
toClose = append(toClose, e)
}
}
size := len(c.data)
c.mu.Unlock()
closedNum := 0
for _, e := range toClose {
<-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)
}
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()
}
}
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()
c.metricsClosed(closedNum, size)
}
func (c *oCache) Len() int {
@@ -416,25 +317,35 @@ func (c *oCache) Close() (err error) {
}
c.closed = true
close(c.closeCh)
var toClose, alreadyClosing []*entry
var toClose []*entry
for _, e := range c.data {
if e.isClosing {
alreadyClosing = append(alreadyClosing, e)
} else {
toClose = append(toClose, e)
}
e.cancelLoad()
toClose = append(toClose, e)
}
c.mu.Unlock()
for _, e := range toClose {
<-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)
}
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)
}
}
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))
}
+354 -50
View File
@@ -3,6 +3,8 @@ package ocache
import (
"context"
"errors"
"fmt"
"sync"
"sync/atomic"
"testing"
"time"
@@ -11,26 +13,48 @@ import (
"github.com/stretchr/testify/require"
)
var ctx = context.Background()
type testObject struct {
name string
closeErr error
closeCh chan struct{}
name string
closeErr error
closeCh chan struct{}
tryReturn bool
closeCalled bool
tryCloseCalled bool
}
func NewTestObject(name string, closeCh chan struct{}) *testObject {
func NewTestObject(name string, tryReturn bool, closeCh chan struct{}) *testObject {
return &testObject{
name: name,
closeCh: closeCh,
name: name,
closeCh: closeCh,
tryReturn: tryReturn,
}
}
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) {
@@ -116,42 +140,37 @@ func TestOCache_Get(t *testing.T) {
}
func TestOCache_GC(t *testing.T) {
t.Run("test without close wait", func(t *testing.T) {
t.Run("test gc expired object", func(t *testing.T) {
c := New(func(ctx context.Context, id string) (value Object, err error) {
return &testObject{name: id}, nil
}, WithTTL(time.Millisecond*10), WithRefCounter(true))
return NewTestObject(id, true, nil), 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())
c.GC()
assert.Equal(t, 1, c.Len())
time.Sleep(time.Millisecond * 30)
c.GC()
assert.Equal(t, 1, c.Len())
assert.True(t, c.Release("id"))
time.Sleep(time.Millisecond * 20)
c.GC()
assert.Equal(t, 0, c.Len())
assert.False(t, c.Release("id"))
})
t.Run("test with close wait", func(t *testing.T) {
t.Run("test gc tryClose true, close before get", 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, closeCh), nil
}, WithTTL(time.Millisecond*10), WithRefCounter(true))
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())
assert.True(t, c.Release("id"))
// making ttl pass
time.Sleep(time.Millisecond * 40)
time.Sleep(time.Millisecond * 20)
// 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 * 40)
time.Sleep(time.Millisecond * 20)
var events []string
go func() {
_, err := c.Get(context.TODO(), "id")
@@ -160,47 +179,332 @@ func TestOCache_GC(t *testing.T) {
events = append(events, "get")
close(getCh)
}()
events = append(events, "close")
// sleeping to make sure that Get is called
time.Sleep(time.Millisecond * 40)
time.Sleep(time.Millisecond * 20)
events = append(events, "close")
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) {
closeCh := make(chan struct{})
getCh := make(chan struct{})
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))
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")
val, err := c.Get(context.TODO(), "id")
require.NoError(t, err)
require.NotNil(t, val)
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)
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)
<-getCh
require.Equal(t, []string{"close", "get"}, events)
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 TestOCacheCancelWhenRemove(t *testing.T) {
c := New(func(ctx context.Context, id string) (value Object, err error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
}
}, WithTTL(time.Millisecond*10))
stopLoad := make(chan struct{})
var err error
go func() {
_, err = c.Get(context.TODO(), "id")
stopLoad <- struct{}{}
}()
time.Sleep(time.Millisecond * 10)
c.Close()
<-stopLoad
require.Equal(t, context.Canceled, err)
}
func TestOCacheFuzzy(t *testing.T) {
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())
})
}
+12 -2
View File
@@ -2,8 +2,8 @@ package fileblockstore
import (
"context"
"github.com/anytypeio/any-sync/app/logger"
"github.com/anytypeio/any-sync/commonfile/fileproto/fileprotoerr"
"github.com/anyproto/any-sync/app/logger"
"github.com/anyproto/any-sync/commonfile/fileproto/fileprotoerr"
blocks "github.com/ipfs/go-block-format"
"github.com/ipfs/go-cid"
)
@@ -21,6 +21,7 @@ type ctxKey uint
const (
ctxKeySpaceId ctxKey = iota
ctxKeyFileId
)
type BlockStore interface {
@@ -48,3 +49,12 @@ 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
+96 -16
View File
@@ -1,5 +1,5 @@
// Code generated by protoc-gen-go-drpc. DO NOT EDIT.
// protoc-gen-go-drpc version: v0.0.32
// protoc-gen-go-drpc version: v0.0.33
// source: commonfile/fileproto/protos/file.proto
package fileproto
@@ -44,8 +44,10 @@ type DRPCFileClient interface {
BlockPush(ctx context.Context, in *BlockPushRequest) (*BlockPushResponse, error)
BlocksCheck(ctx context.Context, in *BlocksCheckRequest) (*BlocksCheckResponse, error)
BlocksBind(ctx context.Context, in *BlocksBindRequest) (*BlocksBindResponse, error)
BlocksDelete(ctx context.Context, in *BlocksDeleteRequest) (*BlocksDeleteResponse, error)
FilesDelete(ctx context.Context, in *FilesDeleteRequest) (*FilesDeleteResponse, error)
FilesInfo(ctx context.Context, in *FilesInfoRequest) (*FilesInfoResponse, error)
Check(ctx context.Context, in *CheckRequest) (*CheckResponse, error)
SpaceInfo(ctx context.Context, in *SpaceInfoRequest) (*SpaceInfoResponse, error)
}
type drpcFileClient struct {
@@ -94,9 +96,18 @@ func (c *drpcFileClient) BlocksBind(ctx context.Context, in *BlocksBindRequest)
return out, nil
}
func (c *drpcFileClient) BlocksDelete(ctx context.Context, in *BlocksDeleteRequest) (*BlocksDeleteResponse, error) {
out := new(BlocksDeleteResponse)
err := c.cc.Invoke(ctx, "/filesync.File/BlocksDelete", drpcEncoding_File_commonfile_fileproto_protos_file_proto{}, in, out)
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)
if err != nil {
return nil, err
}
@@ -112,13 +123,24 @@ func (c *drpcFileClient) Check(ctx context.Context, in *CheckRequest) (*CheckRes
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)
if err != nil {
return nil, err
}
return out, nil
}
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)
BlocksDelete(context.Context, *BlocksDeleteRequest) (*BlocksDeleteResponse, error)
FilesDelete(context.Context, *FilesDeleteRequest) (*FilesDeleteResponse, error)
FilesInfo(context.Context, *FilesInfoRequest) (*FilesInfoResponse, error)
Check(context.Context, *CheckRequest) (*CheckResponse, error)
SpaceInfo(context.Context, *SpaceInfoRequest) (*SpaceInfoResponse, error)
}
type DRPCFileUnimplementedServer struct{}
@@ -139,7 +161,11 @@ func (s *DRPCFileUnimplementedServer) BlocksBind(context.Context, *BlocksBindReq
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
}
func (s *DRPCFileUnimplementedServer) BlocksDelete(context.Context, *BlocksDeleteRequest) (*BlocksDeleteResponse, error) {
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) {
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
}
@@ -147,9 +173,13 @@ 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 6 }
func (DRPCFileDescription) NumMethods() int { return 8 }
func (DRPCFileDescription) Method(n int) (string, drpc.Encoding, drpc.Receiver, interface{}, bool) {
switch n {
@@ -190,15 +220,24 @@ func (DRPCFileDescription) Method(n int) (string, drpc.Encoding, drpc.Receiver,
)
}, DRPCFileServer.BlocksBind, true
case 4:
return "/filesync.File/BlocksDelete", drpcEncoding_File_commonfile_fileproto_protos_file_proto{},
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).
BlocksDelete(
FilesDelete(
ctx,
in1.(*BlocksDeleteRequest),
in1.(*FilesDeleteRequest),
)
}, DRPCFileServer.BlocksDelete, true
}, 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{},
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
return srv.(DRPCFileServer).
@@ -207,6 +246,15 @@ 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
}
@@ -280,16 +328,32 @@ func (x *drpcFile_BlocksBindStream) SendAndClose(m *BlocksBindResponse) error {
return x.CloseSend()
}
type DRPCFile_BlocksDeleteStream interface {
type DRPCFile_FilesDeleteStream interface {
drpc.Stream
SendAndClose(*BlocksDeleteResponse) error
SendAndClose(*FilesDeleteResponse) error
}
type drpcFile_BlocksDeleteStream struct {
type drpcFile_FilesDeleteStream struct {
drpc.Stream
}
func (x *drpcFile_BlocksDeleteStream) SendAndClose(m *BlocksDeleteResponse) error {
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 {
if err := x.MsgSend(m, drpcEncoding_File_commonfile_fileproto_protos_file_proto{}); err != nil {
return err
}
@@ -311,3 +375,19 @@ 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,12 +2,16 @@ package fileprotoerr
import (
"fmt"
"github.com/anytypeio/any-sync/commonfile/fileproto"
"github.com/anytypeio/any-sync/net/rpc/rpcerr"
"github.com/anyproto/any-sync/commonfile/fileproto"
"github.com/anyproto/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))
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))
)
+50 -12
View File
@@ -6,6 +6,10 @@ option go_package = "commonfile/fileproto";
enum ErrCodes {
Unexpected = 0;
CIDNotFound = 1;
Forbidden = 2;
SpaceLimitExceeded = 3;
QuerySizeExceeded = 4;
WrongHash = 5;
ErrorOffset = 200;
}
@@ -18,10 +22,14 @@ service File {
rpc BlocksCheck(BlocksCheckRequest) returns (BlocksCheckResponse);
// BlocksBind binds CIDs to space
rpc BlocksBind(BlocksBindRequest) returns (BlocksBindResponse);
// BlocksDelete deletes block from space
rpc BlocksDelete(BlocksDeleteRequest) returns (BlocksDeleteResponse);
// 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
rpc Check(CheckRequest) returns (CheckResponse);
// SpaceInfo returns usage, limit, etc about space
rpc SpaceInfo(SpaceInfoRequest) returns (SpaceInfoResponse);
}
message BlockGetRequest {
@@ -36,19 +44,13 @@ message BlockGetResponse {
message BlockPushRequest {
string spaceId = 1;
bytes cid = 2;
bytes data = 3;
string fileId = 2;
bytes cid = 3;
bytes data = 4;
}
message BlockPushResponse {}
message BlocksDeleteRequest {
string spaceId = 1;
repeated bytes cids = 2;
}
message BlocksDeleteResponse {}
message BlocksCheckRequest {
string spaceId = 1;
@@ -72,14 +74,50 @@ enum AvailabilityStatus {
message BlocksBindRequest {
string spaceId = 1;
repeated bytes cids = 2;
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 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;
}
+1 -1
View File
@@ -2,7 +2,7 @@ package fileservice
import (
"context"
"github.com/anytypeio/any-sync/commonfile/fileblockstore"
"github.com/anyproto/any-sync/commonfile/fileblockstore"
blocks "github.com/ipfs/go-block-format"
"github.com/ipfs/go-blockservice"
"github.com/ipfs/go-cid"
+8 -4
View File
@@ -3,9 +3,9 @@ package fileservice
import (
"context"
"fmt"
"github.com/anytypeio/any-sync/app"
"github.com/anytypeio/any-sync/app/logger"
"github.com/anytypeio/any-sync/commonfile/fileblockstore"
"github.com/anyproto/any-sync/app"
"github.com/anyproto/any-sync/app/logger"
"github.com/anyproto/any-sync/commonfile/fileblockstore"
"github.com/ipfs/go-cid"
chunker "github.com/ipfs/go-ipfs-chunker"
ipld "github.com/ipfs/go-ipld-format"
@@ -22,6 +22,10 @@ const CName = "common.commonfile.fileservice"
var log = logger.NewNamed(CName)
const (
ChunkSize = 1 << 20
)
func New() FileService {
return &fileService{}
}
@@ -74,7 +78,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.DefaultSplitter(r))
dbh, err := dbp.New(chunker.NewSizeSplitter(r, ChunkSize))
if err != nil {
return nil, err
}
-62
View File
@@ -1,62 +0,0 @@
package commonspace
import (
"context"
"github.com/anytypeio/any-sync/commonspace/object/syncobjectgetter"
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
"github.com/anytypeio/any-sync/commonspace/object/treegetter"
"sync/atomic"
)
type commonGetter struct {
treegetter.TreeGetter
spaceId string
reservedObjects []syncobjectgetter.SyncObject
spaceIsClosed *atomic.Bool
}
func newCommonGetter(spaceId string, getter treegetter.TreeGetter, spaceIsClosed *atomic.Bool) *commonGetter {
return &commonGetter{
TreeGetter: getter,
spaceId: spaceId,
spaceIsClosed: spaceIsClosed,
}
}
func (c *commonGetter) AddObject(object syncobjectgetter.SyncObject) {
c.reservedObjects = append(c.reservedObjects, object)
}
func (c *commonGetter) GetTree(ctx context.Context, spaceId, treeId string) (objecttree.ObjectTree, error) {
if c.spaceIsClosed.Load() {
return nil, ErrSpaceClosed
}
if obj := c.getReservedObject(treeId); obj != nil {
return obj.(objecttree.ObjectTree), nil
}
return c.TreeGetter.GetTree(ctx, spaceId, treeId)
}
func (c *commonGetter) getReservedObject(id string) syncobjectgetter.SyncObject {
for _, obj := range c.reservedObjects {
if obj != nil && obj.Id() == id {
return obj
}
}
return nil
}
func (c *commonGetter) GetObject(ctx context.Context, objectId string) (obj syncobjectgetter.SyncObject, err error) {
if c.spaceIsClosed.Load() {
return nil, ErrSpaceClosed
}
if obj := c.getReservedObject(objectId); obj != nil {
return obj, nil
}
t, err := c.TreeGetter.GetTree(ctx, c.spaceId, objectId)
if err != nil {
return
}
obj = t.(syncobjectgetter.SyncObject)
return
}
+2 -2
View File
@@ -1,8 +1,8 @@
package commonspace
import (
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
"github.com/anytypeio/any-sync/commonspace/spacestorage"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
"github.com/anyproto/any-sync/commonspace/spacestorage"
)
type commonStorage struct {
-10
View File
@@ -1,10 +0,0 @@
package commonspace
type ConfigGetter interface {
GetSpace() Config
}
type Config struct {
GCTTL int `yaml:"gcTTL"`
SyncPeriod int `yaml:"syncPeriod"`
}
+11
View File
@@ -0,0 +1,11 @@
package config
type ConfigGetter interface {
GetSpace() Config
}
type Config struct {
GCTTL int `yaml:"gcTTL"`
SyncPeriod int `yaml:"syncPeriod"`
KeepTreeDataInMemory bool `yaml:"keepTreeDataInMemory"`
}
@@ -0,0 +1,34 @@
//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/app"
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
)
const CName = "common.commonspace.credentialprovider"
func NewNoOp() CredentialProvider {
return &noOpProvider{}
}
type CredentialProvider interface {
app.Component
GetCredential(ctx context.Context, spaceHeader *spacesyncproto.RawSpaceHeaderWithId) ([]byte, error)
}
type noOpProvider struct {
}
func (n noOpProvider) Init(a *app.App) (err error) {
return nil
}
func (n noOpProvider) Name() (name string) {
return CName
}
func (n noOpProvider) GetCredential(ctx context.Context, spaceHeader *spacesyncproto.RawSpaceHeaderWithId) ([]byte, error) {
return nil, nil
}
@@ -0,0 +1,80 @@
// 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"
app "github.com/anyproto/any-sync/app"
spacesyncproto "github.com/anyproto/any-sync/commonspace/spacesyncproto"
gomock "go.uber.org/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)
}
// Init mocks base method.
func (m *MockCredentialProvider) Init(arg0 *app.App) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Init", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// Init indicates an expected call of Init.
func (mr *MockCredentialProviderMockRecorder) Init(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockCredentialProvider)(nil).Init), arg0)
}
// Name mocks base method.
func (m *MockCredentialProvider) Name() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Name")
ret0, _ := ret[0].(string)
return ret0
}
// Name indicates an expected call of Name.
func (mr *MockCredentialProviderMockRecorder) Name() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockCredentialProvider)(nil).Name))
}
+278
View File
@@ -0,0 +1,278 @@
package commonspace
import (
"context"
"fmt"
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
"github.com/anyproto/any-sync/commonspace/settings"
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
"github.com/anyproto/any-sync/commonspace/spacestorage"
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
"github.com/anyproto/any-sync/util/crypto"
"github.com/stretchr/testify/require"
"math/rand"
"testing"
"time"
)
func addIncorrectSnapshot(settingsObject settings.SettingsObject, acc *accountdata.AccountKeys, partialIds map[string]struct{}, newId string) (err error) {
factory := settingsstate.NewChangeFactory()
bytes, err := factory.CreateObjectDeleteChange(newId, &settingsstate.State{DeletedIds: partialIds}, true)
if err != nil {
return
}
ch, err := settingsObject.PrepareChange(objecttree.SignableChangeContent{
Data: bytes,
Key: acc.SignKey,
IsSnapshot: true,
IsEncrypted: false,
Timestamp: time.Now().Unix(),
})
if err != nil {
return
}
res, err := settingsObject.AddRawChanges(context.Background(), objecttree.RawChangesPayload{
NewHeads: []string{ch.Id},
RawChanges: []*treechangeproto.RawTreeChangeWithId{ch},
})
if err != nil {
return
}
if res.Mode != objecttree.Rebuild {
return fmt.Errorf("incorrect mode: %d", res.Mode)
}
return
}
func TestSpaceDeleteIds(t *testing.T) {
fx := newFixture(t)
acc := fx.account.Account()
rk := crypto.NewAES()
ctx := context.Background()
totalObjs := 1500
// creating space
sp, err := fx.spaceService.CreateSpace(ctx, SpaceCreatePayload{
SigningKey: acc.SignKey,
SpaceType: "type",
ReadKey: rk.Bytes(),
ReplicationKey: 10,
MasterKey: acc.PeerKey,
})
require.NoError(t, err)
require.NotNil(t, sp)
// initializing space
spc, err := fx.spaceService.NewSpace(ctx, sp)
require.NoError(t, err)
require.NotNil(t, spc)
// adding space to tree manager
fx.treeManager.space = spc
err = spc.Init(ctx)
require.NoError(t, err)
close(fx.treeManager.waitLoad)
var ids []string
for i := 0; i < totalObjs; i++ {
// creating a tree
bytes := make([]byte, 32)
rand.Read(bytes)
doc, err := spc.TreeBuilder().CreateTree(ctx, objecttree.ObjectTreeCreatePayload{
PrivKey: acc.SignKey,
ChangeType: "some",
SpaceId: spc.Id(),
IsEncrypted: false,
Seed: bytes,
Timestamp: time.Now().Unix(),
})
require.NoError(t, err)
tr, err := spc.TreeBuilder().PutTree(ctx, doc, nil)
require.NoError(t, err)
ids = append(ids, tr.Id())
tr.Close()
}
// deleting trees
for _, id := range ids {
err = spc.DeleteTree(ctx, id)
require.NoError(t, err)
}
time.Sleep(3 * time.Second)
spc.Close()
require.Equal(t, len(ids), len(fx.treeManager.deletedIds))
}
func createTree(t *testing.T, ctx context.Context, spc Space, acc *accountdata.AccountKeys) string {
bytes := make([]byte, 32)
rand.Read(bytes)
doc, err := spc.TreeBuilder().CreateTree(ctx, objecttree.ObjectTreeCreatePayload{
PrivKey: acc.SignKey,
ChangeType: "some",
SpaceId: spc.Id(),
IsEncrypted: false,
Seed: bytes,
Timestamp: time.Now().Unix(),
})
require.NoError(t, err)
tr, err := spc.TreeBuilder().PutTree(ctx, doc, nil)
require.NoError(t, err)
tr.Close()
return tr.Id()
}
func TestSpaceDeleteIdsIncorrectSnapshot(t *testing.T) {
fx := newFixture(t)
acc := fx.account.Account()
rk := crypto.NewAES()
ctx := context.Background()
totalObjs := 1500
partialObjs := 300
// creating space
sp, err := fx.spaceService.CreateSpace(ctx, SpaceCreatePayload{
SigningKey: acc.SignKey,
SpaceType: "type",
ReadKey: rk.Bytes(),
ReplicationKey: 10,
MasterKey: acc.PeerKey,
})
require.NoError(t, err)
require.NotNil(t, sp)
// initializing space
spc, err := fx.spaceService.NewSpace(ctx, sp)
require.NoError(t, err)
require.NotNil(t, spc)
// adding space to tree manager
fx.treeManager.space = spc
err = spc.Init(ctx)
close(fx.treeManager.waitLoad)
require.NoError(t, err)
settingsObject := spc.(*space).app.MustComponent(settings.CName).(settings.Settings).SettingsObject()
var ids []string
for i := 0; i < totalObjs; i++ {
id := createTree(t, ctx, spc, acc)
ids = append(ids, id)
}
// copying storage, so we will have all the trees locally
inmemory := spc.Storage().(*commonStorage).SpaceStorage.(*spacestorage.InMemorySpaceStorage)
storageCopy := inmemory.CopyStorage()
treesCopy := inmemory.AllTrees()
// deleting trees
for _, id := range ids {
err = spc.DeleteTree(ctx, id)
require.NoError(t, err)
}
mapIds := map[string]struct{}{}
for _, id := range ids[:partialObjs] {
mapIds[id] = struct{}{}
}
// adding snapshot that breaks the state
err = addIncorrectSnapshot(settingsObject, acc, mapIds, ids[partialObjs])
require.NoError(t, err)
// copying the contents of the settings tree
treesCopy[settingsObject.Id()] = settingsObject.Storage()
storageCopy.SetTrees(treesCopy)
spc.Close()
time.Sleep(100 * time.Millisecond)
// now we replace the storage, so the trees are back, but the settings object says that they are deleted
fx.storageProvider.(*spacestorage.InMemorySpaceStorageProvider).SetStorage(storageCopy)
spc, err = fx.spaceService.NewSpace(ctx, sp)
require.NoError(t, err)
require.NotNil(t, spc)
fx.treeManager.waitLoad = make(chan struct{})
fx.treeManager.space = spc
fx.treeManager.deletedIds = nil
err = spc.Init(ctx)
require.NoError(t, err)
close(fx.treeManager.waitLoad)
// waiting until everything is deleted
time.Sleep(3 * time.Second)
require.Equal(t, len(ids), len(fx.treeManager.deletedIds))
// checking that new snapshot will contain all the changes
settingsObject = spc.(*space).app.MustComponent(settings.CName).(settings.Settings).SettingsObject()
settings.DoSnapshot = func(treeLen int) bool {
return true
}
id := createTree(t, ctx, spc, acc)
err = spc.DeleteTree(ctx, id)
require.NoError(t, err)
delIds := settingsObject.Root().Model.(*spacesyncproto.SettingsData).Snapshot.DeletedIds
require.Equal(t, totalObjs+1, len(delIds))
}
func TestSpaceDeleteIdsMarkDeleted(t *testing.T) {
fx := newFixture(t)
acc := fx.account.Account()
rk := crypto.NewAES()
ctx := context.Background()
totalObjs := 1500
// creating space
sp, err := fx.spaceService.CreateSpace(ctx, SpaceCreatePayload{
SigningKey: acc.SignKey,
SpaceType: "type",
ReadKey: rk.Bytes(),
ReplicationKey: 10,
MasterKey: acc.PeerKey,
})
require.NoError(t, err)
require.NotNil(t, sp)
// initializing space
spc, err := fx.spaceService.NewSpace(ctx, sp)
require.NoError(t, err)
require.NotNil(t, spc)
// adding space to tree manager
fx.treeManager.space = spc
err = spc.Init(ctx)
require.NoError(t, err)
close(fx.treeManager.waitLoad)
settingsObject := spc.(*space).app.MustComponent(settings.CName).(settings.Settings).SettingsObject()
var ids []string
for i := 0; i < totalObjs; i++ {
id := createTree(t, ctx, spc, acc)
ids = append(ids, id)
}
// copying storage, so we will have the same contents, except for empty trees
inmemory := spc.Storage().(*commonStorage).SpaceStorage.(*spacestorage.InMemorySpaceStorage)
storageCopy := inmemory.CopyStorage()
// deleting trees, this will prepare the document to have all the deletion changes
for _, id := range ids {
err = spc.DeleteTree(ctx, id)
require.NoError(t, err)
}
treesMap := map[string]treestorage.TreeStorage{}
// copying the contents of the settings tree
treesMap[settingsObject.Id()] = settingsObject.Storage()
storageCopy.SetTrees(treesMap)
spc.Close()
time.Sleep(100 * time.Millisecond)
// now we replace the storage, so the trees are back, but the settings object says that they are deleted
fx.storageProvider.(*spacestorage.InMemorySpaceStorageProvider).SetStorage(storageCopy)
spc, err = fx.spaceService.NewSpace(ctx, sp)
require.NoError(t, err)
require.NotNil(t, spc)
fx.treeManager.space = spc
fx.treeManager.waitLoad = make(chan struct{})
fx.treeManager.deletedIds = nil
fx.treeManager.markedIds = nil
err = spc.Init(ctx)
require.NoError(t, err)
close(fx.treeManager.waitLoad)
// waiting until everything is deleted
time.Sleep(3 * time.Second)
require.Equal(t, len(ids), len(fx.treeManager.markedIds))
require.Zero(t, len(fx.treeManager.deletedIds))
}
@@ -1,59 +1,73 @@
//go:generate mockgen -destination mock_deletionstate/mock_deletionstate.go github.com/anytypeio/any-sync/commonspace/settings/deletionstate DeletionState
//go:generate mockgen -destination mock_deletionstate/mock_deletionstate.go github.com/anyproto/any-sync/commonspace/deletionstate ObjectDeletionState
package deletionstate
import (
"github.com/anytypeio/any-sync/commonspace/spacestorage"
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
"github.com/anyproto/any-sync/app"
"github.com/anyproto/any-sync/app/logger"
"github.com/anyproto/any-sync/commonspace/spacestorage"
"go.uber.org/zap"
"sync"
)
var log = logger.NewNamed(CName)
const CName = "common.commonspace.deletionstate"
type StateUpdateObserver func(ids []string)
type DeletionState interface {
type ObjectDeletionState interface {
app.Component
AddObserver(observer StateUpdateObserver)
Add(ids []string) (err error)
Add(ids map[string]struct{})
GetQueued() (ids []string)
Delete(id string) (err error)
Exists(id string) bool
FilterJoin(ids ...[]string) (filtered []string)
CreateDeleteChange(id string, isSnapshot bool) (res []byte, err error)
Filter(ids []string) (filtered []string)
}
type deletionState struct {
type objectDeletionState struct {
sync.RWMutex
log logger.CtxLogger
queued map[string]struct{}
deleted map[string]struct{}
stateUpdateObservers []StateUpdateObserver
storage spacestorage.SpaceStorage
}
func NewDeletionState(storage spacestorage.SpaceStorage) DeletionState {
return &deletionState{
func (st *objectDeletionState) Init(a *app.App) (err error) {
st.storage = a.MustComponent(spacestorage.CName).(spacestorage.SpaceStorage)
return nil
}
func (st *objectDeletionState) Name() (name string) {
return CName
}
func New() ObjectDeletionState {
return &objectDeletionState{
log: log,
queued: map[string]struct{}{},
deleted: map[string]struct{}{},
storage: storage,
}
}
func (st *deletionState) AddObserver(observer StateUpdateObserver) {
func (st *objectDeletionState) AddObserver(observer StateUpdateObserver) {
st.Lock()
defer st.Unlock()
st.stateUpdateObservers = append(st.stateUpdateObservers, observer)
}
func (st *deletionState) Add(ids []string) (err error) {
func (st *objectDeletionState) Add(ids map[string]struct{}) {
var added []string
st.Lock()
defer func() {
st.Unlock()
if err != nil {
return
}
for _, ob := range st.stateUpdateObservers {
ob(ids)
ob(added)
}
}()
for _, id := range ids {
for id := range ids {
if _, exists := st.deleted[id]; exists {
continue
}
@@ -62,9 +76,10 @@ func (st *deletionState) Add(ids []string) (err error) {
}
var status string
status, err = st.storage.TreeDeletedStatus(id)
status, err := st.storage.TreeDeletedStatus(id)
if err != nil {
return
st.log.Warn("failed to get deleted status", zap.String("treeId", id), zap.Error(err))
continue
}
switch status {
@@ -73,17 +88,18 @@ func (st *deletionState) Add(ids []string) (err error) {
case spacestorage.TreeDeletedStatusDeleted:
st.deleted[id] = struct{}{}
default:
st.queued[id] = struct{}{}
err = st.storage.SetTreeDeletedStatus(id, spacestorage.TreeDeletedStatusQueued)
err := st.storage.SetTreeDeletedStatus(id, spacestorage.TreeDeletedStatusQueued)
if err != nil {
return
st.log.Warn("failed to set deleted status", zap.String("treeId", id), zap.Error(err))
continue
}
st.queued[id] = struct{}{}
}
added = append(added, id)
}
return
}
func (st *deletionState) GetQueued() (ids []string) {
func (st *objectDeletionState) GetQueued() (ids []string) {
st.RLock()
defer st.RUnlock()
ids = make([]string, 0, len(st.queued))
@@ -93,7 +109,7 @@ func (st *deletionState) GetQueued() (ids []string) {
return
}
func (st *deletionState) Delete(id string) (err error) {
func (st *objectDeletionState) Delete(id string) (err error) {
st.Lock()
defer st.Unlock()
delete(st.queued, id)
@@ -105,44 +121,24 @@ func (st *deletionState) Delete(id string) (err error) {
return
}
func (st *deletionState) Exists(id string) bool {
func (st *objectDeletionState) Exists(id string) bool {
st.RLock()
defer st.RUnlock()
return st.exists(id)
}
func (st *deletionState) FilterJoin(ids ...[]string) (filtered []string) {
func (st *objectDeletionState) Filter(ids []string) (filtered []string) {
st.RLock()
defer st.RUnlock()
filter := func(ids []string) {
for _, id := range ids {
if !st.exists(id) {
filtered = append(filtered, id)
}
for _, id := range ids {
if !st.exists(id) {
filtered = append(filtered, id)
}
}
for _, arr := range ids {
filter(arr)
}
return
}
func (st *deletionState) CreateDeleteChange(id string, isSnapshot bool) (res []byte, err error) {
content := &spacesyncproto.SpaceSettingsContent_ObjectDelete{
ObjectDelete: &spacesyncproto.ObjectDelete{Id: id},
}
change := &spacesyncproto.SettingsData{
Content: []*spacesyncproto.SpaceSettingsContent{
{Value: content},
},
Snapshot: nil,
}
// TODO: add snapshot logic
res, err = change.Marshal()
return
}
func (st *deletionState) exists(id string) bool {
func (st *objectDeletionState) exists(id string) bool {
if _, exists := st.deleted[id]; exists {
return true
}
@@ -1,24 +1,25 @@
package deletionstate
import (
"github.com/anytypeio/any-sync/commonspace/spacestorage"
"github.com/anytypeio/any-sync/commonspace/spacestorage/mock_spacestorage"
"github.com/golang/mock/gomock"
"github.com/anyproto/any-sync/commonspace/spacestorage"
"github.com/anyproto/any-sync/commonspace/spacestorage/mock_spacestorage"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"sort"
"testing"
)
type fixture struct {
ctrl *gomock.Controller
delState *deletionState
delState *objectDeletionState
spaceStorage *mock_spacestorage.MockSpaceStorage
}
func newFixture(t *testing.T) *fixture {
ctrl := gomock.NewController(t)
spaceStorage := mock_spacestorage.NewMockSpaceStorage(ctrl)
delState := NewDeletionState(spaceStorage).(*deletionState)
delState := New().(*objectDeletionState)
delState.storage = spaceStorage
return &fixture{
ctrl: ctrl,
delState: delState,
@@ -37,8 +38,7 @@ func TestDeletionState_Add(t *testing.T) {
id := "newId"
fx.spaceStorage.EXPECT().TreeDeletedStatus(id).Return("", nil)
fx.spaceStorage.EXPECT().SetTreeDeletedStatus(id, spacestorage.TreeDeletedStatusQueued).Return(nil)
err := fx.delState.Add([]string{id})
require.NoError(t, err)
fx.delState.Add(map[string]struct{}{id: {}})
require.Contains(t, fx.delState.queued, id)
})
@@ -47,8 +47,7 @@ func TestDeletionState_Add(t *testing.T) {
defer fx.stop()
id := "newId"
fx.spaceStorage.EXPECT().TreeDeletedStatus(id).Return(spacestorage.TreeDeletedStatusQueued, nil)
err := fx.delState.Add([]string{id})
require.NoError(t, err)
fx.delState.Add(map[string]struct{}{id: {}})
require.Contains(t, fx.delState.queued, id)
})
@@ -57,8 +56,7 @@ func TestDeletionState_Add(t *testing.T) {
defer fx.stop()
id := "newId"
fx.spaceStorage.EXPECT().TreeDeletedStatus(id).Return(spacestorage.TreeDeletedStatusDeleted, nil)
err := fx.delState.Add([]string{id})
require.NoError(t, err)
fx.delState.Add(map[string]struct{}{id: {}})
require.Contains(t, fx.delState.deleted, id)
})
}
@@ -82,8 +80,8 @@ func TestDeletionState_FilterJoin(t *testing.T) {
fx.delState.queued["id1"] = struct{}{}
fx.delState.queued["id2"] = struct{}{}
filtered := fx.delState.FilterJoin([]string{"id1"}, []string{"id3", "id2"}, []string{"id4"})
require.Equal(t, []string{"id3", "id4"}, filtered)
filtered := fx.delState.Filter([]string{"id3", "id2"})
require.Equal(t, []string{"id3"}, filtered)
}
func TestDeletionState_AddObserver(t *testing.T) {
@@ -98,8 +96,7 @@ func TestDeletionState_AddObserver(t *testing.T) {
id := "newId"
fx.spaceStorage.EXPECT().TreeDeletedStatus(id).Return("", nil)
fx.spaceStorage.EXPECT().SetTreeDeletedStatus(id, spacestorage.TreeDeletedStatusQueued).Return(nil)
err := fx.delState.Add([]string{id})
require.NoError(t, err)
fx.delState.Add(map[string]struct{}{id: {}})
require.Contains(t, fx.delState.queued, id)
require.Equal(t, []string{id}, queued)
}
@@ -0,0 +1,144 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/anyproto/any-sync/commonspace/deletionstate (interfaces: ObjectDeletionState)
// Package mock_deletionstate is a generated GoMock package.
package mock_deletionstate
import (
reflect "reflect"
app "github.com/anyproto/any-sync/app"
deletionstate "github.com/anyproto/any-sync/commonspace/deletionstate"
gomock "go.uber.org/mock/gomock"
)
// MockObjectDeletionState is a mock of ObjectDeletionState interface.
type MockObjectDeletionState struct {
ctrl *gomock.Controller
recorder *MockObjectDeletionStateMockRecorder
}
// MockObjectDeletionStateMockRecorder is the mock recorder for MockObjectDeletionState.
type MockObjectDeletionStateMockRecorder struct {
mock *MockObjectDeletionState
}
// NewMockObjectDeletionState creates a new mock instance.
func NewMockObjectDeletionState(ctrl *gomock.Controller) *MockObjectDeletionState {
mock := &MockObjectDeletionState{ctrl: ctrl}
mock.recorder = &MockObjectDeletionStateMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockObjectDeletionState) EXPECT() *MockObjectDeletionStateMockRecorder {
return m.recorder
}
// Add mocks base method.
func (m *MockObjectDeletionState) Add(arg0 map[string]struct{}) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Add", arg0)
}
// Add indicates an expected call of Add.
func (mr *MockObjectDeletionStateMockRecorder) Add(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockObjectDeletionState)(nil).Add), arg0)
}
// AddObserver mocks base method.
func (m *MockObjectDeletionState) AddObserver(arg0 deletionstate.StateUpdateObserver) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "AddObserver", arg0)
}
// AddObserver indicates an expected call of AddObserver.
func (mr *MockObjectDeletionStateMockRecorder) AddObserver(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddObserver", reflect.TypeOf((*MockObjectDeletionState)(nil).AddObserver), arg0)
}
// Delete mocks base method.
func (m *MockObjectDeletionState) Delete(arg0 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockObjectDeletionStateMockRecorder) Delete(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockObjectDeletionState)(nil).Delete), arg0)
}
// Exists mocks base method.
func (m *MockObjectDeletionState) Exists(arg0 string) bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Exists", arg0)
ret0, _ := ret[0].(bool)
return ret0
}
// Exists indicates an expected call of Exists.
func (mr *MockObjectDeletionStateMockRecorder) Exists(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exists", reflect.TypeOf((*MockObjectDeletionState)(nil).Exists), arg0)
}
// Filter mocks base method.
func (m *MockObjectDeletionState) Filter(arg0 []string) []string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Filter", arg0)
ret0, _ := ret[0].([]string)
return ret0
}
// Filter indicates an expected call of Filter.
func (mr *MockObjectDeletionStateMockRecorder) Filter(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Filter", reflect.TypeOf((*MockObjectDeletionState)(nil).Filter), arg0)
}
// GetQueued mocks base method.
func (m *MockObjectDeletionState) GetQueued() []string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetQueued")
ret0, _ := ret[0].([]string)
return ret0
}
// GetQueued indicates an expected call of GetQueued.
func (mr *MockObjectDeletionStateMockRecorder) GetQueued() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetQueued", reflect.TypeOf((*MockObjectDeletionState)(nil).GetQueued))
}
// Init mocks base method.
func (m *MockObjectDeletionState) Init(arg0 *app.App) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Init", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// Init indicates an expected call of Init.
func (mr *MockObjectDeletionStateMockRecorder) Init(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockObjectDeletionState)(nil).Init), arg0)
}
// Name mocks base method.
func (m *MockObjectDeletionState) Name() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Name")
ret0, _ := ret[0].(string)
return ret0
}
// Name indicates an expected call of Name.
func (mr *MockObjectDeletionStateMockRecorder) Name() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockObjectDeletionState)(nil).Name))
}
+89 -73
View File
@@ -3,17 +3,17 @@ package headsync
import (
"context"
"fmt"
"github.com/anytypeio/any-sync/app/ldiff"
"github.com/anytypeio/any-sync/app/logger"
"github.com/anytypeio/any-sync/commonspace/object/tree/synctree"
"github.com/anytypeio/any-sync/commonspace/object/treegetter"
"github.com/anytypeio/any-sync/commonspace/peermanager"
"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"
"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/deletionstate"
"github.com/anyproto/any-sync/commonspace/object/treemanager"
"github.com/anyproto/any-sync/commonspace/peermanager"
"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"
"go.uber.org/zap"
"time"
)
@@ -22,45 +22,42 @@ type DiffSyncer interface {
Sync(ctx context.Context) error
RemoveObjects(ids []string)
UpdateHeads(id string, heads []string)
Init(deletionState deletionstate.DeletionState)
Init()
Close() error
}
func newDiffSyncer(
spaceId string,
diff ldiff.Diff,
peerManager peermanager.PeerManager,
cache treegetter.TreeGetter,
storage spacestorage.SpaceStorage,
clientFactory spacesyncproto.ClientFactory,
syncStatus syncstatus.StatusUpdater,
log logger.CtxLogger) DiffSyncer {
func newDiffSyncer(hs *headSync) DiffSyncer {
return &diffSyncer{
diff: diff,
spaceId: spaceId,
cache: cache,
storage: storage,
peerManager: peerManager,
clientFactory: clientFactory,
log: log,
syncStatus: syncStatus,
diff: hs.diff,
spaceId: hs.spaceId,
treeManager: hs.treeManager,
storage: hs.storage,
peerManager: hs.peerManager,
clientFactory: spacesyncproto.ClientFactoryFunc(spacesyncproto.NewDRPCSpaceSyncClient),
credentialProvider: hs.credentialProvider,
log: log,
syncStatus: hs.syncStatus,
deletionState: hs.deletionState,
}
}
type diffSyncer struct {
spaceId string
diff ldiff.Diff
peerManager peermanager.PeerManager
cache treegetter.TreeGetter
storage spacestorage.SpaceStorage
clientFactory spacesyncproto.ClientFactory
log logger.CtxLogger
deletionState deletionstate.DeletionState
syncStatus syncstatus.StatusUpdater
spaceId string
diff ldiff.Diff
peerManager peermanager.PeerManager
treeManager treemanager.TreeManager
storage spacestorage.SpaceStorage
clientFactory spacesyncproto.ClientFactory
log logger.CtxLogger
deletionState deletionstate.ObjectDeletionState
credentialProvider credentialprovider.CredentialProvider
syncStatus syncstatus.StatusUpdater
treeSyncer treemanager.TreeSyncer
}
func (d *diffSyncer) Init(deletionState deletionstate.DeletionState) {
d.deletionState = deletionState
func (d *diffSyncer) Init() {
d.deletionState.AddObserver(d.RemoveObjects)
d.treeSyncer = d.treeManager.NewTreeSyncer(d.spaceId, d.treeManager)
}
func (d *diffSyncer) RemoveObjects(ids []string) {
@@ -86,6 +83,7 @@ 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)
@@ -108,8 +106,14 @@ func (d *diffSyncer) Sync(ctx context.Context) error {
func (d *diffSyncer) syncWithPeer(ctx context.Context, p peer.Peer) (err error) {
ctx = logger.CtxWithFields(ctx, zap.String("peerId", p.Id()))
conn, err := p.AcquireDrpcConn(ctx)
if err != nil {
return
}
defer p.ReleaseDrpcConn(conn)
var (
cl = d.clientFactory.Client(p)
cl = d.clientFactory.Client(conn)
rdiff = NewRemoteDiff(d.spaceId, cl)
stateCounter = d.syncStatus.StateCounter()
)
@@ -117,57 +121,40 @@ 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)
}
d.syncStatus.SetNodesOnline(p.Id(), true)
if err == spacesyncproto.ErrSpaceMissing {
return d.sendPushSpaceRequest(ctx, cl)
return d.sendPushSpaceRequest(ctx, p.Id(), cl)
}
totalLen := len(newIds) + len(changedIds) + len(removedIds)
// not syncing ids which were removed through settings document
filteredIds := d.deletionState.FilterJoin(newIds, changedIds, removedIds)
missingIds := d.deletionState.Filter(newIds)
existingIds := append(d.deletionState.Filter(removedIds), d.deletionState.Filter(changedIds)...)
d.syncStatus.RemoveAllExcept(p.Id(), filteredIds, stateCounter)
d.syncTrees(ctx, p.Id(), filteredIds)
d.syncStatus.RemoveAllExcept(p.Id(), existingIds, stateCounter)
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(filteredIds)),
zap.Int("already deleted ids", totalLen-len(existingIds)-len(missingIds)),
zap.String("peerId", p.Id()),
)
return
}
func (d *diffSyncer) syncTrees(ctx context.Context, peerId string, trees []string) {
for _, tId := range trees {
tree, err := d.cache.GetTree(ctx, d.spaceId, tId)
if err != nil {
d.log.InfoCtx(ctx, "can't load tree", zap.Error(err))
continue
}
syncTree, ok := tree.(synctree.SyncTree)
if !ok {
d.log.InfoCtx(ctx, "not a sync tree", zap.String("objectId", tId))
continue
}
// the idea why we call it directly is that if we try to get it from cache
// it may be already there (i.e. loaded)
// and build func will not be called, thus we won't sync the tree
// therefore we just do it manually
if err = syncTree.SyncWithPeer(ctx, peerId); err != nil {
d.log.WarnCtx(ctx, "synctree.SyncWithPeer error", zap.Error(err), zap.String("treeId", tId))
} else {
d.log.DebugCtx(ctx, "success synctree.SyncWithPeer", zap.String("treeId", tId))
}
}
}
func (d *diffSyncer) sendPushSpaceRequest(ctx context.Context, cl spacesyncproto.DRPCSpaceSyncClient) (err error) {
func (d *diffSyncer) sendPushSpaceRequest(ctx context.Context, peerId string, cl spacesyncproto.DRPCSpaceSyncClient) (err error) {
aclStorage, err := d.storage.AclStorage()
if err != nil {
return
@@ -192,6 +179,10 @@ func (d *diffSyncer) sendPushSpaceRequest(ctx context.Context, cl spacesyncproto
return
}
cred, err := d.credentialProvider.GetCredential(ctx, header)
if err != nil {
return
}
spacePayload := &spacesyncproto.SpacePayload{
SpaceHeader: header,
AclPayload: root.Payload,
@@ -200,7 +191,32 @@ func (d *diffSyncer) sendPushSpaceRequest(ctx context.Context, cl spacesyncproto
SpaceSettingsPayloadId: spaceSettingsRoot.Id,
}
_, err = cl.SpacePush(ctx, &spacesyncproto.SpacePushRequest{
Payload: spacePayload,
Payload: spacePayload,
Credential: cred,
})
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()
}
+143 -112
View File
@@ -1,170 +1,176 @@
package headsync
import (
"bytes"
"context"
"fmt"
"github.com/anytypeio/any-sync/app/ldiff"
"github.com/anytypeio/any-sync/app/ldiff/mock_ldiff"
"github.com/anytypeio/any-sync/app/logger"
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anytypeio/any-sync/commonspace/object/acl/liststorage/mock_liststorage"
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
mock_treestorage "github.com/anytypeio/any-sync/commonspace/object/tree/treestorage/mock_treestorage"
"github.com/anytypeio/any-sync/commonspace/object/treegetter/mock_treegetter"
"github.com/anytypeio/any-sync/commonspace/peermanager/mock_peermanager"
"github.com/anytypeio/any-sync/commonspace/settings/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"
"storj.io/drpc"
"testing"
"time"
"github.com/anyproto/any-sync/app/ldiff"
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage/mock_liststorage"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage/mock_treestorage"
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
"github.com/anyproto/any-sync/consensus/consensusproto"
"github.com/anyproto/any-sync/net/peer"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"storj.io/drpc"
)
type pushSpaceRequestMatcher struct {
spaceId string
aclRootId string
settingsId string
credential []byte
spaceHeader *spacesyncproto.RawSpaceHeaderWithId
}
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,
}
}
func (p pushSpaceRequestMatcher) Matches(x interface{}) bool {
res, ok := x.(*spacesyncproto.SpacePushRequest)
if !ok {
return false
}
return res.Payload.AclPayloadId == p.aclRootId && res.Payload.SpaceHeader == p.spaceHeader && res.Payload.SpaceSettingsPayloadId == p.settingsId
return res.Payload.AclPayloadId == p.aclRootId && res.Payload.SpaceHeader == p.spaceHeader && res.Payload.SpaceSettingsPayloadId == p.settingsId && bytes.Equal(p.credential, res.Credential)
}
func (p pushSpaceRequestMatcher) String() string {
return ""
}
type mockPeer struct{}
type mockPeer struct {
}
func (m mockPeer) Id() string {
return "mockId"
return "peerId"
}
func (m mockPeer) LastUsage() time.Time {
return time.Time{}
func (m mockPeer) Context() context.Context {
return context.Background()
}
func (m mockPeer) Secure() sec.SecureConn {
return nil
}
func (m mockPeer) UpdateLastUsage() {
}
func (m mockPeer) Close() error {
return nil
}
func (m mockPeer) Closed() <-chan struct{} {
return make(chan struct{})
}
func (m mockPeer) Invoke(ctx context.Context, rpc string, enc drpc.Encoding, in, out drpc.Message) error {
return nil
}
func (m mockPeer) NewStream(ctx context.Context, rpc string, enc drpc.Encoding) (drpc.Stream, error) {
func (m mockPeer) AcquireDrpcConn(ctx context.Context) (drpc.Conn, error) {
return nil, nil
}
func newPushSpaceRequestMatcher(
spaceId string,
aclRootId string,
settingsId string,
spaceHeader *spacesyncproto.RawSpaceHeaderWithId) *pushSpaceRequestMatcher {
return &pushSpaceRequestMatcher{
spaceId: spaceId,
aclRootId: aclRootId,
settingsId: settingsId,
spaceHeader: spaceHeader,
}
func (m mockPeer) ReleaseDrpcConn(conn drpc.Conn) {
return
}
func TestDiffSyncer_Sync(t *testing.T) {
// setup
ctx := context.Background()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
func (m mockPeer) DoDrpc(ctx context.Context, do func(conn drpc.Conn) error) error {
return nil
}
diffMock := mock_ldiff.NewMockDiff(ctrl)
peerManagerMock := mock_peermanager.NewMockPeerManager(ctrl)
cacheMock := mock_treegetter.NewMockTreeGetter(ctrl)
stMock := mock_spacestorage.NewMockSpaceStorage(ctrl)
clientMock := mock_spacesyncproto.NewMockDRPCSpaceSyncClient(ctrl)
factory := spacesyncproto.ClientFactoryFunc(func(cc drpc.Conn) spacesyncproto.DRPCSpaceSyncClient {
return clientMock
func (m mockPeer) IsClosed() bool {
return false
}
func (m mockPeer) TryClose(objectTTL time.Duration) (res bool, err error) {
return false, err
}
func (m mockPeer) Close() (err error) {
return nil
}
func (fx *headSyncFixture) initDiffSyncer(t *testing.T) {
fx.init(t)
fx.diffSyncer = newDiffSyncer(fx.headSync).(*diffSyncer)
fx.diffSyncer.clientFactory = spacesyncproto.ClientFactoryFunc(func(cc drpc.Conn) spacesyncproto.DRPCSpaceSyncClient {
return fx.clientMock
})
delState := mock_deletionstate.NewMockDeletionState(ctrl)
spaceId := "spaceId"
aclRootId := "aclRootId"
l := logger.NewNamed(spaceId)
diffSyncer := newDiffSyncer(spaceId, diffMock, peerManagerMock, cacheMock, stMock, factory, syncstatus.NewNoOpSyncStatus(), l)
delState.EXPECT().AddObserver(gomock.Any())
diffSyncer.Init(delState)
fx.deletionStateMock.EXPECT().AddObserver(gomock.Any())
fx.treeManagerMock.EXPECT().NewTreeSyncer(fx.spaceState.SpaceId, fx.treeManagerMock).Return(fx.treeSyncerMock)
fx.diffSyncer.Init()
}
func TestDiffSyncer(t *testing.T) {
ctx := context.Background()
t.Run("diff syncer sync", func(t *testing.T) {
peerManagerMock.EXPECT().
fx := newHeadSyncFixture(t)
fx.initDiffSyncer(t)
defer fx.stop()
mPeer := mockPeer{}
fx.peerManagerMock.EXPECT().
GetResponsiblePeers(gomock.Any()).
Return([]peer.Peer{mockPeer{}}, nil)
diffMock.EXPECT().
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(spaceId, clientMock))).
Return([]peer.Peer{mPeer}, nil)
fx.diffMock.EXPECT().
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(fx.spaceState.SpaceId, fx.clientMock))).
Return([]string{"new"}, []string{"changed"}, nil, nil)
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))
fx.deletionStateMock.EXPECT().Filter([]string{"new"}).Return([]string{"new"}).Times(1)
fx.deletionStateMock.EXPECT().Filter([]string{"changed"}).Return([]string{"changed"}).Times(1)
fx.deletionStateMock.EXPECT().Filter(nil).Return(nil).Times(1)
fx.treeSyncerMock.EXPECT().SyncAll(gomock.Any(), mPeer.Id(), []string{"changed"}, []string{"new"}).Return(nil)
require.NoError(t, fx.diffSyncer.Sync(ctx))
})
t.Run("diff syncer sync conf error", func(t *testing.T) {
peerManagerMock.EXPECT().
fx := newHeadSyncFixture(t)
fx.initDiffSyncer(t)
defer fx.stop()
ctx := context.Background()
fx.peerManagerMock.EXPECT().
GetResponsiblePeers(gomock.Any()).
Return(nil, fmt.Errorf("some error"))
require.Error(t, diffSyncer.Sync(ctx))
require.Error(t, fx.diffSyncer.Sync(ctx))
})
t.Run("deletion state remove objects", func(t *testing.T) {
fx := newHeadSyncFixture(t)
fx.initDiffSyncer(t)
defer fx.stop()
deletedId := "id"
delState.EXPECT().Exists(deletedId).Return(true)
fx.deletionStateMock.EXPECT().Exists(deletedId).Return(true)
// this should not result in any mock being called
diffSyncer.UpdateHeads(deletedId, []string{"someHead"})
fx.diffSyncer.UpdateHeads(deletedId, []string{"someHead"})
})
t.Run("update heads updates diff", func(t *testing.T) {
fx := newHeadSyncFixture(t)
fx.initDiffSyncer(t)
defer fx.stop()
newId := "newId"
newHeads := []string{"h1", "h2"}
hash := "hash"
diffMock.EXPECT().Set(ldiff.Element{
fx.diffMock.EXPECT().Set(ldiff.Element{
Id: newId,
Head: concatStrings(newHeads),
})
diffMock.EXPECT().Hash().Return(hash)
delState.EXPECT().Exists(newId).Return(false)
stMock.EXPECT().WriteSpaceHash(hash)
diffSyncer.UpdateHeads(newId, newHeads)
fx.diffMock.EXPECT().Hash().Return(hash)
fx.deletionStateMock.EXPECT().Exists(newId).Return(false)
fx.storageMock.EXPECT().WriteSpaceHash(hash)
fx.diffSyncer.UpdateHeads(newId, newHeads)
})
t.Run("diff syncer sync space missing", func(t *testing.T) {
aclStorageMock := mock_liststorage.NewMockListStorage(ctrl)
settingsStorage := mock_treestorage.NewMockTreeStorage(ctrl)
fx := newHeadSyncFixture(t)
fx.initDiffSyncer(t)
defer fx.stop()
aclStorageMock := mock_liststorage.NewMockListStorage(fx.ctrl)
settingsStorage := mock_treestorage.NewMockTreeStorage(fx.ctrl)
settingsId := "settingsId"
aclRoot := &aclrecordproto.RawAclRecordWithId{
aclRootId := "aclRootId"
aclRoot := &consensusproto.RawRecordWithId{
Id: aclRootId,
}
settingsRoot := &treechangeproto.RawTreeChangeWithId{
@@ -172,38 +178,63 @@ func TestDiffSyncer_Sync(t *testing.T) {
}
spaceHeader := &spacesyncproto.RawSpaceHeaderWithId{}
spaceSettingsId := "spaceSettingsId"
credential := []byte("credential")
peerManagerMock.EXPECT().
fx.peerManagerMock.EXPECT().
GetResponsiblePeers(gomock.Any()).
Return([]peer.Peer{mockPeer{}}, nil)
diffMock.EXPECT().
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(spaceId, clientMock))).
fx.diffMock.EXPECT().
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(fx.spaceState.SpaceId, fx.clientMock))).
Return(nil, nil, nil, spacesyncproto.ErrSpaceMissing)
stMock.EXPECT().AclStorage().Return(aclStorageMock, nil)
stMock.EXPECT().SpaceHeader().Return(spaceHeader, nil)
stMock.EXPECT().SpaceSettingsId().Return(spaceSettingsId)
stMock.EXPECT().TreeStorage(spaceSettingsId).Return(settingsStorage, nil)
fx.storageMock.EXPECT().AclStorage().Return(aclStorageMock, nil)
fx.storageMock.EXPECT().SpaceHeader().Return(spaceHeader, nil)
fx.storageMock.EXPECT().SpaceSettingsId().Return(spaceSettingsId)
fx.storageMock.EXPECT().TreeStorage(spaceSettingsId).Return(settingsStorage, nil)
settingsStorage.EXPECT().Root().Return(settingsRoot, nil)
aclStorageMock.EXPECT().
Root().
Return(aclRoot, nil)
clientMock.EXPECT().
SpacePush(gomock.Any(), newPushSpaceRequestMatcher(spaceId, aclRootId, settingsId, spaceHeader)).
fx.credentialProviderMock.EXPECT().
GetCredential(gomock.Any(), spaceHeader).
Return(credential, nil)
fx.clientMock.EXPECT().
SpacePush(gomock.Any(), newPushSpaceRequestMatcher(fx.spaceState.SpaceId, aclRootId, settingsId, credential, spaceHeader)).
Return(nil, nil)
fx.peerManagerMock.EXPECT().SendPeer(gomock.Any(), "peerId", gomock.Any())
require.NoError(t, diffSyncer.Sync(ctx))
require.NoError(t, fx.diffSyncer.Sync(ctx))
})
t.Run("diff syncer sync other error", func(t *testing.T) {
peerManagerMock.EXPECT().
t.Run("diff syncer sync unexpected", func(t *testing.T) {
fx := newHeadSyncFixture(t)
fx.initDiffSyncer(t)
defer fx.stop()
fx.peerManagerMock.EXPECT().
GetResponsiblePeers(gomock.Any()).
Return([]peer.Peer{mockPeer{}}, nil)
diffMock.EXPECT().
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(spaceId, clientMock))).
fx.diffMock.EXPECT().
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(fx.spaceState.SpaceId, fx.clientMock))).
Return(nil, nil, nil, spacesyncproto.ErrUnexpected)
require.NoError(t, diffSyncer.Sync(ctx))
require.NoError(t, fx.diffSyncer.Sync(ctx))
})
t.Run("diff syncer sync space is deleted error", func(t *testing.T) {
fx := newHeadSyncFixture(t)
fx.initDiffSyncer(t)
defer fx.stop()
mPeer := mockPeer{}
fx.peerManagerMock.EXPECT().
GetResponsiblePeers(gomock.Any()).
Return([]peer.Peer{mPeer}, nil)
fx.diffMock.EXPECT().
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(fx.spaceState.SpaceId, fx.clientMock))).
Return(nil, nil, nil, spacesyncproto.ErrSpaceIsDeleted)
fx.storageMock.EXPECT().SpaceSettingsId().Return("settingsId")
fx.treeSyncerMock.EXPECT().SyncAll(gomock.Any(), mPeer.Id(), []string{"settingsId"}, nil).Return(nil)
require.NoError(t, fx.diffSyncer.Sync(ctx))
})
}
+118 -91
View File
@@ -1,96 +1,147 @@
//go:generate mockgen -destination mock_headsync/mock_headsync.go github.com/anytypeio/any-sync/commonspace/headsync DiffSyncer
//go:generate mockgen -destination mock_headsync/mock_headsync.go github.com/anyproto/any-sync/commonspace/headsync DiffSyncer
package headsync
import (
"context"
"github.com/anytypeio/any-sync/app/ldiff"
"github.com/anytypeio/any-sync/app/logger"
"github.com/anytypeio/any-sync/commonspace/object/treegetter"
"github.com/anytypeio/any-sync/commonspace/peermanager"
"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"
"github.com/anyproto/any-sync/app"
"github.com/anyproto/any-sync/app/ldiff"
"github.com/anyproto/any-sync/app/logger"
config2 "github.com/anyproto/any-sync/commonspace/config"
"github.com/anyproto/any-sync/commonspace/credentialprovider"
"github.com/anyproto/any-sync/commonspace/deletionstate"
"github.com/anyproto/any-sync/commonspace/object/treemanager"
"github.com/anyproto/any-sync/commonspace/peermanager"
"github.com/anyproto/any-sync/commonspace/spacestate"
"github.com/anyproto/any-sync/commonspace/spacestorage"
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
"github.com/anyproto/any-sync/commonspace/syncstatus"
"github.com/anyproto/any-sync/net/peer"
"github.com/anyproto/any-sync/nodeconf"
"github.com/anyproto/any-sync/util/periodicsync"
"github.com/anyproto/any-sync/util/slice"
"go.uber.org/zap"
"strings"
"golang.org/x/exp/slices"
"sync/atomic"
"time"
)
var log = logger.NewNamed(CName)
const CName = "common.commonspace.headsync"
type TreeHeads struct {
Id string
Heads []string
}
type HeadSync interface {
Init(objectIds []string, deletionState deletionstate.DeletionState)
app.ComponentRunnable
ExternalIds() []string
DebugAllHeads() (res []TreeHeads)
AllIds() []string
UpdateHeads(id string, heads []string)
HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error)
RemoveObjects(ids []string)
AllIds() []string
DebugAllHeads() (res []TreeHeads)
Close() (err error)
}
type headSync struct {
spaceId string
periodicSync periodicsync.PeriodicSync
storage spacestorage.SpaceStorage
diff ldiff.Diff
log logger.CtxLogger
syncer DiffSyncer
spaceId string
spaceIsDeleted *atomic.Bool
syncPeriod int
syncPeriod int
periodicSync periodicsync.PeriodicSync
storage spacestorage.SpaceStorage
diff ldiff.Diff
log logger.CtxLogger
syncer DiffSyncer
configuration nodeconf.NodeConf
peerManager peermanager.PeerManager
treeManager treemanager.TreeManager
credentialProvider credentialprovider.CredentialProvider
syncStatus syncstatus.StatusService
deletionState deletionstate.ObjectDeletionState
}
func NewHeadSync(
spaceId string,
syncPeriod int,
storage spacestorage.SpaceStorage,
peerManager peermanager.PeerManager,
cache treegetter.TreeGetter,
syncStatus syncstatus.StatusUpdater,
log logger.CtxLogger) HeadSync {
func New() HeadSync {
return &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, l)
periodicSync := periodicsync.NewPeriodicSync(syncPeriod, time.Minute, syncer.Sync, l)
var createDiffSyncer = newDiffSyncer
return &headSync{
spaceId: spaceId,
storage: storage,
syncer: syncer,
periodicSync: periodicSync,
diff: diff,
log: log,
syncPeriod: syncPeriod,
func (h *headSync) Init(a *app.App) (err error) {
shared := a.MustComponent(spacestate.CName).(*spacestate.SpaceState)
cfg := a.MustComponent("config").(config2.ConfigGetter)
h.spaceId = shared.SpaceId
h.spaceIsDeleted = shared.SpaceIsDeleted
h.syncPeriod = cfg.GetSpace().SyncPeriod
h.configuration = a.MustComponent(nodeconf.CName).(nodeconf.NodeConf)
h.log = log.With(zap.String("spaceId", h.spaceId))
h.storage = a.MustComponent(spacestorage.CName).(spacestorage.SpaceStorage)
h.diff = ldiff.New(16, 16)
h.peerManager = a.MustComponent(peermanager.CName).(peermanager.PeerManager)
h.credentialProvider = a.MustComponent(credentialprovider.CName).(credentialprovider.CredentialProvider)
h.syncStatus = a.MustComponent(syncstatus.CName).(syncstatus.StatusService)
h.treeManager = a.MustComponent(treemanager.CName).(treemanager.TreeManager)
h.deletionState = a.MustComponent(deletionstate.CName).(deletionstate.ObjectDeletionState)
h.syncer = createDiffSyncer(h)
sync := func(ctx context.Context) (err error) {
// for clients cancelling the sync process
if h.spaceIsDeleted.Load() && !h.configuration.IsResponsible(h.spaceId) {
return spacesyncproto.ErrSpaceIsDeleted
}
return h.syncer.Sync(ctx)
}
h.periodicSync = periodicsync.NewPeriodicSync(h.syncPeriod, time.Minute, sync, h.log)
// TODO: move to run?
h.syncer.Init()
return nil
}
func (d *headSync) Init(objectIds []string, deletionState deletionstate.DeletionState) {
d.fillDiff(objectIds)
d.syncer.Init(deletionState)
d.periodicSync.Run()
func (h *headSync) Name() (name string) {
return CName
}
func (d *headSync) HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error) {
return HandleRangeRequest(ctx, d.diff, req)
func (h *headSync) Run(ctx context.Context) (err error) {
initialIds, err := h.storage.StoredIds()
if err != nil {
return
}
h.fillDiff(initialIds)
h.periodicSync.Run()
return
}
func (d *headSync) UpdateHeads(id string, heads []string) {
d.syncer.UpdateHeads(id, heads)
func (h *headSync) HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error) {
if h.spaceIsDeleted.Load() {
peerId, err := peer.CtxPeerId(ctx)
if err != nil {
return nil, err
}
// stop receiving all request for sync from clients
if !slices.Contains(h.configuration.NodeIds(h.spaceId), peerId) {
return nil, spacesyncproto.ErrSpaceIsDeleted
}
}
return HandleRangeRequest(ctx, h.diff, req)
}
func (d *headSync) AllIds() []string {
return d.diff.Ids()
func (h *headSync) UpdateHeads(id string, heads []string) {
h.syncer.UpdateHeads(id, heads)
}
func (d *headSync) DebugAllHeads() (res []TreeHeads) {
els := d.diff.Elements()
func (h *headSync) AllIds() []string {
return h.diff.Ids()
}
func (h *headSync) ExternalIds() []string {
settingsId := h.storage.SpaceSettingsId()
return slice.DiscardFromSlice(h.AllIds(), func(id string) bool {
return id == settingsId
})
}
func (h *headSync) DebugAllHeads() (res []TreeHeads) {
els := h.diff.Elements()
for _, el := range els {
idHead := TreeHeads{
Id: el.Id,
@@ -101,19 +152,19 @@ func (d *headSync) DebugAllHeads() (res []TreeHeads) {
return
}
func (d *headSync) RemoveObjects(ids []string) {
d.syncer.RemoveObjects(ids)
func (h *headSync) RemoveObjects(ids []string) {
h.syncer.RemoveObjects(ids)
}
func (d *headSync) Close() (err error) {
d.periodicSync.Close()
return nil
func (h *headSync) Close(ctx context.Context) (err error) {
h.periodicSync.Close()
return h.syncer.Close()
}
func (d *headSync) fillDiff(objectIds []string) {
func (h *headSync) fillDiff(objectIds []string) {
var els = make([]ldiff.Element, 0, len(objectIds))
for _, id := range objectIds {
st, err := d.storage.TreeStorage(id)
st, err := h.storage.TreeStorage(id)
if err != nil {
continue
}
@@ -126,32 +177,8 @@ func (d *headSync) fillDiff(objectIds []string) {
Head: concatStrings(heads),
})
}
d.diff.Set(els...)
if err := d.storage.WriteSpaceHash(d.diff.Hash()); err != nil {
d.log.Error("can't write space hash", zap.Error(err))
h.diff.Set(els...)
if err := h.storage.WriteSpaceHash(h.diff.Hash()); err != nil {
h.log.Error("can't write space hash", zap.Error(err))
}
}
func concatStrings(strs []string) string {
var (
b strings.Builder
totalLen int
)
for _, s := range strs {
totalLen += len(s)
}
b.Grow(totalLen)
for _, s := range strs {
b.WriteString(s)
}
return b.String()
}
func splitString(str string) (res []string) {
const cidLen = 59
for i := 0; i < len(str); i += cidLen {
res = append(res, str[i:i+cidLen])
}
return
}
+158 -49
View File
@@ -1,70 +1,179 @@
package headsync
import (
"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"
"context"
"github.com/anyproto/any-sync/app"
"github.com/anyproto/any-sync/app/ldiff"
"github.com/anyproto/any-sync/app/ldiff/mock_ldiff"
"github.com/anyproto/any-sync/commonspace/config"
"github.com/anyproto/any-sync/commonspace/credentialprovider"
"github.com/anyproto/any-sync/commonspace/credentialprovider/mock_credentialprovider"
"github.com/anyproto/any-sync/commonspace/deletionstate"
"github.com/anyproto/any-sync/commonspace/deletionstate/mock_deletionstate"
"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/object/treemanager"
"github.com/anyproto/any-sync/commonspace/object/treemanager/mock_treemanager"
"github.com/anyproto/any-sync/commonspace/peermanager"
"github.com/anyproto/any-sync/commonspace/peermanager/mock_peermanager"
"github.com/anyproto/any-sync/commonspace/spacestate"
"github.com/anyproto/any-sync/commonspace/spacestorage"
"github.com/anyproto/any-sync/commonspace/spacestorage/mock_spacestorage"
"github.com/anyproto/any-sync/commonspace/spacesyncproto/mock_spacesyncproto"
"github.com/anyproto/any-sync/commonspace/syncstatus"
"github.com/anyproto/any-sync/nodeconf"
"github.com/anyproto/any-sync/nodeconf/mock_nodeconf"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"sync/atomic"
"testing"
)
func TestDiffService(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
type mockConfig struct {
}
spaceId := "spaceId"
l := logger.NewNamed("sync")
pSyncMock := mock_periodicsync.NewMockPeriodicSync(ctrl)
storageMock := mock_spacestorage.NewMockSpaceStorage(ctrl)
treeStorageMock := mock_treestorage.NewMockTreeStorage(ctrl)
diffMock := mock_ldiff.NewMockDiff(ctrl)
syncer := mock_headsync.NewMockDiffSyncer(ctrl)
delState := mock_deletionstate.NewMockDeletionState(ctrl)
syncPeriod := 1
initId := "initId"
func (m mockConfig) Init(a *app.App) (err error) {
return nil
}
service := &headSync{
spaceId: spaceId,
storage: storageMock,
periodicSync: pSyncMock,
syncer: syncer,
diff: diffMock,
log: l,
syncPeriod: syncPeriod,
func (m mockConfig) Name() (name string) {
return "config"
}
func (m mockConfig) GetSpace() config.Config {
return config.Config{}
}
type headSyncFixture struct {
spaceState *spacestate.SpaceState
ctrl *gomock.Controller
app *app.App
configurationMock *mock_nodeconf.MockService
storageMock *mock_spacestorage.MockSpaceStorage
peerManagerMock *mock_peermanager.MockPeerManager
credentialProviderMock *mock_credentialprovider.MockCredentialProvider
syncStatus syncstatus.StatusService
treeManagerMock *mock_treemanager.MockTreeManager
deletionStateMock *mock_deletionstate.MockObjectDeletionState
diffSyncerMock *mock_headsync.MockDiffSyncer
treeSyncerMock *mock_treemanager.MockTreeSyncer
diffMock *mock_ldiff.MockDiff
clientMock *mock_spacesyncproto.MockDRPCSpaceSyncClient
headSync *headSync
diffSyncer *diffSyncer
}
func newHeadSyncFixture(t *testing.T) *headSyncFixture {
spaceState := &spacestate.SpaceState{
SpaceId: "spaceId",
SpaceIsDeleted: &atomic.Bool{},
}
ctrl := gomock.NewController(t)
configurationMock := mock_nodeconf.NewMockService(ctrl)
configurationMock.EXPECT().Name().AnyTimes().Return(nodeconf.CName)
storageMock := mock_spacestorage.NewMockSpaceStorage(ctrl)
storageMock.EXPECT().Name().AnyTimes().Return(spacestorage.CName)
peerManagerMock := mock_peermanager.NewMockPeerManager(ctrl)
peerManagerMock.EXPECT().Name().AnyTimes().Return(peermanager.CName)
credentialProviderMock := mock_credentialprovider.NewMockCredentialProvider(ctrl)
credentialProviderMock.EXPECT().Name().AnyTimes().Return(credentialprovider.CName)
syncStatus := syncstatus.NewNoOpSyncStatus()
treeManagerMock := mock_treemanager.NewMockTreeManager(ctrl)
treeManagerMock.EXPECT().Name().AnyTimes().Return(treemanager.CName)
deletionStateMock := mock_deletionstate.NewMockObjectDeletionState(ctrl)
deletionStateMock.EXPECT().Name().AnyTimes().Return(deletionstate.CName)
diffSyncerMock := mock_headsync.NewMockDiffSyncer(ctrl)
treeSyncerMock := mock_treemanager.NewMockTreeSyncer(ctrl)
diffMock := mock_ldiff.NewMockDiff(ctrl)
clientMock := mock_spacesyncproto.NewMockDRPCSpaceSyncClient(ctrl)
hs := &headSync{}
a := &app.App{}
a.Register(spaceState).
Register(mockConfig{}).
Register(configurationMock).
Register(storageMock).
Register(peerManagerMock).
Register(credentialProviderMock).
Register(syncStatus).
Register(treeManagerMock).
Register(deletionStateMock).
Register(hs)
return &headSyncFixture{
spaceState: spaceState,
ctrl: ctrl,
app: a,
configurationMock: configurationMock,
storageMock: storageMock,
peerManagerMock: peerManagerMock,
credentialProviderMock: credentialProviderMock,
syncStatus: syncStatus,
treeManagerMock: treeManagerMock,
deletionStateMock: deletionStateMock,
headSync: hs,
diffSyncerMock: diffSyncerMock,
treeSyncerMock: treeSyncerMock,
diffMock: diffMock,
clientMock: clientMock,
}
}
t.Run("init", func(t *testing.T) {
storageMock.EXPECT().TreeStorage(initId).Return(treeStorageMock, nil)
treeStorageMock.EXPECT().Heads().Return([]string{"h1", "h2"}, nil)
syncer.EXPECT().Init(delState)
diffMock.EXPECT().Set(ldiff.Element{
Id: initId,
func (fx *headSyncFixture) init(t *testing.T) {
createDiffSyncer = func(hs *headSync) DiffSyncer {
return fx.diffSyncerMock
}
fx.diffSyncerMock.EXPECT().Init()
err := fx.headSync.Init(fx.app)
require.NoError(t, err)
fx.headSync.diff = fx.diffMock
}
func (fx *headSyncFixture) stop() {
fx.ctrl.Finish()
}
func TestHeadSync(t *testing.T) {
ctx := context.Background()
t.Run("run close", func(t *testing.T) {
fx := newHeadSyncFixture(t)
fx.init(t)
defer fx.stop()
ids := []string{"id1"}
treeMock := mock_treestorage.NewMockTreeStorage(fx.ctrl)
fx.storageMock.EXPECT().StoredIds().Return(ids, nil)
fx.storageMock.EXPECT().TreeStorage(ids[0]).Return(treeMock, nil)
treeMock.EXPECT().Heads().Return([]string{"h1", "h2"}, nil)
fx.diffMock.EXPECT().Set(ldiff.Element{
Id: "id1",
Head: "h1h2",
})
hash := "123"
diffMock.EXPECT().Hash().Return(hash)
storageMock.EXPECT().WriteSpaceHash(hash)
pSyncMock.EXPECT().Run()
service.Init([]string{initId}, delState)
fx.diffMock.EXPECT().Hash().Return("hash")
fx.storageMock.EXPECT().WriteSpaceHash("hash").Return(nil)
fx.diffSyncerMock.EXPECT().Sync(gomock.Any()).Return(nil)
fx.diffSyncerMock.EXPECT().Close().Return(nil)
err := fx.headSync.Run(ctx)
require.NoError(t, err)
err = fx.headSync.Close(ctx)
require.NoError(t, err)
})
t.Run("update heads", func(t *testing.T) {
syncer.EXPECT().UpdateHeads(initId, []string{"h1", "h2"})
service.UpdateHeads(initId, []string{"h1", "h2"})
fx := newHeadSyncFixture(t)
fx.init(t)
defer fx.stop()
fx.diffSyncerMock.EXPECT().UpdateHeads("id1", []string{"h1"})
fx.headSync.UpdateHeads("id1", []string{"h1"})
})
t.Run("remove objects", func(t *testing.T) {
syncer.EXPECT().RemoveObjects([]string{"h1", "h2"})
service.RemoveObjects([]string{"h1", "h2"})
})
fx := newHeadSyncFixture(t)
fx.init(t)
defer fx.stop()
t.Run("close", func(t *testing.T) {
pSyncMock.EXPECT().Close()
service.Close()
fx.diffSyncerMock.EXPECT().RemoveObjects([]string{"id1"})
fx.headSync.RemoveObjects([]string{"id1"})
})
}
@@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/anytypeio/any-sync/commonspace/headsync (interfaces: DiffSyncer)
// Source: github.com/anyproto/any-sync/commonspace/headsync (interfaces: DiffSyncer)
// Package mock_headsync is a generated GoMock package.
package mock_headsync
@@ -8,8 +8,7 @@ import (
context "context"
reflect "reflect"
deletionstate "github.com/anytypeio/any-sync/commonspace/settings/deletionstate"
gomock "github.com/golang/mock/gomock"
gomock "go.uber.org/mock/gomock"
)
// MockDiffSyncer is a mock of DiffSyncer interface.
@@ -35,16 +34,30 @@ func (m *MockDiffSyncer) EXPECT() *MockDiffSyncerMockRecorder {
return m.recorder
}
// Init mocks base method.
func (m *MockDiffSyncer) Init(arg0 deletionstate.DeletionState) {
// Close mocks base method.
func (m *MockDiffSyncer) Close() error {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Init", arg0)
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() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Init")
}
// Init indicates an expected call of Init.
func (mr *MockDiffSyncerMockRecorder) Init(arg0 interface{}) *gomock.Call {
func (mr *MockDiffSyncerMockRecorder) Init() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockDiffSyncer)(nil).Init), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockDiffSyncer)(nil).Init))
}
// RemoveObjects mocks base method.
+2 -2
View File
@@ -2,8 +2,8 @@ package headsync
import (
"context"
"github.com/anytypeio/any-sync/app/ldiff"
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
"github.com/anyproto/any-sync/app/ldiff"
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
)
type Client interface {
+2 -2
View File
@@ -3,8 +3,8 @@ package headsync
import (
"context"
"fmt"
"github.com/anytypeio/any-sync/app/ldiff"
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
"github.com/anyproto/any-sync/app/ldiff"
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
+27
View File
@@ -0,0 +1,27 @@
package headsync
import "strings"
func concatStrings(strs []string) string {
var (
b strings.Builder
totalLen int
)
for _, s := range strs {
totalLen += len(s)
}
b.Grow(totalLen)
for _, s := range strs {
b.WriteString(s)
}
return b.String()
}
func splitString(str string) (res []string) {
const cidLen = 59
for i := 0; i < len(str); i += cidLen {
res = append(res, str[i:i+cidLen])
}
return
}
+30 -8
View File
@@ -1,14 +1,36 @@
package accountdata
import (
"github.com/anytypeio/any-sync/util/keys/asymmetric/encryptionkey"
"github.com/anytypeio/any-sync/util/keys/asymmetric/signingkey"
"crypto/rand"
"github.com/anyproto/any-sync/util/crypto"
)
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
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
}
@@ -1,12 +0,0 @@
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
@@ -2,117 +2,105 @@ syntax = "proto3";
package aclrecord;
option go_package = "commonspace/object/acl/aclrecordproto";
message RawAclRecord {
bytes payload = 1;
bytes signature = 2;
bytes acceptorIdentity = 3;
bytes acceptorSignature = 4;
}
message RawAclRecordWithId {
bytes payload = 1;
string id = 2;
}
message AclRecord {
string prevId = 1;
bytes identity = 2;
bytes data = 3;
uint64 currentReadKeyHash = 4;
int64 timestamp = 5;
}
// AclRoot is a root of access control list
message AclRoot {
bytes identity = 1;
bytes encryptionKey = 2;
bytes masterKey = 2;
string spaceId = 3;
bytes encryptedReadKey = 4;
string derivationScheme = 5;
uint64 currentReadKeyHash = 6;
int64 timestamp = 7;
int64 timestamp = 5;
bytes identitySignature = 6;
}
message AclContentValue {
oneof value {
AclUserAdd userAdd = 1;
AclUserRemove userRemove = 2;
AclUserPermissionChange userPermissionChange = 3;
AclUserInvite userInvite = 4;
AclUserJoin userJoin = 5;
}
// AclAccountInvite contains the public invite key, the private part of which is sent to the user directly
message AclAccountInvite {
bytes inviteKey = 1;
}
message AclData {
repeated AclContentValue aclContent = 1;
// AclAccountRequestJoin contains the reference to the invite record and the data of the person who wants to join, confirmed by the private invite key
message AclAccountRequestJoin {
bytes inviteIdentity = 1;
string inviteRecordId = 2;
bytes inviteIdentitySignature = 3;
bytes metadata = 4;
}
message AclState {
repeated uint64 readKeyHashes = 1;
repeated AclUserState userStates = 2;
map<string, AclUserInvite> invites = 3;
}
message AclUserState {
// AclAccountRequestAccept contains the reference to join record and all read keys, encrypted with the identity of the requestor
message AclAccountRequestAccept {
bytes identity = 1;
bytes encryptionKey = 2;
AclUserPermissions permissions = 3;
}
message AclUserAdd {
bytes identity = 1;
bytes encryptionKey = 2;
repeated bytes encryptedReadKeys = 3;
string requestRecordId = 2;
repeated AclReadKeyWithRecord encryptedReadKeys = 3;
AclUserPermissions permissions = 4;
}
message AclUserInvite {
bytes acceptPublicKey = 1;
uint64 encryptSymKeyHash = 2;
repeated bytes encryptedReadKeys = 3;
AclUserPermissions permissions = 4;
// AclAccountRequestDecline contains the reference to join record
message AclAccountRequestDecline {
string requestRecordId = 1;
}
message AclUserJoin {
// AclAccountInviteRevoke revokes the invite record
message AclAccountInviteRevoke {
string inviteRecordId = 1;
}
// AclReadKeys are a read key with record id
message AclReadKeyWithRecord {
string recordId = 1;
bytes encryptedReadKey = 2;
}
// AclEncryptedReadKeys are new key for specific identity
message AclEncryptedReadKey {
bytes identity = 1;
bytes encryptionKey = 2;
bytes acceptSignature = 3;
bytes acceptPubKey = 4;
repeated bytes encryptedReadKeys = 5;
bytes encryptedReadKey = 2;
}
message AclUserRemove {
bytes identity = 1;
repeated AclReadKeyReplace readKeyReplaces = 2;
}
message AclReadKeyReplace {
bytes identity = 1;
bytes encryptionKey = 2;
bytes encryptedReadKey = 3;
}
message AclUserPermissionChange {
// AclAccountPermissionChange changes permissions of specific account
message AclAccountPermissionChange {
bytes identity = 1;
AclUserPermissions permissions = 2;
}
enum AclUserPermissions {
Admin = 0;
Writer = 1;
Reader = 2;
// AclReadKeyChange changes the key for a space
message AclReadKeyChange {
repeated AclEncryptedReadKey accountKeys = 1;
}
message AclSyncMessage {
AclSyncContentValue content = 2;
// AclAccountRemove removes an account and changes read key for space
message AclAccountRemove {
repeated bytes identities = 1;
repeated AclEncryptedReadKey accountKeys = 2;
}
// AclSyncContentValue provides different types for acl sync
message AclSyncContentValue {
// AclAccountRequestRemove adds a request to remove an account
message AclAccountRequestRemove {
}
// AclContentValue contains possible values for Acl
message AclContentValue {
oneof value {
AclAddRecords addRecords = 1;
AclAccountInvite invite = 1;
AclAccountInviteRevoke inviteRevoke = 2;
AclAccountRequestJoin requestJoin = 3;
AclAccountRequestAccept requestAccept = 4;
AclAccountPermissionChange permissionChange = 5;
AclAccountRemove accountRemove = 6;
AclReadKeyChange readKeyChange = 7;
AclAccountRequestDecline requestDecline = 8;
AclAccountRequestRemove accountRequestRemove = 9;
}
}
message AclAddRecords {
repeated RawAclRecordWithId records = 1;
}
// AclData contains different acl content
message AclData {
repeated AclContentValue aclContent = 1;
}
// AclUserPermissions contains different possible user roles
enum AclUserPermissions {
None = 0;
Owner = 1;
Admin = 2;
Writer = 3;
Reader = 4;
}
+464 -103
View File
@@ -1,166 +1,499 @@
package list
import (
"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"
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anyproto/any-sync/consensus/consensusproto"
"github.com/anyproto/any-sync/util/cidutil"
"github.com/anyproto/any-sync/util/crypto"
"github.com/gogo/protobuf/proto"
)
// remove interface
type RootContent struct {
PrivKey crypto.PrivKey
MasterKey crypto.PrivKey
SpaceId string
EncryptedReadKey []byte
}
type RequestJoinPayload struct {
InviteRecordId string
InviteKey crypto.PrivKey
Metadata []byte
}
type RequestAcceptPayload struct {
RequestRecordId string
Permissions AclPermissions
}
type PermissionChangePayload struct {
Identity crypto.PubKey
Permissions AclPermissions
}
type AccountRemovePayload struct {
Identities []crypto.PubKey
ReadKey crypto.SymKey
}
type InviteResult struct {
InviteRec *consensusproto.RawRecord
InviteKey crypto.PrivKey
}
type AclRecordBuilder interface {
ConvertFromRaw(rawIdRecord *aclrecordproto.RawAclRecordWithId) (rec *AclRecord, err error)
BuildUserJoin(acceptPrivKeyBytes []byte, encSymKeyBytes []byte, state *AclState) (rec *aclrecordproto.RawAclRecord, err error)
UnmarshallWithId(rawIdRecord *consensusproto.RawRecordWithId) (rec *AclRecord, err error)
Unmarshall(rawRecord *consensusproto.RawRecord) (rec *AclRecord, err error)
BuildRoot(content RootContent) (rec *consensusproto.RawRecordWithId, err error)
BuildInvite() (res InviteResult, err error)
BuildInviteRevoke(inviteRecordId string) (rawRecord *consensusproto.RawRecord, err error)
BuildRequestJoin(payload RequestJoinPayload) (rawRecord *consensusproto.RawRecord, err error)
BuildRequestAccept(payload RequestAcceptPayload) (rawRecord *consensusproto.RawRecord, err error)
BuildRequestDecline(requestRecordId string) (rawRecord *consensusproto.RawRecord, err error)
BuildRequestRemove() (rawRecord *consensusproto.RawRecord, err error)
BuildPermissionChange(payload PermissionChangePayload) (rawRecord *consensusproto.RawRecord, err error)
BuildReadKeyChange(newKey crypto.SymKey) (rawRecord *consensusproto.RawRecord, err error)
BuildAccountRemove(payload AccountRemovePayload) (rawRecord *consensusproto.RawRecord, err error)
}
type aclRecordBuilder struct {
id string
keychain *keychain.Keychain
id string
keyStorage crypto.KeyStorage
accountKeys *accountdata.AccountKeys
verifier AcceptorVerifier
state *AclState
}
func newAclRecordBuilder(id string, keychain *keychain.Keychain) AclRecordBuilder {
func NewAclRecordBuilder(id string, keyStorage crypto.KeyStorage, keys *accountdata.AccountKeys, verifier AcceptorVerifier) AclRecordBuilder {
return &aclRecordBuilder{
id: id,
keychain: keychain,
id: id,
keyStorage: keyStorage,
accountKeys: keys,
verifier: verifier,
}
}
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,
}
func (a *aclRecordBuilder) buildRecord(aclContent *aclrecordproto.AclContentValue) (rawRec *consensusproto.RawRecord, err error) {
aclData := &aclrecordproto.AclData{AclContent: []*aclrecordproto.AclContentValue{
{Value: &aclrecordproto.AclContentValue_UserJoin{UserJoin: userJoin}},
aclContent,
}}
marshalledJoin, err := aclData.Marshal()
marshalledData, 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()
protoKey, err := a.accountKeys.SignKey.GetPublic().Marshall()
if err != nil {
return
}
recSignature, err := signPrivKey.Sign(marshalledRecord)
rec := &consensusproto.Record{
PrevId: a.state.lastRecordId,
Identity: protoKey,
Data: marshalledData,
Timestamp: time.Now().Unix(),
}
marshalledRec, err := rec.Marshal()
if err != nil {
return
}
rec = &aclrecordproto.RawAclRecord{
Payload: marshalledRecord,
Signature: recSignature,
signature, err := a.accountKeys.SignKey.Sign(marshalledRec)
if err != nil {
return
}
rawRec = &consensusproto.RawRecord{
Payload: marshalledRec,
Signature: signature,
}
return
}
func (a *aclRecordBuilder) ConvertFromRaw(rawIdRecord *aclrecordproto.RawAclRecordWithId) (rec *AclRecord, err error) {
rawRec := &aclrecordproto.RawAclRecord{}
func (a *aclRecordBuilder) BuildInvite() (res InviteResult, err error) {
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
err = ErrInsufficientPermissions
return
}
privKey, pubKey, err := crypto.GenerateRandomEd25519KeyPair()
if err != nil {
return
}
invitePubKey, err := pubKey.Marshall()
if err != nil {
return
}
inviteRec := &aclrecordproto.AclAccountInvite{InviteKey: invitePubKey}
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_Invite{Invite: inviteRec}}
rawRec, err := a.buildRecord(content)
if err != nil {
return
}
res.InviteKey = privKey
res.InviteRec = rawRec
return
}
func (a *aclRecordBuilder) BuildInviteRevoke(inviteRecordId string) (rawRecord *consensusproto.RawRecord, err error) {
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
err = ErrInsufficientPermissions
return
}
_, exists := a.state.inviteKeys[inviteRecordId]
if !exists {
err = ErrNoSuchInvite
return
}
revokeRec := &aclrecordproto.AclAccountInviteRevoke{InviteRecordId: inviteRecordId}
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_InviteRevoke{InviteRevoke: revokeRec}}
return a.buildRecord(content)
}
func (a *aclRecordBuilder) BuildRequestJoin(payload RequestJoinPayload) (rawRecord *consensusproto.RawRecord, err error) {
key, exists := a.state.inviteKeys[payload.InviteRecordId]
if !exists {
err = ErrNoSuchInvite
return
}
if !payload.InviteKey.GetPublic().Equals(key) {
err = ErrIncorrectInviteKey
}
rawIdentity, err := a.accountKeys.SignKey.GetPublic().Raw()
if err != nil {
return
}
signature, err := payload.InviteKey.Sign(rawIdentity)
if err != nil {
return
}
protoIdentity, err := a.accountKeys.SignKey.GetPublic().Marshall()
if err != nil {
return
}
joinRec := &aclrecordproto.AclAccountRequestJoin{
InviteIdentity: protoIdentity,
InviteRecordId: payload.InviteRecordId,
InviteIdentitySignature: signature,
Metadata: payload.Metadata,
}
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_RequestJoin{RequestJoin: joinRec}}
return a.buildRecord(content)
}
func (a *aclRecordBuilder) BuildRequestAccept(payload RequestAcceptPayload) (rawRecord *consensusproto.RawRecord, err error) {
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
err = ErrInsufficientPermissions
return
}
request, exists := a.state.requestRecords[payload.RequestRecordId]
if !exists {
err = ErrNoSuchRequest
return
}
var encryptedReadKeys []*aclrecordproto.AclReadKeyWithRecord
for keyId, key := range a.state.userReadKeys {
rawKey, err := key.Raw()
if err != nil {
return nil, err
}
enc, err := request.RequestIdentity.Encrypt(rawKey)
if err != nil {
return nil, err
}
encryptedReadKeys = append(encryptedReadKeys, &aclrecordproto.AclReadKeyWithRecord{
RecordId: keyId,
EncryptedReadKey: enc,
})
}
if err != nil {
return
}
requestIdentityProto, err := request.RequestIdentity.Marshall()
if err != nil {
return
}
acceptRec := &aclrecordproto.AclAccountRequestAccept{
Identity: requestIdentityProto,
RequestRecordId: payload.RequestRecordId,
EncryptedReadKeys: encryptedReadKeys,
Permissions: aclrecordproto.AclUserPermissions(payload.Permissions),
}
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_RequestAccept{RequestAccept: acceptRec}}
return a.buildRecord(content)
}
func (a *aclRecordBuilder) BuildRequestDecline(requestRecordId string) (rawRecord *consensusproto.RawRecord, err error) {
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
err = ErrInsufficientPermissions
return
}
_, exists := a.state.requestRecords[requestRecordId]
if !exists {
err = ErrNoSuchRequest
return
}
declineRec := &aclrecordproto.AclAccountRequestDecline{RequestRecordId: requestRecordId}
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_RequestDecline{RequestDecline: declineRec}}
return a.buildRecord(content)
}
func (a *aclRecordBuilder) BuildPermissionChange(payload PermissionChangePayload) (rawRecord *consensusproto.RawRecord, err error) {
permissions := a.state.Permissions(a.state.pubKey)
if !permissions.CanManageAccounts() || payload.Identity.Equals(a.state.pubKey) {
err = ErrInsufficientPermissions
return
}
if payload.Permissions.IsOwner() {
err = ErrIsOwner
return
}
protoIdentity, err := payload.Identity.Marshall()
if err != nil {
return
}
permissionRec := &aclrecordproto.AclAccountPermissionChange{
Identity: protoIdentity,
Permissions: aclrecordproto.AclUserPermissions(payload.Permissions),
}
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_PermissionChange{PermissionChange: permissionRec}}
return a.buildRecord(content)
}
func (a *aclRecordBuilder) BuildReadKeyChange(newKey crypto.SymKey) (rawRecord *consensusproto.RawRecord, err error) {
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
err = ErrInsufficientPermissions
return
}
rawKey, err := newKey.Raw()
if err != nil {
return
}
if len(rawKey) != crypto.KeyBytes {
err = ErrIncorrectReadKey
return
}
var aclReadKeys []*aclrecordproto.AclEncryptedReadKey
for _, st := range a.state.userStates {
protoIdentity, err := st.PubKey.Marshall()
if err != nil {
return nil, err
}
enc, err := st.PubKey.Encrypt(rawKey)
if err != nil {
return nil, err
}
aclReadKeys = append(aclReadKeys, &aclrecordproto.AclEncryptedReadKey{
Identity: protoIdentity,
EncryptedReadKey: enc,
})
}
readRec := &aclrecordproto.AclReadKeyChange{AccountKeys: aclReadKeys}
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_ReadKeyChange{ReadKeyChange: readRec}}
return a.buildRecord(content)
}
func (a *aclRecordBuilder) BuildAccountRemove(payload AccountRemovePayload) (rawRecord *consensusproto.RawRecord, err error) {
deletedMap := map[string]struct{}{}
for _, key := range payload.Identities {
permissions := a.state.Permissions(key)
if permissions.IsOwner() {
return nil, ErrInsufficientPermissions
}
if permissions.NoPermissions() {
return nil, ErrNoSuchAccount
}
deletedMap[mapKeyFromPubKey(key)] = struct{}{}
}
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
err = ErrInsufficientPermissions
return
}
rawKey, err := payload.ReadKey.Raw()
if err != nil {
return
}
if len(rawKey) != crypto.KeyBytes {
err = ErrIncorrectReadKey
return
}
var aclReadKeys []*aclrecordproto.AclEncryptedReadKey
for _, st := range a.state.userStates {
if _, exists := deletedMap[mapKeyFromPubKey(st.PubKey)]; exists {
continue
}
protoIdentity, err := st.PubKey.Marshall()
if err != nil {
return nil, err
}
enc, err := st.PubKey.Encrypt(rawKey)
if err != nil {
return nil, err
}
aclReadKeys = append(aclReadKeys, &aclrecordproto.AclEncryptedReadKey{
Identity: protoIdentity,
EncryptedReadKey: enc,
})
}
var marshalledIdentities [][]byte
for _, key := range payload.Identities {
protoIdentity, err := key.Marshall()
if err != nil {
return nil, err
}
marshalledIdentities = append(marshalledIdentities, protoIdentity)
}
removeRec := &aclrecordproto.AclAccountRemove{AccountKeys: aclReadKeys, Identities: marshalledIdentities}
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_AccountRemove{AccountRemove: removeRec}}
return a.buildRecord(content)
}
func (a *aclRecordBuilder) BuildRequestRemove() (rawRecord *consensusproto.RawRecord, err error) {
permissions := a.state.Permissions(a.state.pubKey)
if permissions.NoPermissions() {
err = ErrNoSuchAccount
return
}
if permissions.IsOwner() {
err = ErrIsOwner
return
}
removeRec := &aclrecordproto.AclAccountRequestRemove{}
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_AccountRequestRemove{AccountRequestRemove: removeRec}}
return a.buildRecord(content)
}
func (a *aclRecordBuilder) Unmarshall(rawRecord *consensusproto.RawRecord) (rec *AclRecord, err error) {
aclRecord := &consensusproto.Record{}
err = proto.Unmarshal(rawRecord.Payload, aclRecord)
if err != nil {
return
}
pubKey, err := a.keyStorage.PubKeyFromProto(aclRecord.Identity)
if err != nil {
return
}
aclData := &aclrecordproto.AclData{}
err = proto.Unmarshal(aclRecord.Data, aclData)
if err != nil {
return
}
rec = &AclRecord{
PrevId: aclRecord.PrevId,
Timestamp: aclRecord.Timestamp,
Data: aclRecord.Data,
Signature: rawRecord.Signature,
Identity: pubKey,
Model: aclData,
}
res, err := pubKey.Verify(rawRecord.Payload, rawRecord.Signature)
if err != nil {
return
}
if !res {
err = ErrInvalidSignature
return
}
return
}
func (a *aclRecordBuilder) UnmarshallWithId(rawIdRecord *consensusproto.RawRecordWithId) (rec *AclRecord, err error) {
var (
rawRec = &consensusproto.RawRecord{}
pubKey crypto.PubKey
)
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,
CurrentReadKeyHash: aclRoot.CurrentReadKeyHash,
Timestamp: aclRoot.Timestamp,
Signature: rawRec.Signature,
Identity: aclRoot.Identity,
Model: aclRoot,
Id: rawIdRecord.Id,
Timestamp: aclRoot.Timestamp,
Signature: rawRec.Signature,
Identity: pubKey,
Model: aclRoot,
}
} else {
aclRecord := &aclrecordproto.AclRecord{}
err = a.verifier.VerifyAcceptor(rawRec)
if err != nil {
return
}
aclRecord := &consensusproto.Record{}
err = proto.Unmarshal(rawRec.Payload, aclRecord)
if err != nil {
return
}
pubKey, err = a.keyStorage.PubKeyFromProto(aclRecord.Identity)
if err != nil {
return
}
aclData := &aclrecordproto.AclData{}
err = proto.Unmarshal(aclRecord.Data, aclData)
if err != nil {
return
}
rec = &AclRecord{
Id: rawIdRecord.Id,
PrevId: aclRecord.PrevId,
CurrentReadKeyHash: aclRecord.CurrentReadKeyHash,
Timestamp: aclRecord.Timestamp,
Data: aclRecord.Data,
Signature: rawRec.Signature,
Identity: aclRecord.Identity,
Id: rawIdRecord.Id,
PrevId: aclRecord.PrevId,
Timestamp: aclRecord.Timestamp,
Data: aclRecord.Data,
Signature: rawRec.Signature,
Identity: pubKey,
Model: aclData,
}
}
err = verifyRaw(a.keychain, rawRec, rawIdRecord, rec.Identity)
err = verifyRaw(pubKey, rawRec, rawIdRecord)
return
}
func verifyRaw(
keychain *keychain.Keychain,
rawRec *aclrecordproto.RawAclRecord,
recWithId *aclrecordproto.RawAclRecordWithId,
identity []byte) (err error) {
identityKey, err := keychain.GetOrAdd(string(identity))
func (a *aclRecordBuilder) BuildRoot(content RootContent) (rec *consensusproto.RawRecordWithId, 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,
rawRec *consensusproto.RawRecord,
recWithId *consensusproto.RawRecordWithId) (err error) {
// verifying signature
res, err := identityKey.Verify(rawRec.Payload, rawRec.Signature)
res, err := pubKey.Verify(rawRec.Payload, rawRec.Signature)
if err != nil {
return
}
@@ -175,3 +508,31 @@ func verifyRaw(
}
return
}
func marshalAclRoot(aclRoot *aclrecordproto.AclRoot, key crypto.PrivKey) (rawWithId *consensusproto.RawRecordWithId, err error) {
marshalledRoot, err := aclRoot.Marshal()
if err != nil {
return
}
signature, err := key.Sign(marshalledRoot)
if err != nil {
return
}
raw := &consensusproto.RawRecord{
Payload: marshalledRoot,
Signature: signature,
}
marshalledRaw, err := raw.Marshal()
if err != nil {
return
}
aclHeadId, err := cidutil.NewCidFromBytes(marshalledRaw)
if err != nil {
return
}
rawWithId = &consensusproto.RawRecordWithId{
Payload: marshalledRaw,
Id: aclHeadId,
}
return
}
@@ -1,50 +0,0 @@
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) {
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)
}
+310 -310
View File
@@ -1,120 +1,142 @@
package list
import (
"bytes"
"errors"
"fmt"
"github.com/anytypeio/any-sync/app/logger"
aclrecordproto "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/anyproto/any-sync/app/logger"
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anyproto/any-sync/util/crypto"
"github.com/gogo/protobuf/proto"
"go.uber.org/zap"
"hash/fnv"
)
var log = logger.NewNamed("acllist").Sugar()
var log = logger.NewNamedSugared("common.commonspace.acllist")
var (
ErrNoSuchUser = errors.New("no such user")
ErrFailedToDecrypt = errors.New("failed to decrypt key")
ErrUserRemoved = errors.New("user was removed from the document")
ErrDocumentForbidden = errors.New("your user was forbidden access to the document")
ErrUserAlreadyExists = errors.New("user already exists")
ErrNoSuchRecord = errors.New("no such record")
ErrNoSuchInvite = errors.New("no such invite")
ErrOldInvite = errors.New("invite is too old")
ErrInsufficientPermissions = errors.New("insufficient permissions")
ErrNoReadKey = errors.New("acl state doesn't have a read key")
ErrInvalidSignature = errors.New("signature is invalid")
ErrIncorrectRoot = errors.New("incorrect root")
ErrIncorrectRecordSequence = errors.New("incorrect prev id of a record")
ErrNoSuchAccount = errors.New("no such account")
ErrPendingRequest = errors.New("already exists pending request")
ErrUnexpectedContentType = errors.New("unexpected content type")
ErrIncorrectIdentity = errors.New("incorrect identity")
ErrIncorrectInviteKey = errors.New("incorrect invite key")
ErrFailedToDecrypt = errors.New("failed to decrypt key")
ErrNoSuchRecord = errors.New("no such record")
ErrNoSuchRequest = errors.New("no such request")
ErrNoSuchInvite = errors.New("no such invite")
ErrInsufficientPermissions = errors.New("insufficient permissions")
ErrIsOwner = errors.New("can't be made by owner")
ErrIncorrectNumberOfAccounts = errors.New("incorrect number of accounts")
ErrDuplicateAccounts = errors.New("duplicate accounts")
ErrNoReadKey = errors.New("acl state doesn't have a read key")
ErrIncorrectReadKey = errors.New("incorrect read key")
ErrInvalidSignature = errors.New("signature is invalid")
ErrIncorrectRoot = errors.New("incorrect root")
ErrIncorrectRecordSequence = errors.New("incorrect prev id of a record")
)
type UserPermissionPair struct {
Identity string
Identity crypto.PubKey
Permission aclrecordproto.AclUserPermissions
}
type AclState struct {
id string
currentReadKeyHash uint64
userReadKeys map[uint64]*symmetric.Key
userStates map[string]*aclrecordproto.AclUserState
userInvites map[string]*aclrecordproto.AclUserInvite
encryptionKey encryptionkey.PrivKey
signingKey signingkey.PrivKey
totalReadKeys int
id string
currentReadKeyId string
// userReadKeys is a map recordId -> read key which tells us about every read key
userReadKeys map[string]crypto.SymKey
// userStates is a map pubKey -> state which defines current user state
userStates map[string]AclUserState
// statesAtRecord is a map recordId -> state which define user state at particular record
// probably this can grow rather large at some point, so we can maybe optimise later to have:
// - map pubKey -> []recordIds (where recordIds is an array where such identity permissions were changed)
statesAtRecord map[string][]AclUserState
// inviteKeys is a map recordId -> invite
inviteKeys map[string]crypto.PubKey
// requestRecords is a map recordId -> RequestRecord
requestRecords map[string]RequestRecord
// pendingRequests is a map pubKey -> recordId
pendingRequests map[string]string
key crypto.PrivKey
pubKey crypto.PubKey
keyStore crypto.KeyStorage
totalReadKeys int
identity string
permissionsAtRecord map[string][]UserPermissionPair
lastRecordId string
keychain *keychain.Keychain
lastRecordId string
contentValidator ContentValidator
}
func newAclStateWithKeys(
id string,
signingKey signingkey.PrivKey,
encryptionKey encryptionkey.PrivKey) (*AclState, error) {
identity, err := signingKey.GetPublic().Raw()
if err != nil {
return nil, err
key crypto.PrivKey) (*AclState, error) {
st := &AclState{
id: id,
key: key,
pubKey: key.GetPublic(),
userReadKeys: make(map[string]crypto.SymKey),
userStates: make(map[string]AclUserState),
statesAtRecord: make(map[string][]AclUserState),
inviteKeys: make(map[string]crypto.PubKey),
requestRecords: make(map[string]RequestRecord),
pendingRequests: make(map[string]string),
keyStore: crypto.NewKeyStorage(),
}
return &AclState{
id: id,
identity: string(identity),
signingKey: signingKey,
encryptionKey: encryptionKey,
userReadKeys: make(map[uint64]*symmetric.Key),
userStates: make(map[string]*aclrecordproto.AclUserState),
userInvites: make(map[string]*aclrecordproto.AclUserInvite),
permissionsAtRecord: make(map[string][]UserPermissionPair),
}, nil
st.contentValidator = &contentValidator{
keyStore: st.keyStore,
aclState: st,
}
return st, nil
}
func newAclState(id string) *AclState {
return &AclState{
id: id,
userReadKeys: make(map[uint64]*symmetric.Key),
userStates: make(map[string]*aclrecordproto.AclUserState),
userInvites: make(map[string]*aclrecordproto.AclUserInvite),
permissionsAtRecord: make(map[string][]UserPermissionPair),
st := &AclState{
id: id,
userReadKeys: make(map[string]crypto.SymKey),
userStates: make(map[string]AclUserState),
statesAtRecord: make(map[string][]AclUserState),
inviteKeys: make(map[string]crypto.PubKey),
requestRecords: make(map[string]RequestRecord),
pendingRequests: make(map[string]string),
keyStore: crypto.NewKeyStorage(),
}
st.contentValidator = &contentValidator{
keyStore: st.keyStore,
aclState: st,
}
return st
}
func (st *AclState) CurrentReadKeyHash() uint64 {
return st.currentReadKeyHash
func (st *AclState) Validator() ContentValidator {
return st.contentValidator
}
func (st *AclState) CurrentReadKey() (*symmetric.Key, error) {
key, exists := st.userReadKeys[st.currentReadKeyHash]
func (st *AclState) CurrentReadKeyId() string {
return st.currentReadKeyId
}
func (st *AclState) CurrentReadKey() (crypto.SymKey, error) {
key, exists := st.userReadKeys[st.CurrentReadKeyId()]
if !exists {
return nil, ErrNoReadKey
}
return key, nil
}
func (st *AclState) UserReadKeys() map[uint64]*symmetric.Key {
func (st *AclState) UserReadKeys() map[string]crypto.SymKey {
return st.userReadKeys
}
func (st *AclState) PermissionsAtRecord(id string, identity string) (UserPermissionPair, error) {
permissions, ok := st.permissionsAtRecord[id]
func (st *AclState) StateAtRecord(id string, pubKey crypto.PubKey) (AclUserState, error) {
userState, ok := st.statesAtRecord[id]
if !ok {
log.Errorf("missing record at id %s", id)
return UserPermissionPair{}, ErrNoSuchRecord
return AclUserState{}, ErrNoSuchRecord
}
for _, perm := range permissions {
if perm.Identity == identity {
for _, perm := range userState {
if perm.PubKey.Equals(pubKey) {
return perm, nil
}
}
return UserPermissionPair{}, ErrNoSuchUser
return AclUserState{}, ErrNoSuchAccount
}
func (st *AclState) applyRecord(record *AclRecord) (err error) {
@@ -127,338 +149,316 @@ func (st *AclState) applyRecord(record *AclRecord) (err error) {
err = ErrIncorrectRecordSequence
return
}
// if the record is root record
if record.Id == st.id {
root, ok := record.Model.(*aclrecordproto.AclRoot)
if !ok {
return ErrIncorrectRoot
}
err = st.applyRoot(root)
err = st.applyRoot(record)
if err != nil {
return
}
st.permissionsAtRecord[record.Id] = []UserPermissionPair{
{Identity: string(root.Identity), Permission: aclrecordproto.AclUserPermissions_Admin},
st.statesAtRecord[record.Id] = []AclUserState{
st.userStates[mapKeyFromPubKey(record.Identity)],
}
return
}
aclData := &aclrecordproto.AclData{}
if record.Model != nil {
aclData = record.Model.(*aclrecordproto.AclData)
} else {
// if the model is not cached
if record.Model == nil {
aclData := &aclrecordproto.AclData{}
err = proto.Unmarshal(record.Data, aclData)
if err != nil {
return
}
record.Model = aclData
}
err = st.applyChangeData(aclData, record.CurrentReadKeyHash, record.Identity)
// applying records contents
err = st.applyChangeData(record)
if err != nil {
return
}
// getting all permissions for users at record
var permissions []UserPermissionPair
// getting all states for users at record and saving them
var states []AclUserState
for _, state := range st.userStates {
permission := UserPermissionPair{
Identity: string(state.Identity),
Permission: state.Permissions,
}
permissions = append(permissions, permission)
states = append(states, state)
}
st.permissionsAtRecord[record.Id] = permissions
st.statesAtRecord[record.Id] = states
return
}
func (st *AclState) applyRoot(root *aclrecordproto.AclRoot) (err error) {
if st.signingKey != nil && st.encryptionKey != nil && st.identity == string(root.Identity) {
err = st.saveReadKeyFromRoot(root)
func (st *AclState) applyRoot(record *AclRecord) (err error) {
if st.key != nil && st.pubKey.Equals(record.Identity) {
err = st.saveReadKeyFromRoot(record)
if err != nil {
return
}
}
// adding user to the list
userState := &aclrecordproto.AclUserState{
Identity: root.Identity,
EncryptionKey: root.EncryptionKey,
Permissions: aclrecordproto.AclUserPermissions_Admin,
userState := AclUserState{
PubKey: record.Identity,
Permissions: AclPermissions(aclrecordproto.AclUserPermissions_Owner),
}
st.currentReadKeyHash = root.CurrentReadKeyHash
st.userStates[string(root.Identity)] = userState
st.currentReadKeyId = record.Id
st.userStates[mapKeyFromPubKey(record.Identity)] = userState
st.totalReadKeys++
return
}
func (st *AclState) saveReadKeyFromRoot(root *aclrecordproto.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 = aclrecordproto.AclReadKeyDerive(signPrivKey, encPrivKey)
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()
if err != nil {
return
}
} else {
readKey, _, err = st.decryptReadKeyAndHash(root.EncryptedReadKey)
readKey, err = st.decryptReadKey(root.EncryptedReadKey)
if err != nil {
return
}
}
hasher := fnv.New64()
_, err = hasher.Write(readKey.Bytes())
if err != nil {
return
}
if hasher.Sum64() != root.CurrentReadKeyHash {
return ErrIncorrectRoot
}
st.userReadKeys[root.CurrentReadKeyHash] = readKey
st.userReadKeys[record.Id] = readKey
return
}
func (st *AclState) applyChangeData(changeData *aclrecordproto.AclData, hash uint64, identity []byte) (err error) {
defer func() {
if err != nil {
return
}
if hash != st.currentReadKeyHash {
st.totalReadKeys++
st.currentReadKeyHash = hash
}
}()
if !st.isUserJoin(changeData) {
// we check signature when we add this to the List, so no need to do it here
if _, exists := st.userStates[string(identity)]; !exists {
err = ErrNoSuchUser
return
}
if !st.HasPermission(identity, aclrecordproto.AclUserPermissions_Admin) {
err = fmt.Errorf("user %s must have admin permissions", identity)
return
}
}
for _, ch := range changeData.GetAclContent() {
if err = st.applyChangeContent(ch); err != nil {
func (st *AclState) applyChangeData(record *AclRecord) (err error) {
model := record.Model.(*aclrecordproto.AclData)
for _, ch := range model.GetAclContent() {
if err = st.applyChangeContent(ch, record.Id, record.Identity); err != nil {
log.Info("error while applying changes: %v; ignore", zap.Error(err))
return err
}
}
return nil
}
func (st *AclState) applyChangeContent(ch *aclrecordproto.AclContentValue) error {
func (st *AclState) applyChangeContent(ch *aclrecordproto.AclContentValue, recordId string, authorIdentity crypto.PubKey) error {
switch {
case ch.GetUserPermissionChange() != nil:
return st.applyUserPermissionChange(ch.GetUserPermissionChange())
case ch.GetUserAdd() != nil:
return st.applyUserAdd(ch.GetUserAdd())
case ch.GetUserRemove() != nil:
return st.applyUserRemove(ch.GetUserRemove())
case ch.GetUserInvite() != nil:
return st.applyUserInvite(ch.GetUserInvite())
case ch.GetUserJoin() != nil:
return st.applyUserJoin(ch.GetUserJoin())
case ch.GetPermissionChange() != nil:
return st.applyPermissionChange(ch.GetPermissionChange(), recordId, authorIdentity)
case ch.GetInvite() != nil:
return st.applyInvite(ch.GetInvite(), recordId, authorIdentity)
case ch.GetInviteRevoke() != nil:
return st.applyInviteRevoke(ch.GetInviteRevoke(), recordId, authorIdentity)
case ch.GetRequestJoin() != nil:
return st.applyRequestJoin(ch.GetRequestJoin(), recordId, authorIdentity)
case ch.GetRequestAccept() != nil:
return st.applyRequestAccept(ch.GetRequestAccept(), recordId, authorIdentity)
case ch.GetRequestDecline() != nil:
return st.applyRequestDecline(ch.GetRequestDecline(), recordId, authorIdentity)
case ch.GetAccountRemove() != nil:
return st.applyAccountRemove(ch.GetAccountRemove(), recordId, authorIdentity)
case ch.GetReadKeyChange() != nil:
return st.applyReadKeyChange(ch.GetReadKeyChange(), recordId, authorIdentity)
case ch.GetAccountRequestRemove() != nil:
return st.applyRequestRemove(ch.GetAccountRequestRemove(), recordId, authorIdentity)
default:
return fmt.Errorf("unexpected change type: %v", ch)
return ErrUnexpectedContentType
}
}
func (st *AclState) applyUserPermissionChange(ch *aclrecordproto.AclUserPermissionChange) error {
chIdentity := string(ch.Identity)
state, exists := st.userStates[chIdentity]
if !exists {
return ErrNoSuchUser
func (st *AclState) applyPermissionChange(ch *aclrecordproto.AclAccountPermissionChange, recordId string, authorIdentity crypto.PubKey) error {
chIdentity, err := st.keyStore.PubKeyFromProto(ch.Identity)
if err != nil {
return err
}
state.Permissions = ch.Permissions
err = st.contentValidator.ValidatePermissionChange(ch, authorIdentity)
if err != nil {
return err
}
stringKey := mapKeyFromPubKey(chIdentity)
state, _ := st.userStates[stringKey]
state.Permissions = AclPermissions(ch.Permissions)
st.userStates[stringKey] = state
return nil
}
func (st *AclState) applyUserInvite(ch *aclrecordproto.AclUserInvite) error {
st.userInvites[string(ch.AcceptPublicKey)] = ch
func (st *AclState) applyInvite(ch *aclrecordproto.AclAccountInvite, recordId string, authorIdentity crypto.PubKey) error {
inviteKey, err := st.keyStore.PubKeyFromProto(ch.InviteKey)
if err != nil {
return err
}
err = st.contentValidator.ValidateInvite(ch, authorIdentity)
if err != nil {
return err
}
st.inviteKeys[recordId] = inviteKey
return nil
}
func (st *AclState) applyUserJoin(ch *aclrecordproto.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)
if _, exists = st.userStates[chIdentity]; exists {
return ErrUserAlreadyExists
}
// validating signature
signature := ch.GetAcceptSignature()
verificationKey, err := signingkey.NewSigningEd25519PubKeyFromBytes(invite.AcceptPublicKey)
func (st *AclState) applyInviteRevoke(ch *aclrecordproto.AclAccountInviteRevoke, recordId string, authorIdentity crypto.PubKey) error {
err := st.contentValidator.ValidateInviteRevoke(ch, authorIdentity)
if err != nil {
return fmt.Errorf("public key verifying invite accepts is given in incorrect format: %v", err)
return err
}
delete(st.inviteKeys, ch.InviteRecordId)
return nil
}
res, err := verificationKey.Verify(ch.Identity, signature)
func (st *AclState) applyRequestJoin(ch *aclrecordproto.AclAccountRequestJoin, recordId string, authorIdentity crypto.PubKey) error {
err := st.contentValidator.ValidateRequestJoin(ch, authorIdentity)
if err != nil {
return fmt.Errorf("verification returned error: %w", err)
return err
}
if !res {
return ErrInvalidSignature
st.pendingRequests[mapKeyFromPubKey(authorIdentity)] = recordId
st.requestRecords[recordId] = RequestRecord{
RequestIdentity: authorIdentity,
RequestMetadata: ch.Metadata,
Type: RequestTypeJoin,
}
return nil
}
// if ourselves -> we need to decrypt the read keys
if st.identity == chIdentity {
for _, key := range ch.EncryptedReadKeys {
key, hash, err := st.decryptReadKeyAndHash(key)
func (st *AclState) applyRequestAccept(ch *aclrecordproto.AclAccountRequestAccept, recordId string, authorIdentity crypto.PubKey) error {
err := st.contentValidator.ValidateRequestAccept(ch, authorIdentity)
if err != nil {
return err
}
acceptIdentity, err := st.keyStore.PubKeyFromProto(ch.Identity)
if err != nil {
return err
}
record, _ := st.requestRecords[ch.RequestRecordId]
st.userStates[mapKeyFromPubKey(acceptIdentity)] = AclUserState{
PubKey: acceptIdentity,
Permissions: AclPermissions(ch.Permissions),
RequestMetadata: record.RequestMetadata,
}
delete(st.pendingRequests, mapKeyFromPubKey(st.requestRecords[ch.RequestRecordId].RequestIdentity))
if !st.pubKey.Equals(acceptIdentity) {
return nil
}
for _, key := range ch.EncryptedReadKeys {
decrypted, err := st.key.Decrypt(key.EncryptedReadKey)
if err != nil {
return err
}
sym, err := crypto.UnmarshallAESKey(decrypted)
if err != nil {
return err
}
st.userReadKeys[key.RecordId] = sym
}
return nil
}
func (st *AclState) applyRequestDecline(ch *aclrecordproto.AclAccountRequestDecline, recordId string, authorIdentity crypto.PubKey) error {
err := st.contentValidator.ValidateRequestDecline(ch, authorIdentity)
if err != nil {
return err
}
delete(st.pendingRequests, mapKeyFromPubKey(st.requestRecords[ch.RequestRecordId].RequestIdentity))
delete(st.requestRecords, ch.RequestRecordId)
return nil
}
func (st *AclState) applyRequestRemove(ch *aclrecordproto.AclAccountRequestRemove, recordId string, authorIdentity crypto.PubKey) error {
err := st.contentValidator.ValidateRequestRemove(ch, authorIdentity)
if err != nil {
return err
}
st.requestRecords[recordId] = RequestRecord{
RequestIdentity: authorIdentity,
Type: RequestTypeRemove,
}
st.pendingRequests[mapKeyFromPubKey(authorIdentity)] = recordId
return nil
}
func (st *AclState) applyAccountRemove(ch *aclrecordproto.AclAccountRemove, recordId string, authorIdentity crypto.PubKey) error {
err := st.contentValidator.ValidateAccountRemove(ch, authorIdentity)
if err != nil {
return err
}
for _, rawIdentity := range ch.Identities {
identity, err := st.keyStore.PubKeyFromProto(rawIdentity)
if err != nil {
return err
}
idKey := mapKeyFromPubKey(identity)
delete(st.userStates, idKey)
delete(st.pendingRequests, idKey)
}
return st.updateReadKey(ch.AccountKeys, recordId)
}
func (st *AclState) applyReadKeyChange(ch *aclrecordproto.AclReadKeyChange, recordId string, authorIdentity crypto.PubKey) error {
err := st.contentValidator.ValidateReadKeyChange(ch, authorIdentity)
if err != nil {
return err
}
return st.updateReadKey(ch.AccountKeys, recordId)
}
func (st *AclState) updateReadKey(keys []*aclrecordproto.AclEncryptedReadKey, recordId string) error {
for _, accKey := range keys {
identity, _ := st.keyStore.PubKeyFromProto(accKey.Identity)
if st.pubKey.Equals(identity) {
res, err := st.decryptReadKey(accKey.EncryptedReadKey)
if err != nil {
return ErrFailedToDecrypt
return err
}
st.userReadKeys[hash] = key
st.userReadKeys[recordId] = res
}
}
// adding user to the list
userState := &aclrecordproto.AclUserState{
Identity: ch.Identity,
EncryptionKey: ch.EncryptionKey,
Permissions: invite.Permissions,
}
st.userStates[chIdentity] = userState
st.currentReadKeyId = recordId
return nil
}
func (st *AclState) applyUserAdd(ch *aclrecordproto.AclUserAdd) error {
chIdentity := string(ch.Identity)
if _, exists := st.userStates[chIdentity]; exists {
return ErrUserAlreadyExists
}
st.userStates[chIdentity] = &aclrecordproto.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 *aclrecordproto.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)
func (st *AclState) decryptReadKey(msg []byte) (crypto.SymKey, error) {
decrypted, err := st.key.Decrypt(msg)
if err != nil {
return nil, 0, ErrFailedToDecrypt
return nil, ErrFailedToDecrypt
}
key, err := symmetric.FromBytes(decrypted)
key, err := crypto.UnmarshallAESKey(decrypted)
if err != nil {
return nil, 0, ErrFailedToDecrypt
return nil, ErrFailedToDecrypt
}
hasher := fnv.New64()
hasher.Write(decrypted)
return key, hasher.Sum64(), nil
return key, nil
}
func (st *AclState) HasPermission(identity []byte, permission aclrecordproto.AclUserPermissions) bool {
state, exists := st.userStates[string(identity)]
func (st *AclState) Permissions(identity crypto.PubKey) AclPermissions {
state, exists := st.userStates[mapKeyFromPubKey(identity)]
if !exists {
return false
return AclPermissions(aclrecordproto.AclUserPermissions_None)
}
return state.Permissions == permission
return state.Permissions
}
func (st *AclState) isUserJoin(data *aclrecordproto.AclData) bool {
// if we have a UserJoin, then it should always be the first one applied
return data.GetAclContent() != nil && data.GetAclContent()[0].GetUserJoin() != nil
}
func (st *AclState) isUserAdd(data *aclrecordproto.AclData, identity []byte) bool {
// if we have a UserAdd, then it should always be the first one applied
userAdd := data.GetAclContent()[0].GetUserAdd()
return data.GetAclContent() != nil && userAdd != nil && bytes.Compare(userAdd.GetIdentity(), identity) == 0
}
func (st *AclState) UserStates() map[string]*aclrecordproto.AclUserState {
return st.userStates
}
func (st *AclState) Invite(acceptPubKey []byte) (invite *aclrecordproto.AclUserInvite, err error) {
invite, exists := st.userInvites[string(acceptPubKey)]
if !exists {
err = ErrNoSuchInvite
return
}
if len(invite.EncryptedReadKeys) != st.totalReadKeys {
err = ErrOldInvite
func (st *AclState) JoinRecords() (records []RequestRecord) {
for _, recId := range st.pendingRequests {
rec := st.requestRecords[recId]
if rec.Type == RequestTypeJoin {
records = append(records, rec)
}
}
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) RemoveRecords() (records []RequestRecord) {
for _, recId := range st.pendingRequests {
rec := st.requestRecords[recId]
if rec.Type == RequestTypeRemove {
records = append(records, rec)
}
}
return
}
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())
}
+8 -11
View File
@@ -1,21 +1,18 @@
package list
import (
"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"
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anyproto/any-sync/util/crypto"
)
type aclStateBuilder struct {
signPrivKey signingkey.PrivKey
encPrivKey encryptionkey.PrivKey
id string
privKey crypto.PrivKey
id string
}
func newAclStateBuilderWithIdentity(accountData *accountdata.AccountData) *aclStateBuilder {
func newAclStateBuilderWithIdentity(keys *accountdata.AccountKeys) *aclStateBuilder {
return &aclStateBuilder{
signPrivKey: accountData.SignKey,
encPrivKey: accountData.EncKey,
privKey: keys.SignKey,
}
}
@@ -28,8 +25,8 @@ func (sb *aclStateBuilder) Init(id string) {
}
func (sb *aclStateBuilder) Build(records []*AclRecord) (state *AclState, err error) {
if sb.encPrivKey != nil && sb.signPrivKey != nil {
state, err = newAclStateWithKeys(sb.id, sb.signPrivKey, sb.encPrivKey)
if sb.privKey != nil {
state, err = newAclStateWithKeys(sb.id, sb.privKey)
if err != nil {
return
}
+97 -32
View File
@@ -1,20 +1,24 @@
//go:generate mockgen -destination mock_list/mock_list.go github.com/anytypeio/any-sync/commonspace/object/acl/list AclList
//go:generate mockgen -destination mock_list/mock_list.go github.com/anyproto/any-sync/commonspace/object/acl/list AclList
package list
import (
"context"
"errors"
"fmt"
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anytypeio/any-sync/commonspace/object/acl/liststorage"
"github.com/anytypeio/any-sync/commonspace/object/keychain"
"sync"
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
"github.com/anyproto/any-sync/consensus/consensusproto"
"github.com/anyproto/any-sync/util/crypto"
)
type IterFunc = func(record *AclRecord) (IsContinue bool)
var ErrIncorrectCID = errors.New("incorrect CID")
var (
ErrIncorrectCID = errors.New("incorrect CID")
ErrRecordAlreadyExists = errors.New("record already exists")
)
type RWLocker interface {
sync.Locker
@@ -22,48 +26,93 @@ type RWLocker interface {
RUnlock()
}
type AcceptorVerifier interface {
VerifyAcceptor(rec *consensusproto.RawRecord) (err error)
}
type NoOpAcceptorVerifier struct {
}
func (n NoOpAcceptorVerifier) VerifyAcceptor(rec *consensusproto.RawRecord) (err error) {
return nil
}
type AclList interface {
RWLocker
Id() string
Root() *aclrecordproto.RawAclRecordWithId
Root() *consensusproto.RawRecordWithId
Records() []*AclRecord
AclState() *AclState
IsAfter(first string, second string) (bool, error)
Head() *AclRecord
Get(id string) (*AclRecord, error)
GetIndex(idx int) (*AclRecord, error)
Iterate(iterFunc IterFunc)
IterateFrom(startId string, iterFunc IterFunc)
AddRawRecord(rawRec *aclrecordproto.RawAclRecordWithId) (added bool, err error)
KeyStorage() crypto.KeyStorage
RecordBuilder() AclRecordBuilder
ValidateRawRecord(record *consensusproto.RawRecord) (err error)
AddRawRecord(rawRec *consensusproto.RawRecordWithId) (err error)
Close() (err error)
}
type aclList struct {
root *aclrecordproto.RawAclRecordWithId
root *consensusproto.RawRecordWithId
records []*AclRecord
indexes map[string]int
id string
stateBuilder *aclStateBuilder
recordBuilder AclRecordBuilder
keyStorage crypto.KeyStorage
aclState *AclState
keychain *keychain.Keychain
storage liststorage.ListStorage
sync.RWMutex
}
func BuildAclListWithIdentity(acc *accountdata.AccountData, storage liststorage.ListStorage) (AclList, error) {
builder := newAclStateBuilderWithIdentity(acc)
return build(storage.Id(), builder, newAclRecordBuilder(storage.Id(), keychain.NewKeychain()), storage)
type internalDeps struct {
storage liststorage.ListStorage
keyStorage crypto.KeyStorage
stateBuilder *aclStateBuilder
recordBuilder AclRecordBuilder
acceptorVerifier AcceptorVerifier
}
func BuildAclList(storage liststorage.ListStorage) (AclList, error) {
return build(storage.Id(), newAclStateBuilder(), newAclRecordBuilder(storage.Id(), keychain.NewKeychain()), storage)
func BuildAclListWithIdentity(acc *accountdata.AccountKeys, storage liststorage.ListStorage, verifier AcceptorVerifier) (AclList, error) {
keyStorage := crypto.NewKeyStorage()
deps := internalDeps{
storage: storage,
keyStorage: keyStorage,
stateBuilder: newAclStateBuilderWithIdentity(acc),
recordBuilder: NewAclRecordBuilder(storage.Id(), keyStorage, acc, verifier),
acceptorVerifier: verifier,
}
return build(deps)
}
func build(id string, stateBuilder *aclStateBuilder, recBuilder AclRecordBuilder, storage liststorage.ListStorage) (list AclList, err error) {
func BuildAclList(storage liststorage.ListStorage, verifier AcceptorVerifier) (AclList, error) {
keyStorage := crypto.NewKeyStorage()
deps := internalDeps{
storage: storage,
keyStorage: keyStorage,
stateBuilder: newAclStateBuilder(),
recordBuilder: NewAclRecordBuilder(storage.Id(), keyStorage, nil, verifier),
acceptorVerifier: verifier,
}
return build(deps)
}
func build(deps internalDeps) (list AclList, err error) {
var (
storage = deps.storage
id = deps.storage.Id()
recBuilder = deps.recordBuilder
stateBuilder = deps.stateBuilder
)
head, err := storage.Head()
if err != nil {
return
@@ -74,7 +123,7 @@ func build(id string, stateBuilder *aclStateBuilder, recBuilder AclRecordBuilder
return
}
record, err := recBuilder.ConvertFromRaw(rawRecordWithId)
record, err := recBuilder.UnmarshallWithId(rawRecordWithId)
if err != nil {
return
}
@@ -86,7 +135,7 @@ func build(id string, stateBuilder *aclStateBuilder, recBuilder AclRecordBuilder
return
}
record, err = recBuilder.ConvertFromRaw(rawRecordWithId)
record, err = recBuilder.UnmarshallWithId(rawRecordWithId)
if err != nil {
return
}
@@ -116,6 +165,7 @@ func build(id string, stateBuilder *aclStateBuilder, recBuilder AclRecordBuilder
return
}
recBuilder.(*aclRecordBuilder).state = state
list = &aclList{
root: rootWithId,
records: records,
@@ -129,15 +179,27 @@ func build(id string, stateBuilder *aclStateBuilder, recBuilder AclRecordBuilder
return
}
func (a *aclList) RecordBuilder() AclRecordBuilder {
return a.recordBuilder
}
func (a *aclList) Records() []*AclRecord {
return a.records
}
func (a *aclList) AddRawRecord(rawRec *aclrecordproto.RawAclRecordWithId) (added bool, err error) {
if _, ok := a.indexes[rawRec.Id]; ok {
func (a *aclList) ValidateRawRecord(rawRec *consensusproto.RawRecord) (err error) {
record, err := a.recordBuilder.Unmarshall(rawRec)
if err != nil {
return
}
record, err := a.recordBuilder.ConvertFromRaw(rawRec)
return a.aclState.Validator().ValidateAclRecordContents(record)
}
func (a *aclList) AddRawRecord(rawRec *consensusproto.RawRecordWithId) (err error) {
if _, ok := a.indexes[rawRec.Id]; ok {
return ErrRecordAlreadyExists
}
record, err := a.recordBuilder.UnmarshallWithId(rawRec)
if err != nil {
return
}
@@ -152,15 +214,6 @@ func (a *aclList) AddRawRecord(rawRec *aclrecordproto.RawAclRecordWithId) (added
if err = a.storage.SetHead(rawRec.Id); err != nil {
return
}
return true, nil
}
func (a *aclList) IsValidNext(rawRec *aclrecordproto.RawAclRecordWithId) (err error) {
_, err = a.recordBuilder.ConvertFromRaw(rawRec)
if err != nil {
return
}
// TODO: change state and add "check" method for records
return
}
@@ -168,7 +221,7 @@ func (a *aclList) Id() string {
return a.id
}
func (a *aclList) Root() *aclrecordproto.RawAclRecordWithId {
func (a *aclList) Root() *consensusproto.RawRecordWithId {
return a.root
}
@@ -176,6 +229,10 @@ 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]
@@ -192,11 +249,19 @@ func (a *aclList) Head() *AclRecord {
func (a *aclList) Get(id string) (*AclRecord, error) {
recIdx, ok := a.indexes[id]
if !ok {
return nil, fmt.Errorf("no such record")
return nil, ErrNoSuchRecord
}
return a.records[recIdx], nil
}
func (a *aclList) GetIndex(idx int) (*AclRecord, error) {
// TODO: when we add snapshots we will have to monitor record num in snapshots
if idx < 0 || idx >= len(a.records) {
return nil, ErrNoSuchRecord
}
return a.records[idx], nil
}
func (a *aclList) Iterate(iterFunc IterFunc) {
for _, rec := range a.records {
if !iterFunc(rec) {
+300 -82
View File
@@ -1,91 +1,309 @@
package list
import (
"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"
"fmt"
"testing"
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anyproto/any-sync/consensus/consensusproto"
"github.com/anyproto/any-sync/util/cidutil"
"github.com/anyproto/any-sync/util/crypto"
"github.com/stretchr/testify/require"
)
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 wrapRecord(rawRec *consensusproto.RawRecord) *consensusproto.RawRecordWithId {
payload, err := rawRec.Marshal()
if err != nil {
panic(err)
}
id, err := cidutil.NewCidFromBytes(payload)
if err != nil {
panic(err)
}
return &consensusproto.RawRecordWithId{
Payload: payload,
Id: id,
}
}
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")
type aclFixture struct {
ownerKeys *accountdata.AccountKeys
accountKeys *accountdata.AccountKeys
ownerAcl *aclList
accountAcl *aclList
spaceId string
}
func newFixture(t *testing.T) *aclFixture {
ownerKeys, err := accountdata.NewRandom()
require.NoError(t, err)
accountKeys, err := accountdata.NewRandom()
require.NoError(t, err)
spaceId := "spaceId"
ownerAcl, err := NewTestDerivedAcl(spaceId, ownerKeys)
require.NoError(t, err)
accountAcl, err := NewTestAclWithRoot(accountKeys, ownerAcl.Root())
require.NoError(t, err)
return &aclFixture{
ownerKeys: ownerKeys,
accountKeys: accountKeys,
ownerAcl: ownerAcl.(*aclList),
accountAcl: accountAcl.(*aclList),
spaceId: spaceId,
}
}
func (fx *aclFixture) addRec(t *testing.T, rec *consensusproto.RawRecordWithId) {
err := fx.ownerAcl.AddRawRecord(rec)
require.NoError(t, err)
err = fx.accountAcl.AddRawRecord(rec)
require.NoError(t, err)
}
func (fx *aclFixture) inviteAccount(t *testing.T, perms AclPermissions) {
var (
ownerAcl = fx.ownerAcl
ownerState = fx.ownerAcl.aclState
accountAcl = fx.accountAcl
accountState = fx.accountAcl.aclState
)
// building invite
inv, err := ownerAcl.RecordBuilder().BuildInvite()
require.NoError(t, err)
inviteRec := wrapRecord(inv.InviteRec)
fx.addRec(t, inviteRec)
// building request join
requestJoin, err := accountAcl.RecordBuilder().BuildRequestJoin(RequestJoinPayload{
InviteRecordId: inviteRec.Id,
InviteKey: inv.InviteKey,
})
require.NoError(t, err)
requestJoinRec := wrapRecord(requestJoin)
fx.addRec(t, requestJoinRec)
// building request accept
requestAccept, err := ownerAcl.RecordBuilder().BuildRequestAccept(RequestAcceptPayload{
RequestRecordId: requestJoinRec.Id,
Permissions: perms,
})
require.NoError(t, err)
// validate
err = ownerAcl.ValidateRawRecord(requestAccept)
require.NoError(t, err)
requestAcceptRec := wrapRecord(requestAccept)
fx.addRec(t, requestAcceptRec)
// checking acl state
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
require.True(t, ownerState.Permissions(accountState.pubKey).CanWrite())
require.Equal(t, 0, len(ownerState.pendingRequests))
require.Equal(t, 0, len(accountState.pendingRequests))
require.True(t, accountState.Permissions(ownerState.pubKey).IsOwner())
require.True(t, accountState.Permissions(accountState.pubKey).CanWrite())
_, err = ownerState.StateAtRecord(requestJoinRec.Id, accountState.pubKey)
require.Equal(t, ErrNoSuchAccount, err)
stateAtRec, err := ownerState.StateAtRecord(requestAcceptRec.Id, accountState.pubKey)
require.NoError(t, err)
require.True(t, stateAtRec.Permissions == perms)
}
func TestAclList_BuildRoot(t *testing.T) {
randomKeys, err := accountdata.NewRandom()
require.NoError(t, err)
randomAcl, err := NewTestDerivedAcl("spaceId", randomKeys)
require.NoError(t, err)
fmt.Println(randomAcl.Id())
}
func TestAclList_InvitePipeline(t *testing.T) {
fx := newFixture(t)
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Writer))
}
func TestAclList_InviteRevoke(t *testing.T) {
fx := newFixture(t)
var (
ownerState = fx.ownerAcl.aclState
accountState = fx.accountAcl.aclState
)
// building invite
inv, err := fx.ownerAcl.RecordBuilder().BuildInvite()
require.NoError(t, err)
inviteRec := wrapRecord(inv.InviteRec)
fx.addRec(t, inviteRec)
// building invite revoke
inviteRevoke, err := fx.ownerAcl.RecordBuilder().BuildInviteRevoke(ownerState.lastRecordId)
require.NoError(t, err)
inviteRevokeRec := wrapRecord(inviteRevoke)
fx.addRec(t, inviteRevokeRec)
// checking acl state
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
require.True(t, ownerState.Permissions(accountState.pubKey).NoPermissions())
require.Empty(t, ownerState.inviteKeys)
require.Empty(t, accountState.inviteKeys)
}
func TestAclList_RequestDecline(t *testing.T) {
fx := newFixture(t)
var (
ownerAcl = fx.ownerAcl
ownerState = fx.ownerAcl.aclState
accountAcl = fx.accountAcl
accountState = fx.accountAcl.aclState
)
// building invite
inv, err := ownerAcl.RecordBuilder().BuildInvite()
require.NoError(t, err)
inviteRec := wrapRecord(inv.InviteRec)
fx.addRec(t, inviteRec)
// building request join
requestJoin, err := accountAcl.RecordBuilder().BuildRequestJoin(RequestJoinPayload{
InviteRecordId: inviteRec.Id,
InviteKey: inv.InviteKey,
})
require.NoError(t, err)
requestJoinRec := wrapRecord(requestJoin)
fx.addRec(t, requestJoinRec)
// building request decline
requestDecline, err := ownerAcl.RecordBuilder().BuildRequestDecline(ownerState.lastRecordId)
require.NoError(t, err)
requestDeclineRec := wrapRecord(requestDecline)
fx.addRec(t, requestDeclineRec)
// checking acl state
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
require.True(t, ownerState.Permissions(accountState.pubKey).NoPermissions())
require.Empty(t, ownerState.pendingRequests)
require.Empty(t, accountState.pendingRequests)
}
func TestAclList_Remove(t *testing.T) {
fx := newFixture(t)
var (
ownerState = fx.ownerAcl.aclState
accountState = fx.accountAcl.aclState
)
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Writer))
newReadKey := crypto.NewAES()
remove, err := fx.ownerAcl.RecordBuilder().BuildAccountRemove(AccountRemovePayload{
Identities: []crypto.PubKey{fx.accountKeys.SignKey.GetPublic()},
ReadKey: newReadKey,
})
require.NoError(t, err)
removeRec := wrapRecord(remove)
fx.addRec(t, removeRec)
// checking acl state
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
require.True(t, ownerState.Permissions(accountState.pubKey).NoPermissions())
require.True(t, ownerState.userReadKeys[removeRec.Id].Equals(newReadKey))
require.NotNil(t, ownerState.userReadKeys[fx.ownerAcl.Id()])
require.Equal(t, 0, len(ownerState.pendingRequests))
require.Equal(t, 0, len(accountState.pendingRequests))
require.True(t, accountState.Permissions(ownerState.pubKey).IsOwner())
require.True(t, accountState.Permissions(accountState.pubKey).NoPermissions())
require.Nil(t, accountState.userReadKeys[removeRec.Id])
require.NotNil(t, accountState.userReadKeys[fx.ownerAcl.Id()])
}
func TestAclList_ReadKeyChange(t *testing.T) {
fx := newFixture(t)
var (
ownerState = fx.ownerAcl.aclState
accountState = fx.accountAcl.aclState
)
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Admin))
newReadKey := crypto.NewAES()
readKeyChange, err := fx.ownerAcl.RecordBuilder().BuildReadKeyChange(newReadKey)
require.NoError(t, err)
readKeyRec := wrapRecord(readKeyChange)
fx.addRec(t, readKeyRec)
// checking acl state
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
require.True(t, ownerState.Permissions(accountState.pubKey).CanManageAccounts())
require.True(t, ownerState.userReadKeys[readKeyRec.Id].Equals(newReadKey))
require.True(t, accountState.userReadKeys[readKeyRec.Id].Equals(newReadKey))
require.NotNil(t, ownerState.userReadKeys[fx.ownerAcl.Id()])
require.NotNil(t, accountState.userReadKeys[fx.ownerAcl.Id()])
readKey, err := ownerState.CurrentReadKey()
require.NoError(t, err)
require.True(t, newReadKey.Equals(readKey))
require.Equal(t, 0, len(ownerState.pendingRequests))
require.Equal(t, 0, len(accountState.pendingRequests))
}
func TestAclList_PermissionChange(t *testing.T) {
fx := newFixture(t)
var (
ownerState = fx.ownerAcl.aclState
accountState = fx.accountAcl.aclState
)
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Admin))
permissionChange, err := fx.ownerAcl.RecordBuilder().BuildPermissionChange(PermissionChangePayload{
Identity: fx.accountKeys.SignKey.GetPublic(),
Permissions: AclPermissions(aclrecordproto.AclUserPermissions_Writer),
})
require.NoError(t, err)
permissionChangeRec := wrapRecord(permissionChange)
fx.addRec(t, permissionChangeRec)
// checking acl state
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
require.True(t, ownerState.Permissions(accountState.pubKey) == AclPermissions(aclrecordproto.AclUserPermissions_Writer))
require.True(t, accountState.Permissions(ownerState.pubKey).IsOwner())
require.True(t, accountState.Permissions(accountState.pubKey) == AclPermissions(aclrecordproto.AclUserPermissions_Writer))
require.NotNil(t, ownerState.userReadKeys[fx.ownerAcl.Id()])
require.NotNil(t, accountState.userReadKeys[fx.ownerAcl.Id()])
require.Equal(t, 0, len(ownerState.pendingRequests))
require.Equal(t, 0, len(accountState.pendingRequests))
}
func TestAclList_RequestRemove(t *testing.T) {
fx := newFixture(t)
var (
ownerState = fx.ownerAcl.aclState
accountState = fx.accountAcl.aclState
)
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Writer))
removeRequest, err := fx.accountAcl.RecordBuilder().BuildRequestRemove()
require.NoError(t, err)
removeRequestRec := wrapRecord(removeRequest)
fx.addRec(t, removeRequestRec)
recs := fx.accountAcl.AclState().RemoveRecords()
require.Len(t, recs, 1)
require.True(t, accountState.pubKey.Equals(recs[0].RequestIdentity))
newReadKey := crypto.NewAES()
remove, err := fx.ownerAcl.RecordBuilder().BuildAccountRemove(AccountRemovePayload{
Identities: []crypto.PubKey{recs[0].RequestIdentity},
ReadKey: newReadKey,
})
require.NoError(t, err)
removeRec := wrapRecord(remove)
fx.addRec(t, removeRec)
// checking acl state
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
require.True(t, ownerState.Permissions(accountState.pubKey).NoPermissions())
require.True(t, ownerState.userReadKeys[removeRec.Id].Equals(newReadKey))
require.NotNil(t, ownerState.userReadKeys[fx.ownerAcl.Id()])
require.Equal(t, 0, len(ownerState.pendingRequests))
require.Equal(t, 0, len(accountState.pendingRequests))
require.True(t, accountState.Permissions(ownerState.pubKey).IsOwner())
require.True(t, accountState.Permissions(accountState.pubKey).NoPermissions())
require.Nil(t, accountState.userReadKeys[removeRec.Id])
require.NotNil(t, accountState.userReadKeys[fx.ownerAcl.Id()])
}
+41
View File
@@ -0,0 +1,41 @@
package list
import (
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
"github.com/anyproto/any-sync/consensus/consensusproto"
"github.com/anyproto/any-sync/util/crypto"
)
func NewTestDerivedAcl(spaceId string, keys *accountdata.AccountKeys) (AclList, error) {
builder := NewAclRecordBuilder("", crypto.NewKeyStorage(), keys, NoOpAcceptorVerifier{})
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, []*consensusproto.RawRecordWithId{
root,
})
if err != nil {
return nil, err
}
return BuildAclListWithIdentity(keys, st, NoOpAcceptorVerifier{})
}
func NewTestAclWithRoot(keys *accountdata.AccountKeys, root *consensusproto.RawRecordWithId) (AclList, error) {
st, err := liststorage.NewInMemoryAclListStorage(root.Id, []*consensusproto.RawRecordWithId{
root,
})
if err != nil {
return nil, err
}
return BuildAclListWithIdentity(keys, st, NoOpAcceptorVerifier{})
}
@@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/anytypeio/any-sync/commonspace/object/acl/list (interfaces: AclList)
// Source: github.com/anyproto/any-sync/commonspace/object/acl/list (interfaces: AclList)
// Package mock_list is a generated GoMock package.
package mock_list
@@ -7,9 +7,10 @@ package mock_list
import (
reflect "reflect"
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"
list "github.com/anyproto/any-sync/commonspace/object/acl/list"
consensusproto "github.com/anyproto/any-sync/consensus/consensusproto"
crypto "github.com/anyproto/any-sync/util/crypto"
gomock "go.uber.org/mock/gomock"
)
// MockAclList is a mock of AclList interface.
@@ -50,12 +51,11 @@ func (mr *MockAclListMockRecorder) AclState() *gomock.Call {
}
// AddRawRecord mocks base method.
func (m *MockAclList) AddRawRecord(arg0 *aclrecordproto.RawAclRecordWithId) (bool, error) {
func (m *MockAclList) AddRawRecord(arg0 *consensusproto.RawRecordWithId) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AddRawRecord", arg0)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
ret0, _ := ret[0].(error)
return ret0
}
// AddRawRecord indicates an expected call of AddRawRecord.
@@ -93,6 +93,21 @@ func (mr *MockAclListMockRecorder) Get(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockAclList)(nil).Get), arg0)
}
// GetIndex mocks base method.
func (m *MockAclList) GetIndex(arg0 int) (*list.AclRecord, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetIndex", arg0)
ret0, _ := ret[0].(*list.AclRecord)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetIndex indicates an expected call of GetIndex.
func (mr *MockAclListMockRecorder) GetIndex(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIndex", reflect.TypeOf((*MockAclList)(nil).GetIndex), arg0)
}
// Head mocks base method.
func (m *MockAclList) Head() *list.AclRecord {
m.ctrl.T.Helper()
@@ -160,6 +175,20 @@ 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()
@@ -196,6 +225,20 @@ func (mr *MockAclListMockRecorder) RUnlock() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RUnlock", reflect.TypeOf((*MockAclList)(nil).RUnlock))
}
// RecordBuilder mocks base method.
func (m *MockAclList) RecordBuilder() list.AclRecordBuilder {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RecordBuilder")
ret0, _ := ret[0].(list.AclRecordBuilder)
return ret0
}
// RecordBuilder indicates an expected call of RecordBuilder.
func (mr *MockAclListMockRecorder) RecordBuilder() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordBuilder", reflect.TypeOf((*MockAclList)(nil).RecordBuilder))
}
// Records mocks base method.
func (m *MockAclList) Records() []*list.AclRecord {
m.ctrl.T.Helper()
@@ -211,10 +254,10 @@ func (mr *MockAclListMockRecorder) Records() *gomock.Call {
}
// Root mocks base method.
func (m *MockAclList) Root() *aclrecordproto.RawAclRecordWithId {
func (m *MockAclList) Root() *consensusproto.RawRecordWithId {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Root")
ret0, _ := ret[0].(*aclrecordproto.RawAclRecordWithId)
ret0, _ := ret[0].(*consensusproto.RawRecordWithId)
return ret0
}
@@ -235,3 +278,17 @@ func (mr *MockAclListMockRecorder) Unlock() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockAclList)(nil).Unlock))
}
// ValidateRawRecord mocks base method.
func (m *MockAclList) ValidateRawRecord(arg0 *consensusproto.RawRecord) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ValidateRawRecord", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// ValidateRawRecord indicates an expected call of ValidateRawRecord.
func (mr *MockAclListMockRecorder) ValidateRawRecord(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateRawRecord", reflect.TypeOf((*MockAclList)(nil).ValidateRawRecord), arg0)
}
+69
View File
@@ -0,0 +1,69 @@
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
Timestamp int64
Data []byte
Identity crypto.PubKey
Model interface{}
Signature []byte
}
type RequestRecord struct {
RequestIdentity crypto.PubKey
RequestMetadata []byte
Type RequestType
}
type AclUserState struct {
PubKey crypto.PubKey
Permissions AclPermissions
RequestMetadata []byte
}
type RequestType int
const (
RequestTypeRemove RequestType = iota
RequestTypeJoin
)
type AclPermissions aclrecordproto.AclUserPermissions
func (p AclPermissions) NoPermissions() bool {
return aclrecordproto.AclUserPermissions(p) == aclrecordproto.AclUserPermissions_None
}
func (p AclPermissions) IsOwner() bool {
return aclrecordproto.AclUserPermissions(p) == aclrecordproto.AclUserPermissions_Owner
}
func (p AclPermissions) CanWrite() bool {
switch aclrecordproto.AclUserPermissions(p) {
case aclrecordproto.AclUserPermissions_Admin:
return true
case aclrecordproto.AclUserPermissions_Writer:
return true
case aclrecordproto.AclUserPermissions_Owner:
return true
default:
return false
}
}
func (p AclPermissions) CanManageAccounts() bool {
switch aclrecordproto.AclUserPermissions(p) {
case aclrecordproto.AclUserPermissions_Admin:
return true
case aclrecordproto.AclUserPermissions_Owner:
return true
default:
return false
}
}
-12
View File
@@ -1,12 +0,0 @@
package list
type AclRecord struct {
Id string
PrevId string
CurrentReadKeyHash uint64
Timestamp int64
Data []byte
Identity []byte
Model interface{}
Signature []byte
}
+218
View File
@@ -0,0 +1,218 @@
package list
import (
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anyproto/any-sync/util/crypto"
)
type ContentValidator interface {
ValidateAclRecordContents(ch *AclRecord) (err error)
ValidatePermissionChange(ch *aclrecordproto.AclAccountPermissionChange, authorIdentity crypto.PubKey) (err error)
ValidateInvite(ch *aclrecordproto.AclAccountInvite, authorIdentity crypto.PubKey) (err error)
ValidateInviteRevoke(ch *aclrecordproto.AclAccountInviteRevoke, authorIdentity crypto.PubKey) (err error)
ValidateRequestJoin(ch *aclrecordproto.AclAccountRequestJoin, authorIdentity crypto.PubKey) (err error)
ValidateRequestAccept(ch *aclrecordproto.AclAccountRequestAccept, authorIdentity crypto.PubKey) (err error)
ValidateRequestDecline(ch *aclrecordproto.AclAccountRequestDecline, authorIdentity crypto.PubKey) (err error)
ValidateAccountRemove(ch *aclrecordproto.AclAccountRemove, authorIdentity crypto.PubKey) (err error)
ValidateRequestRemove(ch *aclrecordproto.AclAccountRequestRemove, authorIdentity crypto.PubKey) (err error)
ValidateReadKeyChange(ch *aclrecordproto.AclReadKeyChange, authorIdentity crypto.PubKey) (err error)
}
type contentValidator struct {
keyStore crypto.KeyStorage
aclState *AclState
}
func (c *contentValidator) ValidateAclRecordContents(ch *AclRecord) (err error) {
if ch.PrevId != c.aclState.lastRecordId {
return ErrIncorrectRecordSequence
}
aclData := ch.Model.(*aclrecordproto.AclData)
for _, content := range aclData.AclContent {
err = c.validateAclRecordContent(content, ch.Identity)
if err != nil {
return
}
}
return
}
func (c *contentValidator) validateAclRecordContent(ch *aclrecordproto.AclContentValue, authorIdentity crypto.PubKey) (err error) {
switch {
case ch.GetPermissionChange() != nil:
return c.ValidatePermissionChange(ch.GetPermissionChange(), authorIdentity)
case ch.GetInvite() != nil:
return c.ValidateInvite(ch.GetInvite(), authorIdentity)
case ch.GetInviteRevoke() != nil:
return c.ValidateInviteRevoke(ch.GetInviteRevoke(), authorIdentity)
case ch.GetRequestJoin() != nil:
return c.ValidateRequestJoin(ch.GetRequestJoin(), authorIdentity)
case ch.GetRequestAccept() != nil:
return c.ValidateRequestAccept(ch.GetRequestAccept(), authorIdentity)
case ch.GetRequestDecline() != nil:
return c.ValidateRequestDecline(ch.GetRequestDecline(), authorIdentity)
case ch.GetAccountRemove() != nil:
return c.ValidateAccountRemove(ch.GetAccountRemove(), authorIdentity)
case ch.GetAccountRequestRemove() != nil:
return c.ValidateRequestRemove(ch.GetAccountRequestRemove(), authorIdentity)
case ch.GetReadKeyChange() != nil:
return c.ValidateReadKeyChange(ch.GetReadKeyChange(), authorIdentity)
default:
return ErrUnexpectedContentType
}
}
func (c *contentValidator) ValidatePermissionChange(ch *aclrecordproto.AclAccountPermissionChange, authorIdentity crypto.PubKey) (err error) {
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
return ErrInsufficientPermissions
}
chIdentity, err := c.keyStore.PubKeyFromProto(ch.Identity)
if err != nil {
return err
}
_, exists := c.aclState.userStates[mapKeyFromPubKey(chIdentity)]
if !exists {
return ErrNoSuchAccount
}
return
}
func (c *contentValidator) ValidateInvite(ch *aclrecordproto.AclAccountInvite, authorIdentity crypto.PubKey) (err error) {
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
return ErrInsufficientPermissions
}
_, err = c.keyStore.PubKeyFromProto(ch.InviteKey)
return
}
func (c *contentValidator) ValidateInviteRevoke(ch *aclrecordproto.AclAccountInviteRevoke, authorIdentity crypto.PubKey) (err error) {
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
return ErrInsufficientPermissions
}
_, exists := c.aclState.inviteKeys[ch.InviteRecordId]
if !exists {
return ErrNoSuchInvite
}
return
}
func (c *contentValidator) ValidateRequestJoin(ch *aclrecordproto.AclAccountRequestJoin, authorIdentity crypto.PubKey) (err error) {
inviteKey, exists := c.aclState.inviteKeys[ch.InviteRecordId]
if !exists {
return ErrNoSuchInvite
}
inviteIdentity, err := c.keyStore.PubKeyFromProto(ch.InviteIdentity)
if err != nil {
return
}
if _, exists := c.aclState.pendingRequests[mapKeyFromPubKey(inviteIdentity)]; exists {
return ErrPendingRequest
}
if !authorIdentity.Equals(inviteIdentity) {
return ErrIncorrectIdentity
}
rawInviteIdentity, err := inviteIdentity.Raw()
if err != nil {
return err
}
ok, err := inviteKey.Verify(rawInviteIdentity, ch.InviteIdentitySignature)
if err != nil {
return ErrInvalidSignature
}
if !ok {
return ErrInvalidSignature
}
return
}
func (c *contentValidator) ValidateRequestAccept(ch *aclrecordproto.AclAccountRequestAccept, authorIdentity crypto.PubKey) (err error) {
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
return ErrInsufficientPermissions
}
record, exists := c.aclState.requestRecords[ch.RequestRecordId]
if !exists {
return ErrNoSuchRequest
}
acceptIdentity, err := c.keyStore.PubKeyFromProto(ch.Identity)
if err != nil {
return
}
if !acceptIdentity.Equals(record.RequestIdentity) {
return ErrIncorrectIdentity
}
if ch.Permissions == aclrecordproto.AclUserPermissions_Owner {
return ErrInsufficientPermissions
}
return
}
func (c *contentValidator) ValidateRequestDecline(ch *aclrecordproto.AclAccountRequestDecline, authorIdentity crypto.PubKey) (err error) {
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
return ErrInsufficientPermissions
}
_, exists := c.aclState.requestRecords[ch.RequestRecordId]
if !exists {
return ErrNoSuchRequest
}
return
}
func (c *contentValidator) ValidateAccountRemove(ch *aclrecordproto.AclAccountRemove, authorIdentity crypto.PubKey) (err error) {
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
return ErrInsufficientPermissions
}
seenIdentities := map[string]struct{}{}
for _, rawIdentity := range ch.Identities {
identity, err := c.keyStore.PubKeyFromProto(rawIdentity)
if err != nil {
return err
}
if identity.Equals(authorIdentity) {
return ErrInsufficientPermissions
}
permissions := c.aclState.Permissions(identity)
if permissions.NoPermissions() {
return ErrNoSuchAccount
}
if permissions.IsOwner() {
return ErrInsufficientPermissions
}
idKey := mapKeyFromPubKey(identity)
if _, exists := seenIdentities[idKey]; exists {
return ErrDuplicateAccounts
}
seenIdentities[mapKeyFromPubKey(identity)] = struct{}{}
}
return c.validateAccountReadKeys(ch.AccountKeys, len(c.aclState.userStates)-len(ch.Identities))
}
func (c *contentValidator) ValidateRequestRemove(ch *aclrecordproto.AclAccountRequestRemove, authorIdentity crypto.PubKey) (err error) {
if c.aclState.Permissions(authorIdentity).NoPermissions() {
return ErrInsufficientPermissions
}
if _, exists := c.aclState.pendingRequests[mapKeyFromPubKey(authorIdentity)]; exists {
return ErrPendingRequest
}
return
}
func (c *contentValidator) ValidateReadKeyChange(ch *aclrecordproto.AclReadKeyChange, authorIdentity crypto.PubKey) (err error) {
return c.validateAccountReadKeys(ch.AccountKeys, len(c.aclState.userStates))
}
func (c *contentValidator) validateAccountReadKeys(accountKeys []*aclrecordproto.AclEncryptedReadKey, usersNum int) (err error) {
if len(accountKeys) != usersNum {
return ErrIncorrectNumberOfAccounts
}
for _, encKeys := range accountKeys {
identity, err := c.keyStore.PubKeyFromProto(encKeys.Identity)
if err != nil {
return err
}
_, exists := c.aclState.userStates[mapKeyFromPubKey(identity)]
if !exists {
return ErrNoSuchAccount
}
}
return
}
+10 -8
View File
@@ -3,24 +3,26 @@ package liststorage
import (
"context"
"fmt"
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anyproto/any-sync/consensus/consensusproto"
"sync"
)
type inMemoryAclListStorage struct {
id string
root *aclrecordproto.RawAclRecordWithId
root *consensusproto.RawRecordWithId
head string
records map[string]*aclrecordproto.RawAclRecordWithId
records map[string]*consensusproto.RawRecordWithId
sync.RWMutex
}
func NewInMemoryAclListStorage(
id string,
records []*aclrecordproto.RawAclRecordWithId) (ListStorage, error) {
records []*consensusproto.RawRecordWithId) (ListStorage, error) {
allRecords := make(map[string]*aclrecordproto.RawAclRecordWithId)
allRecords := make(map[string]*consensusproto.RawRecordWithId)
for _, ch := range records {
allRecords[ch.Id] = ch
}
@@ -41,7 +43,7 @@ func (t *inMemoryAclListStorage) Id() string {
return t.id
}
func (t *inMemoryAclListStorage) Root() (*aclrecordproto.RawAclRecordWithId, error) {
func (t *inMemoryAclListStorage) Root() (*consensusproto.RawRecordWithId, error) {
t.RLock()
defer t.RUnlock()
return t.root, nil
@@ -60,7 +62,7 @@ func (t *inMemoryAclListStorage) SetHead(head string) error {
return nil
}
func (t *inMemoryAclListStorage) AddRawRecord(ctx context.Context, record *aclrecordproto.RawAclRecordWithId) error {
func (t *inMemoryAclListStorage) AddRawRecord(ctx context.Context, record *consensusproto.RawRecordWithId) error {
t.Lock()
defer t.Unlock()
// TODO: better to do deep copy
@@ -68,7 +70,7 @@ func (t *inMemoryAclListStorage) AddRawRecord(ctx context.Context, record *aclre
return nil
}
func (t *inMemoryAclListStorage) GetRawRecord(ctx context.Context, recordId string) (*aclrecordproto.RawAclRecordWithId, error) {
func (t *inMemoryAclListStorage) GetRawRecord(ctx context.Context, recordId string) (*consensusproto.RawRecordWithId, error) {
t.RLock()
defer t.RUnlock()
if res, exists := t.records[recordId]; exists {
@@ -1,10 +1,11 @@
//go:generate mockgen -destination mock_liststorage/mock_liststorage.go github.com/anytypeio/any-sync/commonspace/object/acl/liststorage ListStorage
//go:generate mockgen -destination mock_liststorage/mock_liststorage.go github.com/anyproto/any-sync/commonspace/object/acl/liststorage ListStorage
package liststorage
import (
"context"
"errors"
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anyproto/any-sync/consensus/consensusproto"
)
var (
@@ -14,15 +15,15 @@ var (
)
type Exporter interface {
ListStorage(root *aclrecordproto.RawAclRecordWithId) (ListStorage, error)
ListStorage(root *consensusproto.RawRecordWithId) (ListStorage, error)
}
type ListStorage interface {
Id() string
Root() (*aclrecordproto.RawAclRecordWithId, error)
Root() (*consensusproto.RawRecordWithId, error)
Head() (string, error)
SetHead(headId string) error
GetRawRecord(ctx context.Context, id string) (*aclrecordproto.RawAclRecordWithId, error)
AddRawRecord(ctx context.Context, rec *aclrecordproto.RawAclRecordWithId) error
GetRawRecord(ctx context.Context, id string) (*consensusproto.RawRecordWithId, error)
AddRawRecord(ctx context.Context, rec *consensusproto.RawRecordWithId) error
}
@@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/anytypeio/any-sync/commonspace/object/acl/liststorage (interfaces: ListStorage)
// Source: github.com/anyproto/any-sync/commonspace/object/acl/liststorage (interfaces: ListStorage)
// Package mock_liststorage is a generated GoMock package.
package mock_liststorage
@@ -8,8 +8,8 @@ import (
context "context"
reflect "reflect"
aclrecordproto "github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
gomock "github.com/golang/mock/gomock"
consensusproto "github.com/anyproto/any-sync/consensus/consensusproto"
gomock "go.uber.org/mock/gomock"
)
// MockListStorage is a mock of ListStorage interface.
@@ -36,7 +36,7 @@ func (m *MockListStorage) EXPECT() *MockListStorageMockRecorder {
}
// AddRawRecord mocks base method.
func (m *MockListStorage) AddRawRecord(arg0 context.Context, arg1 *aclrecordproto.RawAclRecordWithId) error {
func (m *MockListStorage) AddRawRecord(arg0 context.Context, arg1 *consensusproto.RawRecordWithId) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AddRawRecord", arg0, arg1)
ret0, _ := ret[0].(error)
@@ -50,10 +50,10 @@ func (mr *MockListStorageMockRecorder) AddRawRecord(arg0, arg1 interface{}) *gom
}
// GetRawRecord mocks base method.
func (m *MockListStorage) GetRawRecord(arg0 context.Context, arg1 string) (*aclrecordproto.RawAclRecordWithId, error) {
func (m *MockListStorage) GetRawRecord(arg0 context.Context, arg1 string) (*consensusproto.RawRecordWithId, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetRawRecord", arg0, arg1)
ret0, _ := ret[0].(*aclrecordproto.RawAclRecordWithId)
ret0, _ := ret[0].(*consensusproto.RawRecordWithId)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@@ -94,10 +94,10 @@ func (mr *MockListStorageMockRecorder) Id() *gomock.Call {
}
// Root mocks base method.
func (m *MockListStorage) Root() (*aclrecordproto.RawAclRecordWithId, error) {
func (m *MockListStorage) Root() (*consensusproto.RawRecordWithId, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Root")
ret0, _ := ret[0].(*aclrecordproto.RawAclRecordWithId)
ret0, _ := ret[0].(*consensusproto.RawRecordWithId)
ret1, _ := ret[1].(error)
return ret0, ret1
}
+34 -11
View File
@@ -1,21 +1,44 @@
package syncacl
import (
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
"github.com/anytypeio/any-sync/commonspace/objectsync"
"github.com/anytypeio/any-sync/commonspace/objectsync/synchandler"
"context"
"github.com/anyproto/any-sync/accountservice"
"github.com/anyproto/any-sync/app"
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/any-sync/commonspace/spacestorage"
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
)
const CName = "common.acl.syncacl"
func New() *SyncAcl {
return &SyncAcl{}
}
type SyncAcl struct {
list.AclList
synchandler.SyncHandler
messagePool objectsync.MessagePool
}
func NewSyncAcl(aclList list.AclList, messagePool objectsync.MessagePool) *SyncAcl {
return &SyncAcl{
AclList: aclList,
SyncHandler: nil,
messagePool: messagePool,
}
func (s *SyncAcl) HandleRequest(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (response *spacesyncproto.ObjectSyncMessage, err error) {
return nil, nil
}
func (s *SyncAcl) HandleMessage(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (err error) {
return nil
}
func (s *SyncAcl) Init(a *app.App) (err error) {
storage := a.MustComponent(spacestorage.CName).(spacestorage.SpaceStorage)
aclStorage, err := storage.AclStorage()
if err != nil {
return err
}
acc := a.MustComponent(accountservice.CName).(accountservice.Service)
s.AclList, err = list.BuildAclListWithIdentity(acc.Account(), aclStorage, list.NoOpAcceptorVerifier{})
return err
}
func (s *SyncAcl) Name() (name string) {
return CName
}
@@ -2,10 +2,9 @@ package syncacl
import (
"context"
"fmt"
"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"
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
)
type syncAclHandler struct {
@@ -13,19 +12,5 @@ type syncAclHandler struct {
}
func (s *syncAclHandler) HandleMessage(ctx context.Context, senderId string, req *spacesyncproto.ObjectSyncMessage) (err error) {
aclMsg := &aclrecordproto.AclSyncMessage{}
if err = aclMsg.Unmarshal(req.Payload); err != nil {
return
}
content := aclMsg.GetContent()
switch {
case content.GetAddRecords() != nil:
return s.handleAddRecords(ctx, senderId, content.GetAddRecords())
default:
return fmt.Errorf("unexpected aclSync message: %T", content.Value)
}
}
func (s *syncAclHandler) handleAddRecords(ctx context.Context, senderId string, addRecord *aclrecordproto.AclAddRecords) (err error) {
return
return nil
}
@@ -1,194 +0,0 @@
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]
}
@@ -1,295 +0,0 @@
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)
}
@@ -1,11 +0,0 @@
//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")
}
@@ -1,120 +0,0 @@
//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
}
@@ -1,70 +0,0 @@
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"`
}
@@ -1,15 +0,0 @@
package yamltests
import (
"path/filepath"
"runtime"
)
var (
_, b, _, _ = runtime.Caller(0)
basepath = filepath.Dir(b)
)
func Path() string {
return basepath
}
@@ -1,53 +0,0 @@
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
@@ -1,58 +0,0 @@
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
@@ -1,28 +0,0 @@
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,7 +2,7 @@ package syncobjectgetter
import (
"context"
"github.com/anytypeio/any-sync/commonspace/objectsync/synchandler"
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
)
type SyncObject interface {
@@ -1,11 +1,11 @@
package exporter
import (
"github.com/anytypeio/any-sync/commonspace/object/acl/liststorage"
"github.com/anytypeio/any-sync/commonspace/object/keychain"
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
"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 {
@@ -48,7 +48,7 @@ func (t *treeExporter) ExportUnencrypted(tree objecttree.ReadableObjectTree) (er
if err != nil {
return
}
changeBuilder := objecttree.NewChangeBuilder(keychain.NewKeychain(), tree.Header())
changeBuilder := objecttree.NewChangeBuilder(crypto.NewKeyStorage(), tree.Header())
putStorage := func(change *objecttree.Change) (err error) {
var raw *treechangeproto.RawTreeChangeWithId
raw, err = changeBuilder.Marshall(change)
@@ -68,7 +68,7 @@ func (t *treeExporter) ExportUnencrypted(tree objecttree.ReadableObjectTree) (er
return false
}
// that means that change is unencrypted
change.ReadKeyHash = 0
change.ReadKeyId = ""
change.Data = data
err = putStorage(change)
return err == nil
@@ -1,10 +1,10 @@
package exporter
import (
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
"github.com/anytypeio/any-sync/commonspace/object/acl/liststorage"
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
"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 {
@@ -15,7 +15,7 @@ type TreeImportParams struct {
}
func ImportHistoryTree(params TreeImportParams) (tree objecttree.ReadableObjectTree, err error) {
aclList, err := list.BuildAclList(params.ListStorage)
aclList, err := list.BuildAclList(params.ListStorage, list.NoOpAcceptorVerifier{})
if err != nil {
return
}
+19 -12
View File
@@ -2,7 +2,9 @@ package objecttree
import (
"errors"
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/util/crypto"
"github.com/gogo/protobuf/proto"
)
var (
@@ -17,46 +19,51 @@ type Change struct {
AclHeadId string
Id string
SnapshotId string
IsSnapshot bool
Timestamp int64
ReadKeyHash uint64
Identity string
ReadKeyId string
Identity crypto.PubKey
Data []byte
Model interface{}
Signature []byte
// iterator helpers
visited bool
branchesFinished bool
Signature []byte
IsSnapshot bool
}
func NewChange(id string, ch *treechangeproto.TreeChange, signature []byte) *Change {
func NewChange(id string, identity crypto.PubKey, ch *treechangeproto.TreeChange, signature []byte) *Change {
return &Change{
Next: nil,
PreviousIds: ch.TreeHeadIds,
AclHeadId: ch.AclHeadId,
Timestamp: ch.Timestamp,
ReadKeyHash: ch.CurrentReadKeyHash,
ReadKeyId: ch.ReadKeyId,
Id: id,
Data: ch.ChangesData,
SnapshotId: ch.SnapshotBaseId,
IsSnapshot: ch.IsSnapshot,
Identity: string(ch.Identity),
Identity: identity,
Signature: signature,
}
}
func NewChangeFromRoot(id string, ch *treechangeproto.RootChange, signature []byte) *Change {
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)
return &Change{
Next: nil,
AclHeadId: ch.AclHeadId,
Id: id,
IsSnapshot: true,
Timestamp: ch.Timestamp,
Identity: string(ch.Identity),
Identity: identity,
Signature: signature,
Data: []byte(ch.ChangeType),
Data: data,
Model: changeInfo,
}
}
@@ -2,37 +2,34 @@ package objecttree
import (
"errors"
"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/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/util/cidutil"
"github.com/anyproto/any-sync/util/crypto"
"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
CurrentReadKeyHash uint64
Identity []byte
IsSnapshot bool
SigningKey signingkey.PrivKey
ReadKey *symmetric.Key
Content []byte
TreeHeadIds []string
AclHeadId string
SnapshotBaseId string
ReadKeyId string
IsSnapshot bool
PrivKey crypto.PrivKey
ReadKey crypto.SymKey
Content []byte
Timestamp int64
}
type InitialContent struct {
AclHeadId string
Identity []byte
SigningKey signingkey.PrivKey
SpaceId string
Seed []byte
ChangeType string
Timestamp int64
AclHeadId string
PrivKey crypto.PrivKey
SpaceId string
Seed []byte
ChangeType string
ChangePayload []byte
Timestamp int64
}
type nonVerifiableChangeBuilder struct {
@@ -62,13 +59,16 @@ type ChangeBuilder interface {
Marshall(ch *Change) (*treechangeproto.RawTreeChangeWithId, error)
}
type newChangeFunc = func(id string, identity crypto.PubKey, ch *treechangeproto.TreeChange, signature []byte) *Change
type changeBuilder struct {
rootChange *treechangeproto.RawTreeChangeWithId
keys *keychain.Keychain
keys crypto.KeyStorage
newChange newChangeFunc
}
func NewChangeBuilder(keys *keychain.Keychain, rootChange *treechangeproto.RawTreeChangeWithId) ChangeBuilder {
return &changeBuilder{keys: keys, rootChange: rootChange}
func NewChangeBuilder(keys crypto.KeyStorage, rootChange *treechangeproto.RawTreeChangeWithId) ChangeBuilder {
return &changeBuilder{keys: keys, rootChange: rootChange, newChange: NewChange}
}
func (c *changeBuilder) Unmarshall(rawIdChange *treechangeproto.RawTreeChangeWithId, verify bool) (ch *Change, err error) {
@@ -96,15 +96,9 @@ 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 = identityKey.Verify(raw.Payload, raw.Signature)
res, err = ch.Identity.Verify(raw.Payload, raw.Signature)
if err != nil {
return
}
@@ -121,42 +115,40 @@ func (c *changeBuilder) SetRootRawChange(rawIdChange *treechangeproto.RawTreeCha
}
func (c *changeBuilder) BuildRoot(payload InitialContent) (ch *Change, rawIdChange *treechangeproto.RawTreeChangeWithId, err error) {
change := &treechangeproto.RootChange{
AclHeadId: payload.AclHeadId,
Timestamp: payload.Timestamp,
Identity: payload.Identity,
ChangeType: payload.ChangeType,
SpaceId: payload.SpaceId,
Seed: payload.Seed,
identity, err := payload.PrivKey.GetPublic().Marshall()
if err != nil {
return
}
change := &treechangeproto.RootChange{
AclHeadId: payload.AclHeadId,
Timestamp: payload.Timestamp,
Identity: identity,
ChangeType: payload.ChangeType,
ChangePayload: payload.ChangePayload,
SpaceId: payload.SpaceId,
Seed: payload.Seed,
}
marshalledChange, err := proto.Marshal(change)
if err != nil {
return
}
signature, err := payload.SigningKey.Sign(marshalledChange)
signature, err := payload.PrivKey.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, change, signature)
ch = NewChangeFromRoot(id, payload.PrivKey.GetPublic(), change, signature)
rawIdChange = &treechangeproto.RawTreeChangeWithId{
RawChange: marshalledRawChange,
Id: id,
@@ -165,14 +157,18 @@ func (c *changeBuilder) BuildRoot(payload InitialContent) (ch *Change, rawIdChan
}
func (c *changeBuilder) Build(payload BuilderContent) (ch *Change, rawIdChange *treechangeproto.RawTreeChangeWithId, err error) {
identity, err := payload.PrivKey.GetPublic().Marshall()
if err != nil {
return
}
change := &treechangeproto.TreeChange{
TreeHeadIds: payload.TreeHeadIds,
AclHeadId: payload.AclHeadId,
SnapshotBaseId: payload.SnapshotBaseId,
CurrentReadKeyHash: payload.CurrentReadKeyHash,
Timestamp: time.Now().UnixNano(),
Identity: payload.Identity,
IsSnapshot: payload.IsSnapshot,
TreeHeadIds: payload.TreeHeadIds,
AclHeadId: payload.AclHeadId,
SnapshotBaseId: payload.SnapshotBaseId,
ReadKeyId: payload.ReadKeyId,
Timestamp: payload.Timestamp,
Identity: identity,
IsSnapshot: payload.IsSnapshot,
}
if payload.ReadKey != nil {
var encrypted []byte
@@ -184,34 +180,27 @@ 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.SigningKey.Sign(marshalledChange)
signature, err := payload.PrivKey.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 = NewChange(id, change, signature)
ch = c.newChange(id, payload.PrivKey.GetPublic(), change, signature)
rawIdChange = &treechangeproto.RawTreeChangeWithId{
RawChange: marshalledRawChange,
Id: id,
@@ -220,18 +209,22 @@ func (c *changeBuilder) Build(payload BuilderContent) (ch *Change, rawIdChange *
}
func (c *changeBuilder) Marshall(ch *Change) (raw *treechangeproto.RawTreeChangeWithId, err error) {
if ch.Id == c.rootChange.Id {
if c.isRoot(ch.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,
CurrentReadKeyHash: ch.ReadKeyHash,
Timestamp: ch.Timestamp,
Identity: []byte(ch.Identity),
IsSnapshot: ch.IsSnapshot,
TreeHeadIds: ch.PreviousIds,
AclHeadId: ch.AclHeadId,
SnapshotBaseId: ch.SnapshotId,
ChangesData: ch.Data,
ReadKeyId: ch.ReadKeyId,
Timestamp: ch.Timestamp,
Identity: identity,
IsSnapshot: ch.IsSnapshot,
}
var marshalled []byte
marshalled, err = treeChange.Marshal()
@@ -255,22 +248,36 @@ func (c *changeBuilder) Marshall(ch *Change) (raw *treechangeproto.RawTreeChange
}
func (c *changeBuilder) unmarshallRawChange(raw *treechangeproto.RawTreeChange, id string) (ch *Change, err error) {
if c.rootChange.Id == id {
var key crypto.PubKey
if c.isRoot(id) {
unmarshalled := &treechangeproto.RootChange{}
err = proto.Unmarshal(raw.Payload, unmarshalled)
if err != nil {
return
}
ch = NewChangeFromRoot(id, unmarshalled, raw.Signature)
key, err = c.keys.PubKeyFromProto(unmarshalled.Identity)
if err != nil {
return
}
ch = NewChangeFromRoot(id, key, unmarshalled, raw.Signature)
return
}
unmarshalled := &treechangeproto.TreeChange{}
err = proto.Unmarshal(raw.Payload, unmarshalled)
if err != nil {
return
}
ch = NewChange(id, unmarshalled, raw.Signature)
key, err = c.keys.PubKeyFromProto(unmarshalled.Identity)
if err != nil {
return
}
ch = c.newChange(id, key, unmarshalled, raw.Signature)
return
}
func (c *changeBuilder) isRoot(id string) bool {
if c.rootChange != nil {
return c.rootChange.Id == id
}
return false
}
@@ -14,39 +14,47 @@ type historyTree struct {
*objectTree
}
func (h *historyTree) rebuildFromStorage(beforeId string, include bool) (err error) {
ot := h.objectTree
ot.treeBuilder.Reset()
if beforeId == ot.Id() && !include {
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 = ot.treeStorage.Heads()
heads, err = h.treeStorage.Heads()
if err != nil {
return
}
} else if !include {
beforeChange, err := ot.treeBuilder.loadChange(beforeId)
beforeChange, err := h.treeBuilder.loadChange(beforeId)
if err != nil {
return err
}
heads = beforeChange.PreviousIds
}
ot.tree, err = ot.treeBuilder.build(heads, nil, nil)
if err != nil {
return
}
ot.aclList.RLock()
defer ot.aclList.RUnlock()
state := ot.aclList.AclState()
if len(ot.keys) != len(state.UserReadKeys()) {
for key, value := range state.UserReadKeys() {
ot.keys[key] = value
}
}
h.tree, err = h.treeBuilder.build(heads, nil, nil)
return
}
@@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/anytypeio/any-sync/commonspace/object/tree/objecttree (interfaces: ObjectTree)
// Source: github.com/anyproto/any-sync/commonspace/object/tree/objecttree (interfaces: ObjectTree)
// Package mock_objecttree is a generated GoMock package.
package mock_objecttree
@@ -7,12 +7,13 @@ package mock_objecttree
import (
context "context"
reflect "reflect"
time "time"
list "github.com/anytypeio/any-sync/commonspace/object/acl/list"
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"
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"
gomock "go.uber.org/mock/gomock"
)
// MockObjectTree is a mock of ObjectTree interface.
@@ -82,6 +83,20 @@ 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()
@@ -243,6 +258,20 @@ 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()
@@ -255,6 +284,21 @@ 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()
@@ -321,6 +365,21 @@ 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()
@@ -374,3 +433,18 @@ 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)
}
+124 -82
View File
@@ -1,17 +1,18 @@
//go:generate mockgen -destination mock_objecttree/mock_objecttree.go github.com/anytypeio/any-sync/commonspace/object/tree/objecttree ObjectTree
//go:generate mockgen -destination mock_objecttree/mock_objecttree.go github.com/anyproto/any-sync/commonspace/object/tree/objecttree ObjectTree
package objecttree
import (
"context"
"errors"
"sync"
"time"
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
"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"
"github.com/anyproto/any-sync/util/crypto"
"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 {
@@ -26,6 +27,7 @@ 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
@@ -52,8 +54,10 @@ type ReadableObjectTree interface {
Id() string
Header() *treechangeproto.RawTreeChangeWithId
UnmarshalledHeader() *Change
ChangeInfo() *treechangeproto.TreeChangeInfo
Heads() []string
Root() *Change
Len() int
AclList() list.AclList
@@ -76,8 +80,12 @@ 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 {
@@ -93,7 +101,8 @@ type objectTree struct {
root *Change
tree *Tree
keys map[uint64]*symmetric.Key
keys map[string]crypto.SymKey
currentReadKey crypto.SymKey
// buffers
difSnapshotBuf []*treechangeproto.RawTreeChangeWithId
@@ -107,13 +116,25 @@ type objectTree struct {
}
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()
@@ -127,6 +148,10 @@ 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
}
@@ -139,6 +164,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
}
@@ -170,7 +199,7 @@ func (ot *objectTree) AddContent(ctx context.Context, content SignableChangeCont
panic(err)
}
err = ot.treeStorage.TransactionAdd([]*treechangeproto.RawTreeChangeWithId{rawChange}, []string{objChange.Id})
err = ot.treeStorage.AddRawChangesSetHeads([]*treechangeproto.RawTreeChangeWithId{rawChange}, []string{objChange.Id})
if err != nil {
return
}
@@ -191,39 +220,61 @@ func (ot *objectTree) AddContent(ctx context.Context, content SignableChangeCont
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 *symmetric.Key
readKeyHash uint64
state = ot.aclList.AclState() // special method for own keys
readKey crypto.SymKey
pubKey = content.Key.GetPublic()
readKeyId string
)
canWrite := state.HasPermission(content.Identity, aclrecordproto.AclUserPermissions_Writer) ||
state.HasPermission(content.Identity, aclrecordproto.AclUserPermissions_Admin)
if !canWrite {
if !state.Permissions(pubKey).CanWrite() {
err = list.ErrInsufficientPermissions
return
}
if content.IsEncrypted {
readKeyHash = state.CurrentReadKeyHash()
readKey, err = state.CurrentReadKey()
if err != nil {
readKeyId = state.CurrentReadKeyId()
if ot.currentReadKey == nil {
err = ErrMissingKey
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(),
CurrentReadKeyHash: readKeyHash,
Identity: content.Identity,
IsSnapshot: content.IsSnapshot,
SigningKey: content.Key,
ReadKey: readKey,
Content: content.Data,
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,
}
return
}
@@ -243,7 +294,11 @@ func (ot *objectTree) AddRawChanges(ctx context.Context, changesPayload RawChang
addResult.Mode = Rebuild
}
err = ot.treeStorage.TransactionAdd(addResult.Added, addResult.Heads)
err = ot.treeStorage.AddRawChangesSetHeads(addResult.Added, addResult.Heads)
if err != nil {
// rolling back all changes made to inmemory state
ot.rebuildFromStorage(nil, nil)
}
return
}
@@ -306,7 +361,7 @@ func (ot *objectTree) addRawChanges(ctx context.Context, changesPayload RawChang
}
// checks if we need to go to database
isOldSnapshot := func(ch *Change) bool {
snapshotNotInTree := func(ch *Change) bool {
if ch.SnapshotId == ot.tree.RootId() {
return false
}
@@ -321,26 +376,12 @@ func (ot *objectTree) addRawChanges(ctx context.Context, changesPayload RawChang
shouldRebuildFromStorage := false
// checking if we have some changes with different snapshot and then rebuilding
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
for _, ch := range ot.newChangesBuf {
if snapshotNotInTree(ch) {
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)
@@ -437,6 +478,11 @@ 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
}
@@ -460,11 +506,11 @@ func (ot *objectTree) IterateFrom(id string, convert ChangeConvertFunc, iterate
}
decrypt := func(c *Change) (decrypted []byte, err error) {
// the change is not encrypted
if c.ReadKeyHash == 0 {
if c.ReadKeyId == "" {
decrypted = c.Data
return
}
readKey, exists := ot.keys[c.ReadKeyHash]
readKey, exists := ot.keys[c.ReadKeyId]
if !exists {
err = list.ErrNoReadKey
return
@@ -503,22 +549,8 @@ 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 !hasChange(ch) {
if _, attachedExists := ot.tree.attached[ch]; !attachedExists {
return false
}
}
@@ -534,6 +566,10 @@ 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
}
@@ -581,19 +617,7 @@ func (ot *objectTree) ChangesAfterCommonSnapshot(theirPath, theirHeads []string)
}
}
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)
return ot.rawChangeLoader.Load(commonSnapshot, ot.tree, theirHeads)
}
func (ot *objectTree) snapshotPathIsActual() bool {
@@ -605,11 +629,9 @@ func (ot *objectTree) validateTree(newChanges []*Change) error {
defer ot.aclList.RUnlock()
state := ot.aclList.AclState()
// 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
}
err := ot.readKeysFromAclState(state)
if err != nil {
return err
}
if len(newChanges) == 0 {
return ot.validator.ValidateFullTree(ot.tree, ot.aclList)
@@ -618,6 +640,26 @@ 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)
}
@@ -2,91 +2,45 @@ package objecttree
import (
"context"
"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"
"fmt"
"testing"
"time"
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
"github.com/gogo/protobuf/proto"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)
type mockChangeCreator struct{}
func (c *mockChangeCreator) createRoot(id, aclId string) *treechangeproto.RawTreeChangeWithId {
aclChange := &treechangeproto.RootChange{
AclHeadId: aclId,
}
res, _ := aclChange.Marshal()
raw := &treechangeproto.RawTreeChange{
Payload: res,
Signature: nil,
}
rawMarshalled, _ := raw.Marshal()
return &treechangeproto.RawTreeChangeWithId{
RawChange: rawMarshalled,
Id: id,
}
}
func (c *mockChangeCreator) createRaw(id, aclId, snapshotId string, isSnapshot bool, prevIds ...string) *treechangeproto.RawTreeChangeWithId {
aclChange := &treechangeproto.TreeChange{
TreeHeadIds: prevIds,
AclHeadId: aclId,
SnapshotBaseId: snapshotId,
ChangesData: nil,
IsSnapshot: isSnapshot,
}
res, _ := aclChange.Marshal()
raw := &treechangeproto.RawTreeChange{
Payload: res,
Signature: nil,
}
rawMarshalled, _ := raw.Marshal()
return &treechangeproto.RawTreeChangeWithId{
RawChange: rawMarshalled,
Id: id,
}
}
func (c *mockChangeCreator) createNewTreeStorage(treeId, aclHeadId string) treestorage.TreeStorage {
root := c.createRoot(treeId, aclHeadId)
treeStorage, _ := treestorage.NewInMemoryTreeStorage(root, []string{root.Id}, []*treechangeproto.RawTreeChangeWithId{root})
return treeStorage
}
type testTreeContext struct {
aclList list.AclList
treeStorage treestorage.TreeStorage
changeBuilder ChangeBuilder
changeCreator *mockChangeCreator
changeCreator *MockChangeCreator
objTree ObjectTree
}
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)
func prepareAclList(t *testing.T) (list.AclList, *accountdata.AccountKeys) {
randKeys, err := accountdata.NewRandom()
require.NoError(t, err)
aclList, err := list.NewTestDerivedAcl("spaceId", randKeys)
require.NoError(t, err, "building acl list should be without error")
return aclList
return aclList, randKeys
}
func prepareTreeDeps(aclList list.AclList) (*mockChangeCreator, objectTreeDeps) {
changeCreator := &mockChangeCreator{}
treeStorage := changeCreator.createNewTreeStorage("0", aclList.Head().Id)
func prepareHistoryTreeDeps(aclList list.AclList) (*MockChangeCreator, objectTreeDeps) {
changeCreator := NewMockChangeCreator()
treeStorage := changeCreator.CreateNewTreeStorage("0", aclList.Head().Id)
root, _ := treeStorage.Root()
changeBuilder := &nonVerifiableChangeBuilder{
ChangeBuilder: NewChangeBuilder(nil, root),
ChangeBuilder: NewChangeBuilder(newMockKeyStorage(), root),
}
deps := objectTreeDeps{
changeBuilder: changeBuilder,
treeBuilder: newTreeBuilder(treeStorage, changeBuilder),
treeBuilder: newTreeBuilder(true, treeStorage, changeBuilder),
treeStorage: treeStorage,
rawChangeLoader: newRawChangeLoader(treeStorage, changeBuilder),
validator: &noOpTreeValidator{},
@@ -96,23 +50,26 @@ func prepareTreeDeps(aclList list.AclList) (*mockChangeCreator, objectTreeDeps)
}
func prepareTreeContext(t *testing.T, aclList list.AclList) testTreeContext {
changeCreator := &mockChangeCreator{}
treeStorage := changeCreator.createNewTreeStorage("0", aclList.Head().Id)
root, _ := treeStorage.Root()
changeBuilder := &nonVerifiableChangeBuilder{
ChangeBuilder: NewChangeBuilder(nil, root),
}
deps := objectTreeDeps{
changeBuilder: changeBuilder,
treeBuilder: newTreeBuilder(treeStorage, changeBuilder),
treeStorage: treeStorage,
rawChangeLoader: newRawChangeLoader(treeStorage, changeBuilder),
validator: &noOpTreeValidator{},
aclList: aclList,
}
return prepareContext(t, aclList, BuildTestableTree, nil)
}
// check build
objTree, err := buildObjectTree(deps)
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)
}
objTree, err := objTreeBuilder(treeStorage, aclList)
require.NoError(t, err, "building tree should be without error")
// check tree iterate
@@ -122,18 +79,71 @@ func prepareTreeContext(t *testing.T, aclList list.AclList) testTreeContext {
return true
})
require.NoError(t, err, "iterate should be without error")
assert.Equal(t, []string{"0"}, iterChangesId)
if additionalChanges == nil {
assert.Equal(t, []string{"0"}, iterChangesId)
}
return testTreeContext{
aclList: aclList,
treeStorage: treeStorage,
changeBuilder: changeBuilder,
changeCreator: changeCreator,
objTree: objTree,
}
}
func TestObjectTree(t *testing.T) {
aclList := prepareAclList(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)
require.Equal(t, res.Added[0].Id, oTree.(*objectTree).tree.lastIteratedHeadId)
})
t.Run("timestamp is set correctly", func(t *testing.T) {
someTs := time.Now().Add(time.Hour).Unix()
res, err := oTree.AddContent(ctx, SignableChangeContent{
Data: []byte("some"),
Key: keys.SignKey,
IsSnapshot: false,
IsEncrypted: true,
Timestamp: someTs,
})
require.NoError(t, err)
require.Len(t, oTree.Heads(), 1)
require.Equal(t, res.Added[0].Id, oTree.Heads()[0])
ch, err := oTree.(*objectTree).changeBuilder.Unmarshall(res.Added[0], true)
require.NoError(t, err)
require.Equal(t, ch.Timestamp, someTs)
require.Equal(t, res.Added[0].Id, oTree.(*objectTree).tree.lastIteratedHeadId)
})
})
t.Run("add simple", func(t *testing.T) {
ctx := prepareTreeContext(t, aclList)
@@ -142,8 +152,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},
@@ -187,7 +197,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},
@@ -211,7 +221,33 @@ func TestObjectTree(t *testing.T) {
objTree := ctx.objTree
rawChanges := []*treechangeproto.RawTreeChangeWithId{
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
changeCreator.CreateRaw("2", aclList.Head().Id, "0", false, "1"),
}
payload := RawChangesPayload{
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
RawChanges: rawChanges,
}
res, err := objTree.AddRawChanges(context.Background(), payload)
require.NoError(t, err, "adding changes should be without error")
// check result
assert.Equal(t, []string{"0"}, res.OldHeads)
assert.Equal(t, []string{"0"}, res.Heads)
assert.Equal(t, 0, len(res.Added))
assert.Equal(t, Nothing, res.Mode)
// check tree heads
assert.Equal(t, []string{"0"}, objTree.Heads())
})
t.Run("add not connected changes", func(t *testing.T) {
ctx := prepareTreeContext(t, aclList)
changeCreator := ctx.changeCreator
objTree := ctx.objTree
// this change could in theory replace current snapshot, we should prevent that
rawChanges := []*treechangeproto.RawTreeChangeWithId{
changeCreator.CreateRaw("2", aclList.Head().Id, "0", true, "1"),
}
payload := RawChangesPayload{
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
@@ -237,10 +273,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,9 +323,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},
@@ -305,18 +341,195 @@ 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{
@@ -390,13 +603,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{
@@ -471,9 +684,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},
@@ -485,9 +698,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},
@@ -528,17 +741,17 @@ func TestObjectTree(t *testing.T) {
})
t.Run("test history tree not include", func(t *testing.T) {
changeCreator, deps := prepareTreeDeps(aclList)
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"),
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.TransactionAdd(rawChanges, []string{"6"})
deps.treeStorage.AddRawChangesSetHeads(rawChanges, []string{"6"})
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
BeforeId: "6",
IncludeBeforeId: false,
@@ -558,18 +771,49 @@ func TestObjectTree(t *testing.T) {
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 := prepareTreeDeps(aclList)
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"),
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.TransactionAdd(rawChanges, []string{"6"})
deps.treeStorage.AddRawChangesSetHeads(rawChanges, []string{"6"})
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
BeforeId: "6",
IncludeBeforeId: true,
@@ -590,7 +834,7 @@ func TestObjectTree(t *testing.T) {
})
t.Run("test history tree root", func(t *testing.T) {
_, deps := prepareTreeDeps(aclList)
_, deps := prepareHistoryTreeDeps(aclList)
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
BeforeId: "0",
IncludeBeforeId: true,
@@ -609,4 +853,40 @@ func TestObjectTree(t *testing.T) {
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,22 +1,20 @@
package objecttree
import (
"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"
"math/rand"
"time"
"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"
)
type ObjectTreeCreatePayload struct {
SignKey signingkey.PrivKey
ChangeType string
SpaceId string
Identity []byte
IsEncrypted bool
PrivKey crypto.PrivKey
ChangeType string
ChangePayload []byte
SpaceId string
IsEncrypted bool
Seed []byte
Timestamp int64
}
type HistoryTreeParams struct {
@@ -24,6 +22,7 @@ type HistoryTreeParams struct {
AclList list.AclList
BeforeId string
IncludeBeforeId bool
BuildFullTree bool
}
type objectTreeDeps struct {
@@ -35,14 +34,16 @@ type objectTreeDeps struct {
aclList list.AclList
}
func defaultObjectTreeDeps(
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 {
keychain := keychain.NewKeychain()
changeBuilder := NewChangeBuilder(keychain, rootChange)
treeBuilder := newTreeBuilder(treeStorage, changeBuilder)
changeBuilder := NewChangeBuilder(crypto.NewKeyStorage(), rootChange)
treeBuilder := newTreeBuilder(true, treeStorage, changeBuilder)
return objectTreeDeps{
changeBuilder: changeBuilder,
treeBuilder: treeBuilder,
@@ -53,12 +54,28 @@ func defaultObjectTreeDeps(
}
}
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(nil, rootChange)}
treeBuilder := newTreeBuilder(treeStorage, changeBuilder)
changeBuilder := &nonVerifiableChangeBuilder{NewChangeBuilder(newMockKeyStorage(), rootChange)}
treeBuilder := newTreeBuilder(true, treeStorage, changeBuilder)
return objectTreeDeps{
changeBuilder: changeBuilder,
treeBuilder: treeBuilder,
@@ -69,17 +86,47 @@ func nonVerifiableTreeDeps(
}
}
func CreateObjectTreeRoot(payload ObjectTreeCreatePayload, aclList list.AclList) (root *treechangeproto.RawTreeChangeWithId, err error) {
bytes := make([]byte, 32)
_, err = rand.Read(bytes)
func BuildEmptyDataObjectTree(treeStorage treestorage.TreeStorage, aclList list.AclList) (ObjectTree, error) {
rootChange, err := treeStorage.Root()
if err != nil {
return
return nil, err
}
return createObjectTreeRoot(payload, time.Now().UnixNano(), bytes, aclList)
deps := emptyDataTreeDeps(rootChange, treeStorage, aclList)
return buildObjectTree(deps)
}
func DeriveObjectTreeRoot(payload ObjectTreeCreatePayload, aclList list.AclList) (root *treechangeproto.RawTreeChangeWithId, err error) {
return createObjectTreeRoot(payload, 0, nil, 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 BuildObjectTree(treeStorage treestorage.TreeStorage, aclList list.AclList) (ObjectTree, error) {
@@ -109,54 +156,7 @@ func BuildHistoryTree(params HistoryTreeParams) (HistoryTree, error) {
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 CreateObjectTree(
payload ObjectTreeCreatePayload,
aclList list.AclList,
createStorage treestorage.TreeStorageCreatorFunc) (objTree ObjectTree, err error) {
bytes := make([]byte, 32)
_, err = rand.Read(bytes)
if err != nil {
return
}
return createObjectTree(payload, time.Now().UnixNano(), bytes, aclList, createStorage)
}
func createObjectTree(
payload ObjectTreeCreatePayload,
timestamp int64,
seed []byte,
aclList list.AclList,
createStorage treestorage.TreeStorageCreatorFunc) (objTree ObjectTree, err error) {
raw, err := createObjectTreeRoot(payload, timestamp, seed, aclList)
if err != nil {
return
}
// create storage
st, err := createStorage(treestorage.TreeStorageCreatePayload{
RootRawChange: raw,
Changes: []*treechangeproto.RawTreeChangeWithId{raw},
Heads: []string{raw.Id},
})
if err != nil {
return
}
return BuildObjectTree(st, aclList)
}
func createObjectTreeRoot(
payload ObjectTreeCreatePayload,
timestamp int64,
seed []byte,
aclList list.AclList) (root *treechangeproto.RawTreeChangeWithId, err error) {
func CreateObjectTreeRoot(payload ObjectTreeCreatePayload, aclList list.AclList) (root *treechangeproto.RawTreeChangeWithId, err error) {
aclList.RLock()
aclHeadId := aclList.Head().Id
aclList.RUnlock()
@@ -165,16 +165,16 @@ func createObjectTreeRoot(
return
}
cnt := InitialContent{
AclHeadId: aclHeadId,
Identity: payload.Identity,
SigningKey: payload.SignKey,
SpaceId: payload.SpaceId,
ChangeType: payload.ChangeType,
Timestamp: timestamp,
Seed: seed,
AclHeadId: aclHeadId,
PrivKey: payload.PrivKey,
SpaceId: payload.SpaceId,
ChangeType: payload.ChangeType,
ChangePayload: payload.ChangePayload,
Timestamp: payload.Timestamp,
Seed: payload.Seed,
}
_, root, err = NewChangeBuilder(keychain.NewKeychain(), nil).BuildRoot(cnt)
_, root, err = NewChangeBuilder(crypto.NewKeyStorage(), nil).BuildRoot(cnt)
return
}
@@ -187,7 +187,7 @@ func buildObjectTree(deps objectTreeDeps) (ObjectTree, error) {
aclList: deps.aclList,
changeBuilder: deps.changeBuilder,
rawChangeLoader: deps.rawChangeLoader,
keys: make(map[uint64]*symmetric.Key),
keys: make(map[string]crypto.SymKey),
newChangesBuf: make([]*Change, 0, 10),
difSnapshotBuf: make([]*treechangeproto.RawTreeChangeWithId, 0, 10),
notSeenIdxBuf: make([]int, 0, 10),
@@ -223,7 +223,7 @@ func buildHistoryTree(deps objectTreeDeps, params HistoryTreeParams) (ht History
aclList: deps.aclList,
changeBuilder: deps.changeBuilder,
rawChangeLoader: deps.rawChangeLoader,
keys: make(map[uint64]*symmetric.Key),
keys: make(map[string]crypto.SymKey),
newChangesBuf: make([]*Change, 0, 10),
difSnapshotBuf: make([]*treechangeproto.RawTreeChangeWithId, 0, 10),
notSeenIdxBuf: make([]int, 0, 10),
@@ -231,7 +231,7 @@ func buildHistoryTree(deps objectTreeDeps, params HistoryTreeParams) (ht History
}
hTree := &historyTree{objectTree: objTree}
err = hTree.rebuildFromStorage(params.BeforeId, params.IncludeBeforeId)
err = hTree.rebuildFromStorage(params)
if err != nil {
return nil, err
}
@@ -1,10 +1,12 @@
package objecttree
import (
"context"
"fmt"
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
"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"
)
type ObjectTreeValidator interface {
@@ -50,20 +52,18 @@ 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.UserPermissionPair
state = aclList.AclState()
userState list.AclUserState
state = aclList.AclState()
)
// checking if the user could write
perm, err = state.PermissionsAtRecord(c.AclHeadId, c.Identity)
userState, err = state.StateAtRecord(c.AclHeadId, c.Identity)
if err != nil {
return
}
if perm.Permission != aclrecordproto.AclUserPermissions_Writer && perm.Permission != aclrecordproto.AclUserPermissions_Admin {
if !userState.Permissions.CanWrite() {
err = list.ErrInsufficientPermissions
return
}
if c.Id == tree.RootId() {
return
}
@@ -88,11 +88,23 @@ func (v *objectTreeValidator) validateChange(tree *Tree, aclList list.AclList, c
}
func ValidateRawTree(payload treestorage.TreeStorageCreatePayload, aclList list.AclList) (err error) {
treeStorage, err := treestorage.NewInMemoryTreeStorage(payload.RootRawChange, payload.Heads, payload.Changes)
treeStorage, err := treestorage.NewInMemoryTreeStorage(payload.RootRawChange, []string{payload.RootRawChange.Id}, nil)
if err != nil {
return
}
_, err = BuildObjectTree(treeStorage, aclList)
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
}
+35 -18
View File
@@ -2,15 +2,17 @@ package objecttree
import (
"context"
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
"github.com/anytypeio/any-sync/util/slice"
"time"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
"github.com/anyproto/any-sync/util/slice"
)
type rawChangeLoader struct {
treeStorage treestorage.TreeStorage
changeBuilder ChangeBuilder
treeStorage treestorage.TreeStorage
changeBuilder ChangeBuilder
alwaysFromStorage bool
// buffers
idStack []string
@@ -21,6 +23,13 @@ type rawCacheEntry struct {
change *Change
rawChange *treechangeproto.RawTreeChangeWithId
position int
removed bool
}
func newStorageLoader(treeStorage treestorage.TreeStorage, changeBuilder ChangeBuilder) *rawChangeLoader {
loader := newRawChangeLoader(treeStorage, changeBuilder)
loader.alwaysFromStorage = true
return loader
}
func newRawChangeLoader(treeStorage treestorage.TreeStorage, changeBuilder ChangeBuilder) *rawChangeLoader {
@@ -30,7 +39,15 @@ func newRawChangeLoader(treeStorage treestorage.TreeStorage, changeBuilder Chang
}
}
func (r *rawChangeLoader) LoadFromTree(t *Tree, breakpoints []string) ([]*treechangeproto.RawTreeChangeWithId, error) {
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) {
var stack []*Change
for _, h := range t.headIds {
stack = append(stack, t.attached[h])
@@ -98,7 +115,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() {
@@ -111,7 +128,6 @@ func (r *rawChangeLoader) LoadFromStorage(commonSnapshot string, heads, breakpoi
if err != nil {
continue
}
entry.position = -1
r.cache[b] = entry
existingBreakpoints = append(existingBreakpoints, b)
}
@@ -120,8 +136,7 @@ func (r *rawChangeLoader) LoadFromStorage(commonSnapshot string, heads, breakpoi
dfs := func(
commonSnapshot string,
heads []string,
startCounter int,
shouldVisit func(counter int, mapExists bool) bool,
shouldVisit func(entry rawCacheEntry, mapExists bool) bool,
visit func(entry rawCacheEntry) rawCacheEntry) bool {
// resetting stack
@@ -135,7 +150,7 @@ func (r *rawChangeLoader) LoadFromStorage(commonSnapshot string, heads, breakpoi
r.idStack = r.idStack[:len(r.idStack)-1]
entry, exists := r.cache[id]
if !shouldVisit(entry.position, exists) {
if !shouldVisit(entry, exists) {
continue
}
if id == commonSnapshot {
@@ -144,7 +159,6 @@ func (r *rawChangeLoader) LoadFromStorage(commonSnapshot string, heads, breakpoi
}
if !exists {
entry, err = r.loadEntry(id)
entry.position = -1
if err != nil {
continue
}
@@ -159,7 +173,7 @@ func (r *rawChangeLoader) LoadFromStorage(commonSnapshot string, heads, breakpoi
break
}
prevEntry, exists := r.cache[prev]
if !shouldVisit(prevEntry.position, exists) {
if !shouldVisit(prevEntry, exists) {
continue
}
r.idStack = append(r.idStack, prev)
@@ -172,8 +186,8 @@ func (r *rawChangeLoader) LoadFromStorage(commonSnapshot string, heads, breakpoi
r.idStack = append(r.idStack, heads...)
var buffer []*treechangeproto.RawTreeChangeWithId
rootVisited := dfs(commonSnapshot, heads, 0,
func(counter int, mapExists bool) bool {
rootVisited := dfs(commonSnapshot, heads,
func(_ rawCacheEntry, mapExists bool) bool {
return !mapExists
},
func(entry rawCacheEntry) rawCacheEntry {
@@ -198,11 +212,13 @@ func (r *rawChangeLoader) LoadFromStorage(commonSnapshot string, heads, breakpoi
}
// marking all visited as nil
dfs(commonSnapshot, existingBreakpoints, len(buffer),
func(counter int, mapExists bool) bool {
return !mapExists || counter < len(buffer)
dfs(commonSnapshot, existingBreakpoints,
func(entry rawCacheEntry, mapExists bool) bool {
// only going through already loaded changes
return mapExists && !entry.removed
},
func(entry rawCacheEntry) rawCacheEntry {
entry.removed = true
if entry.position != -1 {
buffer[entry.position] = nil
}
@@ -233,6 +249,7 @@ func (r *rawChangeLoader) loadEntry(id string) (entry rawCacheEntry, err error)
entry = rawCacheEntry{
change: change,
rawChange: rawChange,
position: -1,
}
return
}
@@ -1,13 +1,19 @@
package objecttree
import (
"github.com/anytypeio/any-sync/util/keys/asymmetric/signingkey"
"github.com/anyproto/any-sync/util/crypto"
)
// SignableChangeContent is a payload to be passed when we are creating change
type SignableChangeContent struct {
Data []byte
Key signingkey.PrivKey
Identity []byte
IsSnapshot bool
// 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
IsEncrypted bool
// Timestamp is a timestamp of change, if it is <= 0, then we use current timestamp
Timestamp int64
}
@@ -0,0 +1,121 @@
package objecttree
import (
"fmt"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
"github.com/anyproto/any-sync/util/crypto"
libcrypto "github.com/libp2p/go-libp2p/core/crypto"
)
type mockPubKey struct {
}
const mockKeyValue = "mockKey"
func (m mockPubKey) Equals(key crypto.Key) bool {
return true
}
func (m mockPubKey) Raw() ([]byte, error) {
return []byte(mockKeyValue), nil
}
func (m mockPubKey) Encrypt(message []byte) ([]byte, error) {
return message, nil
}
func (m mockPubKey) Verify(data []byte, sig []byte) (bool, error) {
return true, nil
}
func (m mockPubKey) Marshall() ([]byte, error) {
return []byte(mockKeyValue), nil
}
func (m mockPubKey) Storage() []byte {
return []byte(mockKeyValue)
}
func (m mockPubKey) Account() string {
return mockKeyValue
}
func (m mockPubKey) Network() string {
return mockKeyValue
}
func (m mockPubKey) PeerId() string {
return mockKeyValue
}
func (m mockPubKey) LibP2P() (libcrypto.PubKey, error) {
return nil, fmt.Errorf("can't be converted in libp2p")
}
type mockKeyStorage struct {
}
func newMockKeyStorage() mockKeyStorage {
return mockKeyStorage{}
}
func (m mockKeyStorage) PubKeyFromProto(protoBytes []byte) (crypto.PubKey, error) {
return mockPubKey{}, nil
}
type MockChangeCreator struct{}
func NewMockChangeCreator() *MockChangeCreator {
return &MockChangeCreator{}
}
func (c *MockChangeCreator) CreateRoot(id, aclId string) *treechangeproto.RawTreeChangeWithId {
aclChange := &treechangeproto.RootChange{
AclHeadId: aclId,
}
res, _ := aclChange.Marshal()
raw := &treechangeproto.RawTreeChange{
Payload: res,
Signature: nil,
}
rawMarshalled, _ := raw.Marshal()
return &treechangeproto.RawTreeChangeWithId{
RawChange: rawMarshalled,
Id: id,
}
}
func (c *MockChangeCreator) CreateRaw(id, aclId, snapshotId string, isSnapshot bool, prevIds ...string) *treechangeproto.RawTreeChangeWithId {
return c.CreateRawWithData(id, aclId, snapshotId, isSnapshot, nil, prevIds...)
}
func (c *MockChangeCreator) CreateRawWithData(id, aclId, snapshotId string, isSnapshot bool, data []byte, prevIds ...string) *treechangeproto.RawTreeChangeWithId {
aclChange := &treechangeproto.TreeChange{
TreeHeadIds: prevIds,
AclHeadId: aclId,
SnapshotBaseId: snapshotId,
ChangesData: data,
IsSnapshot: isSnapshot,
}
res, _ := aclChange.Marshal()
raw := &treechangeproto.RawTreeChange{
Payload: res,
Signature: nil,
}
rawMarshalled, _ := raw.Marshal()
return &treechangeproto.RawTreeChangeWithId{
RawChange: rawMarshalled,
Id: id,
}
}
func (c *MockChangeCreator) CreateNewTreeStorage(treeId, aclHeadId string) treestorage.TreeStorage {
root := c.CreateRoot(treeId, aclHeadId)
treeStorage, _ := treestorage.NewInMemoryTreeStorage(root, []string{root.Id}, []*treechangeproto.RawTreeChangeWithId{root})
return treeStorage
}
@@ -82,6 +82,7 @@ func (t *Tree) AddMergedHead(c *Change) error {
}
}
t.headIds = []string{c.Id}
t.lastIteratedHeadId = c.Id
return nil
}
@@ -2,10 +2,12 @@ package objecttree
import (
"fmt"
"github.com/stretchr/testify/assert"
"math/rand"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func newChange(id string, snapshotId string, prevIds ...string) *Change {
@@ -26,6 +28,17 @@ func newSnapshot(id, snapshotId string, prevIds ...string) *Change {
}
}
func TestTree_AddMergedHead(t *testing.T) {
tr := new(Tree)
_, _ = tr.Add(
newSnapshot("root", ""),
newChange("one", "root", "root"),
)
require.Equal(t, tr.lastIteratedHeadId, "one")
tr.AddMergedHead(newChange("two", "root", "one"))
require.Equal(t, tr.lastIteratedHeadId, "two")
}
func TestTree_Add(t *testing.T) {
t.Run("add first el", func(t *testing.T) {
tr := new(Tree)
@@ -125,7 +138,7 @@ func TestTree_Hash(t *testing.T) {
}
func TestTree_AddFuzzy(t *testing.T) {
rand.Seed(time.Now().UnixNano())
rand.Seed(time.Now().Unix())
getChanges := func() []*Change {
changes := []*Change{
newChange("1", "root", "root"),
@@ -4,15 +4,16 @@ import (
"context"
"errors"
"fmt"
"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"
"github.com/anyproto/any-sync/app/logger"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
"github.com/anyproto/any-sync/util/slice"
"go.uber.org/zap"
)
var (
log = logger.NewNamed("acltree").Sugar()
log = logger.NewNamedSugared("common.commonspace.objecttree")
ErrEmpty = errors.New("logs empty")
)
@@ -20,18 +21,20 @@ type treeBuilder struct {
treeStorage treestorage.TreeStorage
builder ChangeBuilder
cache map[string]*Change
tree *Tree
cache map[string]*Change
tree *Tree
keepInMemoryData bool
// buffers
idStack []string
loadBuffer []*Change
}
func newTreeBuilder(storage treestorage.TreeStorage, builder ChangeBuilder) *treeBuilder {
func newTreeBuilder(keepData bool, storage treestorage.TreeStorage, builder ChangeBuilder) *treeBuilder {
return &treeBuilder{
treeStorage: storage,
builder: builder,
treeStorage: storage,
builder: builder,
keepInMemoryData: keepData,
}
}
@@ -48,7 +51,27 @@ func (tb *treeBuilder) Build(theirHeads []string, newChanges []*Change) (*Tree,
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)
@@ -85,7 +108,7 @@ func (tb *treeBuilder) build(heads []string, theirHeads []string, newChanges []*
}
proposedHeads = append(proposedHeads, heads...)
log.With(zap.Strings("heads", proposedHeads)).Debug("building tree")
log.With(zap.Strings("heads", proposedHeads), zap.String("id", tb.treeStorage.Id())).Debug("building tree")
if err = tb.buildTree(proposedHeads, breakpoint); err != nil {
return nil, fmt.Errorf("buildTree error: %v", err)
}
@@ -98,8 +121,8 @@ func (tb *treeBuilder) buildTree(heads []string, breakpoint string) (err error)
if err != nil {
return
}
tb.tree.AddFast(ch)
changes := tb.dfs(heads, breakpoint)
tb.tree.AddFast(ch)
tb.tree.AddFast(changes...)
return
}
@@ -158,6 +181,9 @@ func (tb *treeBuilder) loadChange(id string) (ch *Change, err error) {
if err != nil {
return nil, err
}
if !tb.keepInMemoryData {
ch.Data = nil
}
tb.cache[id] = ch
return ch, nil
@@ -1,5 +1,5 @@
//go:build (!linux && !darwin) || android || ios || nographviz || windows
// +build !linux,!darwin android ios nographviz windows
//go:build (!linux && !darwin) || android || ios || nographviz || !cgo || windows
// +build !linux,!darwin android ios nographviz !cgo windows
package objecttree
@@ -1,8 +1,9 @@
//go:build (linux || darwin) && !android && !ios && !nographviz && (amd64 || arm64)
//go:build (linux || darwin) && !android && !ios && !nographviz && cgo && (amd64 || arm64)
// +build linux darwin
// +build !android
// +build !ios
// +build !nographviz
// +build cgo
// +build amd64 arm64
package objecttree
@@ -10,10 +11,11 @@ package objecttree
import (
"bytes"
"fmt"
"github.com/goccy/go-graphviz"
"github.com/goccy/go-graphviz/cgraph"
"strings"
"time"
"github.com/goccy/go-graphviz"
"github.com/goccy/go-graphviz/cgraph"
)
func (t *Tree) Graph(parser DescriptionParser) (data string, err error) {
@@ -1,5 +1,11 @@
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
@@ -27,3 +33,24 @@ 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/anytypeio/any-sync/commonspace/object/tree/synctree (interfaces: SyncClient,SyncTree,ReceiveQueue,HeadNotifiable)
// Source: github.com/anyproto/any-sync/commonspace/object/tree/synctree (interfaces: SyncTree,ReceiveQueue,HeadNotifiable,SyncClient,RequestFactory,TreeSyncProtocol)
// Package mock_synctree is a generated GoMock package.
package mock_synctree
@@ -7,125 +7,17 @@ package mock_synctree
import (
context "context"
reflect "reflect"
time "time"
list "github.com/anytypeio/any-sync/commonspace/object/acl/list"
objecttree "github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
updatelistener "github.com/anytypeio/any-sync/commonspace/object/tree/synctree/updatelistener"
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"
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"
gomock "go.uber.org/mock/gomock"
)
// MockSyncClient is a mock of SyncClient interface.
type MockSyncClient struct {
ctrl *gomock.Controller
recorder *MockSyncClientMockRecorder
}
// MockSyncClientMockRecorder is the mock recorder for MockSyncClient.
type MockSyncClientMockRecorder struct {
mock *MockSyncClient
}
// NewMockSyncClient creates a new mock instance.
func NewMockSyncClient(ctrl *gomock.Controller) *MockSyncClient {
mock := &MockSyncClient{ctrl: ctrl}
mock.recorder = &MockSyncClientMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockSyncClient) EXPECT() *MockSyncClientMockRecorder {
return m.recorder
}
// Broadcast mocks base method.
func (m *MockSyncClient) Broadcast(arg0 context.Context, arg1 *treechangeproto.TreeSyncMessage) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Broadcast", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// Broadcast indicates an expected call of Broadcast.
func (mr *MockSyncClientMockRecorder) Broadcast(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Broadcast", reflect.TypeOf((*MockSyncClient)(nil).Broadcast), arg0, arg1)
}
// CreateFullSyncRequest mocks base method.
func (m *MockSyncClient) CreateFullSyncRequest(arg0 objecttree.ObjectTree, arg1, arg2 []string) (*treechangeproto.TreeSyncMessage, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateFullSyncRequest", arg0, arg1, arg2)
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateFullSyncRequest indicates an expected call of CreateFullSyncRequest.
func (mr *MockSyncClientMockRecorder) CreateFullSyncRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncRequest", reflect.TypeOf((*MockSyncClient)(nil).CreateFullSyncRequest), arg0, arg1, arg2)
}
// CreateFullSyncResponse mocks base method.
func (m *MockSyncClient) CreateFullSyncResponse(arg0 objecttree.ObjectTree, arg1, arg2 []string) (*treechangeproto.TreeSyncMessage, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateFullSyncResponse", arg0, arg1, arg2)
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateFullSyncResponse indicates an expected call of CreateFullSyncResponse.
func (mr *MockSyncClientMockRecorder) CreateFullSyncResponse(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncResponse", reflect.TypeOf((*MockSyncClient)(nil).CreateFullSyncResponse), arg0, arg1, arg2)
}
// CreateHeadUpdate mocks base method.
func (m *MockSyncClient) CreateHeadUpdate(arg0 objecttree.ObjectTree, arg1 []*treechangeproto.RawTreeChangeWithId) *treechangeproto.TreeSyncMessage {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateHeadUpdate", arg0, arg1)
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
return ret0
}
// CreateHeadUpdate indicates an expected call of CreateHeadUpdate.
func (mr *MockSyncClientMockRecorder) CreateHeadUpdate(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateHeadUpdate", reflect.TypeOf((*MockSyncClient)(nil).CreateHeadUpdate), arg0, arg1)
}
// CreateNewTreeRequest mocks base method.
func (m *MockSyncClient) CreateNewTreeRequest() *treechangeproto.TreeSyncMessage {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateNewTreeRequest")
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
return ret0
}
// CreateNewTreeRequest indicates an expected call of CreateNewTreeRequest.
func (mr *MockSyncClientMockRecorder) CreateNewTreeRequest() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNewTreeRequest", reflect.TypeOf((*MockSyncClient)(nil).CreateNewTreeRequest))
}
// SendWithReply mocks base method.
func (m *MockSyncClient) SendWithReply(arg0 context.Context, arg1 string, arg2 *treechangeproto.TreeSyncMessage, arg3 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SendWithReply", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(error)
return ret0
}
// SendWithReply indicates an expected call of SendWithReply.
func (mr *MockSyncClientMockRecorder) SendWithReply(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendWithReply", reflect.TypeOf((*MockSyncClient)(nil).SendWithReply), arg0, arg1, arg2, arg3)
}
// MockSyncTree is a mock of SyncTree interface.
type MockSyncTree struct {
ctrl *gomock.Controller
@@ -193,6 +85,20 @@ 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()
@@ -280,6 +186,21 @@ func (mr *MockSyncTreeMockRecorder) HandleMessage(arg0, arg1, arg2 interface{})
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleMessage", reflect.TypeOf((*MockSyncTree)(nil).HandleMessage), arg0, arg1, arg2)
}
// HandleRequest mocks base method.
func (m *MockSyncTree) HandleRequest(arg0 context.Context, arg1 string, arg2 *spacesyncproto.ObjectSyncMessage) (*spacesyncproto.ObjectSyncMessage, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HandleRequest", arg0, arg1, arg2)
ret0, _ := ret[0].(*spacesyncproto.ObjectSyncMessage)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// HandleRequest indicates an expected call of HandleRequest.
func (mr *MockSyncTreeMockRecorder) HandleRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleRequest", reflect.TypeOf((*MockSyncTree)(nil).HandleRequest), arg0, arg1, arg2)
}
// HasChanges mocks base method.
func (m *MockSyncTree) HasChanges(arg0 ...string) bool {
m.ctrl.T.Helper()
@@ -368,6 +289,20 @@ 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()
@@ -380,6 +315,21 @@ 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) {
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 *MockSyncTreeMockRecorder) PrepareChange(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrepareChange", reflect.TypeOf((*MockSyncTree)(nil).PrepareChange), arg0)
}
// RLock mocks base method.
func (m *MockSyncTree) RLock() {
m.ctrl.T.Helper()
@@ -472,6 +422,21 @@ func (mr *MockSyncTreeMockRecorder) SyncWithPeer(arg0, arg1 interface{}) *gomock
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()
@@ -526,6 +491,21 @@ 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
@@ -625,3 +605,287 @@ func (mr *MockHeadNotifiableMockRecorder) UpdateHeads(arg0, arg1 interface{}) *g
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateHeads", reflect.TypeOf((*MockHeadNotifiable)(nil).UpdateHeads), arg0, arg1)
}
// MockSyncClient is a mock of SyncClient interface.
type MockSyncClient struct {
ctrl *gomock.Controller
recorder *MockSyncClientMockRecorder
}
// MockSyncClientMockRecorder is the mock recorder for MockSyncClient.
type MockSyncClientMockRecorder struct {
mock *MockSyncClient
}
// NewMockSyncClient creates a new mock instance.
func NewMockSyncClient(ctrl *gomock.Controller) *MockSyncClient {
mock := &MockSyncClient{ctrl: ctrl}
mock.recorder = &MockSyncClientMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockSyncClient) EXPECT() *MockSyncClientMockRecorder {
return m.recorder
}
// Broadcast mocks base method.
func (m *MockSyncClient) Broadcast(arg0 *treechangeproto.TreeSyncMessage) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Broadcast", arg0)
}
// Broadcast indicates an expected call of Broadcast.
func (mr *MockSyncClientMockRecorder) Broadcast(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Broadcast", reflect.TypeOf((*MockSyncClient)(nil).Broadcast), arg0)
}
// CreateFullSyncRequest mocks base method.
func (m *MockSyncClient) CreateFullSyncRequest(arg0 objecttree.ObjectTree, arg1, arg2 []string) (*treechangeproto.TreeSyncMessage, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateFullSyncRequest", arg0, arg1, arg2)
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateFullSyncRequest indicates an expected call of CreateFullSyncRequest.
func (mr *MockSyncClientMockRecorder) CreateFullSyncRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncRequest", reflect.TypeOf((*MockSyncClient)(nil).CreateFullSyncRequest), arg0, arg1, arg2)
}
// CreateFullSyncResponse mocks base method.
func (m *MockSyncClient) CreateFullSyncResponse(arg0 objecttree.ObjectTree, arg1, arg2 []string) (*treechangeproto.TreeSyncMessage, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateFullSyncResponse", arg0, arg1, arg2)
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateFullSyncResponse indicates an expected call of CreateFullSyncResponse.
func (mr *MockSyncClientMockRecorder) CreateFullSyncResponse(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncResponse", reflect.TypeOf((*MockSyncClient)(nil).CreateFullSyncResponse), arg0, arg1, arg2)
}
// CreateHeadUpdate mocks base method.
func (m *MockSyncClient) CreateHeadUpdate(arg0 objecttree.ObjectTree, arg1 []*treechangeproto.RawTreeChangeWithId) *treechangeproto.TreeSyncMessage {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateHeadUpdate", arg0, arg1)
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
return ret0
}
// CreateHeadUpdate indicates an expected call of CreateHeadUpdate.
func (mr *MockSyncClientMockRecorder) CreateHeadUpdate(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateHeadUpdate", reflect.TypeOf((*MockSyncClient)(nil).CreateHeadUpdate), arg0, arg1)
}
// CreateNewTreeRequest mocks base method.
func (m *MockSyncClient) CreateNewTreeRequest() *treechangeproto.TreeSyncMessage {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateNewTreeRequest")
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
return ret0
}
// CreateNewTreeRequest indicates an expected call of CreateNewTreeRequest.
func (mr *MockSyncClientMockRecorder) CreateNewTreeRequest() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNewTreeRequest", reflect.TypeOf((*MockSyncClient)(nil).CreateNewTreeRequest))
}
// QueueRequest mocks base method.
func (m *MockSyncClient) QueueRequest(arg0, arg1 string, arg2 *treechangeproto.TreeSyncMessage) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "QueueRequest", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// QueueRequest indicates an expected call of QueueRequest.
func (mr *MockSyncClientMockRecorder) QueueRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueRequest", reflect.TypeOf((*MockSyncClient)(nil).QueueRequest), arg0, arg1, arg2)
}
// SendRequest mocks base method.
func (m *MockSyncClient) SendRequest(arg0 context.Context, arg1, arg2 string, arg3 *treechangeproto.TreeSyncMessage) (*spacesyncproto.ObjectSyncMessage, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SendRequest", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(*spacesyncproto.ObjectSyncMessage)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SendRequest indicates an expected call of SendRequest.
func (mr *MockSyncClientMockRecorder) SendRequest(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendRequest", reflect.TypeOf((*MockSyncClient)(nil).SendRequest), arg0, arg1, arg2, arg3)
}
// SendUpdate mocks base method.
func (m *MockSyncClient) SendUpdate(arg0, arg1 string, arg2 *treechangeproto.TreeSyncMessage) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SendUpdate", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// SendUpdate indicates an expected call of SendUpdate.
func (mr *MockSyncClientMockRecorder) SendUpdate(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendUpdate", reflect.TypeOf((*MockSyncClient)(nil).SendUpdate), arg0, arg1, arg2)
}
// MockRequestFactory is a mock of RequestFactory interface.
type MockRequestFactory struct {
ctrl *gomock.Controller
recorder *MockRequestFactoryMockRecorder
}
// MockRequestFactoryMockRecorder is the mock recorder for MockRequestFactory.
type MockRequestFactoryMockRecorder struct {
mock *MockRequestFactory
}
// NewMockRequestFactory creates a new mock instance.
func NewMockRequestFactory(ctrl *gomock.Controller) *MockRequestFactory {
mock := &MockRequestFactory{ctrl: ctrl}
mock.recorder = &MockRequestFactoryMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockRequestFactory) EXPECT() *MockRequestFactoryMockRecorder {
return m.recorder
}
// CreateFullSyncRequest mocks base method.
func (m *MockRequestFactory) CreateFullSyncRequest(arg0 objecttree.ObjectTree, arg1, arg2 []string) (*treechangeproto.TreeSyncMessage, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateFullSyncRequest", arg0, arg1, arg2)
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateFullSyncRequest indicates an expected call of CreateFullSyncRequest.
func (mr *MockRequestFactoryMockRecorder) CreateFullSyncRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncRequest", reflect.TypeOf((*MockRequestFactory)(nil).CreateFullSyncRequest), arg0, arg1, arg2)
}
// CreateFullSyncResponse mocks base method.
func (m *MockRequestFactory) CreateFullSyncResponse(arg0 objecttree.ObjectTree, arg1, arg2 []string) (*treechangeproto.TreeSyncMessage, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateFullSyncResponse", arg0, arg1, arg2)
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateFullSyncResponse indicates an expected call of CreateFullSyncResponse.
func (mr *MockRequestFactoryMockRecorder) CreateFullSyncResponse(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncResponse", reflect.TypeOf((*MockRequestFactory)(nil).CreateFullSyncResponse), arg0, arg1, arg2)
}
// CreateHeadUpdate mocks base method.
func (m *MockRequestFactory) CreateHeadUpdate(arg0 objecttree.ObjectTree, arg1 []*treechangeproto.RawTreeChangeWithId) *treechangeproto.TreeSyncMessage {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateHeadUpdate", arg0, arg1)
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
return ret0
}
// CreateHeadUpdate indicates an expected call of CreateHeadUpdate.
func (mr *MockRequestFactoryMockRecorder) CreateHeadUpdate(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateHeadUpdate", reflect.TypeOf((*MockRequestFactory)(nil).CreateHeadUpdate), arg0, arg1)
}
// CreateNewTreeRequest mocks base method.
func (m *MockRequestFactory) CreateNewTreeRequest() *treechangeproto.TreeSyncMessage {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateNewTreeRequest")
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
return ret0
}
// CreateNewTreeRequest indicates an expected call of CreateNewTreeRequest.
func (mr *MockRequestFactoryMockRecorder) CreateNewTreeRequest() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNewTreeRequest", reflect.TypeOf((*MockRequestFactory)(nil).CreateNewTreeRequest))
}
// MockTreeSyncProtocol is a mock of TreeSyncProtocol interface.
type MockTreeSyncProtocol struct {
ctrl *gomock.Controller
recorder *MockTreeSyncProtocolMockRecorder
}
// MockTreeSyncProtocolMockRecorder is the mock recorder for MockTreeSyncProtocol.
type MockTreeSyncProtocolMockRecorder struct {
mock *MockTreeSyncProtocol
}
// NewMockTreeSyncProtocol creates a new mock instance.
func NewMockTreeSyncProtocol(ctrl *gomock.Controller) *MockTreeSyncProtocol {
mock := &MockTreeSyncProtocol{ctrl: ctrl}
mock.recorder = &MockTreeSyncProtocolMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockTreeSyncProtocol) EXPECT() *MockTreeSyncProtocolMockRecorder {
return m.recorder
}
// FullSyncRequest mocks base method.
func (m *MockTreeSyncProtocol) FullSyncRequest(arg0 context.Context, arg1 string, arg2 *treechangeproto.TreeFullSyncRequest) (*treechangeproto.TreeSyncMessage, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FullSyncRequest", arg0, arg1, arg2)
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FullSyncRequest indicates an expected call of FullSyncRequest.
func (mr *MockTreeSyncProtocolMockRecorder) FullSyncRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FullSyncRequest", reflect.TypeOf((*MockTreeSyncProtocol)(nil).FullSyncRequest), arg0, arg1, arg2)
}
// FullSyncResponse mocks base method.
func (m *MockTreeSyncProtocol) FullSyncResponse(arg0 context.Context, arg1 string, arg2 *treechangeproto.TreeFullSyncResponse) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FullSyncResponse", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// FullSyncResponse indicates an expected call of FullSyncResponse.
func (mr *MockTreeSyncProtocolMockRecorder) FullSyncResponse(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FullSyncResponse", reflect.TypeOf((*MockTreeSyncProtocol)(nil).FullSyncResponse), arg0, arg1, arg2)
}
// HeadUpdate mocks base method.
func (m *MockTreeSyncProtocol) HeadUpdate(arg0 context.Context, arg1 string, arg2 *treechangeproto.TreeHeadUpdate) (*treechangeproto.TreeSyncMessage, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HeadUpdate", arg0, arg1, arg2)
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// HeadUpdate indicates an expected call of HeadUpdate.
func (mr *MockTreeSyncProtocolMockRecorder) HeadUpdate(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeadUpdate", reflect.TypeOf((*MockTreeSyncProtocol)(nil).HeadUpdate), arg0, arg1, arg2)
}
@@ -0,0 +1,362 @@
package synctree
import (
"context"
"math/rand"
"testing"
"time"
"github.com/anyproto/any-sync/commonspace/object/accountdata"
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
"github.com/anyproto/any-sync/util/slice"
"github.com/gogo/protobuf/proto"
"github.com/stretchr/testify/require"
)
func TestEmptyClientGetsFullHistory(t *testing.T) {
treeId := "treeId"
spaceId := "spaceId"
keys, err := accountdata.NewRandom()
require.NoError(t, err)
aclList, err := list.NewTestDerivedAcl(spaceId, keys)
require.NoError(t, err)
storage := createStorage(treeId, aclList)
changeCreator := objecttree.NewMockChangeCreator()
deps := fixtureDeps{
aclList: aclList,
initStorage: storage.(*treestorage.InMemoryTreeStorage),
connectionMap: map[string][]string{
"peer1": []string{"peer2"},
"peer2": []string{"peer1"},
},
emptyTrees: []string{"peer2"},
treeBuilder: objecttree.BuildTestableTree,
}
fx := newProtocolFixture(t, spaceId, deps)
fx.run(t)
fx.handlers["peer1"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
NewHeads: nil,
RawChanges: []*treechangeproto.RawTreeChangeWithId{
changeCreator.CreateRaw("1", aclList.Id(), treeId, true, treeId),
},
})
time.Sleep(100 * time.Millisecond)
fx.stop()
firstHeads := fx.handlers["peer1"].tree().Heads()
secondHeads := fx.handlers["peer2"].tree().Heads()
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
require.Equal(t, []string{"1"}, firstHeads)
logMsgs := fx.log.batcher.GetAll()
var fullResponseMsg msgDescription
for _, msg := range logMsgs {
descr := msg.description()
if descr.name == "FullSyncResponse" {
fullResponseMsg = descr
}
}
// that means that we got not only the last snapshot, but also the first one
require.Len(t, fullResponseMsg.changes, 2)
}
func TestTreeFuzzyMerge(t *testing.T) {
testTreeFuzzyMerge(t, true)
testTreeFuzzyMerge(t, false)
}
func testTreeFuzzyMerge(t *testing.T, withData bool) {
var (
rnd = rand.New(rand.NewSource(time.Now().Unix()))
levels = 20
perLevel = 20
rounds = 10
)
for i := 0; i < rounds; i++ {
testTreeMerge(t, levels, perLevel, withData, func() bool {
return true
})
testTreeMerge(t, levels, perLevel, withData, func() bool {
return false
})
testTreeMerge(t, levels, perLevel, withData, func() bool {
return rnd.Intn(10) > 8
})
levels += 2
}
}
func testTreeMerge(t *testing.T, levels, perLevel int, hasData bool, isSnapshot func() bool) {
treeId := "treeId"
spaceId := "spaceId"
keys, err := accountdata.NewRandom()
require.NoError(t, err)
aclList, err := list.NewTestDerivedAcl(spaceId, keys)
storage := createStorage(treeId, aclList)
changeCreator := objecttree.NewMockChangeCreator()
builder := objecttree.BuildTestableTree
if hasData {
builder = objecttree.BuildEmptyDataTestableTree
}
params := genParams{
prefix: "peer1",
aclId: aclList.Id(),
startIdx: 0,
levels: levels,
perLevel: perLevel,
snapshotId: treeId,
prevHeads: []string{treeId},
isSnapshot: isSnapshot,
hasData: hasData,
}
// generating initial tree
initialRes := genChanges(changeCreator, params)
err = storage.AddRawChangesSetHeads(initialRes.changes, initialRes.heads)
require.NoError(t, err)
deps := fixtureDeps{
aclList: aclList,
initStorage: storage.(*treestorage.InMemoryTreeStorage),
connectionMap: map[string][]string{
"peer1": []string{"node1"},
"peer2": []string{"node1"},
"node1": []string{"peer1", "peer2"},
},
emptyTrees: []string{"peer2", "node1"},
treeBuilder: builder,
}
fx := newProtocolFixture(t, spaceId, deps)
fx.run(t)
fx.handlers["peer1"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
NewHeads: initialRes.heads,
RawChanges: initialRes.changes,
})
time.Sleep(50 * time.Millisecond)
firstHeads := fx.handlers["peer1"].tree().Heads()
secondHeads := fx.handlers["peer2"].tree().Heads()
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
params = genParams{
prefix: "peer1",
aclId: aclList.Id(),
startIdx: levels,
levels: levels,
perLevel: perLevel,
snapshotId: initialRes.snapshotId,
prevHeads: initialRes.heads,
isSnapshot: isSnapshot,
hasData: hasData,
}
// generating different additions to the tree for different peers
peer1Res := genChanges(changeCreator, params)
params.prefix = "peer2"
peer2Res := genChanges(changeCreator, params)
fx.handlers["peer1"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
NewHeads: peer1Res.heads,
RawChanges: peer1Res.changes,
})
fx.handlers["peer2"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
NewHeads: peer2Res.heads,
RawChanges: peer2Res.changes,
})
time.Sleep(50 * time.Millisecond)
fx.stop()
firstTree := fx.handlers["peer1"].tree()
secondTree := fx.handlers["peer2"].tree()
firstHeads = firstTree.Heads()
secondHeads = secondTree.Heads()
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
require.True(t, slice.UnsortedEquals(firstHeads, append(peer1Res.heads, peer2Res.heads...)))
firstStorage := firstTree.Storage().(*treestorage.InMemoryTreeStorage)
secondStorage := secondTree.Storage().(*treestorage.InMemoryTreeStorage)
require.True(t, firstStorage.Equal(secondStorage))
if hasData {
for _, ch := range secondStorage.Changes {
if ch.Id == treeId {
continue
}
unmarshallRaw := &treechangeproto.RawTreeChange{}
proto.Unmarshal(ch.RawChange, unmarshallRaw)
treeCh := &treechangeproto.TreeChange{}
proto.Unmarshal(unmarshallRaw.Payload, treeCh)
require.Equal(t, ch.Id, string(treeCh.ChangesData))
}
}
}
func TestTreeStorageHasExtraChanges(t *testing.T) {
var (
rnd = rand.New(rand.NewSource(time.Now().Unix()))
levels = 20
perLevel = 40
)
// simulating cases where one peer has some extra changes saved in storage
// and checking that this will not break the sync
t.Run("tree storage has extra simple", func(t *testing.T) {
testTreeStorageHasExtra(t, levels, perLevel, false, func() bool {
return false
})
testTreeStorageHasExtra(t, levels, perLevel, false, func() bool {
return rnd.Intn(10) > 5
})
testTreeStorageHasExtra(t, levels, perLevel, true, func() bool {
return false
})
testTreeStorageHasExtra(t, levels, perLevel, true, func() bool {
return rnd.Intn(10) > 5
})
})
t.Run("tree storage has extra three parts", func(t *testing.T) {
testTreeStorageHasExtraThreeParts(t, levels, perLevel, false, func() bool {
return false
})
testTreeStorageHasExtraThreeParts(t, levels, perLevel, false, func() bool {
return rnd.Intn(10) > 5
})
testTreeStorageHasExtraThreeParts(t, levels, perLevel, true, func() bool {
return false
})
testTreeStorageHasExtraThreeParts(t, levels, perLevel, true, func() bool {
return rnd.Intn(10) > 5
})
})
}
func testTreeStorageHasExtra(t *testing.T, levels, perLevel int, hasData bool, isSnapshot func() bool) {
treeId := "treeId"
spaceId := "spaceId"
keys, err := accountdata.NewRandom()
require.NoError(t, err)
aclList, err := list.NewTestDerivedAcl(spaceId, keys)
storage := createStorage(treeId, aclList)
changeCreator := objecttree.NewMockChangeCreator()
builder := objecttree.BuildTestableTree
if hasData {
builder = objecttree.BuildEmptyDataTestableTree
}
params := genParams{
prefix: "peer1",
aclId: aclList.Id(),
startIdx: 0,
levels: levels,
perLevel: perLevel,
snapshotId: treeId,
prevHeads: []string{treeId},
isSnapshot: isSnapshot,
hasData: hasData,
}
deps := fixtureDeps{
aclList: aclList,
initStorage: storage.(*treestorage.InMemoryTreeStorage),
connectionMap: map[string][]string{
"peer1": []string{"peer2"},
"peer2": []string{"peer1"},
},
treeBuilder: builder,
}
fx := newProtocolFixture(t, spaceId, deps)
// generating initial tree
initialRes := genChanges(changeCreator, params)
fx.run(t)
// adding some changes to store, but without updating heads
store := fx.handlers["peer1"].tree().Storage().(*treestorage.InMemoryTreeStorage)
oldHeads, _ := store.Heads()
store.AddRawChangesSetHeads(initialRes.changes, oldHeads)
// sending those changes to other peer
fx.handlers["peer2"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
NewHeads: initialRes.heads,
RawChanges: initialRes.changes,
})
time.Sleep(50 * time.Millisecond)
// here we want that the saved changes in storage should not affect the sync protocol
firstHeads := fx.handlers["peer1"].tree().Heads()
secondHeads := fx.handlers["peer2"].tree().Heads()
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
require.True(t, slice.UnsortedEquals(initialRes.heads, firstHeads))
}
func testTreeStorageHasExtraThreeParts(t *testing.T, levels, perLevel int, hasData bool, isSnapshot func() bool) {
treeId := "treeId"
spaceId := "spaceId"
keys, err := accountdata.NewRandom()
require.NoError(t, err)
aclList, err := list.NewTestDerivedAcl(spaceId, keys)
storage := createStorage(treeId, aclList)
changeCreator := objecttree.NewMockChangeCreator()
builder := objecttree.BuildTestableTree
if hasData {
builder = objecttree.BuildEmptyDataTestableTree
}
params := genParams{
prefix: "peer1",
aclId: aclList.Id(),
startIdx: 0,
levels: levels,
perLevel: perLevel,
snapshotId: treeId,
prevHeads: []string{treeId},
isSnapshot: isSnapshot,
hasData: hasData,
}
deps := fixtureDeps{
aclList: aclList,
initStorage: storage.(*treestorage.InMemoryTreeStorage),
connectionMap: map[string][]string{
"peer1": []string{"peer2"},
"peer2": []string{"peer1"},
},
treeBuilder: builder,
}
fx := newProtocolFixture(t, spaceId, deps)
// generating parts
firstPart := genChanges(changeCreator, params)
params.startIdx = levels
params.snapshotId = firstPart.snapshotId
params.prevHeads = firstPart.heads
secondPart := genChanges(changeCreator, params)
params.startIdx = levels * 2
params.snapshotId = secondPart.snapshotId
params.prevHeads = secondPart.heads
thirdPart := genChanges(changeCreator, params)
// adding part1 to first peer and saving part2 and part3 in its storage
res, _ := fx.handlers["peer1"].tree().AddRawChanges(context.Background(), objecttree.RawChangesPayload{
NewHeads: firstPart.heads,
RawChanges: firstPart.changes,
})
require.True(t, slice.UnsortedEquals(res.Heads, firstPart.heads))
store := fx.handlers["peer1"].tree().Storage().(*treestorage.InMemoryTreeStorage)
oldHeads, _ := store.Heads()
store.AddRawChangesSetHeads(secondPart.changes, oldHeads)
store.AddRawChangesSetHeads(thirdPart.changes, oldHeads)
var peer2Initial []*treechangeproto.RawTreeChangeWithId
peer2Initial = append(peer2Initial, firstPart.changes...)
peer2Initial = append(peer2Initial, secondPart.changes...)
// adding part1 and part2 to second peer
res, _ = fx.handlers["peer2"].tree().AddRawChanges(context.Background(), objecttree.RawChangesPayload{
NewHeads: secondPart.heads,
RawChanges: peer2Initial,
})
require.True(t, slice.UnsortedEquals(res.Heads, secondPart.heads))
fx.run(t)
// sending part3 changes to other peer
fx.handlers["peer2"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
NewHeads: thirdPart.heads,
RawChanges: thirdPart.changes,
})
time.Sleep(50 * time.Millisecond)
firstHeads := fx.handlers["peer1"].tree().Heads()
secondHeads := fx.handlers["peer2"].tree().Heads()
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
require.True(t, slice.UnsortedEquals(thirdPart.heads, firstHeads))
}
@@ -2,7 +2,7 @@ package synctree
import (
"errors"
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"sync"
)
@@ -2,9 +2,9 @@ package synctree
import (
"fmt"
"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"
"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"
)
type RequestFactory interface {
@@ -14,10 +14,8 @@ type RequestFactory interface {
CreateFullSyncResponse(t objecttree.ObjectTree, theirHeads, theirSnapshotPath []string) (*treechangeproto.TreeSyncMessage, error)
}
var sharedFactory = &requestFactory{}
func GetRequestFactory() RequestFactory {
return sharedFactory
func NewRequestFactory() RequestFactory {
return &requestFactory{}
}
type requestFactory struct{}
+42 -26
View File
@@ -1,57 +1,73 @@
//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 (
"context"
"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"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/commonspace/peermanager"
"github.com/anyproto/any-sync/commonspace/requestmanager"
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
"go.uber.org/zap"
)
type SyncClient interface {
RequestFactory
Broadcast(ctx context.Context, msg *treechangeproto.TreeSyncMessage) (err error)
SendWithReply(ctx context.Context, peerId string, msg *treechangeproto.TreeSyncMessage, replyId string) (err error)
Broadcast(msg *treechangeproto.TreeSyncMessage)
SendUpdate(peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (err error)
QueueRequest(peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (err error)
SendRequest(ctx context.Context, peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error)
}
type syncClient struct {
objectsync.MessagePool
RequestFactory
spaceId string
configuration nodeconf.Configuration
spaceId string
requestManager requestmanager.RequestManager
peerManager peermanager.PeerManager
}
func newSyncClient(
spaceId string,
pool objectsync.MessagePool,
factory RequestFactory,
configuration nodeconf.Configuration) SyncClient {
func NewSyncClient(spaceId string, requestManager requestmanager.RequestManager, peerManager peermanager.PeerManager) SyncClient {
return &syncClient{
MessagePool: pool,
RequestFactory: factory,
configuration: configuration,
RequestFactory: &requestFactory{},
spaceId: spaceId,
requestManager: requestManager,
peerManager: peerManager,
}
}
func (s *syncClient) Broadcast(ctx context.Context, msg *treechangeproto.TreeSyncMessage) (err error) {
objMsg, err := marshallTreeMessage(msg, s.spaceId, msg.RootChange.Id, "")
func (s *syncClient) Broadcast(msg *treechangeproto.TreeSyncMessage) {
objMsg, err := MarshallTreeMessage(msg, s.spaceId, msg.RootChange.Id, "")
if err != nil {
return
}
return s.MessagePool.Broadcast(ctx, objMsg)
err = s.peerManager.Broadcast(context.Background(), objMsg)
if err != nil {
log.Debug("broadcast error", zap.Error(err))
}
}
func (s *syncClient) SendWithReply(ctx context.Context, peerId string, msg *treechangeproto.TreeSyncMessage, replyId string) (err error) {
objMsg, err := marshallTreeMessage(msg, s.spaceId, msg.RootChange.Id, replyId)
func (s *syncClient) SendUpdate(peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (err error) {
objMsg, err := MarshallTreeMessage(msg, s.spaceId, objectId, "")
if err != nil {
return
}
return s.MessagePool.SendPeer(ctx, peerId, objMsg)
return s.peerManager.SendPeer(context.Background(), peerId, objMsg)
}
func marshallTreeMessage(message *treechangeproto.TreeSyncMessage, spaceId, objectId, replyId string) (objMsg *spacesyncproto.ObjectSyncMessage, err error) {
func (s *syncClient) SendRequest(ctx context.Context, peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error) {
objMsg, err := MarshallTreeMessage(msg, s.spaceId, objectId, "")
if err != nil {
return
}
return s.requestManager.SendRequest(ctx, peerId, objMsg)
}
func (s *syncClient) QueueRequest(peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (err error) {
objMsg, err := MarshallTreeMessage(msg, s.spaceId, objectId, "")
if err != nil {
return
}
return s.requestManager.QueueRequest(peerId, objMsg)
}
func MarshallTreeMessage(message *treechangeproto.TreeSyncMessage, spaceId, objectId, replyId string) (objMsg *spacesyncproto.ObjectSyncMessage, err error) {
payload, err := message.Marshal()
if err != nil {
return
Loaded 100 of 289 files, more files were not shown because too many files have changed in this diff. Show more