Compare commits
1045
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7394b4bd67 | ||
|
|
d48923fbdf | ||
|
|
461410a8f8 | ||
|
|
11b8609a9b | ||
|
|
67ca504f81 | ||
|
|
0ca41dbd47 | ||
|
|
36cb769379 | ||
|
|
590e8080b8 | ||
|
|
fc4bdb6000 | ||
|
|
a12f3fb0dd | ||
|
|
debbf448db | ||
|
|
847b2d40fd | ||
|
|
9855eb4484 | ||
|
|
4bc402f1f0 | ||
|
|
ba5ca3d6be | ||
|
|
7294a74de0 | ||
|
|
ce9fcb5b0a | ||
|
|
d26bce0dea | ||
|
|
0cb7ae1100 | ||
|
|
5fdfb6c8e4 | ||
|
|
235a365ade | ||
|
|
6efde25562 | ||
|
|
6676480e1d | ||
|
|
da0e4f148a | ||
|
|
f3c9c64bce | ||
|
|
75a44c4281 | ||
|
|
1ebe18296d | ||
|
|
4cb88b2bfb | ||
|
|
e9c6f95991 | ||
|
|
46de85a7f8 | ||
|
|
1d29aba45e | ||
|
|
6ec030942f | ||
|
|
cadd63d458 | ||
|
|
2be7753ed9 | ||
|
|
854913a22b | ||
|
|
bf44f1b24f | ||
|
|
4b330e8b2d | ||
|
|
b0f3fec939 | ||
|
|
cc70180db2 | ||
|
|
5082a10a18 | ||
|
|
6cbff3a930 | ||
|
|
9216896777 | ||
|
|
84283a0441 | ||
|
|
6eb08ca438 | ||
|
|
d85e318553 | ||
|
|
63c797d35b | ||
|
|
e684088ca4 | ||
|
|
f0c4ae3f5e | ||
|
|
9aef76be8f | ||
|
|
443ab3c24b | ||
|
|
474b81c8b6 | ||
|
|
a57fb52b63 | ||
|
|
e94e2bcf45 | ||
|
|
5c954d1bdd | ||
|
|
5d13fb6a6e | ||
|
|
e896a88d29 | ||
|
|
7979c48ca3 | ||
|
|
2894b8ef66 | ||
|
|
6f42dc2f7a | ||
|
|
25f90d5f5f | ||
|
|
4c71a9a1c4 | ||
|
|
e79d319925 | ||
|
|
ed16abcede | ||
|
|
3edda33db6 | ||
|
|
aeaad8ab35 | ||
|
|
34865d0e68 | ||
|
|
9acec773fa | ||
|
|
73201843e5 | ||
|
|
58f3c4ef8c | ||
|
|
99bad9ce9d | ||
|
|
fd65e61127 | ||
|
|
d2805a077d | ||
|
|
8fc1f03f54 | ||
|
|
678ec256bf | ||
|
|
125d8b4626 | ||
|
|
b4b1e0b340 | ||
|
|
84859a8796 | ||
|
|
3f0118eb0d | ||
|
|
adb800d56a | ||
|
|
19127c9c64 | ||
|
|
7348f39eff | ||
|
|
cc24e38081 | ||
|
|
d3c9fef7b4 | ||
|
|
f014209d75 | ||
|
|
813d6e339c | ||
|
|
5426e968d9 | ||
|
|
649e8fe538 | ||
|
|
73ee67c5c0 | ||
|
|
673925df84 | ||
|
|
e101389af0 | ||
|
|
f7cc1f4b5d | ||
|
|
b97721f729 | ||
|
|
803500ee34 | ||
|
|
1dc05faf58 | ||
|
|
eb8f760370 | ||
|
|
cae5578cc5 | ||
|
|
caa63463d1 | ||
|
|
022893dbc9 | ||
|
|
950b41f72b | ||
|
|
77ad931cc3 | ||
|
|
4d36015a31 | ||
|
|
9d95d57788 | ||
|
|
4178b3ed0e | ||
|
|
55878490e9 | ||
|
|
aab53579e0 | ||
|
|
c37e039360 | ||
|
|
752294e4ea | ||
|
|
8fdd7b5f3d | ||
|
|
d71a47692f | ||
|
|
031afe69bb | ||
|
|
8cae38c0ae | ||
|
|
c77b9620c6 | ||
|
|
0865ed5374 | ||
|
|
7d0507e650 | ||
|
|
5af3bc7b2d | ||
|
|
ee8aeabf05 | ||
|
|
e7faa2ee3c | ||
|
|
c843b27b19 | ||
|
|
295a88c8c6 | ||
|
|
72306e6a40 | ||
|
|
86ac8e0d75 | ||
|
|
9cee18c834 | ||
|
|
85997631ff | ||
|
|
73a919ff96 | ||
|
|
c8c3380a8f | ||
|
|
e9d1de7781 | ||
|
|
357f1c935d | ||
|
|
7258df1c2d | ||
|
|
13df75d0e0 | ||
|
|
20a4d85836 | ||
|
|
6af4238664 | ||
|
|
2c73e1947d | ||
|
|
cc8c9cb0a5 | ||
|
|
c66b570396 | ||
|
|
ad1bea2a6a | ||
|
|
cad467dede | ||
|
|
a4b3430d6e | ||
|
|
de28c1e3f8 | ||
|
|
7135b436ca | ||
|
|
bb3d1b1d7e | ||
|
|
254fcd5ea8 | ||
|
|
fb4455e16a | ||
|
|
19fd4e3a56 | ||
|
|
274a83a039 | ||
|
|
5cabb4fbc3 | ||
|
|
4b19d06415 | ||
|
|
f472fd83c4 | ||
|
|
178159b0c5 | ||
|
|
a8ef63eca0 | ||
|
|
13f30aa60f | ||
|
|
f4bc677d02 | ||
|
|
6d70bc3786 | ||
|
|
b62ad57988 | ||
|
|
f074a1fa53 | ||
|
|
6e1c5aab91 | ||
|
|
9679bb3bf3 | ||
|
|
eec2b842bc | ||
|
|
2225aca40f | ||
|
|
3d017d1e0c | ||
|
|
503c4f339f | ||
|
|
cdfb88d6a8 | ||
|
|
58cc732f3a | ||
|
|
2d977ed535 | ||
|
|
0243ec0fca | ||
|
|
ee0f62410f | ||
|
|
3809b20d50 | ||
|
|
067bf797dd | ||
|
|
98931433a5 | ||
|
|
d9868c55c2 | ||
|
|
fd5bd0b099 | ||
|
|
5baa6dc856 | ||
|
|
2273c94cf4 | ||
|
|
d9cf1feaee | ||
|
|
cc48cf88f3 | ||
|
|
33c9a25665 | ||
|
|
add25062f3 | ||
|
|
18240ea5ef | ||
|
|
d20f5fc2b3 | ||
|
|
afae514535 | ||
|
|
fa24020ab1 | ||
|
|
f34bc4061b | ||
|
|
8f4a39ebe9 | ||
|
|
da1a69932a | ||
|
|
207553f638 | ||
|
|
af2026fac8 | ||
|
|
2f21006457 | ||
|
|
eed0458794 | ||
|
|
42200fddcd | ||
|
|
6ce4c67ac1 | ||
|
|
a523641dff | ||
|
|
b3b1afa307 | ||
|
|
0a1dc26a95 | ||
|
|
afad73fe8b | ||
|
|
935a95c247 | ||
|
|
fdb1a59524 | ||
|
|
22a597389d | ||
|
|
49248b02b3 | ||
|
|
feba2c67d4 | ||
|
|
305c509fc6 | ||
|
|
ed1b597fc5 | ||
|
|
2691da66f7 | ||
|
|
417b203b09 | ||
|
|
0746526170 | ||
|
|
829159690f | ||
|
|
41362cdde6 | ||
|
|
5f5aefef53 | ||
|
|
accf68fc60 | ||
|
|
06b6ca22c0 | ||
|
|
5ec53522fe | ||
|
|
e568f899aa | ||
|
|
1a01f361ad | ||
|
|
86dcc83461 | ||
|
|
5de8af82bb | ||
|
|
25b0695d3e | ||
|
|
a2efcafb3e | ||
|
|
6b2667d4dc | ||
|
|
1fbf87995b | ||
|
|
981378c2e3 | ||
|
|
67ca417cb8 | ||
|
|
df9a5f11ae | ||
|
|
ec0d8a0c07 | ||
|
|
bb86e5b7e5 | ||
|
|
a97efd2a5b | ||
|
|
01e170ba69 | ||
|
|
7d39332a79 | ||
|
|
19930e6252 | ||
|
|
ea74562290 | ||
|
|
74f39239c6 | ||
|
|
067ad8cf16 | ||
|
|
0c79741216 | ||
|
|
94f28430a3 | ||
|
|
83476a9e84 | ||
|
|
43a35f4878 | ||
|
|
ca2ea8cef9 | ||
|
|
63ba0a9fab | ||
|
|
4fb34fcb7c | ||
|
|
9836caf2cb | ||
|
|
65496d247f | ||
|
|
07c6e8000c | ||
|
|
4687b1f805 | ||
|
|
7adae54f87 | ||
|
|
f5baccaf08 | ||
|
|
589f885b63 | ||
|
|
52278f02f4 | ||
|
|
0d40838196 | ||
|
|
3693218fc5 | ||
|
|
d4c19f790d | ||
|
|
bc7a1754ad | ||
|
|
f95374bb3e | ||
|
|
848556200b | ||
|
|
e043c84cae | ||
|
|
0314ea1103 | ||
|
|
6a1c3d27f8 | ||
|
|
d22d8f4f25 | ||
|
|
61cd658ab0 | ||
|
|
998f631ee8 | ||
|
|
bee7146a60 | ||
|
|
a497e129c4 | ||
|
|
e1f6dad499 | ||
|
|
58f01da459 | ||
|
|
3895dc8e14 | ||
|
|
3ea11abb56 | ||
|
|
6524762c09 | ||
|
|
70682ff397 | ||
|
|
b37dec42a2 | ||
|
|
3f48831e8b | ||
|
|
cda401356d | ||
|
|
897c50e8e6 | ||
|
|
5218eea8ad | ||
|
|
6e5d044251 | ||
|
|
fcb936bca9 | ||
|
|
1837d89ef9 | ||
|
|
9979a820d3 | ||
|
|
1ed8f162ae | ||
|
|
31b3fed274 | ||
|
|
3758e9cdf3 | ||
|
|
eae4f0a57d | ||
|
|
00c344112e | ||
|
|
6c17d380b7 | ||
|
|
e6352f74ad | ||
|
|
3f95941045 | ||
|
|
c42c4415bf | ||
|
|
cd8dd7a869 | ||
|
|
fef3c7f937 | ||
|
|
4212138546 | ||
|
|
61859bbb09 | ||
|
|
5906091051 | ||
|
|
51a956fd84 | ||
|
|
cd5a4abc87 | ||
|
|
2dd2b309df | ||
|
|
cf80cdbe7d | ||
|
|
74b0269230 | ||
|
|
679b068dfa | ||
|
|
aea36229fb | ||
|
|
67d97c26ec | ||
|
|
0c200ba323 | ||
|
|
be253bb01b | ||
|
|
8d25922f27 | ||
|
|
4ff0d9d42a | ||
|
|
24cc345fbb | ||
|
|
0ababcf913 | ||
|
|
c5b47abf8a | ||
|
|
7960297ea2 | ||
|
|
3cd292db5b | ||
|
|
c708fbd984 | ||
|
|
f5f960bd81 | ||
|
|
2a76b81a32 | ||
|
|
43e4c121ee | ||
|
|
6765150ba9 | ||
|
|
ab14cc9b6d | ||
|
|
64d9689be5 | ||
|
|
904224447f | ||
|
|
47be32c804 | ||
|
|
98bbdc2e4e | ||
|
|
7da3553db3 | ||
|
|
c37c7ba874 | ||
|
|
def3d7cdd5 | ||
|
|
1565f7e509 | ||
|
|
75d503b94c | ||
|
|
63833eb650 | ||
|
|
7cce8baca6 | ||
|
|
59e18ca68c | ||
|
|
030a7d61ba | ||
|
|
40ee06560e | ||
|
|
4ce9ffdf5a | ||
|
|
b3e63984bb | ||
|
|
96f399293d | ||
|
|
49b738fb3c | ||
|
|
5b93739487 | ||
|
|
38a74fd195 | ||
|
|
ea9d101622 | ||
|
|
ea80f4591b | ||
|
|
f2d729022d | ||
|
|
94d33f2c4a | ||
|
|
20768266ca | ||
|
|
1db0e731e0 | ||
|
|
4d356318f0 | ||
|
|
cd82b41cf1 | ||
|
|
3b837b4b6b | ||
|
|
189922e534 | ||
|
|
4cde54bdb3 | ||
|
|
00dc0bcca6 | ||
|
|
047bd1c379 | ||
|
|
ce0a60df67 | ||
|
|
95b7927a4c | ||
|
|
e6d15c6a72 | ||
|
|
0175ffeb3f | ||
|
|
92f22d04b0 | ||
|
|
55ad2a536b | ||
|
|
18451bd4fe | ||
|
|
b34136ce06 | ||
|
|
0f89b5a4f5 | ||
|
|
004c8b7f38 | ||
|
|
4c00dba1cf | ||
|
|
2fe9f8c295 | ||
|
|
e8a7ad7476 | ||
|
|
0601685040 | ||
|
|
adec5fa746 | ||
|
|
f6d3a85a7f | ||
|
|
0eaf9094a1 | ||
|
|
20320f81c3 | ||
|
|
cbee800509 | ||
|
|
d9baeab63e | ||
|
|
7ef55b3c46 | ||
|
|
6b5b3d2b6b | ||
|
|
2d2ad9ee43 | ||
|
|
b2140f3c30 | ||
|
|
d3108b5e6c | ||
|
|
e35c5eb5c7 | ||
|
|
a4d26ab82d | ||
|
|
d3362ba85e | ||
|
|
9bb42a2906 | ||
|
|
9c696ad96a | ||
|
|
f2ff10e3f7 | ||
|
|
0f2a0eec8e | ||
|
|
9289bb424a | ||
|
|
859323c5ca | ||
|
|
61b0a24b28 | ||
|
|
8624b672e2 | ||
|
|
163c0e4058 | ||
|
|
27b1c948e9 | ||
|
|
ea48365d2f | ||
|
|
a193906d94 | ||
|
|
fad981daee | ||
|
|
2da9f84907 | ||
|
|
9ba0620cab | ||
|
|
cc2a98bbad | ||
|
|
75c660916d | ||
|
|
d2a19d21fe | ||
|
|
6d2178dfc9 | ||
|
|
3fcbe3aba9 | ||
|
|
3630744086 | ||
|
|
f4c6679b0e | ||
|
|
aff2ecbca0 | ||
|
|
38f831ab6b | ||
|
|
8ff7cc9e16 | ||
|
|
2ef1970479 | ||
|
|
4cdbecd705 | ||
|
|
0b977c2e1c | ||
|
|
25a51b2cc8 | ||
|
|
1e9960752f | ||
|
|
22ee12e28c | ||
|
|
86aadf9d6c | ||
|
|
c8f6e39199 | ||
|
|
48e55d4827 | ||
|
|
73762e460a | ||
|
|
d04d1f3a3b | ||
|
|
9dc3c807fb | ||
|
|
78067eaa4c | ||
|
|
b2578a19ba | ||
|
|
06bb0f7496 | ||
|
|
e1565f60d6 | ||
|
|
c6910bc2fe | ||
|
|
dccb5a9826 | ||
|
|
8f1cba3770 | ||
|
|
44ffc39729 | ||
|
|
d7a888c1fd | ||
|
|
b78cf2c710 | ||
|
|
92bb1ce576 | ||
|
|
bf56f338f2 | ||
|
|
82b398ff7f | ||
|
|
6cb696d508 | ||
|
|
dffb418e60 | ||
|
|
348b94a554 | ||
|
|
60b2c99660 | ||
|
|
ddd20ae5b5 | ||
|
|
ed615379c4 | ||
|
|
09aee68bd7 | ||
|
|
7174e0e081 | ||
|
|
ebe05db662 | ||
|
|
64e5479ca0 | ||
|
|
7fdd4f215e | ||
|
|
79d81662ce | ||
|
|
330c97ab30 | ||
|
|
c1bf0e12cc | ||
|
|
a5f01265d6 | ||
|
|
9b9c278718 | ||
|
|
e7ee5dd864 | ||
|
|
d466d40cf0 | ||
|
|
0acc14144a | ||
|
|
41f66edfb9 | ||
|
|
9c41451986 | ||
|
|
fb4e2b1fc9 | ||
|
|
ea5856c4b0 | ||
|
|
cf464ef25f | ||
|
|
bded394761 | ||
|
|
ef1155743e | ||
|
|
4ff4c5950d | ||
|
|
8e1053a116 | ||
|
|
576cef2d53 | ||
|
|
5e23680b36 | ||
|
|
4e7c3840ac | ||
|
|
265b64b6f0 | ||
|
|
3bd8c92f51 | ||
|
|
a51f26c785 | ||
|
|
af753ec392 | ||
|
|
8de24af4b9 | ||
|
|
790be1b02b | ||
|
|
a093e1c9f0 | ||
|
|
3381a84dec | ||
|
|
42913d3d18 | ||
|
|
b84dfc595f | ||
|
|
b6be4e8975 | ||
|
|
25a52f41b8 | ||
|
|
18bc3314fe | ||
|
|
4c4abc9266 | ||
|
|
8cf6ad560e | ||
|
|
059f0ca636 | ||
|
|
4cb79eeab9 | ||
|
|
9f0c7f4711 | ||
|
|
cfbae15999 | ||
|
|
a7a54bcc8b | ||
|
|
1eed7e5050 | ||
|
|
d21dd91a77 | ||
|
|
c792ff1fcb | ||
|
|
ec96ecafcc | ||
|
|
b875c504a0 | ||
|
|
1bb707f0a2 | ||
|
|
28dd15e07e | ||
|
|
0fb8ec2b7c | ||
|
|
ca1ebaaf14 | ||
|
|
7f165dd6f8 | ||
|
|
abe4603639 | ||
|
|
da61fec0a0 | ||
|
|
39bffdab9c | ||
|
|
c12bb0f081 | ||
|
|
96896d3060 | ||
|
|
d158b98b35 | ||
|
|
57eb691b53 | ||
|
|
fe8f8c43f5 | ||
|
|
39f79e03ce | ||
|
|
a9c564777a | ||
|
|
876f94ef4e | ||
|
|
3bc23edfd3 | ||
|
|
88cc804d01 | ||
|
|
993403bf76 | ||
|
|
35e58ae20f | ||
|
|
27b6f754b5 | ||
|
|
4186e30912 | ||
|
|
9205f414cc | ||
|
|
d986290db8 | ||
|
|
dcae5da0ec | ||
|
|
bcc0aa8fd6 | ||
|
|
8c36953790 | ||
|
|
18bc40a531 | ||
|
|
da74f45d46 | ||
|
|
527d104ba7 | ||
|
|
7e41284cc8 | ||
|
|
071d85cd22 | ||
|
|
d0ac577686 | ||
|
|
0dd992b7a6 | ||
|
|
2f95c66360 | ||
|
|
c7254ed8be | ||
|
|
bb25dd0ea1 | ||
|
|
c565071dad | ||
|
|
b96654dd14 | ||
|
|
048406edfd | ||
|
|
62c4e80c36 | ||
|
|
b3194da23c | ||
|
|
207e2dce52 | ||
|
|
dfd2b0e8b9 | ||
|
|
248a112236 | ||
|
|
dbfc62eef5 | ||
|
|
b8b4f937d4 | ||
|
|
f79d7f8eb4 | ||
|
|
c88b31ca2f | ||
|
|
4c5cb61802 | ||
|
|
90f0d0bb37 | ||
|
|
71cfefe6fa | ||
|
|
4aa8c9ac09 | ||
|
|
6bb542ca09 | ||
|
|
f9c65bf8e1 | ||
|
|
411a67c31f | ||
|
|
02e92a0db0 | ||
|
|
9b59e6029d | ||
|
|
ac84febce9 | ||
|
|
e57ef72be1 | ||
|
|
2d605ca694 | ||
|
|
c64f732878 | ||
|
|
40f8fb503a | ||
|
|
987bd0154f | ||
|
|
8680c24329 | ||
|
|
1a2ceb6e2b | ||
|
|
8d8aff2680 | ||
|
|
397252d97a | ||
|
|
58389432d7 | ||
|
|
a55306aa27 | ||
|
|
e44bcfedaf | ||
|
|
33a2b5cc84 | ||
|
|
a855f02f63 | ||
|
|
8eafaa266d | ||
|
|
b5dc56d72c | ||
|
|
a103c960a4 | ||
|
|
98b8584efc | ||
|
|
fa076ee83f | ||
|
|
d4b90e5202 | ||
|
|
84b21c62c5 | ||
|
|
caec31dd7f | ||
|
|
f86337ffc8 | ||
|
|
70163c3aaf | ||
|
|
5b98550349 | ||
|
|
0a24886ce6 | ||
|
|
9da4e0e7e9 | ||
|
|
d35d5c9437 | ||
|
|
ac018be8b1 | ||
|
|
6fb720e0ea | ||
|
|
e736ff313f | ||
|
|
f696d774bb | ||
|
|
50dcc735c7 | ||
|
|
3c30f72cd5 | ||
|
|
874d5bbca0 | ||
|
|
acf21c7d54 | ||
|
|
b7d4c27920 | ||
|
|
555ff804fe | ||
|
|
ec900d6e9d | ||
|
|
0b27fe7642 | ||
|
|
ad8055635d | ||
|
|
f4a27fb1d3 | ||
|
|
b0b75ac8ea | ||
|
|
108900e88a | ||
|
|
439182cdfa | ||
|
|
9b0aba1339 | ||
|
|
b3024740d0 | ||
|
|
cfd2ea8ece | ||
|
|
d0adbf5026 | ||
|
|
9d6e0886f4 | ||
|
|
f1d1a26b64 | ||
|
|
15ccf29f0b | ||
|
|
ad87d5e545 | ||
|
|
322a8d144a | ||
|
|
4aa9ce1a27 | ||
|
|
364ec32a39 | ||
|
|
6444042a02 | ||
|
|
5df4f13938 | ||
|
|
4eba413631 | ||
|
|
060c14792c | ||
|
|
a43d25841d | ||
|
|
da0398c103 | ||
|
|
3b320087f3 | ||
|
|
0c85336e82 | ||
|
|
367bd1978d | ||
|
|
8a6a5a6bc1 | ||
|
|
c064f89988 | ||
|
|
c36fe14272 | ||
|
|
8a19ea8dcb | ||
|
|
d0a4b05706 | ||
|
|
38e055ed12 | ||
|
|
0924413056 | ||
|
|
09acec0d11 | ||
|
|
fa8ac48c2a | ||
|
|
da70f7b78c | ||
|
|
e0c1e6f19d | ||
|
|
b71360ec01 | ||
|
|
3807d8e6ef | ||
|
|
d7cce3c9fb | ||
|
|
5eaa3bfe44 | ||
|
|
eb078d311a | ||
|
|
462eb1cc7a | ||
|
|
8b703efb55 | ||
|
|
a08c3873b4 | ||
|
|
2ffd735e25 | ||
|
|
761772f09a | ||
|
|
de12b35216 | ||
|
|
6f8a21e547 | ||
|
|
50e3d70f70 | ||
|
|
f9a064e9aa | ||
|
|
a8507d8120 | ||
|
|
acf5b95646 | ||
|
|
cd197dc7d7 | ||
|
|
016543db40 | ||
|
|
6ed6142129 | ||
|
|
cdfee1232e | ||
|
|
25191a2443 | ||
|
|
339dd2b006 | ||
|
|
eb7a6fb9dc | ||
|
|
8b86ad2918 | ||
|
|
3d624adc09 | ||
|
|
4062a76e21 | ||
|
|
9ae5710854 | ||
|
|
494b7552ce | ||
|
|
aaecd2ae6d | ||
|
|
1e4a25c15d | ||
|
|
ea9a51ec02 | ||
|
|
4593637e23 | ||
|
|
bdae3a76d0 | ||
|
|
a370efda87 | ||
|
|
bab107ebf6 | ||
|
|
ea6dd0dc99 | ||
|
|
20b9a36604 | ||
|
|
b3b50aa8ab | ||
|
|
f749e967f1 | ||
|
|
75b5193dc4 | ||
|
|
6bc8124413 | ||
|
|
06d68e25ac | ||
|
|
19bc9a0c93 | ||
|
|
4a3f20b236 | ||
|
|
82400407b9 | ||
|
|
d49384b98b | ||
|
|
3c1072db4a | ||
|
|
41fde6f1ad | ||
|
|
620731b464 | ||
|
|
558cecea76 | ||
|
|
ff81bf1fac | ||
|
|
71717a5dbb | ||
|
|
9155a3cbf2 | ||
|
|
f98d629a78 | ||
|
|
55e04cd2cd | ||
|
|
f6d8183001 | ||
|
|
b9f4ddd480 | ||
|
|
0d9360490d | ||
|
|
8854bbbe95 | ||
|
|
b617cdc690 | ||
|
|
62d26fbafd | ||
|
|
6c38474c40 | ||
|
|
fed41ba3aa | ||
|
|
181d74a139 | ||
|
|
72d83ca171 | ||
|
|
020b1f1d71 | ||
|
|
11e57a43f7 | ||
|
|
f9647c0691 | ||
|
|
92bdf7d57b | ||
|
|
d5ef4f9313 | ||
|
|
925005a9de | ||
|
|
354ee3b6c7 | ||
|
|
9f080b4533 | ||
|
|
16b7181ebc | ||
|
|
7c1d449743 | ||
|
|
9bbb1a02e5 | ||
|
|
37035bfacd | ||
|
|
434f149e04 | ||
|
|
25f30cacc2 | ||
|
|
8f862fd1a1 | ||
|
|
5904815908 | ||
|
|
36b80c25d4 | ||
|
|
994ffe3d5f | ||
|
|
08b8ecba32 | ||
|
|
675885d875 | ||
|
|
f0b8e5ab64 | ||
|
|
9a202207b4 | ||
|
|
f4027b433d | ||
|
|
ab4fee8229 | ||
|
|
1920a190bc | ||
|
|
b9db1881ca | ||
|
|
9c1c1e609d | ||
|
|
7a2d366bdd | ||
|
|
6497e461bd | ||
|
|
6a6b750b4e | ||
|
|
7b5c51a198 | ||
|
|
600ae38c0f | ||
|
|
f5b9275192 | ||
|
|
c33e944ec4 | ||
|
|
8263bbfde2 | ||
|
|
305cd2d795 | ||
|
|
93bf6d4dc4 | ||
|
|
3546fabdfa | ||
|
|
f507e4cabd | ||
|
|
9992b1fa7e | ||
|
|
e419e59f1d | ||
|
|
a3d5a56077 | ||
|
|
7bd593f0e5 | ||
|
|
63d353a078 | ||
|
|
bf7a8b0854 | ||
|
|
09f28cf829 | ||
|
|
58f0cf2916 | ||
|
|
d6a45cced0 | ||
|
|
8cc44c4a79 | ||
|
|
5b93c78ebd | ||
|
|
2085e3ae40 | ||
|
|
3886436841 | ||
|
|
40ce73db68 | ||
|
|
27b3213bac | ||
|
|
829cfb3054 | ||
|
|
3a3d21cd01 | ||
|
|
aa47d8ecf8 | ||
|
|
e8353897eb | ||
|
|
58f9e623a4 | ||
|
|
d871c9b061 | ||
|
|
4da28bcd86 | ||
|
|
f21f05585c | ||
|
|
a606e5f79e | ||
|
|
15bf4c7018 | ||
|
|
efa3acc368 | ||
|
|
64e0ba3b77 | ||
|
|
c9ded8956c | ||
|
|
6cb6b91bd4 | ||
|
|
6470454221 | ||
|
|
a3814f644c | ||
|
|
a89d6dc468 | ||
|
|
ad8f90d12e | ||
|
|
95cb91ffad | ||
|
|
04fd072ae9 | ||
|
|
6201951e3b | ||
|
|
9f1735110c | ||
|
|
440d63ade0 | ||
|
|
09e3eb3e54 | ||
|
|
22074a5fbd | ||
|
|
5a17320c44 | ||
|
|
7548082355 | ||
|
|
36181b0ef8 | ||
|
|
d478d973fd | ||
|
|
d80986e6d0 | ||
|
|
f929bf280f | ||
|
|
0013f64c61 | ||
|
|
a2d2c61f1e | ||
|
|
700e04cc14 | ||
|
|
baa275b05b | ||
|
|
e6d6cae35c | ||
|
|
3facea9a06 | ||
|
|
a1aacbffed | ||
|
|
401402021a | ||
|
|
e7602a164a | ||
|
|
980357509d | ||
|
|
ad021e5758 | ||
|
|
5e2a6778f0 | ||
|
|
296d9a86d0 | ||
|
|
85d83ed9ba | ||
|
|
d2b64fb272 | ||
|
|
8970730ec9 | ||
|
|
bc9ffc1a79 | ||
|
|
46f562e91d | ||
|
|
3695a29b32 | ||
|
|
03eb8834c1 | ||
|
|
79f5fc6908 | ||
|
|
526ddf12d5 | ||
|
|
d3acc71d95 | ||
|
|
3438770f30 | ||
|
|
46d5d464b6 | ||
|
|
01438d630d | ||
|
|
c3d8273caa | ||
|
|
2999f2dcdd | ||
|
|
1676d02d80 | ||
|
|
65e7d0a2cd | ||
|
|
afc9141a7c | ||
|
|
fd8ba951dc | ||
|
|
6374ef8d1b | ||
|
|
9f16e7c272 | ||
|
|
b59cc28dc5 | ||
|
|
9930db8dc7 | ||
|
|
9f902bfbe9 | ||
|
|
5729a32959 | ||
|
|
86616b6936 | ||
|
|
d7a2a12129 | ||
|
|
274573df28 | ||
|
|
20234269e9 | ||
|
|
edc9a14ef5 | ||
|
|
7e6c72a198 | ||
|
|
48ae45550d | ||
|
|
aaaab9deca | ||
|
|
53d10dc597 | ||
|
|
86ef6d2dd6 | ||
|
|
beccba6d95 | ||
|
|
c01441460b | ||
|
|
68ed132e61 | ||
|
|
b9c9424103 | ||
|
|
82eda9a092 | ||
|
|
8e6237e885 | ||
|
|
e99650615d | ||
|
|
58b4dc760e | ||
|
|
131f4a6968 | ||
|
|
533880e9f1 | ||
|
|
9be23b4ff7 | ||
|
|
53af9c6c1e | ||
|
|
51af8c223d | ||
|
|
dbc951a9d0 | ||
|
|
ac3a50a31c | ||
|
|
c111257f63 | ||
|
|
67f0b82ae4 | ||
|
|
0b97aaa302 | ||
|
|
227e7d47ab | ||
|
|
9d73e1d5c1 | ||
|
|
632d73698f | ||
|
|
674e784318 | ||
|
|
d77d8dbdd1 | ||
|
|
58812a568f | ||
|
|
e768e83507 | ||
|
|
dc69e93064 | ||
|
|
832d0d5bfa | ||
|
|
04184b1582 | ||
|
|
70dabf70dd | ||
|
|
f420ac144c | ||
|
|
1bc9242644 | ||
|
|
eb36eed4c0 | ||
|
|
91aacdd955 | ||
|
|
a48e8f357a | ||
|
|
34ef43786c | ||
|
|
fbdf820734 | ||
|
|
aecb95bc92 | ||
|
|
f900cbff0b | ||
|
|
206b695e62 | ||
|
|
c94a0c94f3 | ||
|
|
5599277bf2 | ||
|
|
7a62b348cd | ||
|
|
a907cde399 | ||
|
|
77bbb64feb | ||
|
|
b5aa0b135f | ||
|
|
9976a84d42 | ||
|
|
018406d25c | ||
|
|
639cc302d2 | ||
|
|
3cbdf798a2 | ||
|
|
74ebcc5616 | ||
|
|
0685f401f3 | ||
|
|
235c179d60 | ||
|
|
caa725dc20 | ||
|
|
feff4ad890 | ||
|
|
bee067ad76 | ||
|
|
2e47b23827 | ||
|
|
88bf93a2cc | ||
|
|
3f284889ac | ||
|
|
698acad3bd | ||
|
|
20f462e603 | ||
|
|
11cb1e1f31 | ||
|
|
5b1228e14d | ||
|
|
de1a5baaca | ||
|
|
3ca8814e46 | ||
|
|
7afde45ac6 | ||
|
|
357e889dad | ||
|
|
024f316b3a | ||
|
|
2e1c2cbc6e | ||
|
|
88832596da | ||
|
|
1375854237 | ||
|
|
c7fdd81691 | ||
|
|
276d935cee | ||
|
|
9905b2608b | ||
|
|
06f80f0646 | ||
|
|
cde907e15a | ||
|
|
94c95b44ec | ||
|
|
b8e3ec8b7b | ||
|
|
9a1dc89a99 | ||
|
|
90cee00319 | ||
|
|
1ad4eba17e | ||
|
|
b87c38e232 | ||
|
|
b602c08603 | ||
|
|
b18514d9fe | ||
|
|
e2dffad962 | ||
|
|
3957fa3be6 | ||
|
|
535358b057 | ||
|
|
9f4dbf0002 | ||
|
|
10a5677241 | ||
|
|
f2cedfd08e | ||
|
|
13236d8ebc | ||
|
|
d19f0534e4 | ||
|
|
f5d79a5469 | ||
|
|
88c58237c1 | ||
|
|
03d8c187ed | ||
|
|
d3ed644b9c | ||
|
|
6bfda6743e | ||
|
|
8cf5acd183 | ||
|
|
b0432487cc | ||
|
|
e5fe1b5a92 | ||
|
|
1f403291cc | ||
|
|
edaad85990 | ||
|
|
f4aeecae84 | ||
|
|
9142f28d55 | ||
|
|
23deea0c37 | ||
|
|
cc2d6194a9 | ||
|
|
340528f5af | ||
|
|
66067a3506 | ||
|
|
eed3c518cd | ||
|
|
88b0e8cd7e | ||
|
|
028cb99586 | ||
|
|
37eacb5d61 | ||
|
|
e7956901de | ||
|
|
0e3d7cc8ca | ||
|
|
103c8a4aef | ||
|
|
b7190b6a74 | ||
|
|
ddf976e240 | ||
|
|
d23cc0a239 | ||
|
|
ff5a0d6a45 | ||
|
|
6a1e7d9a20 | ||
|
|
7eb965977a | ||
|
|
48627c0901 | ||
|
|
fbaf96eb38 | ||
|
|
31517db9bf | ||
|
|
d0ecf74836 | ||
|
|
89fb94420a | ||
|
|
3e81762f27 | ||
|
|
06a362225f | ||
|
|
62c5d8e3b9 | ||
|
|
4724fab97e | ||
|
|
b7e94f2b52 | ||
|
|
ad99bbb7e8 | ||
|
|
7a1ddf9c83 | ||
|
|
cc134da1ed | ||
|
|
e6be9ea84e | ||
|
|
718df194fe | ||
|
|
26eb52adf1 | ||
|
|
f5ec49b375 | ||
|
|
9aaa80dcc1 | ||
|
|
3463b8d374 | ||
|
|
16b3787258 | ||
|
|
269f907d1d | ||
|
|
5f2144db0f | ||
|
|
693c159360 | ||
|
|
7a6c42df62 | ||
|
|
9f984054b6 | ||
|
|
1400e57cf7 | ||
|
|
7f4b18104f | ||
|
|
d9452509b5 | ||
|
|
d120da8000 | ||
|
|
bc5ffe1828 | ||
|
|
5211267cd2 | ||
|
|
67e0956042 | ||
|
|
0467933ae9 | ||
|
|
48af2866d2 | ||
|
|
d3922d812a | ||
|
|
2893c4b4ad | ||
|
|
b8dcac03cc | ||
|
|
5f8dc32ed6 | ||
|
|
293ada96c8 | ||
|
|
6c4c4f1e9d | ||
|
|
cbfaf7fd91 | ||
|
|
abc6f68062 | ||
|
|
1331123fb4 | ||
|
|
5f92bc3d8d | ||
|
|
c8c6b63cd2 | ||
|
|
8552b676cb | ||
|
|
fe90278b8e | ||
|
|
37e6722ffb | ||
|
|
7b27fca2ad | ||
|
|
f9919bd72c | ||
|
|
3a72587a9e | ||
|
|
71d88cdcba | ||
|
|
d439b804fe | ||
|
|
84c2ee1e1c | ||
|
|
fecae2228d | ||
|
|
2580b7693c | ||
|
|
a62e713219 | ||
|
|
410780d051 | ||
|
|
bb910e20ac | ||
|
|
31a735ca25 | ||
|
|
3d069dbb1c | ||
|
|
5b3f594b2b | ||
|
|
3643719161 | ||
|
|
d743317069 | ||
|
|
f274181270 | ||
|
|
c90af47f02 | ||
|
|
7496c79ae5 | ||
|
|
11d3d93693 | ||
|
|
277d40761b | ||
|
|
27ec72cdb9 | ||
|
|
e17e8e15c7 | ||
|
|
4b6ec6f310 | ||
|
|
04c55cd602 | ||
|
|
c242ef49a6 | ||
|
|
4502ca7bf4 | ||
|
|
9c72f05fbd | ||
|
|
1fb82a6d7f | ||
|
|
9242f78b88 | ||
|
|
5b9d403b7d | ||
|
|
cc802ebc69 | ||
|
|
0129c97c83 | ||
|
|
fc01b705a5 | ||
|
|
126d080c63 | ||
|
|
d5614eb5ee | ||
|
|
c07b26d568 | ||
|
|
11fcb088e5 | ||
|
|
f889aa1b71 | ||
|
|
ee123efd7d | ||
|
|
94f53aabe1 | ||
|
|
9fe502057e | ||
|
|
17c556607a | ||
|
|
a18938d0b1 | ||
|
|
d08fb78357 | ||
|
|
906d2b6614 | ||
|
|
a09e7ca1cb | ||
|
|
3ad690d624 | ||
|
|
db06096169 | ||
|
|
99d67d531b | ||
|
|
4e8585bf9a | ||
|
|
376e5ba119 | ||
|
|
3395f35b59 | ||
|
|
102b33c3dc | ||
|
|
aa4222588a | ||
|
|
15deb66dff | ||
|
|
4a5da29cfe | ||
|
|
18963546ef | ||
|
|
3404626dd6 | ||
|
|
e970fa1d4d | ||
|
|
e5b3b7a3c0 | ||
|
|
64eb6581e3 | ||
|
|
dc9f6f3917 | ||
|
|
3c2cd5d936 | ||
|
|
6e98744aab | ||
|
|
4beb0d2902 | ||
|
|
9c62ec1d2c |
No files matched your search
@@ -10,5 +10,5 @@ updates:
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
ignore:
|
||||
- dependency-name: "github.com/anyproto/go-chash"
|
||||
- dependency-name: "github.com/anytypeio/go-chash"
|
||||
|
||||
@@ -7,7 +7,7 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GOPRIVATE: github.com/anyproto
|
||||
GOPRIVATE: github.com/anytypeio
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v3
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Any Association
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,5 +1,5 @@
|
||||
.PHONY: proto test deps
|
||||
export GOPRIVATE=github.com/anyproto
|
||||
export GOPRIVATE=github.com/anytypeio
|
||||
export PATH:=deps:$(PATH)
|
||||
|
||||
proto:
|
||||
@@ -8,8 +8,8 @@ proto:
|
||||
@$(eval P_ACL_RECORDS_PATH_PB := commonspace/object/acl/aclrecordproto)
|
||||
@$(eval P_TREE_CHANGES_PATH_PB := commonspace/object/tree/treechangeproto)
|
||||
@$(eval P_CRYPTO_PATH_PB := util/crypto/cryptoproto)
|
||||
@$(eval P_ACL_RECORDS := M$(P_ACL_RECORDS_PATH_PB)/protos/aclrecord.proto=github.com/anyproto/any-sync/$(P_ACL_RECORDS_PATH_PB))
|
||||
@$(eval P_TREE_CHANGES := M$(P_TREE_CHANGES_PATH_PB)/protos/treechange.proto=github.com/anyproto/any-sync/$(P_TREE_CHANGES_PATH_PB))
|
||||
@$(eval P_ACL_RECORDS := M$(P_ACL_RECORDS_PATH_PB)/protos/aclrecord.proto=github.com/anytypeio/any-sync/$(P_ACL_RECORDS_PATH_PB))
|
||||
@$(eval P_TREE_CHANGES := M$(P_TREE_CHANGES_PATH_PB)/protos/treechange.proto=github.com/anytypeio/any-sync/$(P_TREE_CHANGES_PATH_PB))
|
||||
|
||||
protoc --gogofaster_out=:. $(P_ACL_RECORDS_PATH_PB)/protos/*.proto
|
||||
protoc --gogofaster_out=:. $(P_TREE_CHANGES_PATH_PB)/protos/*.proto
|
||||
@@ -20,7 +20,6 @@ proto:
|
||||
protoc --gogofaster_out=$(PKGMAP):. --go-drpc_out=protolib=github.com/gogo/protobuf:. net/streampool/testservice/protos/*.proto
|
||||
protoc --gogofaster_out=:. 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
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
//go:generate mockgen -destination mock_accountservice/mock_accountservice.go github.com/anyproto/any-sync/accountservice Service
|
||||
//go:generate mockgen -destination mock_accountservice/mock_accountservice.go github.com/anytypeio/any-sync/accountservice Service
|
||||
package accountservice
|
||||
|
||||
import (
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anytypeio/any-sync/app"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||
)
|
||||
|
||||
const CName = "common.accountservice"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package mock_accountservice
|
||||
|
||||
import (
|
||||
"github.com/anyproto/any-sync/accountservice"
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/anytypeio/any-sync/accountservice"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||
"github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
func NewAccountServiceWithAccount(ctrl *gomock.Controller, acc *accountdata.AccountKeys) *MockService {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anyproto/any-sync/accountservice (interfaces: Service)
|
||||
// Source: github.com/anytypeio/any-sync/accountservice (interfaces: Service)
|
||||
|
||||
// Package mock_accountservice is a generated GoMock package.
|
||||
package mock_accountservice
|
||||
@@ -7,9 +7,9 @@ package mock_accountservice
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
app "github.com/anyproto/any-sync/app"
|
||||
accountdata "github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
app "github.com/anytypeio/any-sync/app"
|
||||
accountdata "github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockService is a mock of Service interface.
|
||||
|
||||
+18
-87
@@ -4,11 +4,10 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anytypeio/any-sync/app/logger"
|
||||
"go.uber.org/zap"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -16,8 +15,8 @@ import (
|
||||
|
||||
var (
|
||||
// values of this vars will be defined while compilation
|
||||
AppName, GitCommit, GitBranch, GitState, GitSummary, BuildDate string
|
||||
name string
|
||||
GitCommit, GitBranch, GitState, GitSummary, BuildDate string
|
||||
name string
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -55,14 +54,11 @@ type ComponentStatable interface {
|
||||
// App is the central part of the application
|
||||
// It contains and manages all components
|
||||
type App struct {
|
||||
parent *App
|
||||
components []Component
|
||||
mu sync.RWMutex
|
||||
startStat Stat
|
||||
stopStat Stat
|
||||
deviceState int
|
||||
versionName string
|
||||
anySyncVersion string
|
||||
components []Component
|
||||
mu sync.RWMutex
|
||||
startStat Stat
|
||||
stopStat Stat
|
||||
deviceState int
|
||||
}
|
||||
|
||||
// Name returns app name
|
||||
@@ -70,28 +66,11 @@ 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
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -124,16 +103,6 @@ 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 {
|
||||
@@ -153,14 +122,10 @@ func (app *App) Register(s Component) *App {
|
||||
func (app *App) Component(name string) Component {
|
||||
app.mu.RLock()
|
||||
defer app.mu.RUnlock()
|
||||
current := app
|
||||
for current != nil {
|
||||
for _, s := range current.components {
|
||||
if s.Name() == name {
|
||||
return s
|
||||
}
|
||||
for _, s := range app.components {
|
||||
if s.Name() == name {
|
||||
return s
|
||||
}
|
||||
current = current.parent
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -178,14 +143,10 @@ func (app *App) MustComponent(name string) Component {
|
||||
func MustComponent[i any](app *App) i {
|
||||
app.mu.RLock()
|
||||
defer app.mu.RUnlock()
|
||||
current := app
|
||||
for current != nil {
|
||||
for _, s := range current.components {
|
||||
if v, ok := s.(i); ok {
|
||||
return v
|
||||
}
|
||||
for _, s := range app.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))
|
||||
@@ -195,13 +156,9 @@ func MustComponent[i any](app *App) i {
|
||||
func (app *App) ComponentNames() (names []string) {
|
||||
app.mu.RLock()
|
||||
defer app.mu.RUnlock()
|
||||
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
|
||||
names = make([]string, len(app.components))
|
||||
for i, c := range app.components {
|
||||
names[i] = c.Name()
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -262,15 +219,6 @@ func (app *App) Start(ctx context.Context) (err error) {
|
||||
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 {
|
||||
@@ -309,7 +257,7 @@ func (app *App) Close(ctx context.Context) error {
|
||||
case <-time.After(StopWarningAfter):
|
||||
statLogger(app.stopStat, log).
|
||||
With(zap.String("in_progress", currentComponentStopping)).
|
||||
Warn("components close in progress")
|
||||
Warn("components close in progress")
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
@@ -363,20 +311,3 @@ func (app *App) SetDeviceState(state int) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var onceVersion sync.Once
|
||||
|
||||
func (app *App) AnySyncVersion() string {
|
||||
onceVersion.Do(func() {
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if ok {
|
||||
for _, mod := range info.Deps {
|
||||
if mod.Path == "github.com/anyproto/any-sync" {
|
||||
app.anySyncVersion = mod.Version
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return app.anySyncVersion
|
||||
}
|
||||
@@ -34,40 +34,6 @@ func TestAppServiceRegistry(t *testing.T) {
|
||||
names := app.ComponentNames()
|
||||
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
@@ -1,7 +1,7 @@
|
||||
// Package ldiff provides a container of elements with fixed id and changeable content.
|
||||
// Diff can calculate the difference with another diff container (you can make it remote) with minimum hops and traffic.
|
||||
//
|
||||
//go:generate mockgen -destination mock_ldiff/mock_ldiff.go github.com/anyproto/any-sync/app/ldiff Diff,Remote
|
||||
//go:generate mockgen -destination mock_ldiff/mock_ldiff.go github.com/anytypeio/any-sync/app/ldiff Diff,Remote
|
||||
package ldiff
|
||||
|
||||
import (
|
||||
|
||||
@@ -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 := uuid.NewString()
|
||||
head := bson.NewObjectId().Hex()
|
||||
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: uuid.NewString(),
|
||||
Head: bson.NewObjectId().Hex(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ func TestDiff_Diff(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
d2.Set(Element{
|
||||
Id: fmt.Sprint(i),
|
||||
Head: uuid.NewString(),
|
||||
Head: bson.NewObjectId().Hex(),
|
||||
})
|
||||
}
|
||||
var cancel func()
|
||||
@@ -123,7 +123,7 @@ func BenchmarkDiff_Ranges(b *testing.B) {
|
||||
d := New(16, 16)
|
||||
for i := 0; i < 10000; i++ {
|
||||
id := fmt.Sprint(i)
|
||||
head := uuid.NewString()
|
||||
head := bson.NewObjectId().Hex()
|
||||
d.Set(Element{
|
||||
Id: id,
|
||||
Head: head,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anyproto/any-sync/app/ldiff (interfaces: Diff,Remote)
|
||||
// Source: github.com/anytypeio/any-sync/app/ldiff (interfaces: Diff,Remote)
|
||||
|
||||
// Package mock_ldiff is a generated GoMock package.
|
||||
package mock_ldiff
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
ldiff "github.com/anyproto/any-sync/app/ldiff"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
ldiff "github.com/anytypeio/any-sync/app/ldiff"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockDiff is a mock of Diff interface.
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
"github.com/anyproto/any-sync/util/slice"
|
||||
"github.com/anytypeio/any-sync/util/slice"
|
||||
)
|
||||
|
||||
type LogFormat int
|
||||
|
||||
+1
-17
@@ -2,10 +2,9 @@ package ocache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go.uber.org/zap"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type entryState int
|
||||
@@ -26,7 +25,6 @@ type entry struct {
|
||||
value Object
|
||||
close chan struct{}
|
||||
mx sync.Mutex
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func newEntry(id string, value Object, state entryState) *entry {
|
||||
@@ -51,20 +49,6 @@ func (e *entry) isClosing() bool {
|
||||
return e.state == entryStateClosed || e.state == entryStateClosing
|
||||
}
|
||||
|
||||
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():
|
||||
|
||||
@@ -6,9 +6,6 @@ import (
|
||||
)
|
||||
|
||||
func WithPrometheus(reg *prometheus.Registry, namespace, subsystem string) Option {
|
||||
if reg == nil {
|
||||
return nil
|
||||
}
|
||||
if subsystem == "" {
|
||||
subsystem = "cache"
|
||||
}
|
||||
@@ -16,7 +13,9 @@ func WithPrometheus(reg *prometheus.Registry, namespace, subsystem string) Optio
|
||||
subSplit := strings.Split(subsystem, ".")
|
||||
namespace = strings.Join(nameSplit, "_")
|
||||
subsystem = strings.Join(subSplit, "_")
|
||||
|
||||
if reg == nil {
|
||||
return nil
|
||||
}
|
||||
return func(cache *oCache) {
|
||||
cache.metrics = &metrics{
|
||||
hit: prometheus.NewCounter(prometheus.CounterOpts{
|
||||
|
||||
@@ -3,11 +3,10 @@ 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 (
|
||||
@@ -158,10 +157,7 @@ func (c *oCache) Pick(ctx context.Context, id string) (value Object, err error)
|
||||
|
||||
func (c *oCache) load(ctx context.Context, id string, e *entry) {
|
||||
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()
|
||||
@@ -319,7 +315,6 @@ func (c *oCache) Close() (err error) {
|
||||
close(c.closeCh)
|
||||
var toClose []*entry
|
||||
for _, e := range c.data {
|
||||
e.cancelLoad()
|
||||
toClose = append(toClose, e)
|
||||
}
|
||||
c.mu.Unlock()
|
||||
|
||||
@@ -386,25 +386,6 @@ func Test_OCache_Remove(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestOCacheCancelWhenRemove(t *testing.T) {
|
||||
c := New(func(ctx context.Context, id string) (value Object, err error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
}, WithTTL(time.Millisecond*10))
|
||||
stopLoad := make(chan struct{})
|
||||
var err error
|
||||
go func() {
|
||||
_, err = c.Get(context.TODO(), "id")
|
||||
stopLoad <- struct{}{}
|
||||
}()
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
c.Close()
|
||||
<-stopLoad
|
||||
require.Equal(t, context.Canceled, err)
|
||||
}
|
||||
|
||||
func TestOCacheFuzzy(t *testing.T) {
|
||||
t.Run("test many objects gc, get and remove simultaneously, close after", func(t *testing.T) {
|
||||
tryCloseIds := make(map[string]bool)
|
||||
|
||||
@@ -2,8 +2,8 @@ package fileblockstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonfile/fileproto/fileprotoerr"
|
||||
"github.com/anytypeio/any-sync/app/logger"
|
||||
"github.com/anytypeio/any-sync/commonfile/fileproto/fileprotoerr"
|
||||
blocks "github.com/ipfs/go-block-format"
|
||||
"github.com/ipfs/go-cid"
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by protoc-gen-go-drpc. DO NOT EDIT.
|
||||
// protoc-gen-go-drpc version: v0.0.33
|
||||
// protoc-gen-go-drpc version: v0.0.32
|
||||
// source: commonfile/fileproto/protos/file.proto
|
||||
|
||||
package fileproto
|
||||
|
||||
@@ -2,8 +2,8 @@ package fileprotoerr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/commonfile/fileproto"
|
||||
"github.com/anyproto/any-sync/net/rpc/rpcerr"
|
||||
"github.com/anytypeio/any-sync/commonfile/fileproto"
|
||||
"github.com/anytypeio/any-sync/net/rpc/rpcerr"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -2,7 +2,7 @@ package fileservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anyproto/any-sync/commonfile/fileblockstore"
|
||||
"github.com/anytypeio/any-sync/commonfile/fileblockstore"
|
||||
blocks "github.com/ipfs/go-block-format"
|
||||
"github.com/ipfs/go-blockservice"
|
||||
"github.com/ipfs/go-cid"
|
||||
|
||||
@@ -3,9 +3,9 @@ package fileservice
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonfile/fileblockstore"
|
||||
"github.com/anytypeio/any-sync/app"
|
||||
"github.com/anytypeio/any-sync/app/logger"
|
||||
"github.com/anytypeio/any-sync/commonfile/fileblockstore"
|
||||
"github.com/ipfs/go-cid"
|
||||
chunker "github.com/ipfs/go-ipfs-chunker"
|
||||
ipld "github.com/ipfs/go-ipld-format"
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package commonspace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/syncobjectgetter"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/treemanager"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type commonGetter struct {
|
||||
treemanager.TreeManager
|
||||
spaceId string
|
||||
reservedObjects []syncobjectgetter.SyncObject
|
||||
spaceIsClosed *atomic.Bool
|
||||
}
|
||||
|
||||
func newCommonGetter(spaceId string, getter treemanager.TreeManager, spaceIsClosed *atomic.Bool) *commonGetter {
|
||||
return &commonGetter{
|
||||
TreeManager: 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.TreeManager.GetTree(ctx, spaceId, treeId)
|
||||
}
|
||||
|
||||
func (c *commonGetter) getReservedObject(id string) syncobjectgetter.SyncObject {
|
||||
for _, obj := range c.reservedObjects {
|
||||
if obj != nil && obj.Id() == id {
|
||||
return obj
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *commonGetter) GetObject(ctx context.Context, objectId string) (obj syncobjectgetter.SyncObject, err error) {
|
||||
if c.spaceIsClosed.Load() {
|
||||
return nil, ErrSpaceClosed
|
||||
}
|
||||
if obj := c.getReservedObject(objectId); obj != nil {
|
||||
return obj, nil
|
||||
}
|
||||
t, err := c.TreeManager.GetTree(ctx, c.spaceId, objectId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
obj = t.(syncobjectgetter.SyncObject)
|
||||
return
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package commonspace
|
||||
|
||||
import (
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacestorage"
|
||||
)
|
||||
|
||||
type commonStorage struct {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package config
|
||||
package commonspace
|
||||
|
||||
type ConfigGetter interface {
|
||||
GetSpace() Config
|
||||
@@ -1,10 +1,9 @@
|
||||
//go:generate mockgen -destination mock_credentialprovider/mock_credentialprovider.go github.com/anyproto/any-sync/commonspace/credentialprovider CredentialProvider
|
||||
//go:generate mockgen -destination mock_credentialprovider/mock_credentialprovider.go github.com/anytypeio/any-sync/commonspace/credentialprovider CredentialProvider
|
||||
package credentialprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
)
|
||||
|
||||
const CName = "common.commonspace.credentialprovider"
|
||||
@@ -14,21 +13,12 @@ func NewNoOp() CredentialProvider {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anyproto/any-sync/commonspace/credentialprovider (interfaces: CredentialProvider)
|
||||
// Source: github.com/anytypeio/any-sync/commonspace/credentialprovider (interfaces: CredentialProvider)
|
||||
|
||||
// Package mock_credentialprovider is a generated GoMock package.
|
||||
package mock_credentialprovider
|
||||
@@ -8,9 +8,8 @@ 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"
|
||||
spacesyncproto "github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockCredentialProvider is a mock of CredentialProvider interface.
|
||||
@@ -50,31 +49,3 @@ func (mr *MockCredentialProviderMockRecorder) GetCredential(arg0, arg1 interface
|
||||
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))
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
package commonspace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/settings"
|
||||
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/stretchr/testify/require"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func addIncorrectSnapshot(settingsObject settings.SettingsObject, acc *accountdata.AccountKeys, partialIds map[string]struct{}, newId string) (err error) {
|
||||
factory := settingsstate.NewChangeFactory()
|
||||
bytes, err := factory.CreateObjectDeleteChange(newId, &settingsstate.State{DeletedIds: partialIds}, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ch, err := settingsObject.PrepareChange(objecttree.SignableChangeContent{
|
||||
Data: bytes,
|
||||
Key: acc.SignKey,
|
||||
IsSnapshot: true,
|
||||
IsEncrypted: false,
|
||||
Timestamp: time.Now().Unix(),
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
res, err := settingsObject.AddRawChanges(context.Background(), objecttree.RawChangesPayload{
|
||||
NewHeads: []string{ch.Id},
|
||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{ch},
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if res.Mode != objecttree.Rebuild {
|
||||
return fmt.Errorf("incorrect mode: %d", res.Mode)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func TestSpaceDeleteIds(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
acc := fx.account.Account()
|
||||
rk := crypto.NewAES()
|
||||
ctx := context.Background()
|
||||
totalObjs := 1500
|
||||
|
||||
// creating space
|
||||
sp, err := fx.spaceService.CreateSpace(ctx, SpaceCreatePayload{
|
||||
SigningKey: acc.SignKey,
|
||||
SpaceType: "type",
|
||||
ReadKey: rk.Bytes(),
|
||||
ReplicationKey: 10,
|
||||
MasterKey: acc.PeerKey,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sp)
|
||||
|
||||
// initializing space
|
||||
spc, err := fx.spaceService.NewSpace(ctx, sp)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, spc)
|
||||
// adding space to tree manager
|
||||
fx.treeManager.space = spc
|
||||
err = spc.Init(ctx)
|
||||
require.NoError(t, err)
|
||||
close(fx.treeManager.waitLoad)
|
||||
|
||||
var ids []string
|
||||
for i := 0; i < totalObjs; i++ {
|
||||
// creating a tree
|
||||
bytes := make([]byte, 32)
|
||||
rand.Read(bytes)
|
||||
doc, err := spc.TreeBuilder().CreateTree(ctx, objecttree.ObjectTreeCreatePayload{
|
||||
PrivKey: acc.SignKey,
|
||||
ChangeType: "some",
|
||||
SpaceId: spc.Id(),
|
||||
IsEncrypted: false,
|
||||
Seed: bytes,
|
||||
Timestamp: time.Now().Unix(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
tr, err := spc.TreeBuilder().PutTree(ctx, doc, nil)
|
||||
require.NoError(t, err)
|
||||
ids = append(ids, tr.Id())
|
||||
tr.Close()
|
||||
}
|
||||
// deleting trees
|
||||
for _, id := range ids {
|
||||
err = spc.DeleteTree(ctx, id)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
time.Sleep(3 * time.Second)
|
||||
spc.Close()
|
||||
require.Equal(t, len(ids), len(fx.treeManager.deletedIds))
|
||||
}
|
||||
|
||||
func createTree(t *testing.T, ctx context.Context, spc Space, acc *accountdata.AccountKeys) string {
|
||||
bytes := make([]byte, 32)
|
||||
rand.Read(bytes)
|
||||
doc, err := spc.TreeBuilder().CreateTree(ctx, objecttree.ObjectTreeCreatePayload{
|
||||
PrivKey: acc.SignKey,
|
||||
ChangeType: "some",
|
||||
SpaceId: spc.Id(),
|
||||
IsEncrypted: false,
|
||||
Seed: bytes,
|
||||
Timestamp: time.Now().Unix(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
tr, err := spc.TreeBuilder().PutTree(ctx, doc, nil)
|
||||
require.NoError(t, err)
|
||||
tr.Close()
|
||||
return tr.Id()
|
||||
}
|
||||
|
||||
func TestSpaceDeleteIdsIncorrectSnapshot(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
acc := fx.account.Account()
|
||||
rk := crypto.NewAES()
|
||||
ctx := context.Background()
|
||||
totalObjs := 1500
|
||||
partialObjs := 300
|
||||
|
||||
// creating space
|
||||
sp, err := fx.spaceService.CreateSpace(ctx, SpaceCreatePayload{
|
||||
SigningKey: acc.SignKey,
|
||||
SpaceType: "type",
|
||||
ReadKey: rk.Bytes(),
|
||||
ReplicationKey: 10,
|
||||
MasterKey: acc.PeerKey,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sp)
|
||||
|
||||
// initializing space
|
||||
spc, err := fx.spaceService.NewSpace(ctx, sp)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, spc)
|
||||
// adding space to tree manager
|
||||
fx.treeManager.space = spc
|
||||
err = spc.Init(ctx)
|
||||
close(fx.treeManager.waitLoad)
|
||||
require.NoError(t, err)
|
||||
|
||||
settingsObject := spc.(*space).app.MustComponent(settings.CName).(settings.Settings).SettingsObject()
|
||||
var ids []string
|
||||
for i := 0; i < totalObjs; i++ {
|
||||
id := createTree(t, ctx, spc, acc)
|
||||
ids = append(ids, id)
|
||||
}
|
||||
// copying storage, so we will have all the trees locally
|
||||
inmemory := spc.Storage().(*commonStorage).SpaceStorage.(*spacestorage.InMemorySpaceStorage)
|
||||
storageCopy := inmemory.CopyStorage()
|
||||
treesCopy := inmemory.AllTrees()
|
||||
|
||||
// deleting trees
|
||||
for _, id := range ids {
|
||||
err = spc.DeleteTree(ctx, id)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
mapIds := map[string]struct{}{}
|
||||
for _, id := range ids[:partialObjs] {
|
||||
mapIds[id] = struct{}{}
|
||||
}
|
||||
// adding snapshot that breaks the state
|
||||
err = addIncorrectSnapshot(settingsObject, acc, mapIds, ids[partialObjs])
|
||||
require.NoError(t, err)
|
||||
// copying the contents of the settings tree
|
||||
treesCopy[settingsObject.Id()] = settingsObject.Storage()
|
||||
storageCopy.SetTrees(treesCopy)
|
||||
spc.Close()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
// now we replace the storage, so the trees are back, but the settings object says that they are deleted
|
||||
fx.storageProvider.(*spacestorage.InMemorySpaceStorageProvider).SetStorage(storageCopy)
|
||||
|
||||
spc, err = fx.spaceService.NewSpace(ctx, sp)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, spc)
|
||||
fx.treeManager.waitLoad = make(chan struct{})
|
||||
fx.treeManager.space = spc
|
||||
fx.treeManager.deletedIds = nil
|
||||
err = spc.Init(ctx)
|
||||
require.NoError(t, err)
|
||||
close(fx.treeManager.waitLoad)
|
||||
|
||||
// waiting until everything is deleted
|
||||
time.Sleep(3 * time.Second)
|
||||
require.Equal(t, len(ids), len(fx.treeManager.deletedIds))
|
||||
|
||||
// checking that new snapshot will contain all the changes
|
||||
settingsObject = spc.(*space).app.MustComponent(settings.CName).(settings.Settings).SettingsObject()
|
||||
settings.DoSnapshot = func(treeLen int) bool {
|
||||
return true
|
||||
}
|
||||
id := createTree(t, ctx, spc, acc)
|
||||
err = spc.DeleteTree(ctx, id)
|
||||
require.NoError(t, err)
|
||||
delIds := settingsObject.Root().Model.(*spacesyncproto.SettingsData).Snapshot.DeletedIds
|
||||
require.Equal(t, totalObjs+1, len(delIds))
|
||||
}
|
||||
|
||||
func TestSpaceDeleteIdsMarkDeleted(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
acc := fx.account.Account()
|
||||
rk := crypto.NewAES()
|
||||
ctx := context.Background()
|
||||
totalObjs := 1500
|
||||
|
||||
// creating space
|
||||
sp, err := fx.spaceService.CreateSpace(ctx, SpaceCreatePayload{
|
||||
SigningKey: acc.SignKey,
|
||||
SpaceType: "type",
|
||||
ReadKey: rk.Bytes(),
|
||||
ReplicationKey: 10,
|
||||
MasterKey: acc.PeerKey,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sp)
|
||||
|
||||
// initializing space
|
||||
spc, err := fx.spaceService.NewSpace(ctx, sp)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, spc)
|
||||
// adding space to tree manager
|
||||
fx.treeManager.space = spc
|
||||
err = spc.Init(ctx)
|
||||
require.NoError(t, err)
|
||||
close(fx.treeManager.waitLoad)
|
||||
|
||||
settingsObject := spc.(*space).app.MustComponent(settings.CName).(settings.Settings).SettingsObject()
|
||||
var ids []string
|
||||
for i := 0; i < totalObjs; i++ {
|
||||
id := createTree(t, ctx, spc, acc)
|
||||
ids = append(ids, id)
|
||||
}
|
||||
// copying storage, so we will have the same contents, except for empty trees
|
||||
inmemory := spc.Storage().(*commonStorage).SpaceStorage.(*spacestorage.InMemorySpaceStorage)
|
||||
storageCopy := inmemory.CopyStorage()
|
||||
|
||||
// deleting trees, this will prepare the document to have all the deletion changes
|
||||
for _, id := range ids {
|
||||
err = spc.DeleteTree(ctx, id)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
treesMap := map[string]treestorage.TreeStorage{}
|
||||
// copying the contents of the settings tree
|
||||
treesMap[settingsObject.Id()] = settingsObject.Storage()
|
||||
storageCopy.SetTrees(treesMap)
|
||||
spc.Close()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
// now we replace the storage, so the trees are back, but the settings object says that they are deleted
|
||||
fx.storageProvider.(*spacestorage.InMemorySpaceStorageProvider).SetStorage(storageCopy)
|
||||
|
||||
spc, err = fx.spaceService.NewSpace(ctx, sp)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, spc)
|
||||
fx.treeManager.space = spc
|
||||
fx.treeManager.waitLoad = make(chan struct{})
|
||||
fx.treeManager.deletedIds = nil
|
||||
fx.treeManager.markedIds = nil
|
||||
err = spc.Init(ctx)
|
||||
require.NoError(t, err)
|
||||
close(fx.treeManager.waitLoad)
|
||||
|
||||
// waiting until everything is deleted
|
||||
time.Sleep(3 * time.Second)
|
||||
require.Equal(t, len(ids), len(fx.treeManager.markedIds))
|
||||
require.Zero(t, len(fx.treeManager.deletedIds))
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anyproto/any-sync/commonspace/deletionstate (interfaces: ObjectDeletionState)
|
||||
|
||||
// Package mock_deletionstate is a generated GoMock package.
|
||||
package mock_deletionstate
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
app "github.com/anyproto/any-sync/app"
|
||||
deletionstate "github.com/anyproto/any-sync/commonspace/deletionstate"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockObjectDeletionState is a mock of ObjectDeletionState interface.
|
||||
type MockObjectDeletionState struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockObjectDeletionStateMockRecorder
|
||||
}
|
||||
|
||||
// MockObjectDeletionStateMockRecorder is the mock recorder for MockObjectDeletionState.
|
||||
type MockObjectDeletionStateMockRecorder struct {
|
||||
mock *MockObjectDeletionState
|
||||
}
|
||||
|
||||
// NewMockObjectDeletionState creates a new mock instance.
|
||||
func NewMockObjectDeletionState(ctrl *gomock.Controller) *MockObjectDeletionState {
|
||||
mock := &MockObjectDeletionState{ctrl: ctrl}
|
||||
mock.recorder = &MockObjectDeletionStateMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockObjectDeletionState) EXPECT() *MockObjectDeletionStateMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Add mocks base method.
|
||||
func (m *MockObjectDeletionState) Add(arg0 map[string]struct{}) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Add", arg0)
|
||||
}
|
||||
|
||||
// Add indicates an expected call of Add.
|
||||
func (mr *MockObjectDeletionStateMockRecorder) Add(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockObjectDeletionState)(nil).Add), arg0)
|
||||
}
|
||||
|
||||
// AddObserver mocks base method.
|
||||
func (m *MockObjectDeletionState) AddObserver(arg0 deletionstate.StateUpdateObserver) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "AddObserver", arg0)
|
||||
}
|
||||
|
||||
// AddObserver indicates an expected call of AddObserver.
|
||||
func (mr *MockObjectDeletionStateMockRecorder) AddObserver(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddObserver", reflect.TypeOf((*MockObjectDeletionState)(nil).AddObserver), arg0)
|
||||
}
|
||||
|
||||
// Delete mocks base method.
|
||||
func (m *MockObjectDeletionState) Delete(arg0 string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Delete", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Delete indicates an expected call of Delete.
|
||||
func (mr *MockObjectDeletionStateMockRecorder) Delete(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockObjectDeletionState)(nil).Delete), arg0)
|
||||
}
|
||||
|
||||
// Exists mocks base method.
|
||||
func (m *MockObjectDeletionState) Exists(arg0 string) bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Exists", arg0)
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Exists indicates an expected call of Exists.
|
||||
func (mr *MockObjectDeletionStateMockRecorder) Exists(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exists", reflect.TypeOf((*MockObjectDeletionState)(nil).Exists), arg0)
|
||||
}
|
||||
|
||||
// Filter mocks base method.
|
||||
func (m *MockObjectDeletionState) Filter(arg0 []string) []string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Filter", arg0)
|
||||
ret0, _ := ret[0].([]string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Filter indicates an expected call of Filter.
|
||||
func (mr *MockObjectDeletionStateMockRecorder) Filter(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Filter", reflect.TypeOf((*MockObjectDeletionState)(nil).Filter), arg0)
|
||||
}
|
||||
|
||||
// GetQueued mocks base method.
|
||||
func (m *MockObjectDeletionState) GetQueued() []string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetQueued")
|
||||
ret0, _ := ret[0].([]string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetQueued indicates an expected call of GetQueued.
|
||||
func (mr *MockObjectDeletionStateMockRecorder) GetQueued() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetQueued", reflect.TypeOf((*MockObjectDeletionState)(nil).GetQueued))
|
||||
}
|
||||
|
||||
// Init mocks base method.
|
||||
func (m *MockObjectDeletionState) Init(arg0 *app.App) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Init", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Init indicates an expected call of Init.
|
||||
func (mr *MockObjectDeletionStateMockRecorder) Init(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockObjectDeletionState)(nil).Init), arg0)
|
||||
}
|
||||
|
||||
// Name mocks base method.
|
||||
func (m *MockObjectDeletionState) Name() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Name")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Name indicates an expected call of Name.
|
||||
func (mr *MockObjectDeletionStateMockRecorder) Name() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockObjectDeletionState)(nil).Name))
|
||||
}
|
||||
@@ -3,45 +3,49 @@ package headsync
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"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/acl/syncacl"
|
||||
"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"
|
||||
"github.com/anyproto/any-sync/util/slice"
|
||||
"github.com/anytypeio/any-sync/app/ldiff"
|
||||
"github.com/anytypeio/any-sync/app/logger"
|
||||
"github.com/anytypeio/any-sync/commonspace/credentialprovider"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/synctree"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/treemanager"
|
||||
"github.com/anytypeio/any-sync/commonspace/peermanager"
|
||||
"github.com/anytypeio/any-sync/commonspace/settings/settingsstate"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacestorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/syncstatus"
|
||||
"github.com/anytypeio/any-sync/net/peer"
|
||||
"github.com/anytypeio/any-sync/net/rpc/rpcerr"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DiffSyncer interface {
|
||||
Sync(ctx context.Context) error
|
||||
RemoveObjects(ids []string)
|
||||
UpdateHeads(id string, heads []string)
|
||||
Init()
|
||||
Close() error
|
||||
Init(deletionState settingsstate.ObjectDeletionState)
|
||||
}
|
||||
|
||||
func newDiffSyncer(hs *headSync) DiffSyncer {
|
||||
func newDiffSyncer(
|
||||
spaceId string,
|
||||
diff ldiff.Diff,
|
||||
peerManager peermanager.PeerManager,
|
||||
cache treemanager.TreeManager,
|
||||
storage spacestorage.SpaceStorage,
|
||||
clientFactory spacesyncproto.ClientFactory,
|
||||
syncStatus syncstatus.StatusUpdater,
|
||||
credentialProvider credentialprovider.CredentialProvider,
|
||||
log logger.CtxLogger) DiffSyncer {
|
||||
return &diffSyncer{
|
||||
diff: hs.diff,
|
||||
spaceId: hs.spaceId,
|
||||
treeManager: hs.treeManager,
|
||||
storage: hs.storage,
|
||||
peerManager: hs.peerManager,
|
||||
clientFactory: spacesyncproto.ClientFactoryFunc(spacesyncproto.NewDRPCSpaceSyncClient),
|
||||
credentialProvider: hs.credentialProvider,
|
||||
diff: diff,
|
||||
spaceId: spaceId,
|
||||
cache: cache,
|
||||
storage: storage,
|
||||
peerManager: peerManager,
|
||||
clientFactory: clientFactory,
|
||||
credentialProvider: credentialProvider,
|
||||
log: log,
|
||||
syncStatus: hs.syncStatus,
|
||||
deletionState: hs.deletionState,
|
||||
syncAcl: hs.syncAcl,
|
||||
syncStatus: syncStatus,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,20 +53,18 @@ type diffSyncer struct {
|
||||
spaceId string
|
||||
diff ldiff.Diff
|
||||
peerManager peermanager.PeerManager
|
||||
treeManager treemanager.TreeManager
|
||||
cache treemanager.TreeManager
|
||||
storage spacestorage.SpaceStorage
|
||||
clientFactory spacesyncproto.ClientFactory
|
||||
log logger.CtxLogger
|
||||
deletionState deletionstate.ObjectDeletionState
|
||||
deletionState settingsstate.ObjectDeletionState
|
||||
credentialProvider credentialprovider.CredentialProvider
|
||||
syncStatus syncstatus.StatusUpdater
|
||||
treeSyncer treemanager.TreeSyncer
|
||||
syncAcl syncacl.SyncAcl
|
||||
}
|
||||
|
||||
func (d *diffSyncer) Init() {
|
||||
func (d *diffSyncer) Init(deletionState settingsstate.ObjectDeletionState) {
|
||||
d.deletionState = deletionState
|
||||
d.deletionState.AddObserver(d.RemoveObjects)
|
||||
d.treeSyncer = d.treeManager.NewTreeSyncer(d.spaceId, d.treeManager)
|
||||
}
|
||||
|
||||
func (d *diffSyncer) RemoveObjects(ids []string) {
|
||||
@@ -111,17 +113,10 @@ func (d *diffSyncer) Sync(ctx context.Context) error {
|
||||
|
||||
func (d *diffSyncer) syncWithPeer(ctx context.Context, p peer.Peer) (err error) {
|
||||
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(conn)
|
||||
cl = d.clientFactory.Client(p)
|
||||
rdiff = NewRemoteDiff(d.spaceId, cl)
|
||||
stateCounter = d.syncStatus.StateCounter()
|
||||
syncAclId = d.syncAcl.Id()
|
||||
)
|
||||
|
||||
newIds, changedIds, removedIds, err := d.diff.Diff(ctx, rdiff)
|
||||
@@ -129,7 +124,7 @@ func (d *diffSyncer) syncWithPeer(ctx context.Context, p peer.Peer) (err error)
|
||||
if err != nil && err != spacesyncproto.ErrSpaceMissing {
|
||||
if err == spacesyncproto.ErrSpaceIsDeleted {
|
||||
d.log.Debug("got space deleted while syncing")
|
||||
d.treeSyncer.SyncAll(ctx, p.Id(), []string{d.storage.SpaceSettingsId()}, nil)
|
||||
d.syncTrees(ctx, p.Id(), []string{d.storage.SpaceSettingsId()})
|
||||
}
|
||||
d.syncStatus.SetNodesOnline(p.Id(), false)
|
||||
return fmt.Errorf("diff error: %v", err)
|
||||
@@ -142,36 +137,45 @@ func (d *diffSyncer) syncWithPeer(ctx context.Context, p peer.Peer) (err error)
|
||||
|
||||
totalLen := len(newIds) + len(changedIds) + len(removedIds)
|
||||
// not syncing ids which were removed through settings document
|
||||
missingIds := d.deletionState.Filter(newIds)
|
||||
existingIds := append(d.deletionState.Filter(removedIds), d.deletionState.Filter(changedIds)...)
|
||||
d.syncStatus.RemoveAllExcept(p.Id(), existingIds, stateCounter)
|
||||
filteredIds := d.deletionState.FilterJoin(newIds, changedIds, removedIds)
|
||||
|
||||
prevExistingLen := len(existingIds)
|
||||
existingIds = slice.DiscardFromSlice(existingIds, func(s string) bool {
|
||||
return s == syncAclId
|
||||
})
|
||||
// if we removed acl head from the list
|
||||
if len(existingIds) < prevExistingLen {
|
||||
if syncErr := d.syncAcl.SyncWithPeer(ctx, p.Id()); syncErr != nil {
|
||||
log.Warn("failed to send acl sync message to peer", zap.String("aclId", syncAclId))
|
||||
}
|
||||
}
|
||||
d.syncStatus.RemoveAllExcept(p.Id(), filteredIds, stateCounter)
|
||||
|
||||
// treeSyncer should not get acl id, that's why we filter existing ids before
|
||||
err = d.treeSyncer.SyncAll(ctx, p.Id(), existingIds, missingIds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.log.Info("sync done:",
|
||||
zap.Int("newIds", len(newIds)),
|
||||
d.syncTrees(ctx, p.Id(), filteredIds)
|
||||
|
||||
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-prevExistingLen-len(missingIds)),
|
||||
zap.Int("already deleted ids", totalLen-len(filteredIds)),
|
||||
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, peerId string, cl spacesyncproto.DRPCSpaceSyncClient) (err error) {
|
||||
aclStorage, err := d.storage.AclStorage()
|
||||
if err != nil {
|
||||
@@ -234,7 +238,3 @@ func (d *diffSyncer) subscribe(ctx context.Context, peerId string) (err error) {
|
||||
Payload: payload,
|
||||
})
|
||||
}
|
||||
|
||||
func (d *diffSyncer) Close() error {
|
||||
return d.treeSyncer.Close()
|
||||
}
|
||||
@@ -4,19 +4,28 @@ 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/credentialprovider/mock_credentialprovider"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/liststorage/mock_liststorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
mock_treestorage "github.com/anytypeio/any-sync/commonspace/object/tree/treestorage/mock_treestorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/treemanager/mock_treemanager"
|
||||
"github.com/anytypeio/any-sync/commonspace/peermanager/mock_peermanager"
|
||||
"github.com/anytypeio/any-sync/commonspace/settings/settingsstate/mock_settingsstate"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacestorage/mock_spacestorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacesyncproto/mock_spacesyncproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/syncstatus"
|
||||
"github.com/anytypeio/any-sync/net/peer"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/libp2p/go-libp2p/core/sec"
|
||||
"github.com/stretchr/testify/require"
|
||||
"storj.io/drpc"
|
||||
"testing"
|
||||
"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 {
|
||||
@@ -27,6 +36,60 @@ type pushSpaceRequestMatcher struct {
|
||||
spaceHeader *spacesyncproto.RawSpaceHeaderWithId
|
||||
}
|
||||
|
||||
func (p pushSpaceRequestMatcher) Matches(x interface{}) bool {
|
||||
res, ok := x.(*spacesyncproto.SpacePushRequest)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return res.Payload.AclPayloadId == p.aclRootId && res.Payload.SpaceHeader == p.spaceHeader && res.Payload.SpaceSettingsPayloadId == p.settingsId && bytes.Equal(p.credential, res.Credential)
|
||||
}
|
||||
|
||||
func (p pushSpaceRequestMatcher) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type mockPeer struct{}
|
||||
|
||||
func (m mockPeer) Addr() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m mockPeer) TryClose(objectTTL time.Duration) (res bool, err error) {
|
||||
return true, m.Close()
|
||||
}
|
||||
|
||||
func (m mockPeer) Id() string {
|
||||
return "mockId"
|
||||
}
|
||||
|
||||
func (m mockPeer) LastUsage() time.Time {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func (m mockPeer) Secure() sec.SecureConn {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m mockPeer) UpdateLastUsage() {
|
||||
}
|
||||
|
||||
func (m mockPeer) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m mockPeer) Closed() <-chan struct{} {
|
||||
return make(chan struct{})
|
||||
}
|
||||
|
||||
func (m mockPeer) Invoke(ctx context.Context, rpc string, enc drpc.Encoding, in, out drpc.Message) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m mockPeer) NewStream(ctx context.Context, rpc string, enc drpc.Encoding) (drpc.Stream, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func newPushSpaceRequestMatcher(
|
||||
spaceId string,
|
||||
aclRootId string,
|
||||
@@ -42,159 +105,80 @@ func newPushSpaceRequestMatcher(
|
||||
}
|
||||
}
|
||||
|
||||
func (p pushSpaceRequestMatcher) Matches(x interface{}) bool {
|
||||
res, ok := x.(*spacesyncproto.SpacePushRequest)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return res.Payload.AclPayloadId == p.aclRootId && res.Payload.SpaceHeader == p.spaceHeader && res.Payload.SpaceSettingsPayloadId == p.settingsId && bytes.Equal(p.credential, res.Credential)
|
||||
}
|
||||
|
||||
func (p pushSpaceRequestMatcher) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type mockPeer struct {
|
||||
}
|
||||
|
||||
func (m mockPeer) Id() string {
|
||||
return "peerId"
|
||||
}
|
||||
|
||||
func (m mockPeer) Context() context.Context {
|
||||
return context.Background()
|
||||
}
|
||||
|
||||
func (m mockPeer) AcquireDrpcConn(ctx context.Context) (drpc.Conn, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m mockPeer) ReleaseDrpcConn(conn drpc.Conn) {
|
||||
return
|
||||
}
|
||||
|
||||
func (m mockPeer) DoDrpc(ctx context.Context, do func(conn drpc.Conn) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m mockPeer) IsClosed() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (m mockPeer) TryClose(objectTTL time.Duration) (res bool, err error) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
func (m mockPeer) Close() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fx *headSyncFixture) initDiffSyncer(t *testing.T) {
|
||||
fx.init(t)
|
||||
fx.diffSyncer = newDiffSyncer(fx.headSync).(*diffSyncer)
|
||||
fx.diffSyncer.clientFactory = spacesyncproto.ClientFactoryFunc(func(cc drpc.Conn) spacesyncproto.DRPCSpaceSyncClient {
|
||||
return fx.clientMock
|
||||
})
|
||||
fx.deletionStateMock.EXPECT().AddObserver(gomock.Any())
|
||||
fx.treeManagerMock.EXPECT().NewTreeSyncer(fx.spaceState.SpaceId, fx.treeManagerMock).Return(fx.treeSyncerMock)
|
||||
fx.diffSyncer.Init()
|
||||
}
|
||||
|
||||
func TestDiffSyncer(t *testing.T) {
|
||||
func TestDiffSyncer_Sync(t *testing.T) {
|
||||
// setup
|
||||
ctx := context.Background()
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
diffMock := mock_ldiff.NewMockDiff(ctrl)
|
||||
peerManagerMock := mock_peermanager.NewMockPeerManager(ctrl)
|
||||
cacheMock := mock_treemanager.NewMockTreeManager(ctrl)
|
||||
stMock := mock_spacestorage.NewMockSpaceStorage(ctrl)
|
||||
clientMock := mock_spacesyncproto.NewMockDRPCSpaceSyncClient(ctrl)
|
||||
factory := spacesyncproto.ClientFactoryFunc(func(cc drpc.Conn) spacesyncproto.DRPCSpaceSyncClient {
|
||||
return clientMock
|
||||
})
|
||||
credentialProvider := mock_credentialprovider.NewMockCredentialProvider(ctrl)
|
||||
delState := mock_settingsstate.NewMockObjectDeletionState(ctrl)
|
||||
spaceId := "spaceId"
|
||||
aclRootId := "aclRootId"
|
||||
l := logger.NewNamed(spaceId)
|
||||
diffSyncer := newDiffSyncer(spaceId, diffMock, peerManagerMock, cacheMock, stMock, factory, syncstatus.NewNoOpSyncStatus(), credentialProvider, l)
|
||||
delState.EXPECT().AddObserver(gomock.Any())
|
||||
diffSyncer.Init(delState)
|
||||
|
||||
t.Run("diff syncer sync", func(t *testing.T) {
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.initDiffSyncer(t)
|
||||
defer fx.stop()
|
||||
mPeer := mockPeer{}
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
||||
fx.peerManagerMock.EXPECT().
|
||||
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([]peer.Peer{mockPeer{}}, nil)
|
||||
diffMock.EXPECT().
|
||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(spaceId, clientMock))).
|
||||
Return([]string{"new"}, []string{"changed"}, nil, nil)
|
||||
fx.deletionStateMock.EXPECT().Filter([]string{"new"}).Return([]string{"new"}).Times(1)
|
||||
fx.deletionStateMock.EXPECT().Filter([]string{"changed"}).Return([]string{"changed"}).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, acl changed", func(t *testing.T) {
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.initDiffSyncer(t)
|
||||
defer fx.stop()
|
||||
mPeer := mockPeer{}
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
||||
fx.peerManagerMock.EXPECT().
|
||||
GetResponsiblePeers(gomock.Any()).
|
||||
Return([]peer.Peer{mPeer}, nil)
|
||||
fx.diffMock.EXPECT().
|
||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(fx.spaceState.SpaceId, fx.clientMock))).
|
||||
Return([]string{"new"}, []string{"changed"}, nil, nil)
|
||||
fx.deletionStateMock.EXPECT().Filter([]string{"new"}).Return([]string{"new"}).Times(1)
|
||||
fx.deletionStateMock.EXPECT().Filter([]string{"changed"}).Return([]string{"changed", "aclId"}).Times(1)
|
||||
fx.deletionStateMock.EXPECT().Filter(nil).Return(nil).Times(1)
|
||||
fx.treeSyncerMock.EXPECT().SyncAll(gomock.Any(), mPeer.Id(), []string{"changed"}, []string{"new"}).Return(nil)
|
||||
fx.aclMock.EXPECT().SyncWithPeer(gomock.Any(), mPeer.Id()).Return(nil)
|
||||
require.NoError(t, fx.diffSyncer.Sync(ctx))
|
||||
delState.EXPECT().FilterJoin(gomock.Any()).Return([]string{"new", "changed"})
|
||||
for _, arg := range []string{"new", "changed"} {
|
||||
cacheMock.EXPECT().
|
||||
GetTree(gomock.Any(), spaceId, arg).
|
||||
Return(nil, nil)
|
||||
}
|
||||
require.NoError(t, diffSyncer.Sync(ctx))
|
||||
})
|
||||
|
||||
t.Run("diff syncer sync conf error", func(t *testing.T) {
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.initDiffSyncer(t)
|
||||
defer fx.stop()
|
||||
ctx := context.Background()
|
||||
fx.peerManagerMock.EXPECT().
|
||||
peerManagerMock.EXPECT().
|
||||
GetResponsiblePeers(gomock.Any()).
|
||||
Return(nil, fmt.Errorf("some error"))
|
||||
|
||||
require.Error(t, fx.diffSyncer.Sync(ctx))
|
||||
require.Error(t, diffSyncer.Sync(ctx))
|
||||
})
|
||||
|
||||
t.Run("deletion state remove objects", func(t *testing.T) {
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.initDiffSyncer(t)
|
||||
defer fx.stop()
|
||||
deletedId := "id"
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
||||
fx.deletionStateMock.EXPECT().Exists(deletedId).Return(true)
|
||||
delState.EXPECT().Exists(deletedId).Return(true)
|
||||
|
||||
// this should not result in any mock being called
|
||||
fx.diffSyncer.UpdateHeads(deletedId, []string{"someHead"})
|
||||
diffSyncer.UpdateHeads(deletedId, []string{"someHead"})
|
||||
})
|
||||
|
||||
t.Run("update heads updates diff", func(t *testing.T) {
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.initDiffSyncer(t)
|
||||
defer fx.stop()
|
||||
newId := "newId"
|
||||
newHeads := []string{"h1", "h2"}
|
||||
hash := "hash"
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
||||
fx.diffMock.EXPECT().Set(ldiff.Element{
|
||||
diffMock.EXPECT().Set(ldiff.Element{
|
||||
Id: newId,
|
||||
Head: concatStrings(newHeads),
|
||||
})
|
||||
fx.diffMock.EXPECT().Hash().Return(hash)
|
||||
fx.deletionStateMock.EXPECT().Exists(newId).Return(false)
|
||||
fx.storageMock.EXPECT().WriteSpaceHash(hash)
|
||||
fx.diffSyncer.UpdateHeads(newId, newHeads)
|
||||
diffMock.EXPECT().Hash().Return(hash)
|
||||
delState.EXPECT().Exists(newId).Return(false)
|
||||
stMock.EXPECT().WriteSpaceHash(hash)
|
||||
diffSyncer.UpdateHeads(newId, newHeads)
|
||||
})
|
||||
|
||||
t.Run("diff syncer sync space missing", func(t *testing.T) {
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.initDiffSyncer(t)
|
||||
defer fx.stop()
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
||||
aclStorageMock := mock_liststorage.NewMockListStorage(fx.ctrl)
|
||||
settingsStorage := mock_treestorage.NewMockTreeStorage(fx.ctrl)
|
||||
aclStorageMock := mock_liststorage.NewMockListStorage(ctrl)
|
||||
settingsStorage := mock_treestorage.NewMockTreeStorage(ctrl)
|
||||
settingsId := "settingsId"
|
||||
aclRootId := "aclRootId"
|
||||
aclRoot := &consensusproto.RawRecordWithId{
|
||||
aclRoot := &aclrecordproto.RawAclRecordWithId{
|
||||
Id: aclRootId,
|
||||
}
|
||||
settingsRoot := &treechangeproto.RawTreeChangeWithId{
|
||||
@@ -204,63 +188,56 @@ func TestDiffSyncer(t *testing.T) {
|
||||
spaceSettingsId := "spaceSettingsId"
|
||||
credential := []byte("credential")
|
||||
|
||||
fx.peerManagerMock.EXPECT().
|
||||
peerManagerMock.EXPECT().
|
||||
GetResponsiblePeers(gomock.Any()).
|
||||
Return([]peer.Peer{mockPeer{}}, nil)
|
||||
fx.diffMock.EXPECT().
|
||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(fx.spaceState.SpaceId, fx.clientMock))).
|
||||
diffMock.EXPECT().
|
||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(spaceId, clientMock))).
|
||||
Return(nil, nil, nil, spacesyncproto.ErrSpaceMissing)
|
||||
|
||||
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)
|
||||
stMock.EXPECT().AclStorage().Return(aclStorageMock, nil)
|
||||
stMock.EXPECT().SpaceHeader().Return(spaceHeader, nil)
|
||||
stMock.EXPECT().SpaceSettingsId().Return(spaceSettingsId)
|
||||
stMock.EXPECT().TreeStorage(spaceSettingsId).Return(settingsStorage, nil)
|
||||
|
||||
settingsStorage.EXPECT().Root().Return(settingsRoot, nil)
|
||||
aclStorageMock.EXPECT().
|
||||
Root().
|
||||
Return(aclRoot, nil)
|
||||
fx.credentialProviderMock.EXPECT().
|
||||
credentialProvider.EXPECT().
|
||||
GetCredential(gomock.Any(), spaceHeader).
|
||||
Return(credential, nil)
|
||||
fx.clientMock.EXPECT().
|
||||
SpacePush(gomock.Any(), newPushSpaceRequestMatcher(fx.spaceState.SpaceId, aclRootId, settingsId, credential, spaceHeader)).
|
||||
clientMock.EXPECT().
|
||||
SpacePush(gomock.Any(), newPushSpaceRequestMatcher(spaceId, aclRootId, settingsId, credential, spaceHeader)).
|
||||
Return(nil, nil)
|
||||
fx.peerManagerMock.EXPECT().SendPeer(gomock.Any(), "peerId", gomock.Any())
|
||||
peerManagerMock.EXPECT().SendPeer(gomock.Any(), "mockId", gomock.Any())
|
||||
|
||||
require.NoError(t, fx.diffSyncer.Sync(ctx))
|
||||
require.NoError(t, diffSyncer.Sync(ctx))
|
||||
})
|
||||
|
||||
t.Run("diff syncer sync unexpected", func(t *testing.T) {
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.initDiffSyncer(t)
|
||||
defer fx.stop()
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
||||
fx.peerManagerMock.EXPECT().
|
||||
peerManagerMock.EXPECT().
|
||||
GetResponsiblePeers(gomock.Any()).
|
||||
Return([]peer.Peer{mockPeer{}}, nil)
|
||||
fx.diffMock.EXPECT().
|
||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(fx.spaceState.SpaceId, fx.clientMock))).
|
||||
diffMock.EXPECT().
|
||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(spaceId, clientMock))).
|
||||
Return(nil, nil, nil, spacesyncproto.ErrUnexpected)
|
||||
|
||||
require.NoError(t, fx.diffSyncer.Sync(ctx))
|
||||
require.NoError(t, diffSyncer.Sync(ctx))
|
||||
})
|
||||
|
||||
t.Run("diff syncer sync space is deleted error", func(t *testing.T) {
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.initDiffSyncer(t)
|
||||
defer fx.stop()
|
||||
mPeer := mockPeer{}
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
||||
fx.peerManagerMock.EXPECT().
|
||||
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([]peer.Peer{mockPeer{}}, nil)
|
||||
diffMock.EXPECT().
|
||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(spaceId, 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)
|
||||
stMock.EXPECT().SpaceSettingsId().Return("settingsId")
|
||||
cacheMock.EXPECT().
|
||||
GetTree(gomock.Any(), spaceId, "settingsId").
|
||||
Return(nil, nil)
|
||||
|
||||
require.NoError(t, fx.diffSyncer.Sync(ctx))
|
||||
require.NoError(t, diffSyncer.Sync(ctx))
|
||||
})
|
||||
}
|
||||
+107
-114
@@ -1,152 +1,125 @@
|
||||
//go:generate mockgen -destination mock_headsync/mock_headsync.go github.com/anyproto/any-sync/commonspace/headsync DiffSyncer
|
||||
//go:generate mockgen -destination mock_headsync/mock_headsync.go github.com/anytypeio/any-sync/commonspace/headsync DiffSyncer
|
||||
package headsync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"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/acl/syncacl"
|
||||
"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"
|
||||
"github.com/anytypeio/any-sync/app/ldiff"
|
||||
"github.com/anytypeio/any-sync/app/logger"
|
||||
"github.com/anytypeio/any-sync/commonspace/credentialprovider"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/treemanager"
|
||||
"github.com/anytypeio/any-sync/commonspace/peermanager"
|
||||
"github.com/anytypeio/any-sync/commonspace/settings/settingsstate"
|
||||
"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/nodeconf"
|
||||
"github.com/anytypeio/any-sync/util/periodicsync"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/exp/slices"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
var log = logger.NewNamed(CName)
|
||||
|
||||
const CName = "common.commonspace.headsync"
|
||||
|
||||
type TreeHeads struct {
|
||||
Id string
|
||||
Heads []string
|
||||
}
|
||||
|
||||
type HeadSync interface {
|
||||
app.ComponentRunnable
|
||||
ExternalIds() []string
|
||||
DebugAllHeads() (res []TreeHeads)
|
||||
AllIds() []string
|
||||
Init(objectIds []string, deletionState settingsstate.ObjectDeletionState)
|
||||
|
||||
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
|
||||
configuration nodeconf.NodeConf
|
||||
spaceIsDeleted *atomic.Bool
|
||||
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
|
||||
syncAcl syncacl.SyncAcl
|
||||
syncPeriod int
|
||||
}
|
||||
|
||||
func New() HeadSync {
|
||||
return &headSync{}
|
||||
}
|
||||
func NewHeadSync(
|
||||
spaceId string,
|
||||
spaceIsDeleted *atomic.Bool,
|
||||
syncPeriod int,
|
||||
configuration nodeconf.NodeConf,
|
||||
storage spacestorage.SpaceStorage,
|
||||
peerManager peermanager.PeerManager,
|
||||
cache treemanager.TreeManager,
|
||||
syncStatus syncstatus.StatusUpdater,
|
||||
credentialProvider credentialprovider.CredentialProvider,
|
||||
log logger.CtxLogger) HeadSync {
|
||||
|
||||
var createDiffSyncer = newDiffSyncer
|
||||
|
||||
func (h *headSync) Init(a *app.App) (err error) {
|
||||
shared := a.MustComponent(spacestate.CName).(*spacestate.SpaceState)
|
||||
cfg := a.MustComponent("config").(config2.ConfigGetter)
|
||||
h.syncAcl = a.MustComponent(syncacl.CName).(syncacl.SyncAcl)
|
||||
h.spaceId = shared.SpaceId
|
||||
h.spaceIsDeleted = shared.SpaceIsDeleted
|
||||
h.syncPeriod = cfg.GetSpace().SyncPeriod
|
||||
h.configuration = a.MustComponent(nodeconf.CName).(nodeconf.NodeConf)
|
||||
h.log = log.With(zap.String("spaceId", h.spaceId))
|
||||
h.storage = a.MustComponent(spacestorage.CName).(spacestorage.SpaceStorage)
|
||||
h.diff = ldiff.New(16, 16)
|
||||
h.peerManager = a.MustComponent(peermanager.CName).(peermanager.PeerManager)
|
||||
h.credentialProvider = a.MustComponent(credentialprovider.CName).(credentialprovider.CredentialProvider)
|
||||
h.syncStatus = a.MustComponent(syncstatus.CName).(syncstatus.StatusService)
|
||||
h.treeManager = a.MustComponent(treemanager.CName).(treemanager.TreeManager)
|
||||
h.deletionState = a.MustComponent(deletionstate.CName).(deletionstate.ObjectDeletionState)
|
||||
h.syncer = createDiffSyncer(h)
|
||||
diff := ldiff.New(16, 16)
|
||||
l := log.With(zap.String("spaceId", spaceId))
|
||||
factory := spacesyncproto.ClientFactoryFunc(spacesyncproto.NewDRPCSpaceSyncClient)
|
||||
syncer := newDiffSyncer(spaceId, diff, peerManager, cache, storage, factory, syncStatus, credentialProvider, l)
|
||||
sync := func(ctx context.Context) (err error) {
|
||||
// for clients cancelling the sync process
|
||||
if h.spaceIsDeleted.Load() && !h.configuration.IsResponsible(h.spaceId) {
|
||||
if spaceIsDeleted.Load() && !configuration.IsResponsible(spaceId) {
|
||||
return spacesyncproto.ErrSpaceIsDeleted
|
||||
}
|
||||
return h.syncer.Sync(ctx)
|
||||
return syncer.Sync(ctx)
|
||||
}
|
||||
h.periodicSync = periodicsync.NewPeriodicSync(h.syncPeriod, time.Minute, sync, h.log)
|
||||
h.syncAcl.SetHeadUpdater(h)
|
||||
// TODO: move to run?
|
||||
h.syncer.Init()
|
||||
return nil
|
||||
}
|
||||
periodicSync := periodicsync.NewPeriodicSync(syncPeriod, time.Minute, sync, l)
|
||||
|
||||
func (h *headSync) Name() (name string) {
|
||||
return CName
|
||||
}
|
||||
|
||||
func (h *headSync) Run(ctx context.Context) (err error) {
|
||||
initialIds, err := h.storage.StoredIds()
|
||||
if err != nil {
|
||||
return
|
||||
return &headSync{
|
||||
spaceId: spaceId,
|
||||
storage: storage,
|
||||
syncer: syncer,
|
||||
periodicSync: periodicSync,
|
||||
diff: diff,
|
||||
log: log,
|
||||
syncPeriod: syncPeriod,
|
||||
configuration: configuration,
|
||||
spaceIsDeleted: spaceIsDeleted,
|
||||
}
|
||||
h.fillDiff(initialIds)
|
||||
h.periodicSync.Run()
|
||||
return
|
||||
}
|
||||
|
||||
func (h *headSync) HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error) {
|
||||
if h.spaceIsDeleted.Load() {
|
||||
func (d *headSync) Init(objectIds []string, deletionState settingsstate.ObjectDeletionState) {
|
||||
d.fillDiff(objectIds)
|
||||
d.syncer.Init(deletionState)
|
||||
d.periodicSync.Run()
|
||||
}
|
||||
|
||||
func (d *headSync) HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error) {
|
||||
if d.spaceIsDeleted.Load() {
|
||||
peerId, err := peer.CtxPeerId(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// stop receiving all request for sync from clients
|
||||
if !slices.Contains(h.configuration.NodeIds(h.spaceId), peerId) {
|
||||
if !slices.Contains(d.configuration.NodeIds(d.spaceId), peerId) {
|
||||
return nil, spacesyncproto.ErrSpaceIsDeleted
|
||||
}
|
||||
}
|
||||
return HandleRangeRequest(ctx, h.diff, req)
|
||||
return HandleRangeRequest(ctx, d.diff, req)
|
||||
}
|
||||
|
||||
func (h *headSync) UpdateHeads(id string, heads []string) {
|
||||
h.syncer.UpdateHeads(id, heads)
|
||||
func (d *headSync) UpdateHeads(id string, heads []string) {
|
||||
d.syncer.UpdateHeads(id, heads)
|
||||
}
|
||||
|
||||
func (h *headSync) AllIds() []string {
|
||||
return h.diff.Ids()
|
||||
func (d *headSync) AllIds() []string {
|
||||
return d.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()
|
||||
func (d *headSync) DebugAllHeads() (res []TreeHeads) {
|
||||
els := d.diff.Elements()
|
||||
for _, el := range els {
|
||||
idHead := TreeHeads{
|
||||
Id: el.Id,
|
||||
@@ -157,19 +130,19 @@ func (h *headSync) DebugAllHeads() (res []TreeHeads) {
|
||||
return
|
||||
}
|
||||
|
||||
func (h *headSync) RemoveObjects(ids []string) {
|
||||
h.syncer.RemoveObjects(ids)
|
||||
func (d *headSync) RemoveObjects(ids []string) {
|
||||
d.syncer.RemoveObjects(ids)
|
||||
}
|
||||
|
||||
func (h *headSync) Close(ctx context.Context) (err error) {
|
||||
h.periodicSync.Close()
|
||||
return h.syncer.Close()
|
||||
func (d *headSync) Close() (err error) {
|
||||
d.periodicSync.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *headSync) fillDiff(objectIds []string) {
|
||||
func (d *headSync) fillDiff(objectIds []string) {
|
||||
var els = make([]ldiff.Element, 0, len(objectIds))
|
||||
for _, id := range objectIds {
|
||||
st, err := h.storage.TreeStorage(id)
|
||||
st, err := d.storage.TreeStorage(id)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -182,12 +155,32 @@ func (h *headSync) fillDiff(objectIds []string) {
|
||||
Head: concatStrings(heads),
|
||||
})
|
||||
}
|
||||
els = append(els, ldiff.Element{
|
||||
Id: h.syncAcl.Id(),
|
||||
Head: h.syncAcl.Head().Id,
|
||||
})
|
||||
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))
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
func concatStrings(strs []string) string {
|
||||
var (
|
||||
b strings.Builder
|
||||
totalLen int
|
||||
)
|
||||
for _, s := range strs {
|
||||
totalLen += len(s)
|
||||
}
|
||||
|
||||
b.Grow(totalLen)
|
||||
for _, s := range strs {
|
||||
b.WriteString(s)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func splitString(str string) (res []string) {
|
||||
const cidLen = 59
|
||||
for i := 0; i < len(str); i += cidLen {
|
||||
res = append(res, str[i:i+cidLen])
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,190 +1,70 @@
|
||||
package headsync
|
||||
|
||||
import (
|
||||
"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/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl/mock_syncacl"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage/mock_treestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||
"github.com/anyproto/any-sync/commonspace/object/treemanager/mock_treemanager"
|
||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
||||
"github.com/anyproto/any-sync/commonspace/peermanager/mock_peermanager"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestate"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage/mock_spacestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto/mock_spacesyncproto"
|
||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||
"github.com/anyproto/any-sync/nodeconf"
|
||||
"github.com/anyproto/any-sync/nodeconf/mock_nodeconf"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
"sync/atomic"
|
||||
"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/settingsstate/mock_settingsstate"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacestorage/mock_spacestorage"
|
||||
"github.com/anytypeio/any-sync/util/periodicsync/mock_periodicsync"
|
||||
"github.com/golang/mock/gomock"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type mockConfig struct {
|
||||
}
|
||||
|
||||
func (m mockConfig) Init(a *app.App) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m mockConfig) Name() (name string) {
|
||||
return "config"
|
||||
}
|
||||
|
||||
func (m mockConfig) GetSpace() config.Config {
|
||||
return config.Config{}
|
||||
}
|
||||
|
||||
type headSyncFixture struct {
|
||||
spaceState *spacestate.SpaceState
|
||||
ctrl *gomock.Controller
|
||||
app *app.App
|
||||
|
||||
configurationMock *mock_nodeconf.MockService
|
||||
storageMock *mock_spacestorage.MockSpaceStorage
|
||||
peerManagerMock *mock_peermanager.MockPeerManager
|
||||
credentialProviderMock *mock_credentialprovider.MockCredentialProvider
|
||||
syncStatus syncstatus.StatusService
|
||||
treeManagerMock *mock_treemanager.MockTreeManager
|
||||
deletionStateMock *mock_deletionstate.MockObjectDeletionState
|
||||
diffSyncerMock *mock_headsync.MockDiffSyncer
|
||||
treeSyncerMock *mock_treemanager.MockTreeSyncer
|
||||
diffMock *mock_ldiff.MockDiff
|
||||
clientMock *mock_spacesyncproto.MockDRPCSpaceSyncClient
|
||||
aclMock *mock_syncacl.MockSyncAcl
|
||||
headSync *headSync
|
||||
diffSyncer *diffSyncer
|
||||
}
|
||||
|
||||
func newHeadSyncFixture(t *testing.T) *headSyncFixture {
|
||||
spaceState := &spacestate.SpaceState{
|
||||
SpaceId: "spaceId",
|
||||
SpaceIsDeleted: &atomic.Bool{},
|
||||
}
|
||||
func TestDiffService(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
configurationMock := mock_nodeconf.NewMockService(ctrl)
|
||||
configurationMock.EXPECT().Name().AnyTimes().Return(nodeconf.CName)
|
||||
defer ctrl.Finish()
|
||||
|
||||
spaceId := "spaceId"
|
||||
l := logger.NewNamed("sync")
|
||||
pSyncMock := mock_periodicsync.NewMockPeriodicSync(ctrl)
|
||||
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)
|
||||
treeStorageMock := mock_treestorage.NewMockTreeStorage(ctrl)
|
||||
diffMock := mock_ldiff.NewMockDiff(ctrl)
|
||||
clientMock := mock_spacesyncproto.NewMockDRPCSpaceSyncClient(ctrl)
|
||||
aclMock := mock_syncacl.NewMockSyncAcl(ctrl)
|
||||
aclMock.EXPECT().Name().AnyTimes().Return(syncacl.CName)
|
||||
aclMock.EXPECT().SetHeadUpdater(gomock.Any()).AnyTimes()
|
||||
hs := &headSync{}
|
||||
a := &app.App{}
|
||||
a.Register(spaceState).
|
||||
Register(aclMock).
|
||||
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,
|
||||
aclMock: aclMock,
|
||||
syncer := mock_headsync.NewMockDiffSyncer(ctrl)
|
||||
delState := mock_settingsstate.NewMockObjectDeletionState(ctrl)
|
||||
syncPeriod := 1
|
||||
initId := "initId"
|
||||
|
||||
service := &headSync{
|
||||
spaceId: spaceId,
|
||||
storage: storageMock,
|
||||
periodicSync: pSyncMock,
|
||||
syncer: syncer,
|
||||
diff: diffMock,
|
||||
log: l,
|
||||
syncPeriod: syncPeriod,
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return("aclId")
|
||||
fx.aclMock.EXPECT().Head().AnyTimes().Return(&list.AclRecord{Id: "headId"})
|
||||
treeMock.EXPECT().Heads().Return([]string{"h1", "h2"}, nil)
|
||||
fx.diffMock.EXPECT().Set(ldiff.Element{
|
||||
Id: "id1",
|
||||
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,
|
||||
Head: "h1h2",
|
||||
})
|
||||
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)
|
||||
hash := "123"
|
||||
diffMock.EXPECT().Hash().Return(hash)
|
||||
storageMock.EXPECT().WriteSpaceHash(hash)
|
||||
pSyncMock.EXPECT().Run()
|
||||
service.Init([]string{initId}, delState)
|
||||
})
|
||||
|
||||
t.Run("update heads", func(t *testing.T) {
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.init(t)
|
||||
defer fx.stop()
|
||||
|
||||
fx.diffSyncerMock.EXPECT().UpdateHeads("id1", []string{"h1"})
|
||||
fx.headSync.UpdateHeads("id1", []string{"h1"})
|
||||
syncer.EXPECT().UpdateHeads(initId, []string{"h1", "h2"})
|
||||
service.UpdateHeads(initId, []string{"h1", "h2"})
|
||||
})
|
||||
|
||||
t.Run("remove objects", func(t *testing.T) {
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.init(t)
|
||||
defer fx.stop()
|
||||
syncer.EXPECT().RemoveObjects([]string{"h1", "h2"})
|
||||
service.RemoveObjects([]string{"h1", "h2"})
|
||||
})
|
||||
|
||||
fx.diffSyncerMock.EXPECT().RemoveObjects([]string{"id1"})
|
||||
fx.headSync.RemoveObjects([]string{"id1"})
|
||||
t.Run("close", func(t *testing.T) {
|
||||
pSyncMock.EXPECT().Close()
|
||||
service.Close()
|
||||
})
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anyproto/any-sync/commonspace/headsync (interfaces: DiffSyncer)
|
||||
// Source: github.com/anytypeio/any-sync/commonspace/headsync (interfaces: DiffSyncer)
|
||||
|
||||
// Package mock_headsync is a generated GoMock package.
|
||||
package mock_headsync
|
||||
@@ -8,7 +8,8 @@ import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
settingsstate "github.com/anytypeio/any-sync/commonspace/settings/settingsstate"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockDiffSyncer is a mock of DiffSyncer interface.
|
||||
@@ -34,30 +35,16 @@ func (m *MockDiffSyncer) EXPECT() *MockDiffSyncerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Close mocks base method.
|
||||
func (m *MockDiffSyncer) Close() error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Close")
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Close indicates an expected call of Close.
|
||||
func (mr *MockDiffSyncerMockRecorder) Close() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockDiffSyncer)(nil).Close))
|
||||
}
|
||||
|
||||
// Init mocks base method.
|
||||
func (m *MockDiffSyncer) Init() {
|
||||
func (m *MockDiffSyncer) Init(arg0 settingsstate.ObjectDeletionState) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Init")
|
||||
m.ctrl.Call(m, "Init", arg0)
|
||||
}
|
||||
|
||||
// Init indicates an expected call of Init.
|
||||
func (mr *MockDiffSyncerMockRecorder) Init() *gomock.Call {
|
||||
func (mr *MockDiffSyncerMockRecorder) Init(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockDiffSyncer)(nil).Init))
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockDiffSyncer)(nil).Init), arg0)
|
||||
}
|
||||
|
||||
// RemoveObjects mocks base method.
|
||||
|
||||
@@ -2,8 +2,8 @@ package headsync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anyproto/any-sync/app/ldiff"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/any-sync/app/ldiff"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
)
|
||||
|
||||
type Client interface {
|
||||
|
||||
@@ -3,8 +3,8 @@ package headsync
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/app/ldiff"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/any-sync/app/ldiff"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
package headsync
|
||||
|
||||
import "strings"
|
||||
|
||||
func concatStrings(strs []string) string {
|
||||
var (
|
||||
b strings.Builder
|
||||
totalLen int
|
||||
)
|
||||
for _, s := range strs {
|
||||
totalLen += len(s)
|
||||
}
|
||||
|
||||
b.Grow(totalLen)
|
||||
for _, s := range strs {
|
||||
b.WriteString(s)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func splitString(str string) (res []string) {
|
||||
const cidLen = 59
|
||||
for i := 0; i < len(str); i += cidLen {
|
||||
res = append(res, str[i:i+cidLen])
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package accountdata
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
)
|
||||
|
||||
type AccountKeys struct {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -2,7 +2,26 @@ syntax = "proto3";
|
||||
package aclrecord;
|
||||
option go_package = "commonspace/object/acl/aclrecordproto";
|
||||
|
||||
// AclRoot is a root of access control list
|
||||
message RawAclRecord {
|
||||
bytes payload = 1;
|
||||
bytes signature = 2;
|
||||
bytes acceptorIdentity = 3;
|
||||
bytes acceptorSignature = 4;
|
||||
}
|
||||
|
||||
message RawAclRecordWithId {
|
||||
bytes payload = 1;
|
||||
string id = 2;
|
||||
}
|
||||
|
||||
message AclRecord {
|
||||
string prevId = 1;
|
||||
bytes identity = 2;
|
||||
bytes data = 3;
|
||||
string readKeyId = 4;
|
||||
int64 timestamp = 5;
|
||||
}
|
||||
|
||||
message AclRoot {
|
||||
bytes identity = 1;
|
||||
bytes masterKey = 2;
|
||||
@@ -12,95 +31,82 @@ message AclRoot {
|
||||
bytes identitySignature = 6;
|
||||
}
|
||||
|
||||
// AclAccountInvite contains the public invite key, the private part of which is sent to the user directly
|
||||
message AclAccountInvite {
|
||||
bytes inviteKey = 1;
|
||||
}
|
||||
|
||||
// AclAccountRequestJoin contains the reference to the invite record and the data of the person who wants to join, confirmed by the private invite key
|
||||
message AclAccountRequestJoin {
|
||||
bytes inviteIdentity = 1;
|
||||
string inviteRecordId = 2;
|
||||
bytes inviteIdentitySignature = 3;
|
||||
bytes metadata = 4;
|
||||
}
|
||||
|
||||
// AclAccountRequestAccept contains the reference to join record and all read keys, encrypted with the identity of the requestor
|
||||
message AclAccountRequestAccept {
|
||||
bytes identity = 1;
|
||||
string requestRecordId = 2;
|
||||
repeated AclReadKeyWithRecord encryptedReadKeys = 3;
|
||||
AclUserPermissions permissions = 4;
|
||||
}
|
||||
|
||||
// AclAccountRequestDecline contains the reference to join record
|
||||
message AclAccountRequestDecline {
|
||||
string requestRecordId = 1;
|
||||
}
|
||||
|
||||
// AclAccountInviteRevoke revokes the invite record
|
||||
message AclAccountInviteRevoke {
|
||||
string inviteRecordId = 1;
|
||||
}
|
||||
|
||||
// AclReadKeys are a read key with record id
|
||||
message AclReadKeyWithRecord {
|
||||
string recordId = 1;
|
||||
bytes encryptedReadKey = 2;
|
||||
}
|
||||
|
||||
// AclEncryptedReadKeys are new key for specific identity
|
||||
message AclEncryptedReadKey {
|
||||
bytes identity = 1;
|
||||
bytes encryptedReadKey = 2;
|
||||
}
|
||||
|
||||
// AclAccountPermissionChange changes permissions of specific account
|
||||
message AclAccountPermissionChange {
|
||||
bytes identity = 1;
|
||||
AclUserPermissions permissions = 2;
|
||||
}
|
||||
|
||||
// AclReadKeyChange changes the key for a space
|
||||
message AclReadKeyChange {
|
||||
repeated AclEncryptedReadKey accountKeys = 1;
|
||||
}
|
||||
|
||||
// AclAccountRemove removes an account and changes read key for space
|
||||
message AclAccountRemove {
|
||||
repeated bytes identities = 1;
|
||||
repeated AclEncryptedReadKey accountKeys = 2;
|
||||
}
|
||||
|
||||
// AclAccountRequestRemove adds a request to remove an account
|
||||
message AclAccountRequestRemove {
|
||||
}
|
||||
|
||||
// AclContentValue contains possible values for Acl
|
||||
message AclContentValue {
|
||||
oneof value {
|
||||
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;
|
||||
AclUserAdd userAdd = 1;
|
||||
AclUserRemove userRemove = 2;
|
||||
AclUserPermissionChange userPermissionChange = 3;
|
||||
AclUserInvite userInvite = 4;
|
||||
AclUserJoin userJoin = 5;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
message AclState {
|
||||
repeated string readKeyIds = 1;
|
||||
repeated AclUserState userStates = 2;
|
||||
map<string, AclUserInvite> invites = 3;
|
||||
}
|
||||
|
||||
message AclUserState {
|
||||
bytes identity = 1;
|
||||
AclUserPermissions permissions = 2;
|
||||
}
|
||||
|
||||
message AclUserAdd {
|
||||
bytes identity = 1;
|
||||
repeated bytes encryptedReadKeys = 2;
|
||||
AclUserPermissions permissions = 3;
|
||||
}
|
||||
|
||||
message AclUserInvite {
|
||||
bytes acceptPublicKey = 1;
|
||||
repeated bytes encryptedReadKeys = 2;
|
||||
AclUserPermissions permissions = 3;
|
||||
}
|
||||
|
||||
message AclUserJoin {
|
||||
bytes identity = 1;
|
||||
bytes acceptSignature = 2;
|
||||
bytes acceptPubKey = 3;
|
||||
repeated bytes encryptedReadKeys = 4;
|
||||
}
|
||||
|
||||
message AclUserRemove {
|
||||
bytes identity = 1;
|
||||
repeated AclReadKeyReplace readKeyReplaces = 2;
|
||||
}
|
||||
|
||||
message AclReadKeyReplace {
|
||||
bytes identity = 1;
|
||||
bytes encryptedReadKey = 2;
|
||||
}
|
||||
|
||||
message AclUserPermissionChange {
|
||||
bytes identity = 1;
|
||||
AclUserPermissions permissions = 2;
|
||||
}
|
||||
|
||||
enum AclUserPermissions {
|
||||
Admin = 0;
|
||||
Writer = 1;
|
||||
Reader = 2;
|
||||
}
|
||||
|
||||
message AclSyncMessage {
|
||||
AclSyncContentValue content = 1;
|
||||
}
|
||||
|
||||
// AclSyncContentValue provides different types for acl sync
|
||||
message AclSyncContentValue {
|
||||
oneof value {
|
||||
AclAddRecords addRecords = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message AclAddRecords {
|
||||
repeated RawAclRecordWithId records = 1;
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"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/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anytypeio/any-sync/util/cidutil"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RootContent struct {
|
||||
@@ -18,387 +15,26 @@ type RootContent struct {
|
||||
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 {
|
||||
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)
|
||||
Unmarshall(rawIdRecord *aclrecordproto.RawAclRecordWithId) (rec *AclRecord, err error)
|
||||
BuildRoot(content RootContent) (rec *aclrecordproto.RawAclRecordWithId, err error)
|
||||
}
|
||||
|
||||
type aclRecordBuilder struct {
|
||||
id string
|
||||
keyStorage crypto.KeyStorage
|
||||
accountKeys *accountdata.AccountKeys
|
||||
verifier AcceptorVerifier
|
||||
state *AclState
|
||||
id string
|
||||
keyStorage crypto.KeyStorage
|
||||
}
|
||||
|
||||
func NewAclRecordBuilder(id string, keyStorage crypto.KeyStorage, keys *accountdata.AccountKeys, verifier AcceptorVerifier) AclRecordBuilder {
|
||||
func NewAclRecordBuilder(id string, keyStorage crypto.KeyStorage) AclRecordBuilder {
|
||||
return &aclRecordBuilder{
|
||||
id: id,
|
||||
keyStorage: keyStorage,
|
||||
accountKeys: keys,
|
||||
verifier: verifier,
|
||||
id: id,
|
||||
keyStorage: keyStorage,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) buildRecord(aclContent *aclrecordproto.AclContentValue) (rawRec *consensusproto.RawRecord, err error) {
|
||||
aclData := &aclrecordproto.AclData{AclContent: []*aclrecordproto.AclContentValue{
|
||||
aclContent,
|
||||
}}
|
||||
marshalledData, err := aclData.Marshal()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
protoKey, err := a.accountKeys.SignKey.GetPublic().Marshall()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rec := &consensusproto.Record{
|
||||
PrevId: a.state.lastRecordId,
|
||||
Identity: protoKey,
|
||||
Data: marshalledData,
|
||||
Timestamp: time.Now().Unix(),
|
||||
}
|
||||
marshalledRec, err := rec.Marshal()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
signature, err := a.accountKeys.SignKey.Sign(marshalledRec)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rawRec = &consensusproto.RawRecord{
|
||||
Payload: marshalledRec,
|
||||
Signature: signature,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildInvite() (res InviteResult, err error) {
|
||||
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
||||
err = ErrInsufficientPermissions
|
||||
return
|
||||
}
|
||||
privKey, pubKey, err := crypto.GenerateRandomEd25519KeyPair()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
invitePubKey, err := pubKey.Marshall()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
inviteRec := &aclrecordproto.AclAccountInvite{InviteKey: invitePubKey}
|
||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_Invite{Invite: inviteRec}}
|
||||
rawRec, err := a.buildRecord(content)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
res.InviteKey = privKey
|
||||
res.InviteRec = rawRec
|
||||
return
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildInviteRevoke(inviteRecordId string) (rawRecord *consensusproto.RawRecord, err error) {
|
||||
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
||||
err = ErrInsufficientPermissions
|
||||
return
|
||||
}
|
||||
_, exists := a.state.inviteKeys[inviteRecordId]
|
||||
if !exists {
|
||||
err = ErrNoSuchInvite
|
||||
return
|
||||
}
|
||||
revokeRec := &aclrecordproto.AclAccountInviteRevoke{InviteRecordId: inviteRecordId}
|
||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_InviteRevoke{InviteRevoke: revokeRec}}
|
||||
return a.buildRecord(content)
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildRequestJoin(payload RequestJoinPayload) (rawRecord *consensusproto.RawRecord, err error) {
|
||||
key, exists := a.state.inviteKeys[payload.InviteRecordId]
|
||||
if !exists {
|
||||
err = ErrNoSuchInvite
|
||||
return
|
||||
}
|
||||
if !payload.InviteKey.GetPublic().Equals(key) {
|
||||
err = ErrIncorrectInviteKey
|
||||
}
|
||||
rawIdentity, err := a.accountKeys.SignKey.GetPublic().Raw()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
signature, err := payload.InviteKey.Sign(rawIdentity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
protoIdentity, err := a.accountKeys.SignKey.GetPublic().Marshall()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
joinRec := &aclrecordproto.AclAccountRequestJoin{
|
||||
InviteIdentity: protoIdentity,
|
||||
InviteRecordId: payload.InviteRecordId,
|
||||
InviteIdentitySignature: signature,
|
||||
Metadata: payload.Metadata,
|
||||
}
|
||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_RequestJoin{RequestJoin: joinRec}}
|
||||
return a.buildRecord(content)
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildRequestAccept(payload RequestAcceptPayload) (rawRecord *consensusproto.RawRecord, err error) {
|
||||
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
||||
err = ErrInsufficientPermissions
|
||||
return
|
||||
}
|
||||
request, exists := a.state.requestRecords[payload.RequestRecordId]
|
||||
if !exists {
|
||||
err = ErrNoSuchRequest
|
||||
return
|
||||
}
|
||||
var encryptedReadKeys []*aclrecordproto.AclReadKeyWithRecord
|
||||
for keyId, key := range a.state.userReadKeys {
|
||||
rawKey, err := key.Raw()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
enc, err := request.RequestIdentity.Encrypt(rawKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encryptedReadKeys = append(encryptedReadKeys, &aclrecordproto.AclReadKeyWithRecord{
|
||||
RecordId: keyId,
|
||||
EncryptedReadKey: enc,
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
requestIdentityProto, err := request.RequestIdentity.Marshall()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
acceptRec := &aclrecordproto.AclAccountRequestAccept{
|
||||
Identity: requestIdentityProto,
|
||||
RequestRecordId: payload.RequestRecordId,
|
||||
EncryptedReadKeys: encryptedReadKeys,
|
||||
Permissions: aclrecordproto.AclUserPermissions(payload.Permissions),
|
||||
}
|
||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_RequestAccept{RequestAccept: acceptRec}}
|
||||
return a.buildRecord(content)
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildRequestDecline(requestRecordId string) (rawRecord *consensusproto.RawRecord, err error) {
|
||||
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
||||
err = ErrInsufficientPermissions
|
||||
return
|
||||
}
|
||||
_, exists := a.state.requestRecords[requestRecordId]
|
||||
if !exists {
|
||||
err = ErrNoSuchRequest
|
||||
return
|
||||
}
|
||||
declineRec := &aclrecordproto.AclAccountRequestDecline{RequestRecordId: requestRecordId}
|
||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_RequestDecline{RequestDecline: declineRec}}
|
||||
return a.buildRecord(content)
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildPermissionChange(payload PermissionChangePayload) (rawRecord *consensusproto.RawRecord, err error) {
|
||||
permissions := a.state.Permissions(a.state.pubKey)
|
||||
if !permissions.CanManageAccounts() || payload.Identity.Equals(a.state.pubKey) {
|
||||
err = ErrInsufficientPermissions
|
||||
return
|
||||
}
|
||||
if payload.Permissions.IsOwner() {
|
||||
err = ErrIsOwner
|
||||
return
|
||||
}
|
||||
protoIdentity, err := payload.Identity.Marshall()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
permissionRec := &aclrecordproto.AclAccountPermissionChange{
|
||||
Identity: protoIdentity,
|
||||
Permissions: aclrecordproto.AclUserPermissions(payload.Permissions),
|
||||
}
|
||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_PermissionChange{PermissionChange: permissionRec}}
|
||||
return a.buildRecord(content)
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildReadKeyChange(newKey crypto.SymKey) (rawRecord *consensusproto.RawRecord, err error) {
|
||||
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
||||
err = ErrInsufficientPermissions
|
||||
return
|
||||
}
|
||||
rawKey, err := newKey.Raw()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(rawKey) != crypto.KeyBytes {
|
||||
err = ErrIncorrectReadKey
|
||||
return
|
||||
}
|
||||
var aclReadKeys []*aclrecordproto.AclEncryptedReadKey
|
||||
for _, st := range a.state.userStates {
|
||||
protoIdentity, err := st.PubKey.Marshall()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
enc, err := st.PubKey.Encrypt(rawKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aclReadKeys = append(aclReadKeys, &aclrecordproto.AclEncryptedReadKey{
|
||||
Identity: protoIdentity,
|
||||
EncryptedReadKey: enc,
|
||||
})
|
||||
}
|
||||
readRec := &aclrecordproto.AclReadKeyChange{AccountKeys: aclReadKeys}
|
||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_ReadKeyChange{ReadKeyChange: readRec}}
|
||||
return a.buildRecord(content)
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildAccountRemove(payload AccountRemovePayload) (rawRecord *consensusproto.RawRecord, err error) {
|
||||
deletedMap := map[string]struct{}{}
|
||||
for _, key := range payload.Identities {
|
||||
permissions := a.state.Permissions(key)
|
||||
if permissions.IsOwner() {
|
||||
return nil, ErrInsufficientPermissions
|
||||
}
|
||||
if permissions.NoPermissions() {
|
||||
return nil, ErrNoSuchAccount
|
||||
}
|
||||
deletedMap[mapKeyFromPubKey(key)] = struct{}{}
|
||||
}
|
||||
if !a.state.Permissions(a.state.pubKey).CanManageAccounts() {
|
||||
err = ErrInsufficientPermissions
|
||||
return
|
||||
}
|
||||
rawKey, err := payload.ReadKey.Raw()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(rawKey) != crypto.KeyBytes {
|
||||
err = ErrIncorrectReadKey
|
||||
return
|
||||
}
|
||||
var aclReadKeys []*aclrecordproto.AclEncryptedReadKey
|
||||
for _, st := range a.state.userStates {
|
||||
if _, exists := deletedMap[mapKeyFromPubKey(st.PubKey)]; exists {
|
||||
continue
|
||||
}
|
||||
protoIdentity, err := st.PubKey.Marshall()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
enc, err := st.PubKey.Encrypt(rawKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aclReadKeys = append(aclReadKeys, &aclrecordproto.AclEncryptedReadKey{
|
||||
Identity: protoIdentity,
|
||||
EncryptedReadKey: enc,
|
||||
})
|
||||
}
|
||||
var marshalledIdentities [][]byte
|
||||
for _, key := range payload.Identities {
|
||||
protoIdentity, err := key.Marshall()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
marshalledIdentities = append(marshalledIdentities, protoIdentity)
|
||||
}
|
||||
removeRec := &aclrecordproto.AclAccountRemove{AccountKeys: aclReadKeys, Identities: marshalledIdentities}
|
||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_AccountRemove{AccountRemove: removeRec}}
|
||||
return a.buildRecord(content)
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildRequestRemove() (rawRecord *consensusproto.RawRecord, err error) {
|
||||
permissions := a.state.Permissions(a.state.pubKey)
|
||||
if permissions.NoPermissions() {
|
||||
err = ErrNoSuchAccount
|
||||
return
|
||||
}
|
||||
if permissions.IsOwner() {
|
||||
err = ErrIsOwner
|
||||
return
|
||||
}
|
||||
removeRec := &aclrecordproto.AclAccountRequestRemove{}
|
||||
content := &aclrecordproto.AclContentValue{Value: &aclrecordproto.AclContentValue_AccountRequestRemove{AccountRequestRemove: removeRec}}
|
||||
return a.buildRecord(content)
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) Unmarshall(rawRecord *consensusproto.RawRecord) (rec *AclRecord, err error) {
|
||||
aclRecord := &consensusproto.Record{}
|
||||
err = proto.Unmarshal(rawRecord.Payload, aclRecord)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pubKey, err := a.keyStorage.PubKeyFromProto(aclRecord.Identity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
aclData := &aclrecordproto.AclData{}
|
||||
err = proto.Unmarshal(aclRecord.Data, aclData)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rec = &AclRecord{
|
||||
PrevId: aclRecord.PrevId,
|
||||
Timestamp: aclRecord.Timestamp,
|
||||
Data: aclRecord.Data,
|
||||
Signature: rawRecord.Signature,
|
||||
Identity: pubKey,
|
||||
Model: aclData,
|
||||
}
|
||||
res, err := pubKey.Verify(rawRecord.Payload, rawRecord.Signature)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !res {
|
||||
err = ErrInvalidSignature
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) UnmarshallWithId(rawIdRecord *consensusproto.RawRecordWithId) (rec *AclRecord, err error) {
|
||||
func (a *aclRecordBuilder) Unmarshall(rawIdRecord *aclrecordproto.RawAclRecordWithId) (rec *AclRecord, err error) {
|
||||
var (
|
||||
rawRec = &consensusproto.RawRecord{}
|
||||
rawRec = &aclrecordproto.RawAclRecord{}
|
||||
pubKey crypto.PubKey
|
||||
)
|
||||
err = proto.Unmarshal(rawIdRecord.Payload, rawRec)
|
||||
@@ -417,17 +53,14 @@ func (a *aclRecordBuilder) UnmarshallWithId(rawIdRecord *consensusproto.RawRecor
|
||||
}
|
||||
rec = &AclRecord{
|
||||
Id: rawIdRecord.Id,
|
||||
ReadKeyId: rawIdRecord.Id,
|
||||
Timestamp: aclRoot.Timestamp,
|
||||
Signature: rawRec.Signature,
|
||||
Identity: pubKey,
|
||||
Model: aclRoot,
|
||||
}
|
||||
} else {
|
||||
err = a.verifier.VerifyAcceptor(rawRec)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
aclRecord := &consensusproto.Record{}
|
||||
aclRecord := &aclrecordproto.AclRecord{}
|
||||
err = proto.Unmarshal(rawRec.Payload, aclRecord)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -436,19 +69,14 @@ func (a *aclRecordBuilder) UnmarshallWithId(rawIdRecord *consensusproto.RawRecor
|
||||
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,
|
||||
ReadKeyId: aclRecord.ReadKeyId,
|
||||
Timestamp: aclRecord.Timestamp,
|
||||
Data: aclRecord.Data,
|
||||
Signature: rawRec.Signature,
|
||||
Identity: pubKey,
|
||||
Model: aclData,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,7 +84,7 @@ func (a *aclRecordBuilder) UnmarshallWithId(rawIdRecord *consensusproto.RawRecor
|
||||
return
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildRoot(content RootContent) (rec *consensusproto.RawRecordWithId, err error) {
|
||||
func (a *aclRecordBuilder) BuildRoot(content RootContent) (rec *aclrecordproto.RawAclRecordWithId, err error) {
|
||||
rawIdentity, err := content.PrivKey.GetPublic().Raw()
|
||||
if err != nil {
|
||||
return
|
||||
@@ -490,8 +118,8 @@ func (a *aclRecordBuilder) BuildRoot(content RootContent) (rec *consensusproto.R
|
||||
|
||||
func verifyRaw(
|
||||
pubKey crypto.PubKey,
|
||||
rawRec *consensusproto.RawRecord,
|
||||
recWithId *consensusproto.RawRecordWithId) (err error) {
|
||||
rawRec *aclrecordproto.RawAclRecord,
|
||||
recWithId *aclrecordproto.RawAclRecordWithId) (err error) {
|
||||
// verifying signature
|
||||
res, err := pubKey.Verify(rawRec.Payload, rawRec.Signature)
|
||||
if err != nil {
|
||||
@@ -509,7 +137,7 @@ func verifyRaw(
|
||||
return
|
||||
}
|
||||
|
||||
func marshalAclRoot(aclRoot *aclrecordproto.AclRoot, key crypto.PrivKey) (rawWithId *consensusproto.RawRecordWithId, err error) {
|
||||
func marshalAclRoot(aclRoot *aclrecordproto.AclRoot, key crypto.PrivKey) (rawWithId *aclrecordproto.RawAclRecordWithId, err error) {
|
||||
marshalledRoot, err := aclRoot.Marshal()
|
||||
if err != nil {
|
||||
return
|
||||
@@ -518,7 +146,7 @@ func marshalAclRoot(aclRoot *aclrecordproto.AclRoot, key crypto.PrivKey) (rawWit
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
raw := &consensusproto.RawRecord{
|
||||
raw := &aclrecordproto.RawAclRecord{
|
||||
Payload: marshalledRoot,
|
||||
Signature: signature,
|
||||
}
|
||||
@@ -530,7 +158,7 @@ func marshalAclRoot(aclRoot *aclrecordproto.AclRoot, key crypto.PrivKey) (rawWit
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rawWithId = &consensusproto.RawRecordWithId{
|
||||
rawWithId = &aclrecordproto.RawAclRecordWithId{
|
||||
Payload: marshalledRaw,
|
||||
Id: aclHeadId,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAclRecordBuilder_BuildUserJoin(t *testing.T) {
|
||||
return
|
||||
}
|
||||
@@ -2,10 +2,10 @@ package list
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"fmt"
|
||||
"github.com/anytypeio/any-sync/app/logger"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -13,24 +13,19 @@ import (
|
||||
var log = logger.NewNamedSugared("common.commonspace.acllist")
|
||||
|
||||
var (
|
||||
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")
|
||||
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")
|
||||
)
|
||||
|
||||
type UserPermissionPair struct {
|
||||
@@ -41,71 +36,37 @@ type UserPermissionPair struct {
|
||||
type AclState struct {
|
||||
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
|
||||
userReadKeys map[string]crypto.SymKey
|
||||
userStates map[string]AclUserState
|
||||
statesAtRecord map[string][]AclUserState
|
||||
key crypto.PrivKey
|
||||
pubKey crypto.PubKey
|
||||
keyStore crypto.KeyStorage
|
||||
totalReadKeys int
|
||||
|
||||
lastRecordId string
|
||||
contentValidator ContentValidator
|
||||
lastRecordId string
|
||||
}
|
||||
|
||||
func newAclStateWithKeys(
|
||||
id string,
|
||||
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(),
|
||||
}
|
||||
st.contentValidator = &contentValidator{
|
||||
keyStore: st.keyStore,
|
||||
aclState: st,
|
||||
}
|
||||
return st, nil
|
||||
return &AclState{
|
||||
id: id,
|
||||
key: key,
|
||||
pubKey: key.GetPublic(),
|
||||
userReadKeys: make(map[string]crypto.SymKey),
|
||||
userStates: make(map[string]AclUserState),
|
||||
statesAtRecord: make(map[string][]AclUserState),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newAclState(id string) *AclState {
|
||||
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(),
|
||||
return &AclState{
|
||||
id: id,
|
||||
userReadKeys: make(map[string]crypto.SymKey),
|
||||
userStates: make(map[string]AclUserState),
|
||||
statesAtRecord: make(map[string][]AclUserState),
|
||||
}
|
||||
st.contentValidator = &contentValidator{
|
||||
keyStore: st.keyStore,
|
||||
aclState: st,
|
||||
}
|
||||
return st
|
||||
}
|
||||
|
||||
func (st *AclState) Validator() ContentValidator {
|
||||
return st.contentValidator
|
||||
}
|
||||
|
||||
func (st *AclState) CurrentReadKeyId() string {
|
||||
@@ -113,7 +74,7 @@ func (st *AclState) CurrentReadKeyId() string {
|
||||
}
|
||||
|
||||
func (st *AclState) CurrentReadKey() (crypto.SymKey, error) {
|
||||
key, exists := st.userReadKeys[st.CurrentReadKeyId()]
|
||||
key, exists := st.userReadKeys[st.currentReadKeyId]
|
||||
if !exists {
|
||||
return nil, ErrNoReadKey
|
||||
}
|
||||
@@ -136,7 +97,7 @@ func (st *AclState) StateAtRecord(id string, pubKey crypto.PubKey) (AclUserState
|
||||
return perm, nil
|
||||
}
|
||||
}
|
||||
return AclUserState{}, ErrNoSuchAccount
|
||||
return AclUserState{}, ErrNoSuchUser
|
||||
}
|
||||
|
||||
func (st *AclState) applyRecord(record *AclRecord) (err error) {
|
||||
@@ -149,18 +110,17 @@ func (st *AclState) applyRecord(record *AclRecord) (err error) {
|
||||
err = ErrIncorrectRecordSequence
|
||||
return
|
||||
}
|
||||
// if the record is root record
|
||||
if record.Id == st.id {
|
||||
err = st.applyRoot(record)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
st.statesAtRecord[record.Id] = []AclUserState{
|
||||
st.userStates[mapKeyFromPubKey(record.Identity)],
|
||||
{PubKey: record.Identity, Permissions: aclrecordproto.AclUserPermissions_Admin},
|
||||
}
|
||||
return
|
||||
}
|
||||
// if the model is not cached
|
||||
|
||||
if record.Model == nil {
|
||||
aclData := &aclrecordproto.AclData{}
|
||||
err = proto.Unmarshal(record.Data, aclData)
|
||||
@@ -169,16 +129,18 @@ func (st *AclState) applyRecord(record *AclRecord) (err error) {
|
||||
}
|
||||
record.Model = aclData
|
||||
}
|
||||
// applying records contents
|
||||
|
||||
err = st.applyChangeData(record)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// getting all states for users at record and saving them
|
||||
|
||||
// getting all states for users at record
|
||||
var states []AclUserState
|
||||
for _, state := range st.userStates {
|
||||
states = append(states, state)
|
||||
}
|
||||
|
||||
st.statesAtRecord[record.Id] = states
|
||||
return
|
||||
}
|
||||
@@ -194,9 +156,9 @@ func (st *AclState) applyRoot(record *AclRecord) (err error) {
|
||||
// adding user to the list
|
||||
userState := AclUserState{
|
||||
PubKey: record.Identity,
|
||||
Permissions: AclPermissions(aclrecordproto.AclUserPermissions_Owner),
|
||||
Permissions: aclrecordproto.AclUserPermissions_Admin,
|
||||
}
|
||||
st.currentReadKeyId = record.Id
|
||||
st.currentReadKeyId = record.ReadKeyId
|
||||
st.userStates[mapKeyFromPubKey(record.Identity)] = userState
|
||||
st.totalReadKeys++
|
||||
return
|
||||
@@ -219,191 +181,92 @@ func (st *AclState) saveReadKeyFromRoot(record *AclRecord) (err error) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
st.userReadKeys[record.Id] = readKey
|
||||
return
|
||||
}
|
||||
|
||||
func (st *AclState) applyChangeData(record *AclRecord) (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if record.ReadKeyId != st.currentReadKeyId {
|
||||
st.totalReadKeys++
|
||||
st.currentReadKeyId = record.ReadKeyId
|
||||
}
|
||||
}()
|
||||
model := record.Model.(*aclrecordproto.AclData)
|
||||
if !st.isUserJoin(model) {
|
||||
// we check signature when we add this to the List, so no need to do it here
|
||||
if _, exists := st.userStates[mapKeyFromPubKey(record.Identity)]; !exists {
|
||||
err = ErrNoSuchUser
|
||||
return
|
||||
}
|
||||
|
||||
// only Admins can do non-user join changes
|
||||
if !st.HasPermission(record.Identity, aclrecordproto.AclUserPermissions_Admin) {
|
||||
// TODO: add string encoding
|
||||
err = fmt.Errorf("user %s must have admin permissions", record.Identity.Account())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for _, ch := range model.GetAclContent() {
|
||||
if err = st.applyChangeContent(ch, record.Id, record.Identity); err != nil {
|
||||
if err = st.applyChangeContent(ch, record.Id); err != nil {
|
||||
log.Info("error while applying changes: %v; ignore", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *AclState) applyChangeContent(ch *aclrecordproto.AclContentValue, recordId string, authorIdentity crypto.PubKey) error {
|
||||
func (st *AclState) applyChangeContent(ch *aclrecordproto.AclContentValue, recordId string) error {
|
||||
switch {
|
||||
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)
|
||||
case ch.GetUserPermissionChange() != nil:
|
||||
return st.applyUserPermissionChange(ch.GetUserPermissionChange(), recordId)
|
||||
case ch.GetUserAdd() != nil:
|
||||
return st.applyUserAdd(ch.GetUserAdd(), recordId)
|
||||
case ch.GetUserRemove() != nil:
|
||||
return st.applyUserRemove(ch.GetUserRemove(), recordId)
|
||||
case ch.GetUserInvite() != nil:
|
||||
return st.applyUserInvite(ch.GetUserInvite(), recordId)
|
||||
case ch.GetUserJoin() != nil:
|
||||
return st.applyUserJoin(ch.GetUserJoin(), recordId)
|
||||
default:
|
||||
return ErrUnexpectedContentType
|
||||
return fmt.Errorf("unexpected change type: %v", ch)
|
||||
}
|
||||
}
|
||||
|
||||
func (st *AclState) applyPermissionChange(ch *aclrecordproto.AclAccountPermissionChange, recordId string, authorIdentity crypto.PubKey) error {
|
||||
func (st *AclState) applyUserPermissionChange(ch *aclrecordproto.AclUserPermissionChange, recordId string) error {
|
||||
chIdentity, err := st.keyStore.PubKeyFromProto(ch.Identity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = st.contentValidator.ValidatePermissionChange(ch, authorIdentity)
|
||||
if err != nil {
|
||||
return err
|
||||
state, exists := st.userStates[mapKeyFromPubKey(chIdentity)]
|
||||
if !exists {
|
||||
return ErrNoSuchUser
|
||||
}
|
||||
stringKey := mapKeyFromPubKey(chIdentity)
|
||||
state, _ := st.userStates[stringKey]
|
||||
state.Permissions = AclPermissions(ch.Permissions)
|
||||
st.userStates[stringKey] = state
|
||||
|
||||
state.Permissions = ch.Permissions
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
func (st *AclState) applyUserInvite(ch *aclrecordproto.AclUserInvite, recordId string) error {
|
||||
// TODO: check old code and bring it back :-)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *AclState) applyInviteRevoke(ch *aclrecordproto.AclAccountInviteRevoke, recordId string, authorIdentity crypto.PubKey) error {
|
||||
err := st.contentValidator.ValidateInviteRevoke(ch, authorIdentity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
delete(st.inviteKeys, ch.InviteRecordId)
|
||||
func (st *AclState) applyUserJoin(ch *aclrecordproto.AclUserJoin, recordId string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *AclState) applyRequestJoin(ch *aclrecordproto.AclAccountRequestJoin, recordId string, authorIdentity crypto.PubKey) error {
|
||||
err := st.contentValidator.ValidateRequestJoin(ch, authorIdentity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
st.pendingRequests[mapKeyFromPubKey(authorIdentity)] = recordId
|
||||
st.requestRecords[recordId] = RequestRecord{
|
||||
RequestIdentity: authorIdentity,
|
||||
RequestMetadata: ch.Metadata,
|
||||
Type: RequestTypeJoin,
|
||||
}
|
||||
func (st *AclState) applyUserAdd(ch *aclrecordproto.AclUserAdd, recordId string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
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 err
|
||||
}
|
||||
st.userReadKeys[recordId] = res
|
||||
}
|
||||
}
|
||||
st.currentReadKeyId = recordId
|
||||
func (st *AclState) applyUserRemove(ch *aclrecordproto.AclUserRemove, recordId string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -412,6 +275,7 @@ func (st *AclState) decryptReadKey(msg []byte) (crypto.SymKey, error) {
|
||||
if err != nil {
|
||||
return nil, ErrFailedToDecrypt
|
||||
}
|
||||
|
||||
key, err := crypto.UnmarshallAESKey(decrypted)
|
||||
if err != nil {
|
||||
return nil, ErrFailedToDecrypt
|
||||
@@ -419,31 +283,29 @@ func (st *AclState) decryptReadKey(msg []byte) (crypto.SymKey, error) {
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (st *AclState) Permissions(identity crypto.PubKey) AclPermissions {
|
||||
func (st *AclState) HasPermission(identity crypto.PubKey, permission aclrecordproto.AclUserPermissions) bool {
|
||||
state, exists := st.userStates[mapKeyFromPubKey(identity)]
|
||||
if !exists {
|
||||
return AclPermissions(aclrecordproto.AclUserPermissions_None)
|
||||
return false
|
||||
}
|
||||
return state.Permissions
|
||||
|
||||
return state.Permissions == permission
|
||||
}
|
||||
|
||||
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) 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) RemoveRecords() (records []RequestRecord) {
|
||||
for _, recId := range st.pendingRequests {
|
||||
rec := st.requestRecords[recId]
|
||||
if rec.Type == RequestTypeRemove {
|
||||
records = append(records, rec)
|
||||
}
|
||||
}
|
||||
func (st *AclState) isUserAdd(data *aclrecordproto.AclData, identity []byte) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (st *AclState) UserStates() map[string]AclUserState {
|
||||
return st.userStates
|
||||
}
|
||||
|
||||
func (st *AclState) Invite(acceptPubKey []byte) (invite *aclrecordproto.AclUserInvite, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
)
|
||||
|
||||
type aclStateBuilder struct {
|
||||
|
||||
@@ -1,25 +1,20 @@
|
||||
//go:generate mockgen -destination mock_list/mock_list.go github.com/anyproto/any-sync/commonspace/object/acl/list AclList
|
||||
//go:generate mockgen -destination mock_list/mock_list.go github.com/anytypeio/any-sync/commonspace/object/acl/list AclList
|
||||
package list
|
||||
|
||||
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/util/crypto"
|
||||
"sync"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/anyproto/any-sync/util/cidutil"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
)
|
||||
|
||||
type IterFunc = func(record *AclRecord) (IsContinue bool)
|
||||
|
||||
var (
|
||||
ErrIncorrectCID = errors.New("incorrect CID")
|
||||
ErrRecordAlreadyExists = errors.New("record already exists")
|
||||
)
|
||||
var ErrIncorrectCID = errors.New("incorrect CID")
|
||||
|
||||
type RWLocker interface {
|
||||
sync.Locker
|
||||
@@ -27,45 +22,26 @@ 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() *consensusproto.RawRecordWithId
|
||||
Root() *aclrecordproto.RawAclRecordWithId
|
||||
Records() []*AclRecord
|
||||
AclState() *AclState
|
||||
IsAfter(first string, second string) (bool, error)
|
||||
HasHead(head string) bool
|
||||
Head() *AclRecord
|
||||
|
||||
RecordsAfter(ctx context.Context, id string) (records []*consensusproto.RawRecordWithId, err error)
|
||||
Get(id string) (*AclRecord, error)
|
||||
GetIndex(idx int) (*AclRecord, error)
|
||||
Iterate(iterFunc IterFunc)
|
||||
IterateFrom(startId string, iterFunc IterFunc)
|
||||
|
||||
KeyStorage() crypto.KeyStorage
|
||||
RecordBuilder() AclRecordBuilder
|
||||
|
||||
ValidateRawRecord(record *consensusproto.RawRecord) (err error)
|
||||
AddRawRecord(rawRec *consensusproto.RawRecordWithId) (err error)
|
||||
AddRawRecords(rawRecords []*consensusproto.RawRecordWithId) (err error)
|
||||
AddRawRecord(rawRec *aclrecordproto.RawAclRecordWithId) (added bool, err error)
|
||||
|
||||
Close(ctx context.Context) (err error)
|
||||
Close() (err error)
|
||||
}
|
||||
|
||||
type aclList struct {
|
||||
root *consensusproto.RawRecordWithId
|
||||
root *aclrecordproto.RawAclRecordWithId
|
||||
records []*AclRecord
|
||||
indexes map[string]int
|
||||
id string
|
||||
@@ -79,45 +55,18 @@ type aclList struct {
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
type internalDeps struct {
|
||||
storage liststorage.ListStorage
|
||||
keyStorage crypto.KeyStorage
|
||||
stateBuilder *aclStateBuilder
|
||||
recordBuilder AclRecordBuilder
|
||||
acceptorVerifier AcceptorVerifier
|
||||
}
|
||||
|
||||
func BuildAclListWithIdentity(acc *accountdata.AccountKeys, storage liststorage.ListStorage, verifier AcceptorVerifier) (AclList, error) {
|
||||
func BuildAclListWithIdentity(acc *accountdata.AccountKeys, storage liststorage.ListStorage) (AclList, error) {
|
||||
builder := newAclStateBuilderWithIdentity(acc)
|
||||
keyStorage := crypto.NewKeyStorage()
|
||||
deps := internalDeps{
|
||||
storage: storage,
|
||||
keyStorage: keyStorage,
|
||||
stateBuilder: newAclStateBuilderWithIdentity(acc),
|
||||
recordBuilder: NewAclRecordBuilder(storage.Id(), keyStorage, acc, verifier),
|
||||
acceptorVerifier: verifier,
|
||||
}
|
||||
return build(deps)
|
||||
return build(storage.Id(), keyStorage, builder, NewAclRecordBuilder(storage.Id(), keyStorage), storage)
|
||||
}
|
||||
|
||||
func BuildAclList(storage liststorage.ListStorage, verifier AcceptorVerifier) (AclList, error) {
|
||||
func BuildAclList(storage liststorage.ListStorage) (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)
|
||||
return build(storage.Id(), keyStorage, newAclStateBuilder(), NewAclRecordBuilder(storage.Id(), crypto.NewKeyStorage()), storage)
|
||||
}
|
||||
|
||||
func build(deps internalDeps) (list AclList, err error) {
|
||||
var (
|
||||
storage = deps.storage
|
||||
id = deps.storage.Id()
|
||||
recBuilder = deps.recordBuilder
|
||||
stateBuilder = deps.stateBuilder
|
||||
)
|
||||
func build(id string, keyStorage crypto.KeyStorage, stateBuilder *aclStateBuilder, recBuilder AclRecordBuilder, storage liststorage.ListStorage) (list AclList, err error) {
|
||||
head, err := storage.Head()
|
||||
if err != nil {
|
||||
return
|
||||
@@ -128,7 +77,7 @@ func build(deps internalDeps) (list AclList, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
record, err := recBuilder.UnmarshallWithId(rawRecordWithId)
|
||||
record, err := recBuilder.Unmarshall(rawRecordWithId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -140,7 +89,7 @@ func build(deps internalDeps) (list AclList, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
record, err = recBuilder.UnmarshallWithId(rawRecordWithId)
|
||||
record, err = recBuilder.Unmarshall(rawRecordWithId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -170,7 +119,6 @@ func build(deps internalDeps) (list AclList, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
recBuilder.(*aclRecordBuilder).state = state
|
||||
list = &aclList{
|
||||
root: rootWithId,
|
||||
records: records,
|
||||
@@ -184,37 +132,15 @@ func build(deps internalDeps) (list AclList, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (a *aclList) RecordBuilder() AclRecordBuilder {
|
||||
return a.recordBuilder
|
||||
}
|
||||
|
||||
func (a *aclList) Records() []*AclRecord {
|
||||
return a.records
|
||||
}
|
||||
|
||||
func (a *aclList) ValidateRawRecord(rawRec *consensusproto.RawRecord) (err error) {
|
||||
record, err := a.recordBuilder.Unmarshall(rawRec)
|
||||
if err != nil {
|
||||
func (a *aclList) AddRawRecord(rawRec *aclrecordproto.RawAclRecordWithId) (added bool, err error) {
|
||||
if _, ok := a.indexes[rawRec.Id]; ok {
|
||||
return
|
||||
}
|
||||
return a.aclState.Validator().ValidateAclRecordContents(record)
|
||||
}
|
||||
|
||||
func (a *aclList) AddRawRecords(rawRecords []*consensusproto.RawRecordWithId) (err error) {
|
||||
for _, rec := range rawRecords {
|
||||
err = a.AddRawRecord(rec)
|
||||
if err != nil && err != ErrRecordAlreadyExists {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (a *aclList) AddRawRecord(rawRec *consensusproto.RawRecordWithId) (err error) {
|
||||
if _, ok := a.indexes[rawRec.Id]; ok {
|
||||
return ErrRecordAlreadyExists
|
||||
}
|
||||
record, err := a.recordBuilder.UnmarshallWithId(rawRec)
|
||||
record, err := a.recordBuilder.Unmarshall(rawRec)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -229,6 +155,15 @@ func (a *aclList) AddRawRecord(rawRec *consensusproto.RawRecordWithId) (err erro
|
||||
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.Unmarshall(rawRec)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// TODO: change state and add "check" method for records
|
||||
return
|
||||
}
|
||||
|
||||
@@ -236,7 +171,7 @@ func (a *aclList) Id() string {
|
||||
return a.id
|
||||
}
|
||||
|
||||
func (a *aclList) Root() *consensusproto.RawRecordWithId {
|
||||
func (a *aclList) Root() *aclrecordproto.RawAclRecordWithId {
|
||||
return a.root
|
||||
}
|
||||
|
||||
@@ -261,27 +196,14 @@ func (a *aclList) Head() *AclRecord {
|
||||
return a.records[len(a.records)-1]
|
||||
}
|
||||
|
||||
func (a *aclList) HasHead(head string) bool {
|
||||
_, exists := a.indexes[head]
|
||||
return exists
|
||||
}
|
||||
|
||||
func (a *aclList) Get(id string) (*AclRecord, error) {
|
||||
recIdx, ok := a.indexes[id]
|
||||
if !ok {
|
||||
return nil, ErrNoSuchRecord
|
||||
return nil, fmt.Errorf("no such record")
|
||||
}
|
||||
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) {
|
||||
@@ -290,21 +212,6 @@ func (a *aclList) Iterate(iterFunc IterFunc) {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *aclList) RecordsAfter(ctx context.Context, id string) (records []*consensusproto.RawRecordWithId, err error) {
|
||||
recIdx, ok := a.indexes[id]
|
||||
if !ok {
|
||||
return nil, ErrNoSuchRecord
|
||||
}
|
||||
for i := recIdx + 1; i < len(a.records); i++ {
|
||||
rawRec, err := a.storage.GetRawRecord(ctx, a.records[i].Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
records = append(records, rawRec)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (a *aclList) IterateFrom(startId string, iterFunc IterFunc) {
|
||||
recIdx, ok := a.indexes[startId]
|
||||
if !ok {
|
||||
@@ -317,21 +224,6 @@ func (a *aclList) IterateFrom(startId string, iterFunc IterFunc) {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *aclList) Close(ctx context.Context) (err error) {
|
||||
func (a *aclList) Close() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func WrapAclRecord(rawRec *consensusproto.RawRecord) *consensusproto.RawRecordWithId {
|
||||
payload, err := rawRec.Marshal()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
id, err := cidutil.NewCidFromBytes(payload)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &consensusproto.RawRecordWithId{
|
||||
Payload: payload,
|
||||
Id: id,
|
||||
}
|
||||
}
|
||||
@@ -2,98 +2,11 @@ package list
|
||||
|
||||
import (
|
||||
"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/crypto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type aclFixture struct {
|
||||
ownerKeys *accountdata.AccountKeys
|
||||
accountKeys *accountdata.AccountKeys
|
||||
ownerAcl *aclList
|
||||
accountAcl *aclList
|
||||
spaceId string
|
||||
}
|
||||
|
||||
func newFixture(t *testing.T) *aclFixture {
|
||||
ownerKeys, err := accountdata.NewRandom()
|
||||
require.NoError(t, err)
|
||||
accountKeys, err := accountdata.NewRandom()
|
||||
require.NoError(t, err)
|
||||
spaceId := "spaceId"
|
||||
ownerAcl, err := NewTestDerivedAcl(spaceId, ownerKeys)
|
||||
require.NoError(t, err)
|
||||
accountAcl, err := NewTestAclWithRoot(accountKeys, ownerAcl.Root())
|
||||
require.NoError(t, err)
|
||||
return &aclFixture{
|
||||
ownerKeys: ownerKeys,
|
||||
accountKeys: accountKeys,
|
||||
ownerAcl: ownerAcl.(*aclList),
|
||||
accountAcl: accountAcl.(*aclList),
|
||||
spaceId: spaceId,
|
||||
}
|
||||
}
|
||||
|
||||
func (fx *aclFixture) addRec(t *testing.T, rec *consensusproto.RawRecordWithId) {
|
||||
err := fx.ownerAcl.AddRawRecord(rec)
|
||||
require.NoError(t, err)
|
||||
err = fx.accountAcl.AddRawRecord(rec)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func (fx *aclFixture) inviteAccount(t *testing.T, perms AclPermissions) {
|
||||
var (
|
||||
ownerAcl = fx.ownerAcl
|
||||
ownerState = fx.ownerAcl.aclState
|
||||
accountAcl = fx.accountAcl
|
||||
accountState = fx.accountAcl.aclState
|
||||
)
|
||||
// building invite
|
||||
inv, err := ownerAcl.RecordBuilder().BuildInvite()
|
||||
require.NoError(t, err)
|
||||
inviteRec := WrapAclRecord(inv.InviteRec)
|
||||
fx.addRec(t, inviteRec)
|
||||
|
||||
// building request join
|
||||
requestJoin, err := accountAcl.RecordBuilder().BuildRequestJoin(RequestJoinPayload{
|
||||
InviteRecordId: inviteRec.Id,
|
||||
InviteKey: inv.InviteKey,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
requestJoinRec := WrapAclRecord(requestJoin)
|
||||
fx.addRec(t, requestJoinRec)
|
||||
|
||||
// building request accept
|
||||
requestAccept, err := ownerAcl.RecordBuilder().BuildRequestAccept(RequestAcceptPayload{
|
||||
RequestRecordId: requestJoinRec.Id,
|
||||
Permissions: perms,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// validate
|
||||
err = ownerAcl.ValidateRawRecord(requestAccept)
|
||||
require.NoError(t, err)
|
||||
requestAcceptRec := WrapAclRecord(requestAccept)
|
||||
fx.addRec(t, requestAcceptRec)
|
||||
|
||||
// checking acl state
|
||||
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
||||
require.True(t, ownerState.Permissions(accountState.pubKey).CanWrite())
|
||||
require.Equal(t, 0, len(ownerState.pendingRequests))
|
||||
require.Equal(t, 0, len(accountState.pendingRequests))
|
||||
require.True(t, accountState.Permissions(ownerState.pubKey).IsOwner())
|
||||
require.True(t, accountState.Permissions(accountState.pubKey).CanWrite())
|
||||
|
||||
_, err = ownerState.StateAtRecord(requestJoinRec.Id, accountState.pubKey)
|
||||
require.Equal(t, ErrNoSuchAccount, err)
|
||||
stateAtRec, err := ownerState.StateAtRecord(requestAcceptRec.Id, accountState.pubKey)
|
||||
require.NoError(t, err)
|
||||
require.True(t, stateAtRec.Permissions == perms)
|
||||
}
|
||||
|
||||
func TestAclList_BuildRoot(t *testing.T) {
|
||||
randomKeys, err := accountdata.NewRandom()
|
||||
require.NoError(t, err)
|
||||
@@ -101,193 +14,3 @@ func TestAclList_BuildRoot(t *testing.T) {
|
||||
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 := WrapAclRecord(inv.InviteRec)
|
||||
fx.addRec(t, inviteRec)
|
||||
|
||||
// building invite revoke
|
||||
inviteRevoke, err := fx.ownerAcl.RecordBuilder().BuildInviteRevoke(ownerState.lastRecordId)
|
||||
require.NoError(t, err)
|
||||
inviteRevokeRec := WrapAclRecord(inviteRevoke)
|
||||
fx.addRec(t, inviteRevokeRec)
|
||||
|
||||
// checking acl state
|
||||
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
||||
require.True(t, ownerState.Permissions(accountState.pubKey).NoPermissions())
|
||||
require.Empty(t, ownerState.inviteKeys)
|
||||
require.Empty(t, accountState.inviteKeys)
|
||||
}
|
||||
|
||||
func TestAclList_RequestDecline(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
var (
|
||||
ownerAcl = fx.ownerAcl
|
||||
ownerState = fx.ownerAcl.aclState
|
||||
accountAcl = fx.accountAcl
|
||||
accountState = fx.accountAcl.aclState
|
||||
)
|
||||
// building invite
|
||||
inv, err := ownerAcl.RecordBuilder().BuildInvite()
|
||||
require.NoError(t, err)
|
||||
inviteRec := WrapAclRecord(inv.InviteRec)
|
||||
fx.addRec(t, inviteRec)
|
||||
|
||||
// building request join
|
||||
requestJoin, err := accountAcl.RecordBuilder().BuildRequestJoin(RequestJoinPayload{
|
||||
InviteRecordId: inviteRec.Id,
|
||||
InviteKey: inv.InviteKey,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
requestJoinRec := WrapAclRecord(requestJoin)
|
||||
fx.addRec(t, requestJoinRec)
|
||||
|
||||
// building request decline
|
||||
requestDecline, err := ownerAcl.RecordBuilder().BuildRequestDecline(ownerState.lastRecordId)
|
||||
require.NoError(t, err)
|
||||
requestDeclineRec := WrapAclRecord(requestDecline)
|
||||
fx.addRec(t, requestDeclineRec)
|
||||
|
||||
// checking acl state
|
||||
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
||||
require.True(t, ownerState.Permissions(accountState.pubKey).NoPermissions())
|
||||
require.Empty(t, ownerState.pendingRequests)
|
||||
require.Empty(t, accountState.pendingRequests)
|
||||
}
|
||||
|
||||
func TestAclList_Remove(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
var (
|
||||
ownerState = fx.ownerAcl.aclState
|
||||
accountState = fx.accountAcl.aclState
|
||||
)
|
||||
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Writer))
|
||||
|
||||
newReadKey := crypto.NewAES()
|
||||
remove, err := fx.ownerAcl.RecordBuilder().BuildAccountRemove(AccountRemovePayload{
|
||||
Identities: []crypto.PubKey{fx.accountKeys.SignKey.GetPublic()},
|
||||
ReadKey: newReadKey,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
removeRec := WrapAclRecord(remove)
|
||||
fx.addRec(t, removeRec)
|
||||
|
||||
// checking acl state
|
||||
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
||||
require.True(t, ownerState.Permissions(accountState.pubKey).NoPermissions())
|
||||
require.True(t, ownerState.userReadKeys[removeRec.Id].Equals(newReadKey))
|
||||
require.NotNil(t, ownerState.userReadKeys[fx.ownerAcl.Id()])
|
||||
require.Equal(t, 0, len(ownerState.pendingRequests))
|
||||
require.Equal(t, 0, len(accountState.pendingRequests))
|
||||
require.True(t, accountState.Permissions(ownerState.pubKey).IsOwner())
|
||||
require.True(t, accountState.Permissions(accountState.pubKey).NoPermissions())
|
||||
require.Nil(t, accountState.userReadKeys[removeRec.Id])
|
||||
require.NotNil(t, accountState.userReadKeys[fx.ownerAcl.Id()])
|
||||
}
|
||||
|
||||
func TestAclList_ReadKeyChange(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
var (
|
||||
ownerState = fx.ownerAcl.aclState
|
||||
accountState = fx.accountAcl.aclState
|
||||
)
|
||||
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Admin))
|
||||
|
||||
newReadKey := crypto.NewAES()
|
||||
readKeyChange, err := fx.ownerAcl.RecordBuilder().BuildReadKeyChange(newReadKey)
|
||||
require.NoError(t, err)
|
||||
readKeyRec := WrapAclRecord(readKeyChange)
|
||||
fx.addRec(t, readKeyRec)
|
||||
|
||||
// checking acl state
|
||||
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
||||
require.True(t, ownerState.Permissions(accountState.pubKey).CanManageAccounts())
|
||||
require.True(t, ownerState.userReadKeys[readKeyRec.Id].Equals(newReadKey))
|
||||
require.True(t, accountState.userReadKeys[readKeyRec.Id].Equals(newReadKey))
|
||||
require.NotNil(t, ownerState.userReadKeys[fx.ownerAcl.Id()])
|
||||
require.NotNil(t, accountState.userReadKeys[fx.ownerAcl.Id()])
|
||||
readKey, err := ownerState.CurrentReadKey()
|
||||
require.NoError(t, err)
|
||||
require.True(t, newReadKey.Equals(readKey))
|
||||
require.Equal(t, 0, len(ownerState.pendingRequests))
|
||||
require.Equal(t, 0, len(accountState.pendingRequests))
|
||||
}
|
||||
|
||||
func TestAclList_PermissionChange(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
var (
|
||||
ownerState = fx.ownerAcl.aclState
|
||||
accountState = fx.accountAcl.aclState
|
||||
)
|
||||
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Admin))
|
||||
|
||||
permissionChange, err := fx.ownerAcl.RecordBuilder().BuildPermissionChange(PermissionChangePayload{
|
||||
Identity: fx.accountKeys.SignKey.GetPublic(),
|
||||
Permissions: AclPermissions(aclrecordproto.AclUserPermissions_Writer),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
permissionChangeRec := WrapAclRecord(permissionChange)
|
||||
fx.addRec(t, permissionChangeRec)
|
||||
|
||||
// checking acl state
|
||||
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
||||
require.True(t, ownerState.Permissions(accountState.pubKey) == AclPermissions(aclrecordproto.AclUserPermissions_Writer))
|
||||
require.True(t, accountState.Permissions(ownerState.pubKey).IsOwner())
|
||||
require.True(t, accountState.Permissions(accountState.pubKey) == AclPermissions(aclrecordproto.AclUserPermissions_Writer))
|
||||
require.NotNil(t, ownerState.userReadKeys[fx.ownerAcl.Id()])
|
||||
require.NotNil(t, accountState.userReadKeys[fx.ownerAcl.Id()])
|
||||
require.Equal(t, 0, len(ownerState.pendingRequests))
|
||||
require.Equal(t, 0, len(accountState.pendingRequests))
|
||||
}
|
||||
|
||||
func TestAclList_RequestRemove(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
var (
|
||||
ownerState = fx.ownerAcl.aclState
|
||||
accountState = fx.accountAcl.aclState
|
||||
)
|
||||
fx.inviteAccount(t, AclPermissions(aclrecordproto.AclUserPermissions_Writer))
|
||||
|
||||
removeRequest, err := fx.accountAcl.RecordBuilder().BuildRequestRemove()
|
||||
require.NoError(t, err)
|
||||
removeRequestRec := WrapAclRecord(removeRequest)
|
||||
fx.addRec(t, removeRequestRec)
|
||||
|
||||
recs := fx.accountAcl.AclState().RemoveRecords()
|
||||
require.Len(t, recs, 1)
|
||||
require.True(t, accountState.pubKey.Equals(recs[0].RequestIdentity))
|
||||
|
||||
newReadKey := crypto.NewAES()
|
||||
remove, err := fx.ownerAcl.RecordBuilder().BuildAccountRemove(AccountRemovePayload{
|
||||
Identities: []crypto.PubKey{recs[0].RequestIdentity},
|
||||
ReadKey: newReadKey,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
removeRec := WrapAclRecord(remove)
|
||||
fx.addRec(t, removeRec)
|
||||
|
||||
// checking acl state
|
||||
require.True(t, ownerState.Permissions(ownerState.pubKey).IsOwner())
|
||||
require.True(t, ownerState.Permissions(accountState.pubKey).NoPermissions())
|
||||
require.True(t, ownerState.userReadKeys[removeRec.Id].Equals(newReadKey))
|
||||
require.NotNil(t, ownerState.userReadKeys[fx.ownerAcl.Id()])
|
||||
require.Equal(t, 0, len(ownerState.pendingRequests))
|
||||
require.Equal(t, 0, len(accountState.pendingRequests))
|
||||
require.True(t, accountState.Permissions(ownerState.pubKey).IsOwner())
|
||||
require.True(t, accountState.Permissions(accountState.pubKey).NoPermissions())
|
||||
require.Nil(t, accountState.userReadKeys[removeRec.Id])
|
||||
require.NotNil(t, accountState.userReadKeys[fx.ownerAcl.Id()])
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
package list
|
||||
|
||||
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"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/liststorage"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
)
|
||||
|
||||
func NewTestDerivedAcl(spaceId string, keys *accountdata.AccountKeys) (AclList, error) {
|
||||
builder := NewAclRecordBuilder("", crypto.NewKeyStorage(), keys, NoOpAcceptorVerifier{})
|
||||
builder := NewAclRecordBuilder("", crypto.NewKeyStorage())
|
||||
masterKey, _, err := crypto.GenerateRandomEd25519KeyPair()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -21,21 +21,11 @@ func NewTestDerivedAcl(spaceId string, keys *accountdata.AccountKeys) (AclList,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
st, err := liststorage.NewInMemoryAclListStorage(root.Id, []*consensusproto.RawRecordWithId{
|
||||
st, err := liststorage.NewInMemoryAclListStorage(root.Id, []*aclrecordproto.RawAclRecordWithId{
|
||||
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{})
|
||||
return BuildAclListWithIdentity(keys, st)
|
||||
}
|
||||
@@ -1,17 +1,16 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anyproto/any-sync/commonspace/object/acl/list (interfaces: AclList)
|
||||
// Source: github.com/anytypeio/any-sync/commonspace/object/acl/list (interfaces: AclList)
|
||||
|
||||
// Package mock_list is a generated GoMock package.
|
||||
package mock_list
|
||||
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
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"
|
||||
aclrecordproto "github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
list "github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
crypto "github.com/anytypeio/any-sync/util/crypto"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockAclList is a mock of AclList interface.
|
||||
@@ -52,11 +51,12 @@ func (mr *MockAclListMockRecorder) AclState() *gomock.Call {
|
||||
}
|
||||
|
||||
// AddRawRecord mocks base method.
|
||||
func (m *MockAclList) AddRawRecord(arg0 *consensusproto.RawRecordWithId) error {
|
||||
func (m *MockAclList) AddRawRecord(arg0 *aclrecordproto.RawAclRecordWithId) (bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AddRawRecord", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
ret0, _ := ret[0].(bool)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// AddRawRecord indicates an expected call of AddRawRecord.
|
||||
@@ -65,32 +65,18 @@ func (mr *MockAclListMockRecorder) AddRawRecord(arg0 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawRecord", reflect.TypeOf((*MockAclList)(nil).AddRawRecord), arg0)
|
||||
}
|
||||
|
||||
// AddRawRecords mocks base method.
|
||||
func (m *MockAclList) AddRawRecords(arg0 []*consensusproto.RawRecordWithId) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AddRawRecords", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddRawRecords indicates an expected call of AddRawRecords.
|
||||
func (mr *MockAclListMockRecorder) AddRawRecords(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawRecords", reflect.TypeOf((*MockAclList)(nil).AddRawRecords), arg0)
|
||||
}
|
||||
|
||||
// Close mocks base method.
|
||||
func (m *MockAclList) Close(arg0 context.Context) error {
|
||||
func (m *MockAclList) Close() error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Close", arg0)
|
||||
ret := m.ctrl.Call(m, "Close")
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Close indicates an expected call of Close.
|
||||
func (mr *MockAclListMockRecorder) Close(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockAclListMockRecorder) Close() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockAclList)(nil).Close), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockAclList)(nil).Close))
|
||||
}
|
||||
|
||||
// Get mocks base method.
|
||||
@@ -108,35 +94,6 @@ func (mr *MockAclListMockRecorder) Get(arg0 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockAclList)(nil).Get), arg0)
|
||||
}
|
||||
|
||||
// GetIndex mocks base method.
|
||||
func (m *MockAclList) GetIndex(arg0 int) (*list.AclRecord, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetIndex", arg0)
|
||||
ret0, _ := ret[0].(*list.AclRecord)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetIndex indicates an expected call of GetIndex.
|
||||
func (mr *MockAclListMockRecorder) GetIndex(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIndex", reflect.TypeOf((*MockAclList)(nil).GetIndex), arg0)
|
||||
}
|
||||
|
||||
// HasHead mocks base method.
|
||||
func (m *MockAclList) HasHead(arg0 string) bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "HasHead", arg0)
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// HasHead indicates an expected call of HasHead.
|
||||
func (mr *MockAclListMockRecorder) HasHead(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasHead", reflect.TypeOf((*MockAclList)(nil).HasHead), arg0)
|
||||
}
|
||||
|
||||
// Head mocks base method.
|
||||
func (m *MockAclList) Head() *list.AclRecord {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -254,20 +211,6 @@ func (mr *MockAclListMockRecorder) RUnlock() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RUnlock", reflect.TypeOf((*MockAclList)(nil).RUnlock))
|
||||
}
|
||||
|
||||
// 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()
|
||||
@@ -282,26 +225,11 @@ func (mr *MockAclListMockRecorder) Records() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Records", reflect.TypeOf((*MockAclList)(nil).Records))
|
||||
}
|
||||
|
||||
// RecordsAfter mocks base method.
|
||||
func (m *MockAclList) RecordsAfter(arg0 context.Context, arg1 string) ([]*consensusproto.RawRecordWithId, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "RecordsAfter", arg0, arg1)
|
||||
ret0, _ := ret[0].([]*consensusproto.RawRecordWithId)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// RecordsAfter indicates an expected call of RecordsAfter.
|
||||
func (mr *MockAclListMockRecorder) RecordsAfter(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordsAfter", reflect.TypeOf((*MockAclList)(nil).RecordsAfter), arg0, arg1)
|
||||
}
|
||||
|
||||
// Root mocks base method.
|
||||
func (m *MockAclList) Root() *consensusproto.RawRecordWithId {
|
||||
func (m *MockAclList) Root() *aclrecordproto.RawAclRecordWithId {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Root")
|
||||
ret0, _ := ret[0].(*consensusproto.RawRecordWithId)
|
||||
ret0, _ := ret[0].(*aclrecordproto.RawAclRecordWithId)
|
||||
return ret0
|
||||
}
|
||||
|
||||
@@ -322,17 +250,3 @@ 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)
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
)
|
||||
|
||||
type AclRecord struct {
|
||||
Id string
|
||||
PrevId string
|
||||
ReadKeyId string
|
||||
Timestamp int64
|
||||
Data []byte
|
||||
Identity crypto.PubKey
|
||||
@@ -15,55 +16,7 @@ type AclRecord struct {
|
||||
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
|
||||
}
|
||||
PubKey crypto.PubKey
|
||||
Permissions aclrecordproto.AclUserPermissions
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
)
|
||||
|
||||
type ContentValidator interface {
|
||||
ValidateAclRecordContents(ch *AclRecord) (err error)
|
||||
ValidatePermissionChange(ch *aclrecordproto.AclAccountPermissionChange, authorIdentity crypto.PubKey) (err error)
|
||||
ValidateInvite(ch *aclrecordproto.AclAccountInvite, authorIdentity crypto.PubKey) (err error)
|
||||
ValidateInviteRevoke(ch *aclrecordproto.AclAccountInviteRevoke, authorIdentity crypto.PubKey) (err error)
|
||||
ValidateRequestJoin(ch *aclrecordproto.AclAccountRequestJoin, authorIdentity crypto.PubKey) (err error)
|
||||
ValidateRequestAccept(ch *aclrecordproto.AclAccountRequestAccept, authorIdentity crypto.PubKey) (err error)
|
||||
ValidateRequestDecline(ch *aclrecordproto.AclAccountRequestDecline, authorIdentity crypto.PubKey) (err error)
|
||||
ValidateAccountRemove(ch *aclrecordproto.AclAccountRemove, authorIdentity crypto.PubKey) (err error)
|
||||
ValidateRequestRemove(ch *aclrecordproto.AclAccountRequestRemove, authorIdentity crypto.PubKey) (err error)
|
||||
ValidateReadKeyChange(ch *aclrecordproto.AclReadKeyChange, authorIdentity crypto.PubKey) (err error)
|
||||
}
|
||||
|
||||
type contentValidator struct {
|
||||
keyStore crypto.KeyStorage
|
||||
aclState *AclState
|
||||
}
|
||||
|
||||
func (c *contentValidator) ValidateAclRecordContents(ch *AclRecord) (err error) {
|
||||
if ch.PrevId != c.aclState.lastRecordId {
|
||||
return ErrIncorrectRecordSequence
|
||||
}
|
||||
aclData := ch.Model.(*aclrecordproto.AclData)
|
||||
for _, content := range aclData.AclContent {
|
||||
err = c.validateAclRecordContent(content, ch.Identity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *contentValidator) validateAclRecordContent(ch *aclrecordproto.AclContentValue, authorIdentity crypto.PubKey) (err error) {
|
||||
switch {
|
||||
case ch.GetPermissionChange() != nil:
|
||||
return c.ValidatePermissionChange(ch.GetPermissionChange(), authorIdentity)
|
||||
case ch.GetInvite() != nil:
|
||||
return c.ValidateInvite(ch.GetInvite(), authorIdentity)
|
||||
case ch.GetInviteRevoke() != nil:
|
||||
return c.ValidateInviteRevoke(ch.GetInviteRevoke(), authorIdentity)
|
||||
case ch.GetRequestJoin() != nil:
|
||||
return c.ValidateRequestJoin(ch.GetRequestJoin(), authorIdentity)
|
||||
case ch.GetRequestAccept() != nil:
|
||||
return c.ValidateRequestAccept(ch.GetRequestAccept(), authorIdentity)
|
||||
case ch.GetRequestDecline() != nil:
|
||||
return c.ValidateRequestDecline(ch.GetRequestDecline(), authorIdentity)
|
||||
case ch.GetAccountRemove() != nil:
|
||||
return c.ValidateAccountRemove(ch.GetAccountRemove(), authorIdentity)
|
||||
case ch.GetAccountRequestRemove() != nil:
|
||||
return c.ValidateRequestRemove(ch.GetAccountRequestRemove(), authorIdentity)
|
||||
case ch.GetReadKeyChange() != nil:
|
||||
return c.ValidateReadKeyChange(ch.GetReadKeyChange(), authorIdentity)
|
||||
default:
|
||||
return ErrUnexpectedContentType
|
||||
}
|
||||
}
|
||||
|
||||
func (c *contentValidator) ValidatePermissionChange(ch *aclrecordproto.AclAccountPermissionChange, authorIdentity crypto.PubKey) (err error) {
|
||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
chIdentity, err := c.keyStore.PubKeyFromProto(ch.Identity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, exists := c.aclState.userStates[mapKeyFromPubKey(chIdentity)]
|
||||
if !exists {
|
||||
return ErrNoSuchAccount
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *contentValidator) ValidateInvite(ch *aclrecordproto.AclAccountInvite, authorIdentity crypto.PubKey) (err error) {
|
||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
_, err = c.keyStore.PubKeyFromProto(ch.InviteKey)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *contentValidator) ValidateInviteRevoke(ch *aclrecordproto.AclAccountInviteRevoke, authorIdentity crypto.PubKey) (err error) {
|
||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
_, exists := c.aclState.inviteKeys[ch.InviteRecordId]
|
||||
if !exists {
|
||||
return ErrNoSuchInvite
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *contentValidator) ValidateRequestJoin(ch *aclrecordproto.AclAccountRequestJoin, authorIdentity crypto.PubKey) (err error) {
|
||||
inviteKey, exists := c.aclState.inviteKeys[ch.InviteRecordId]
|
||||
if !exists {
|
||||
return ErrNoSuchInvite
|
||||
}
|
||||
inviteIdentity, err := c.keyStore.PubKeyFromProto(ch.InviteIdentity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if _, exists := c.aclState.pendingRequests[mapKeyFromPubKey(inviteIdentity)]; exists {
|
||||
return ErrPendingRequest
|
||||
}
|
||||
if !authorIdentity.Equals(inviteIdentity) {
|
||||
return ErrIncorrectIdentity
|
||||
}
|
||||
rawInviteIdentity, err := inviteIdentity.Raw()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ok, err := inviteKey.Verify(rawInviteIdentity, ch.InviteIdentitySignature)
|
||||
if err != nil {
|
||||
return ErrInvalidSignature
|
||||
}
|
||||
if !ok {
|
||||
return ErrInvalidSignature
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *contentValidator) ValidateRequestAccept(ch *aclrecordproto.AclAccountRequestAccept, authorIdentity crypto.PubKey) (err error) {
|
||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
record, exists := c.aclState.requestRecords[ch.RequestRecordId]
|
||||
if !exists {
|
||||
return ErrNoSuchRequest
|
||||
}
|
||||
acceptIdentity, err := c.keyStore.PubKeyFromProto(ch.Identity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !acceptIdentity.Equals(record.RequestIdentity) {
|
||||
return ErrIncorrectIdentity
|
||||
}
|
||||
if ch.Permissions == aclrecordproto.AclUserPermissions_Owner {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *contentValidator) ValidateRequestDecline(ch *aclrecordproto.AclAccountRequestDecline, authorIdentity crypto.PubKey) (err error) {
|
||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
_, exists := c.aclState.requestRecords[ch.RequestRecordId]
|
||||
if !exists {
|
||||
return ErrNoSuchRequest
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *contentValidator) ValidateAccountRemove(ch *aclrecordproto.AclAccountRemove, authorIdentity crypto.PubKey) (err error) {
|
||||
if !c.aclState.Permissions(authorIdentity).CanManageAccounts() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
seenIdentities := map[string]struct{}{}
|
||||
for _, rawIdentity := range ch.Identities {
|
||||
identity, err := c.keyStore.PubKeyFromProto(rawIdentity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if identity.Equals(authorIdentity) {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
permissions := c.aclState.Permissions(identity)
|
||||
if permissions.NoPermissions() {
|
||||
return ErrNoSuchAccount
|
||||
}
|
||||
if permissions.IsOwner() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
idKey := mapKeyFromPubKey(identity)
|
||||
if _, exists := seenIdentities[idKey]; exists {
|
||||
return ErrDuplicateAccounts
|
||||
}
|
||||
seenIdentities[mapKeyFromPubKey(identity)] = struct{}{}
|
||||
}
|
||||
return c.validateAccountReadKeys(ch.AccountKeys, len(c.aclState.userStates)-len(ch.Identities))
|
||||
}
|
||||
|
||||
func (c *contentValidator) ValidateRequestRemove(ch *aclrecordproto.AclAccountRequestRemove, authorIdentity crypto.PubKey) (err error) {
|
||||
if c.aclState.Permissions(authorIdentity).NoPermissions() {
|
||||
return ErrInsufficientPermissions
|
||||
}
|
||||
if _, exists := c.aclState.pendingRequests[mapKeyFromPubKey(authorIdentity)]; exists {
|
||||
return ErrPendingRequest
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *contentValidator) ValidateReadKeyChange(ch *aclrecordproto.AclReadKeyChange, authorIdentity crypto.PubKey) (err error) {
|
||||
return c.validateAccountReadKeys(ch.AccountKeys, len(c.aclState.userStates))
|
||||
}
|
||||
|
||||
func (c *contentValidator) validateAccountReadKeys(accountKeys []*aclrecordproto.AclEncryptedReadKey, usersNum int) (err error) {
|
||||
if len(accountKeys) != usersNum {
|
||||
return ErrIncorrectNumberOfAccounts
|
||||
}
|
||||
for _, encKeys := range accountKeys {
|
||||
identity, err := c.keyStore.PubKeyFromProto(encKeys.Identity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, exists := c.aclState.userStates[mapKeyFromPubKey(identity)]
|
||||
if !exists {
|
||||
return ErrNoSuchAccount
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -3,26 +3,24 @@ package liststorage
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type inMemoryAclListStorage struct {
|
||||
id string
|
||||
root *consensusproto.RawRecordWithId
|
||||
root *aclrecordproto.RawAclRecordWithId
|
||||
head string
|
||||
records map[string]*consensusproto.RawRecordWithId
|
||||
records map[string]*aclrecordproto.RawAclRecordWithId
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func NewInMemoryAclListStorage(
|
||||
id string,
|
||||
records []*consensusproto.RawRecordWithId) (ListStorage, error) {
|
||||
records []*aclrecordproto.RawAclRecordWithId) (ListStorage, error) {
|
||||
|
||||
allRecords := make(map[string]*consensusproto.RawRecordWithId)
|
||||
allRecords := make(map[string]*aclrecordproto.RawAclRecordWithId)
|
||||
for _, ch := range records {
|
||||
allRecords[ch.Id] = ch
|
||||
}
|
||||
@@ -43,7 +41,7 @@ func (t *inMemoryAclListStorage) Id() string {
|
||||
return t.id
|
||||
}
|
||||
|
||||
func (t *inMemoryAclListStorage) Root() (*consensusproto.RawRecordWithId, error) {
|
||||
func (t *inMemoryAclListStorage) Root() (*aclrecordproto.RawAclRecordWithId, error) {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
return t.root, nil
|
||||
@@ -62,7 +60,7 @@ func (t *inMemoryAclListStorage) SetHead(head string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *inMemoryAclListStorage) AddRawRecord(ctx context.Context, record *consensusproto.RawRecordWithId) error {
|
||||
func (t *inMemoryAclListStorage) AddRawRecord(ctx context.Context, record *aclrecordproto.RawAclRecordWithId) error {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
// TODO: better to do deep copy
|
||||
@@ -70,7 +68,7 @@ func (t *inMemoryAclListStorage) AddRawRecord(ctx context.Context, record *conse
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *inMemoryAclListStorage) GetRawRecord(ctx context.Context, recordId string) (*consensusproto.RawRecordWithId, error) {
|
||||
func (t *inMemoryAclListStorage) GetRawRecord(ctx context.Context, recordId string) (*aclrecordproto.RawAclRecordWithId, error) {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
if res, exists := t.records[recordId]; exists {
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
//go:generate mockgen -destination mock_liststorage/mock_liststorage.go github.com/anyproto/any-sync/commonspace/object/acl/liststorage ListStorage
|
||||
//go:generate mockgen -destination mock_liststorage/mock_liststorage.go github.com/anytypeio/any-sync/commonspace/object/acl/liststorage ListStorage
|
||||
package liststorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -15,15 +14,15 @@ var (
|
||||
)
|
||||
|
||||
type Exporter interface {
|
||||
ListStorage(root *consensusproto.RawRecordWithId) (ListStorage, error)
|
||||
ListStorage(root *aclrecordproto.RawAclRecordWithId) (ListStorage, error)
|
||||
}
|
||||
|
||||
type ListStorage interface {
|
||||
Id() string
|
||||
Root() (*consensusproto.RawRecordWithId, error)
|
||||
Root() (*aclrecordproto.RawAclRecordWithId, error)
|
||||
Head() (string, error)
|
||||
SetHead(headId string) error
|
||||
|
||||
GetRawRecord(ctx context.Context, id string) (*consensusproto.RawRecordWithId, error)
|
||||
AddRawRecord(ctx context.Context, rec *consensusproto.RawRecordWithId) error
|
||||
GetRawRecord(ctx context.Context, id string) (*aclrecordproto.RawAclRecordWithId, error)
|
||||
AddRawRecord(ctx context.Context, rec *aclrecordproto.RawAclRecordWithId) error
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anyproto/any-sync/commonspace/object/acl/liststorage (interfaces: ListStorage)
|
||||
// Source: github.com/anytypeio/any-sync/commonspace/object/acl/liststorage (interfaces: ListStorage)
|
||||
|
||||
// Package mock_liststorage is a generated GoMock package.
|
||||
package mock_liststorage
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
consensusproto "github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
aclrecordproto "github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
gomock "github.com/golang/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 *consensusproto.RawRecordWithId) error {
|
||||
func (m *MockListStorage) AddRawRecord(arg0 context.Context, arg1 *aclrecordproto.RawAclRecordWithId) 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) (*consensusproto.RawRecordWithId, error) {
|
||||
func (m *MockListStorage) GetRawRecord(arg0 context.Context, arg1 string) (*aclrecordproto.RawAclRecordWithId, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetRawRecord", arg0, arg1)
|
||||
ret0, _ := ret[0].(*consensusproto.RawRecordWithId)
|
||||
ret0, _ := ret[0].(*aclrecordproto.RawAclRecordWithId)
|
||||
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() (*consensusproto.RawRecordWithId, error) {
|
||||
func (m *MockListStorage) Root() (*aclrecordproto.RawAclRecordWithId, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Root")
|
||||
ret0, _ := ret[0].(*consensusproto.RawRecordWithId)
|
||||
ret0, _ := ret[0].(*aclrecordproto.RawAclRecordWithId)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
//go:generate mockgen -destination mock_syncacl/mock_syncacl.go github.com/anyproto/any-sync/commonspace/object/acl/syncacl SyncAcl,SyncClient,RequestFactory,AclSyncProtocol
|
||||
package syncacl
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type AclSyncProtocol interface {
|
||||
HeadUpdate(ctx context.Context, senderId string, update *consensusproto.LogHeadUpdate) (request *consensusproto.LogSyncMessage, err error)
|
||||
FullSyncRequest(ctx context.Context, senderId string, request *consensusproto.LogFullSyncRequest) (response *consensusproto.LogSyncMessage, err error)
|
||||
FullSyncResponse(ctx context.Context, senderId string, response *consensusproto.LogFullSyncResponse) (err error)
|
||||
}
|
||||
|
||||
type aclSyncProtocol struct {
|
||||
log logger.CtxLogger
|
||||
spaceId string
|
||||
aclList list.AclList
|
||||
reqFactory RequestFactory
|
||||
}
|
||||
|
||||
func (a *aclSyncProtocol) HeadUpdate(ctx context.Context, senderId string, update *consensusproto.LogHeadUpdate) (request *consensusproto.LogSyncMessage, err error) {
|
||||
isEmptyUpdate := len(update.Records) == 0
|
||||
log := a.log.With(
|
||||
zap.String("senderId", senderId),
|
||||
zap.String("update head", update.Head),
|
||||
zap.Int("len(update records)", len(update.Records)))
|
||||
log.DebugCtx(ctx, "received acl head update message")
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.ErrorCtx(ctx, "acl head update finished with error", zap.Error(err))
|
||||
} else if request != nil {
|
||||
cnt := request.Content.GetFullSyncRequest()
|
||||
log.DebugCtx(ctx, "returning acl full sync request", zap.String("request head", cnt.Head))
|
||||
} else {
|
||||
if !isEmptyUpdate {
|
||||
log.DebugCtx(ctx, "acl head update finished correctly")
|
||||
}
|
||||
}
|
||||
}()
|
||||
if isEmptyUpdate {
|
||||
headEquals := a.aclList.Head().Id == update.Head
|
||||
log.DebugCtx(ctx, "is empty acl head update", zap.Bool("headEquals", headEquals))
|
||||
if headEquals {
|
||||
return
|
||||
}
|
||||
return a.reqFactory.CreateFullSyncRequest(a.aclList, update.Head)
|
||||
}
|
||||
if a.aclList.HasHead(update.Head) {
|
||||
return
|
||||
}
|
||||
err = a.aclList.AddRawRecords(update.Records)
|
||||
if err == list.ErrIncorrectRecordSequence {
|
||||
return a.reqFactory.CreateFullSyncRequest(a.aclList, update.Head)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (a *aclSyncProtocol) FullSyncRequest(ctx context.Context, senderId string, request *consensusproto.LogFullSyncRequest) (response *consensusproto.LogSyncMessage, err error) {
|
||||
log := a.log.With(
|
||||
zap.String("senderId", senderId),
|
||||
zap.String("request head", request.Head),
|
||||
zap.Int("len(request records)", len(request.Records)))
|
||||
log.DebugCtx(ctx, "received acl full sync request message")
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.ErrorCtx(ctx, "acl full sync request finished with error", zap.Error(err))
|
||||
} else if response != nil {
|
||||
cnt := response.Content.GetFullSyncResponse()
|
||||
log.DebugCtx(ctx, "acl full sync response sent", zap.String("response head", cnt.Head), zap.Int("len(response records)", len(cnt.Records)))
|
||||
}
|
||||
}()
|
||||
if !a.aclList.HasHead(request.Head) {
|
||||
if len(request.Records) > 0 {
|
||||
// in this case we can try to add some records
|
||||
err = a.aclList.AddRawRecords(request.Records)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// here it is impossible for us to do anything, we can't return records after head as defined in request, because we don't have it
|
||||
return nil, list.ErrIncorrectRecordSequence
|
||||
}
|
||||
}
|
||||
return a.reqFactory.CreateFullSyncResponse(a.aclList, request.Head)
|
||||
}
|
||||
|
||||
func (a *aclSyncProtocol) FullSyncResponse(ctx context.Context, senderId string, response *consensusproto.LogFullSyncResponse) (err error) {
|
||||
log := a.log.With(
|
||||
zap.String("senderId", senderId),
|
||||
zap.String("response head", response.Head),
|
||||
zap.Int("len(response records)", len(response.Records)))
|
||||
log.DebugCtx(ctx, "received acl full sync response message")
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.ErrorCtx(ctx, "acl full sync response failed", zap.Error(err))
|
||||
} else {
|
||||
log.DebugCtx(ctx, "acl full sync response succeeded")
|
||||
}
|
||||
}()
|
||||
if a.aclList.HasHead(response.Head) {
|
||||
return
|
||||
}
|
||||
return a.aclList.AddRawRecords(response.Records)
|
||||
}
|
||||
|
||||
func newAclSyncProtocol(spaceId string, aclList list.AclList, reqFactory RequestFactory) *aclSyncProtocol {
|
||||
return &aclSyncProtocol{
|
||||
log: log.With(zap.String("spaceId", spaceId), zap.String("aclId", aclList.Id())),
|
||||
spaceId: spaceId,
|
||||
aclList: aclList,
|
||||
reqFactory: reqFactory,
|
||||
}
|
||||
}
|
||||
@@ -1,213 +0,0 @@
|
||||
package syncacl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list/mock_list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl/mock_syncacl"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type aclSyncProtocolFixture struct {
|
||||
log logger.CtxLogger
|
||||
spaceId string
|
||||
senderId string
|
||||
aclId string
|
||||
aclMock *mock_list.MockAclList
|
||||
reqFactory *mock_syncacl.MockRequestFactory
|
||||
ctrl *gomock.Controller
|
||||
syncProtocol AclSyncProtocol
|
||||
}
|
||||
|
||||
func newSyncProtocolFixture(t *testing.T) *aclSyncProtocolFixture {
|
||||
ctrl := gomock.NewController(t)
|
||||
aclList := mock_list.NewMockAclList(ctrl)
|
||||
spaceId := "spaceId"
|
||||
reqFactory := mock_syncacl.NewMockRequestFactory(ctrl)
|
||||
aclList.EXPECT().Id().Return("aclId")
|
||||
syncProtocol := newAclSyncProtocol(spaceId, aclList, reqFactory)
|
||||
return &aclSyncProtocolFixture{
|
||||
log: log,
|
||||
spaceId: spaceId,
|
||||
senderId: "senderId",
|
||||
aclId: "aclId",
|
||||
aclMock: aclList,
|
||||
reqFactory: reqFactory,
|
||||
ctrl: ctrl,
|
||||
syncProtocol: syncProtocol,
|
||||
}
|
||||
}
|
||||
|
||||
func (fx *aclSyncProtocolFixture) stop() {
|
||||
fx.ctrl.Finish()
|
||||
}
|
||||
|
||||
func TestHeadUpdate(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
fullRequest := &consensusproto.LogSyncMessage{
|
||||
Content: &consensusproto.LogSyncContentValue{
|
||||
Value: &consensusproto.LogSyncContentValue_FullSyncRequest{
|
||||
FullSyncRequest: &consensusproto.LogFullSyncRequest{},
|
||||
},
|
||||
},
|
||||
}
|
||||
t.Run("head update non empty all heads added", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &consensusproto.RawRecordWithId{}
|
||||
headUpdate := &consensusproto.LogHeadUpdate{
|
||||
Head: "h1",
|
||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
||||
}
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
fx.aclMock.EXPECT().HasHead("h1").Return(false)
|
||||
fx.aclMock.EXPECT().AddRawRecords(headUpdate.Records).Return(nil)
|
||||
req, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
||||
require.Nil(t, req)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("head update results in full request", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &consensusproto.RawRecordWithId{}
|
||||
headUpdate := &consensusproto.LogHeadUpdate{
|
||||
Head: "h1",
|
||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
||||
}
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
fx.aclMock.EXPECT().HasHead("h1").Return(false)
|
||||
fx.aclMock.EXPECT().AddRawRecords(headUpdate.Records).Return(list.ErrIncorrectRecordSequence)
|
||||
fx.reqFactory.EXPECT().CreateFullSyncRequest(fx.aclMock, headUpdate.Head).Return(fullRequest, nil)
|
||||
req, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
||||
require.Equal(t, fullRequest, req)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("head update old heads", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &consensusproto.RawRecordWithId{}
|
||||
headUpdate := &consensusproto.LogHeadUpdate{
|
||||
Head: "h1",
|
||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
||||
}
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
fx.aclMock.EXPECT().HasHead("h1").Return(true)
|
||||
req, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
||||
require.Nil(t, req)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("head update empty equals", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
headUpdate := &consensusproto.LogHeadUpdate{
|
||||
Head: "h1",
|
||||
}
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
fx.aclMock.EXPECT().Head().Return(&list.AclRecord{Id: "h1"})
|
||||
req, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
||||
require.Nil(t, req)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("head update empty results in full request", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
headUpdate := &consensusproto.LogHeadUpdate{
|
||||
Head: "h1",
|
||||
}
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
fx.aclMock.EXPECT().Head().Return(&list.AclRecord{Id: "h2"})
|
||||
fx.reqFactory.EXPECT().CreateFullSyncRequest(fx.aclMock, headUpdate.Head).Return(fullRequest, nil)
|
||||
req, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
||||
require.Equal(t, fullRequest, req)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFullSyncRequest(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
fullResponse := &consensusproto.LogSyncMessage{
|
||||
Content: &consensusproto.LogSyncContentValue{
|
||||
Value: &consensusproto.LogSyncContentValue_FullSyncResponse{
|
||||
FullSyncResponse: &consensusproto.LogFullSyncResponse{},
|
||||
},
|
||||
},
|
||||
}
|
||||
t.Run("full sync request non empty all heads added", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &consensusproto.RawRecordWithId{}
|
||||
fullRequest := &consensusproto.LogFullSyncRequest{
|
||||
Head: "h1",
|
||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
||||
}
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
fx.aclMock.EXPECT().HasHead("h1").Return(false)
|
||||
fx.aclMock.EXPECT().AddRawRecords(fullRequest.Records).Return(nil)
|
||||
fx.reqFactory.EXPECT().CreateFullSyncResponse(fx.aclMock, fullRequest.Head).Return(fullResponse, nil)
|
||||
resp, err := fx.syncProtocol.FullSyncRequest(ctx, fx.senderId, fullRequest)
|
||||
require.Equal(t, fullResponse, resp)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("full sync request non empty head exists", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &consensusproto.RawRecordWithId{}
|
||||
fullRequest := &consensusproto.LogFullSyncRequest{
|
||||
Head: "h1",
|
||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
||||
}
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
fx.aclMock.EXPECT().HasHead("h1").Return(true)
|
||||
fx.reqFactory.EXPECT().CreateFullSyncResponse(fx.aclMock, fullRequest.Head).Return(fullResponse, nil)
|
||||
resp, err := fx.syncProtocol.FullSyncRequest(ctx, fx.senderId, fullRequest)
|
||||
require.Equal(t, fullResponse, resp)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("full sync request empty head not exists", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
fullRequest := &consensusproto.LogFullSyncRequest{
|
||||
Head: "h1",
|
||||
}
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
fx.aclMock.EXPECT().HasHead("h1").Return(false)
|
||||
resp, err := fx.syncProtocol.FullSyncRequest(ctx, fx.senderId, fullRequest)
|
||||
require.Nil(t, resp)
|
||||
require.Error(t, list.ErrIncorrectRecordSequence, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFullSyncResponse(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
t.Run("full sync response no heads", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &consensusproto.RawRecordWithId{}
|
||||
fullResponse := &consensusproto.LogFullSyncResponse{
|
||||
Head: "h1",
|
||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
||||
}
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
fx.aclMock.EXPECT().HasHead("h1").Return(false)
|
||||
fx.aclMock.EXPECT().AddRawRecords(fullResponse.Records).Return(nil)
|
||||
err := fx.syncProtocol.FullSyncResponse(ctx, fx.senderId, fullResponse)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("full sync response has heads", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &consensusproto.RawRecordWithId{}
|
||||
fullResponse := &consensusproto.LogFullSyncResponse{
|
||||
Head: "h1",
|
||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
||||
}
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
fx.aclMock.EXPECT().HasHead("h1").Return(true)
|
||||
err := fx.syncProtocol.FullSyncResponse(ctx, fx.senderId, fullResponse)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package headupdater
|
||||
|
||||
type HeadUpdater interface {
|
||||
UpdateHeads(id string, heads []string)
|
||||
}
|
||||
@@ -1,694 +0,0 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anyproto/any-sync/commonspace/object/acl/syncacl (interfaces: SyncAcl,SyncClient,RequestFactory,AclSyncProtocol)
|
||||
|
||||
// Package mock_syncacl is a generated GoMock package.
|
||||
package mock_syncacl
|
||||
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
app "github.com/anyproto/any-sync/app"
|
||||
list "github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
headupdater "github.com/anyproto/any-sync/commonspace/object/acl/syncacl/headupdater"
|
||||
spacesyncproto "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
consensusproto "github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
crypto "github.com/anyproto/any-sync/util/crypto"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockSyncAcl is a mock of SyncAcl interface.
|
||||
type MockSyncAcl struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockSyncAclMockRecorder
|
||||
}
|
||||
|
||||
// MockSyncAclMockRecorder is the mock recorder for MockSyncAcl.
|
||||
type MockSyncAclMockRecorder struct {
|
||||
mock *MockSyncAcl
|
||||
}
|
||||
|
||||
// NewMockSyncAcl creates a new mock instance.
|
||||
func NewMockSyncAcl(ctrl *gomock.Controller) *MockSyncAcl {
|
||||
mock := &MockSyncAcl{ctrl: ctrl}
|
||||
mock.recorder = &MockSyncAclMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockSyncAcl) EXPECT() *MockSyncAclMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// AclState mocks base method.
|
||||
func (m *MockSyncAcl) AclState() *list.AclState {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AclState")
|
||||
ret0, _ := ret[0].(*list.AclState)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AclState indicates an expected call of AclState.
|
||||
func (mr *MockSyncAclMockRecorder) AclState() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AclState", reflect.TypeOf((*MockSyncAcl)(nil).AclState))
|
||||
}
|
||||
|
||||
// AddRawRecord mocks base method.
|
||||
func (m *MockSyncAcl) AddRawRecord(arg0 *consensusproto.RawRecordWithId) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AddRawRecord", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddRawRecord indicates an expected call of AddRawRecord.
|
||||
func (mr *MockSyncAclMockRecorder) AddRawRecord(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawRecord", reflect.TypeOf((*MockSyncAcl)(nil).AddRawRecord), arg0)
|
||||
}
|
||||
|
||||
// AddRawRecords mocks base method.
|
||||
func (m *MockSyncAcl) AddRawRecords(arg0 []*consensusproto.RawRecordWithId) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AddRawRecords", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddRawRecords indicates an expected call of AddRawRecords.
|
||||
func (mr *MockSyncAclMockRecorder) AddRawRecords(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawRecords", reflect.TypeOf((*MockSyncAcl)(nil).AddRawRecords), arg0)
|
||||
}
|
||||
|
||||
// Close mocks base method.
|
||||
func (m *MockSyncAcl) Close(arg0 context.Context) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Close", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Close indicates an expected call of Close.
|
||||
func (mr *MockSyncAclMockRecorder) Close(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockSyncAcl)(nil).Close), arg0)
|
||||
}
|
||||
|
||||
// Get mocks base method.
|
||||
func (m *MockSyncAcl) Get(arg0 string) (*list.AclRecord, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Get", arg0)
|
||||
ret0, _ := ret[0].(*list.AclRecord)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Get indicates an expected call of Get.
|
||||
func (mr *MockSyncAclMockRecorder) Get(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockSyncAcl)(nil).Get), arg0)
|
||||
}
|
||||
|
||||
// GetIndex mocks base method.
|
||||
func (m *MockSyncAcl) GetIndex(arg0 int) (*list.AclRecord, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetIndex", arg0)
|
||||
ret0, _ := ret[0].(*list.AclRecord)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetIndex indicates an expected call of GetIndex.
|
||||
func (mr *MockSyncAclMockRecorder) GetIndex(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIndex", reflect.TypeOf((*MockSyncAcl)(nil).GetIndex), arg0)
|
||||
}
|
||||
|
||||
// HandleMessage mocks base method.
|
||||
func (m *MockSyncAcl) HandleMessage(arg0 context.Context, arg1 string, arg2 *spacesyncproto.ObjectSyncMessage) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "HandleMessage", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// HandleMessage indicates an expected call of HandleMessage.
|
||||
func (mr *MockSyncAclMockRecorder) HandleMessage(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleMessage", reflect.TypeOf((*MockSyncAcl)(nil).HandleMessage), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// HandleRequest mocks base method.
|
||||
func (m *MockSyncAcl) HandleRequest(arg0 context.Context, arg1 string, arg2 *spacesyncproto.ObjectSyncMessage) (*spacesyncproto.ObjectSyncMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "HandleRequest", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(*spacesyncproto.ObjectSyncMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// HandleRequest indicates an expected call of HandleRequest.
|
||||
func (mr *MockSyncAclMockRecorder) HandleRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleRequest", reflect.TypeOf((*MockSyncAcl)(nil).HandleRequest), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// HasHead mocks base method.
|
||||
func (m *MockSyncAcl) HasHead(arg0 string) bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "HasHead", arg0)
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// HasHead indicates an expected call of HasHead.
|
||||
func (mr *MockSyncAclMockRecorder) HasHead(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasHead", reflect.TypeOf((*MockSyncAcl)(nil).HasHead), arg0)
|
||||
}
|
||||
|
||||
// Head mocks base method.
|
||||
func (m *MockSyncAcl) Head() *list.AclRecord {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Head")
|
||||
ret0, _ := ret[0].(*list.AclRecord)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Head indicates an expected call of Head.
|
||||
func (mr *MockSyncAclMockRecorder) Head() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Head", reflect.TypeOf((*MockSyncAcl)(nil).Head))
|
||||
}
|
||||
|
||||
// Id mocks base method.
|
||||
func (m *MockSyncAcl) Id() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Id")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Id indicates an expected call of Id.
|
||||
func (mr *MockSyncAclMockRecorder) Id() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Id", reflect.TypeOf((*MockSyncAcl)(nil).Id))
|
||||
}
|
||||
|
||||
// Init mocks base method.
|
||||
func (m *MockSyncAcl) Init(arg0 *app.App) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Init", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Init indicates an expected call of Init.
|
||||
func (mr *MockSyncAclMockRecorder) Init(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockSyncAcl)(nil).Init), arg0)
|
||||
}
|
||||
|
||||
// IsAfter mocks base method.
|
||||
func (m *MockSyncAcl) IsAfter(arg0, arg1 string) (bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "IsAfter", arg0, arg1)
|
||||
ret0, _ := ret[0].(bool)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// IsAfter indicates an expected call of IsAfter.
|
||||
func (mr *MockSyncAclMockRecorder) IsAfter(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAfter", reflect.TypeOf((*MockSyncAcl)(nil).IsAfter), arg0, arg1)
|
||||
}
|
||||
|
||||
// Iterate mocks base method.
|
||||
func (m *MockSyncAcl) Iterate(arg0 func(*list.AclRecord) bool) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Iterate", arg0)
|
||||
}
|
||||
|
||||
// Iterate indicates an expected call of Iterate.
|
||||
func (mr *MockSyncAclMockRecorder) Iterate(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Iterate", reflect.TypeOf((*MockSyncAcl)(nil).Iterate), arg0)
|
||||
}
|
||||
|
||||
// IterateFrom mocks base method.
|
||||
func (m *MockSyncAcl) IterateFrom(arg0 string, arg1 func(*list.AclRecord) bool) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "IterateFrom", arg0, arg1)
|
||||
}
|
||||
|
||||
// IterateFrom indicates an expected call of IterateFrom.
|
||||
func (mr *MockSyncAclMockRecorder) IterateFrom(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IterateFrom", reflect.TypeOf((*MockSyncAcl)(nil).IterateFrom), arg0, arg1)
|
||||
}
|
||||
|
||||
// KeyStorage mocks base method.
|
||||
func (m *MockSyncAcl) KeyStorage() crypto.KeyStorage {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "KeyStorage")
|
||||
ret0, _ := ret[0].(crypto.KeyStorage)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// KeyStorage indicates an expected call of KeyStorage.
|
||||
func (mr *MockSyncAclMockRecorder) KeyStorage() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeyStorage", reflect.TypeOf((*MockSyncAcl)(nil).KeyStorage))
|
||||
}
|
||||
|
||||
// Lock mocks base method.
|
||||
func (m *MockSyncAcl) Lock() {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Lock")
|
||||
}
|
||||
|
||||
// Lock indicates an expected call of Lock.
|
||||
func (mr *MockSyncAclMockRecorder) Lock() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Lock", reflect.TypeOf((*MockSyncAcl)(nil).Lock))
|
||||
}
|
||||
|
||||
// Name mocks base method.
|
||||
func (m *MockSyncAcl) Name() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Name")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Name indicates an expected call of Name.
|
||||
func (mr *MockSyncAclMockRecorder) Name() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockSyncAcl)(nil).Name))
|
||||
}
|
||||
|
||||
// RLock mocks base method.
|
||||
func (m *MockSyncAcl) RLock() {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "RLock")
|
||||
}
|
||||
|
||||
// RLock indicates an expected call of RLock.
|
||||
func (mr *MockSyncAclMockRecorder) RLock() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RLock", reflect.TypeOf((*MockSyncAcl)(nil).RLock))
|
||||
}
|
||||
|
||||
// RUnlock mocks base method.
|
||||
func (m *MockSyncAcl) RUnlock() {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "RUnlock")
|
||||
}
|
||||
|
||||
// RUnlock indicates an expected call of RUnlock.
|
||||
func (mr *MockSyncAclMockRecorder) RUnlock() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RUnlock", reflect.TypeOf((*MockSyncAcl)(nil).RUnlock))
|
||||
}
|
||||
|
||||
// RecordBuilder mocks base method.
|
||||
func (m *MockSyncAcl) RecordBuilder() list.AclRecordBuilder {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "RecordBuilder")
|
||||
ret0, _ := ret[0].(list.AclRecordBuilder)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// RecordBuilder indicates an expected call of RecordBuilder.
|
||||
func (mr *MockSyncAclMockRecorder) RecordBuilder() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordBuilder", reflect.TypeOf((*MockSyncAcl)(nil).RecordBuilder))
|
||||
}
|
||||
|
||||
// Records mocks base method.
|
||||
func (m *MockSyncAcl) Records() []*list.AclRecord {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Records")
|
||||
ret0, _ := ret[0].([]*list.AclRecord)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Records indicates an expected call of Records.
|
||||
func (mr *MockSyncAclMockRecorder) Records() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Records", reflect.TypeOf((*MockSyncAcl)(nil).Records))
|
||||
}
|
||||
|
||||
// RecordsAfter mocks base method.
|
||||
func (m *MockSyncAcl) RecordsAfter(arg0 context.Context, arg1 string) ([]*consensusproto.RawRecordWithId, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "RecordsAfter", arg0, arg1)
|
||||
ret0, _ := ret[0].([]*consensusproto.RawRecordWithId)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// RecordsAfter indicates an expected call of RecordsAfter.
|
||||
func (mr *MockSyncAclMockRecorder) RecordsAfter(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordsAfter", reflect.TypeOf((*MockSyncAcl)(nil).RecordsAfter), arg0, arg1)
|
||||
}
|
||||
|
||||
// Root mocks base method.
|
||||
func (m *MockSyncAcl) Root() *consensusproto.RawRecordWithId {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Root")
|
||||
ret0, _ := ret[0].(*consensusproto.RawRecordWithId)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Root indicates an expected call of Root.
|
||||
func (mr *MockSyncAclMockRecorder) Root() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Root", reflect.TypeOf((*MockSyncAcl)(nil).Root))
|
||||
}
|
||||
|
||||
// Run mocks base method.
|
||||
func (m *MockSyncAcl) Run(arg0 context.Context) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Run", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Run indicates an expected call of Run.
|
||||
func (mr *MockSyncAclMockRecorder) Run(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockSyncAcl)(nil).Run), arg0)
|
||||
}
|
||||
|
||||
// SetHeadUpdater mocks base method.
|
||||
func (m *MockSyncAcl) SetHeadUpdater(arg0 headupdater.HeadUpdater) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "SetHeadUpdater", arg0)
|
||||
}
|
||||
|
||||
// SetHeadUpdater indicates an expected call of SetHeadUpdater.
|
||||
func (mr *MockSyncAclMockRecorder) SetHeadUpdater(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetHeadUpdater", reflect.TypeOf((*MockSyncAcl)(nil).SetHeadUpdater), arg0)
|
||||
}
|
||||
|
||||
// SyncWithPeer mocks base method.
|
||||
func (m *MockSyncAcl) SyncWithPeer(arg0 context.Context, arg1 string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SyncWithPeer", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SyncWithPeer indicates an expected call of SyncWithPeer.
|
||||
func (mr *MockSyncAclMockRecorder) SyncWithPeer(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncWithPeer", reflect.TypeOf((*MockSyncAcl)(nil).SyncWithPeer), arg0, arg1)
|
||||
}
|
||||
|
||||
// Unlock mocks base method.
|
||||
func (m *MockSyncAcl) Unlock() {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Unlock")
|
||||
}
|
||||
|
||||
// Unlock indicates an expected call of Unlock.
|
||||
func (mr *MockSyncAclMockRecorder) Unlock() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockSyncAcl)(nil).Unlock))
|
||||
}
|
||||
|
||||
// ValidateRawRecord mocks base method.
|
||||
func (m *MockSyncAcl) ValidateRawRecord(arg0 *consensusproto.RawRecord) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ValidateRawRecord", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ValidateRawRecord indicates an expected call of ValidateRawRecord.
|
||||
func (mr *MockSyncAclMockRecorder) ValidateRawRecord(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateRawRecord", reflect.TypeOf((*MockSyncAcl)(nil).ValidateRawRecord), arg0)
|
||||
}
|
||||
|
||||
// MockSyncClient is a mock of SyncClient interface.
|
||||
type MockSyncClient struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockSyncClientMockRecorder
|
||||
}
|
||||
|
||||
// MockSyncClientMockRecorder is the mock recorder for MockSyncClient.
|
||||
type MockSyncClientMockRecorder struct {
|
||||
mock *MockSyncClient
|
||||
}
|
||||
|
||||
// NewMockSyncClient creates a new mock instance.
|
||||
func NewMockSyncClient(ctrl *gomock.Controller) *MockSyncClient {
|
||||
mock := &MockSyncClient{ctrl: ctrl}
|
||||
mock.recorder = &MockSyncClientMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockSyncClient) EXPECT() *MockSyncClientMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Broadcast mocks base method.
|
||||
func (m *MockSyncClient) Broadcast(arg0 *consensusproto.LogSyncMessage) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Broadcast", arg0)
|
||||
}
|
||||
|
||||
// Broadcast indicates an expected call of Broadcast.
|
||||
func (mr *MockSyncClientMockRecorder) Broadcast(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Broadcast", reflect.TypeOf((*MockSyncClient)(nil).Broadcast), arg0)
|
||||
}
|
||||
|
||||
// CreateFullSyncRequest mocks base method.
|
||||
func (m *MockSyncClient) CreateFullSyncRequest(arg0 list.AclList, arg1 string) (*consensusproto.LogSyncMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateFullSyncRequest", arg0, arg1)
|
||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CreateFullSyncRequest indicates an expected call of CreateFullSyncRequest.
|
||||
func (mr *MockSyncClientMockRecorder) CreateFullSyncRequest(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncRequest", reflect.TypeOf((*MockSyncClient)(nil).CreateFullSyncRequest), arg0, arg1)
|
||||
}
|
||||
|
||||
// CreateFullSyncResponse mocks base method.
|
||||
func (m *MockSyncClient) CreateFullSyncResponse(arg0 list.AclList, arg1 string) (*consensusproto.LogSyncMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateFullSyncResponse", arg0, arg1)
|
||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CreateFullSyncResponse indicates an expected call of CreateFullSyncResponse.
|
||||
func (mr *MockSyncClientMockRecorder) CreateFullSyncResponse(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncResponse", reflect.TypeOf((*MockSyncClient)(nil).CreateFullSyncResponse), arg0, arg1)
|
||||
}
|
||||
|
||||
// CreateHeadUpdate mocks base method.
|
||||
func (m *MockSyncClient) CreateHeadUpdate(arg0 list.AclList, arg1 []*consensusproto.RawRecordWithId) *consensusproto.LogSyncMessage {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateHeadUpdate", arg0, arg1)
|
||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// CreateHeadUpdate indicates an expected call of CreateHeadUpdate.
|
||||
func (mr *MockSyncClientMockRecorder) CreateHeadUpdate(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateHeadUpdate", reflect.TypeOf((*MockSyncClient)(nil).CreateHeadUpdate), arg0, arg1)
|
||||
}
|
||||
|
||||
// QueueRequest mocks base method.
|
||||
func (m *MockSyncClient) QueueRequest(arg0 string, arg1 *consensusproto.LogSyncMessage) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "QueueRequest", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// QueueRequest indicates an expected call of QueueRequest.
|
||||
func (mr *MockSyncClientMockRecorder) QueueRequest(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueRequest", reflect.TypeOf((*MockSyncClient)(nil).QueueRequest), arg0, arg1)
|
||||
}
|
||||
|
||||
// SendRequest mocks base method.
|
||||
func (m *MockSyncClient) SendRequest(arg0 context.Context, arg1 string, arg2 *consensusproto.LogSyncMessage) (*spacesyncproto.ObjectSyncMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SendRequest", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(*spacesyncproto.ObjectSyncMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// SendRequest indicates an expected call of SendRequest.
|
||||
func (mr *MockSyncClientMockRecorder) SendRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendRequest", reflect.TypeOf((*MockSyncClient)(nil).SendRequest), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// SendUpdate mocks base method.
|
||||
func (m *MockSyncClient) SendUpdate(arg0 string, arg1 *consensusproto.LogSyncMessage) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SendUpdate", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SendUpdate indicates an expected call of SendUpdate.
|
||||
func (mr *MockSyncClientMockRecorder) SendUpdate(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendUpdate", reflect.TypeOf((*MockSyncClient)(nil).SendUpdate), arg0, arg1)
|
||||
}
|
||||
|
||||
// MockRequestFactory is a mock of RequestFactory interface.
|
||||
type MockRequestFactory struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockRequestFactoryMockRecorder
|
||||
}
|
||||
|
||||
// MockRequestFactoryMockRecorder is the mock recorder for MockRequestFactory.
|
||||
type MockRequestFactoryMockRecorder struct {
|
||||
mock *MockRequestFactory
|
||||
}
|
||||
|
||||
// NewMockRequestFactory creates a new mock instance.
|
||||
func NewMockRequestFactory(ctrl *gomock.Controller) *MockRequestFactory {
|
||||
mock := &MockRequestFactory{ctrl: ctrl}
|
||||
mock.recorder = &MockRequestFactoryMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockRequestFactory) EXPECT() *MockRequestFactoryMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// CreateFullSyncRequest mocks base method.
|
||||
func (m *MockRequestFactory) CreateFullSyncRequest(arg0 list.AclList, arg1 string) (*consensusproto.LogSyncMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateFullSyncRequest", arg0, arg1)
|
||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CreateFullSyncRequest indicates an expected call of CreateFullSyncRequest.
|
||||
func (mr *MockRequestFactoryMockRecorder) CreateFullSyncRequest(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncRequest", reflect.TypeOf((*MockRequestFactory)(nil).CreateFullSyncRequest), arg0, arg1)
|
||||
}
|
||||
|
||||
// CreateFullSyncResponse mocks base method.
|
||||
func (m *MockRequestFactory) CreateFullSyncResponse(arg0 list.AclList, arg1 string) (*consensusproto.LogSyncMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateFullSyncResponse", arg0, arg1)
|
||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CreateFullSyncResponse indicates an expected call of CreateFullSyncResponse.
|
||||
func (mr *MockRequestFactoryMockRecorder) CreateFullSyncResponse(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncResponse", reflect.TypeOf((*MockRequestFactory)(nil).CreateFullSyncResponse), arg0, arg1)
|
||||
}
|
||||
|
||||
// CreateHeadUpdate mocks base method.
|
||||
func (m *MockRequestFactory) CreateHeadUpdate(arg0 list.AclList, arg1 []*consensusproto.RawRecordWithId) *consensusproto.LogSyncMessage {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateHeadUpdate", arg0, arg1)
|
||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// CreateHeadUpdate indicates an expected call of CreateHeadUpdate.
|
||||
func (mr *MockRequestFactoryMockRecorder) CreateHeadUpdate(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateHeadUpdate", reflect.TypeOf((*MockRequestFactory)(nil).CreateHeadUpdate), arg0, arg1)
|
||||
}
|
||||
|
||||
// MockAclSyncProtocol is a mock of AclSyncProtocol interface.
|
||||
type MockAclSyncProtocol struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockAclSyncProtocolMockRecorder
|
||||
}
|
||||
|
||||
// MockAclSyncProtocolMockRecorder is the mock recorder for MockAclSyncProtocol.
|
||||
type MockAclSyncProtocolMockRecorder struct {
|
||||
mock *MockAclSyncProtocol
|
||||
}
|
||||
|
||||
// NewMockAclSyncProtocol creates a new mock instance.
|
||||
func NewMockAclSyncProtocol(ctrl *gomock.Controller) *MockAclSyncProtocol {
|
||||
mock := &MockAclSyncProtocol{ctrl: ctrl}
|
||||
mock.recorder = &MockAclSyncProtocolMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockAclSyncProtocol) EXPECT() *MockAclSyncProtocolMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// FullSyncRequest mocks base method.
|
||||
func (m *MockAclSyncProtocol) FullSyncRequest(arg0 context.Context, arg1 string, arg2 *consensusproto.LogFullSyncRequest) (*consensusproto.LogSyncMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "FullSyncRequest", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// FullSyncRequest indicates an expected call of FullSyncRequest.
|
||||
func (mr *MockAclSyncProtocolMockRecorder) FullSyncRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FullSyncRequest", reflect.TypeOf((*MockAclSyncProtocol)(nil).FullSyncRequest), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// FullSyncResponse mocks base method.
|
||||
func (m *MockAclSyncProtocol) FullSyncResponse(arg0 context.Context, arg1 string, arg2 *consensusproto.LogFullSyncResponse) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "FullSyncResponse", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// FullSyncResponse indicates an expected call of FullSyncResponse.
|
||||
func (mr *MockAclSyncProtocolMockRecorder) FullSyncResponse(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FullSyncResponse", reflect.TypeOf((*MockAclSyncProtocol)(nil).FullSyncResponse), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// HeadUpdate mocks base method.
|
||||
func (m *MockAclSyncProtocol) HeadUpdate(arg0 context.Context, arg1 string, arg2 *consensusproto.LogHeadUpdate) (*consensusproto.LogSyncMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "HeadUpdate", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(*consensusproto.LogSyncMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// HeadUpdate indicates an expected call of HeadUpdate.
|
||||
func (mr *MockAclSyncProtocolMockRecorder) HeadUpdate(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeadUpdate", reflect.TypeOf((*MockAclSyncProtocol)(nil).HeadUpdate), arg0, arg1, arg2)
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package syncacl
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
)
|
||||
|
||||
type RequestFactory interface {
|
||||
CreateHeadUpdate(l list.AclList, added []*consensusproto.RawRecordWithId) (msg *consensusproto.LogSyncMessage)
|
||||
CreateFullSyncRequest(l list.AclList, theirHead string) (req *consensusproto.LogSyncMessage, err error)
|
||||
CreateFullSyncResponse(l list.AclList, theirHead string) (*consensusproto.LogSyncMessage, error)
|
||||
}
|
||||
|
||||
type requestFactory struct{}
|
||||
|
||||
func NewRequestFactory() RequestFactory {
|
||||
return &requestFactory{}
|
||||
}
|
||||
|
||||
func (r *requestFactory) CreateHeadUpdate(l list.AclList, added []*consensusproto.RawRecordWithId) (msg *consensusproto.LogSyncMessage) {
|
||||
return consensusproto.WrapHeadUpdate(&consensusproto.LogHeadUpdate{
|
||||
Head: l.Head().Id,
|
||||
Records: added,
|
||||
}, l.Root())
|
||||
}
|
||||
|
||||
func (r *requestFactory) CreateFullSyncRequest(l list.AclList, theirHead string) (req *consensusproto.LogSyncMessage, err error) {
|
||||
if !l.HasHead(theirHead) {
|
||||
return consensusproto.WrapFullRequest(&consensusproto.LogFullSyncRequest{
|
||||
Head: l.Head().Id,
|
||||
}, l.Root()), nil
|
||||
}
|
||||
records, err := l.RecordsAfter(context.Background(), theirHead)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return consensusproto.WrapFullRequest(&consensusproto.LogFullSyncRequest{
|
||||
Head: l.Head().Id,
|
||||
Records: records,
|
||||
}, l.Root()), nil
|
||||
}
|
||||
|
||||
func (r *requestFactory) CreateFullSyncResponse(l list.AclList, theirHead string) (resp *consensusproto.LogSyncMessage, err error) {
|
||||
records, err := l.RecordsAfter(context.Background(), theirHead)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return consensusproto.WrapFullResponse(&consensusproto.LogFullSyncResponse{
|
||||
Head: l.Head().Id,
|
||||
Records: records,
|
||||
}, l.Root()), nil
|
||||
}
|
||||
@@ -1,130 +1,21 @@
|
||||
package syncacl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl/headupdater"
|
||||
"github.com/anyproto/any-sync/commonspace/object/syncobjectgetter"
|
||||
|
||||
"github.com/anyproto/any-sync/accountservice"
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
||||
"github.com/anyproto/any-sync/commonspace/requestmanager"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync/synchandler"
|
||||
)
|
||||
|
||||
const CName = "common.acl.syncacl"
|
||||
|
||||
var (
|
||||
log = logger.NewNamed(CName)
|
||||
|
||||
ErrSyncAclClosed = errors.New("sync acl is closed")
|
||||
)
|
||||
|
||||
type SyncAcl interface {
|
||||
app.ComponentRunnable
|
||||
type SyncAcl struct {
|
||||
list.AclList
|
||||
syncobjectgetter.SyncObject
|
||||
SetHeadUpdater(updater headupdater.HeadUpdater)
|
||||
SyncWithPeer(ctx context.Context, peerId string) (err error)
|
||||
synchandler.SyncHandler
|
||||
messagePool objectsync.MessagePool
|
||||
}
|
||||
|
||||
func New() SyncAcl {
|
||||
return &syncAcl{}
|
||||
}
|
||||
|
||||
type syncAcl struct {
|
||||
list.AclList
|
||||
syncClient SyncClient
|
||||
syncHandler synchandler.SyncHandler
|
||||
headUpdater headupdater.HeadUpdater
|
||||
isClosed bool
|
||||
}
|
||||
|
||||
func (s *syncAcl) Run(ctx context.Context) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (s *syncAcl) HandleRequest(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (response *spacesyncproto.ObjectSyncMessage, err error) {
|
||||
return s.syncHandler.HandleRequest(ctx, senderId, request)
|
||||
}
|
||||
|
||||
func (s *syncAcl) SetHeadUpdater(updater headupdater.HeadUpdater) {
|
||||
s.headUpdater = updater
|
||||
}
|
||||
|
||||
func (s *syncAcl) HandleMessage(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
return s.syncHandler.HandleMessage(ctx, senderId, request)
|
||||
}
|
||||
|
||||
func (s *syncAcl) Init(a *app.App) (err error) {
|
||||
storage := a.MustComponent(spacestorage.CName).(spacestorage.SpaceStorage)
|
||||
aclStorage, err := storage.AclStorage()
|
||||
if err != nil {
|
||||
return err
|
||||
func NewSyncAcl(aclList list.AclList, messagePool objectsync.MessagePool) *SyncAcl {
|
||||
return &SyncAcl{
|
||||
AclList: aclList,
|
||||
SyncHandler: nil,
|
||||
messagePool: messagePool,
|
||||
}
|
||||
acc := a.MustComponent(accountservice.CName).(accountservice.Service)
|
||||
s.AclList, err = list.BuildAclListWithIdentity(acc.Account(), aclStorage, list.NoOpAcceptorVerifier{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
spaceId := storage.Id()
|
||||
requestManager := a.MustComponent(requestmanager.CName).(requestmanager.RequestManager)
|
||||
peerManager := a.MustComponent(peermanager.CName).(peermanager.PeerManager)
|
||||
syncStatus := a.MustComponent(syncstatus.CName).(syncstatus.StatusService)
|
||||
s.syncClient = NewSyncClient(spaceId, requestManager, peerManager)
|
||||
s.syncHandler = newSyncAclHandler(storage.Id(), s, s.syncClient, syncStatus)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *syncAcl) AddRawRecord(rawRec *consensusproto.RawRecordWithId) (err error) {
|
||||
if s.isClosed {
|
||||
return ErrSyncAclClosed
|
||||
}
|
||||
err = s.AclList.AddRawRecord(rawRec)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
headUpdate := s.syncClient.CreateHeadUpdate(s, []*consensusproto.RawRecordWithId{rawRec})
|
||||
s.headUpdater.UpdateHeads(s.Id(), []string{rawRec.Id})
|
||||
s.syncClient.Broadcast(headUpdate)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *syncAcl) AddRawRecords(rawRecords []*consensusproto.RawRecordWithId) (err error) {
|
||||
if s.isClosed {
|
||||
return ErrSyncAclClosed
|
||||
}
|
||||
err = s.AclList.AddRawRecords(rawRecords)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
headUpdate := s.syncClient.CreateHeadUpdate(s, rawRecords)
|
||||
s.headUpdater.UpdateHeads(s.Id(), []string{rawRecords[len(rawRecords)-1].Id})
|
||||
s.syncClient.Broadcast(headUpdate)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *syncAcl) SyncWithPeer(ctx context.Context, peerId string) (err error) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
headUpdate := s.syncClient.CreateHeadUpdate(s, nil)
|
||||
return s.syncClient.SendUpdate(peerId, headUpdate)
|
||||
}
|
||||
|
||||
func (s *syncAcl) Close(ctx context.Context) (err error) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
s.isClosed = true
|
||||
return
|
||||
}
|
||||
|
||||
func (s *syncAcl) Name() (name string) {
|
||||
return CName
|
||||
}
|
||||
@@ -2,81 +2,30 @@ package syncacl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMessageIsRequest = errors.New("message is request")
|
||||
ErrMessageIsNotRequest = errors.New("message is not request")
|
||||
"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"
|
||||
)
|
||||
|
||||
type syncAclHandler struct {
|
||||
aclList list.AclList
|
||||
syncClient SyncClient
|
||||
syncProtocol AclSyncProtocol
|
||||
syncStatus syncstatus.StatusUpdater
|
||||
spaceId string
|
||||
acl list.AclList
|
||||
}
|
||||
|
||||
func newSyncAclHandler(spaceId string, aclList list.AclList, syncClient SyncClient, syncStatus syncstatus.StatusUpdater) synchandler.SyncHandler {
|
||||
return &syncAclHandler{
|
||||
aclList: aclList,
|
||||
syncClient: syncClient,
|
||||
syncProtocol: newAclSyncProtocol(spaceId, aclList, syncClient),
|
||||
syncStatus: syncStatus,
|
||||
spaceId: spaceId,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *syncAclHandler) HandleMessage(ctx context.Context, senderId string, message *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
unmarshalled := &consensusproto.LogSyncMessage{}
|
||||
err = proto.Unmarshal(message.Payload, unmarshalled)
|
||||
if err != nil {
|
||||
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 := unmarshalled.GetContent()
|
||||
head := consensusproto.GetHead(unmarshalled)
|
||||
s.syncStatus.HeadsReceive(senderId, s.aclList.Id(), []string{head})
|
||||
s.aclList.Lock()
|
||||
defer s.aclList.Unlock()
|
||||
content := aclMsg.GetContent()
|
||||
switch {
|
||||
case content.GetHeadUpdate() != nil:
|
||||
var syncReq *consensusproto.LogSyncMessage
|
||||
syncReq, err = s.syncProtocol.HeadUpdate(ctx, senderId, content.GetHeadUpdate())
|
||||
if err != nil || syncReq == nil {
|
||||
return
|
||||
}
|
||||
return s.syncClient.QueueRequest(senderId, syncReq)
|
||||
case content.GetFullSyncRequest() != nil:
|
||||
return ErrMessageIsRequest
|
||||
case content.GetFullSyncResponse() != nil:
|
||||
return s.syncProtocol.FullSyncResponse(ctx, senderId, content.GetFullSyncResponse())
|
||||
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
|
||||
}
|
||||
|
||||
func (s *syncAclHandler) HandleRequest(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (response *spacesyncproto.ObjectSyncMessage, err error) {
|
||||
unmarshalled := &consensusproto.LogSyncMessage{}
|
||||
err = proto.Unmarshal(request.Payload, unmarshalled)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fullSyncRequest := unmarshalled.GetContent().GetFullSyncRequest()
|
||||
if fullSyncRequest == nil {
|
||||
return nil, ErrMessageIsNotRequest
|
||||
}
|
||||
s.aclList.Lock()
|
||||
defer s.aclList.Unlock()
|
||||
aclResp, err := s.syncProtocol.FullSyncRequest(ctx, senderId, fullSyncRequest)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return spacesyncproto.MarshallSyncMessage(aclResp, s.spaceId, s.aclList.Id())
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
package syncacl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list/mock_list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl/mock_syncacl"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testAclMock struct {
|
||||
*mock_list.MockAclList
|
||||
m sync.RWMutex
|
||||
}
|
||||
|
||||
func newTestAclMock(mockAcl *mock_list.MockAclList) *testAclMock {
|
||||
return &testAclMock{
|
||||
MockAclList: mockAcl,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *testAclMock) Lock() {
|
||||
t.m.Lock()
|
||||
}
|
||||
|
||||
func (t *testAclMock) RLock() {
|
||||
t.m.RLock()
|
||||
}
|
||||
|
||||
func (t *testAclMock) Unlock() {
|
||||
t.m.Unlock()
|
||||
}
|
||||
|
||||
func (t *testAclMock) RUnlock() {
|
||||
t.m.RUnlock()
|
||||
}
|
||||
|
||||
func (t *testAclMock) TryLock() bool {
|
||||
return t.m.TryLock()
|
||||
}
|
||||
|
||||
func (t *testAclMock) TryRLock() bool {
|
||||
return t.m.TryRLock()
|
||||
}
|
||||
|
||||
type syncHandlerFixture struct {
|
||||
ctrl *gomock.Controller
|
||||
syncClientMock *mock_syncacl.MockSyncClient
|
||||
aclMock *testAclMock
|
||||
syncProtocolMock *mock_syncacl.MockAclSyncProtocol
|
||||
spaceId string
|
||||
senderId string
|
||||
aclId string
|
||||
|
||||
syncHandler *syncAclHandler
|
||||
}
|
||||
|
||||
func newSyncHandlerFixture(t *testing.T) *syncHandlerFixture {
|
||||
ctrl := gomock.NewController(t)
|
||||
aclMock := newTestAclMock(mock_list.NewMockAclList(ctrl))
|
||||
syncClientMock := mock_syncacl.NewMockSyncClient(ctrl)
|
||||
syncProtocolMock := mock_syncacl.NewMockAclSyncProtocol(ctrl)
|
||||
spaceId := "spaceId"
|
||||
|
||||
syncHandler := &syncAclHandler{
|
||||
aclList: aclMock,
|
||||
syncClient: syncClientMock,
|
||||
syncProtocol: syncProtocolMock,
|
||||
syncStatus: syncstatus.NewNoOpSyncStatus(),
|
||||
spaceId: spaceId,
|
||||
}
|
||||
return &syncHandlerFixture{
|
||||
ctrl: ctrl,
|
||||
syncClientMock: syncClientMock,
|
||||
aclMock: aclMock,
|
||||
syncProtocolMock: syncProtocolMock,
|
||||
spaceId: spaceId,
|
||||
senderId: "senderId",
|
||||
aclId: "aclId",
|
||||
syncHandler: syncHandler,
|
||||
}
|
||||
}
|
||||
|
||||
func (fx *syncHandlerFixture) stop() {
|
||||
fx.ctrl.Finish()
|
||||
}
|
||||
|
||||
func TestSyncAclHandler_HandleMessage(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
t.Run("handle head update, request returned", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &consensusproto.RawRecordWithId{}
|
||||
headUpdate := &consensusproto.LogHeadUpdate{
|
||||
Head: "h1",
|
||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
||||
}
|
||||
logMessage := consensusproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
|
||||
|
||||
syncReq := &consensusproto.LogSyncMessage{}
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
fx.syncProtocolMock.EXPECT().HeadUpdate(ctx, fx.senderId, gomock.Any()).Return(syncReq, nil)
|
||||
fx.syncClientMock.EXPECT().QueueRequest(fx.senderId, syncReq).Return(nil)
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("handle head update, no request", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &consensusproto.RawRecordWithId{}
|
||||
headUpdate := &consensusproto.LogHeadUpdate{
|
||||
Head: "h1",
|
||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
||||
}
|
||||
logMessage := consensusproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
|
||||
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
fx.syncProtocolMock.EXPECT().HeadUpdate(ctx, fx.senderId, gomock.Any()).Return(nil, nil)
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("handle head update, returned error", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &consensusproto.RawRecordWithId{}
|
||||
headUpdate := &consensusproto.LogHeadUpdate{
|
||||
Head: "h1",
|
||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
||||
}
|
||||
logMessage := consensusproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
|
||||
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
expectedErr := fmt.Errorf("some error")
|
||||
fx.syncProtocolMock.EXPECT().HeadUpdate(ctx, fx.senderId, gomock.Any()).Return(nil, expectedErr)
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
||||
require.Error(t, expectedErr, err)
|
||||
})
|
||||
t.Run("handle full sync request is forbidden", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &consensusproto.RawRecordWithId{}
|
||||
fullRequest := &consensusproto.LogFullSyncRequest{
|
||||
Head: "h1",
|
||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
||||
}
|
||||
logMessage := consensusproto.WrapFullRequest(fullRequest, chWithId)
|
||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
|
||||
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
||||
require.Error(t, ErrMessageIsRequest, err)
|
||||
})
|
||||
t.Run("handle full sync response, no error", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &consensusproto.RawRecordWithId{}
|
||||
fullResponse := &consensusproto.LogFullSyncResponse{
|
||||
Head: "h1",
|
||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
||||
}
|
||||
logMessage := consensusproto.WrapFullResponse(fullResponse, chWithId)
|
||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
|
||||
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
fx.syncProtocolMock.EXPECT().FullSyncResponse(ctx, fx.senderId, gomock.Any()).Return(nil)
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSyncAclHandler_HandleRequest(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
t.Run("handle full sync request, no error", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &consensusproto.RawRecordWithId{}
|
||||
fullRequest := &consensusproto.LogFullSyncRequest{
|
||||
Head: "h1",
|
||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
||||
}
|
||||
logMessage := consensusproto.WrapFullRequest(fullRequest, chWithId)
|
||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
|
||||
fullResp := &consensusproto.LogSyncMessage{
|
||||
Content: &consensusproto.LogSyncContentValue{
|
||||
Value: &consensusproto.LogSyncContentValue_FullSyncResponse{
|
||||
FullSyncResponse: &consensusproto.LogFullSyncResponse{
|
||||
Head: "returnedHead",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
fx.syncProtocolMock.EXPECT().FullSyncRequest(ctx, fx.senderId, gomock.Any()).Return(fullResp, nil)
|
||||
res, err := fx.syncHandler.HandleRequest(ctx, fx.senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
unmarshalled := &consensusproto.LogSyncMessage{}
|
||||
err = proto.Unmarshal(res.Payload, unmarshalled)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
require.Equal(t, "returnedHead", consensusproto.GetHead(unmarshalled))
|
||||
})
|
||||
t.Run("handle other message returns error", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &consensusproto.RawRecordWithId{}
|
||||
headUpdate := &consensusproto.LogHeadUpdate{
|
||||
Head: "h1",
|
||||
Records: []*consensusproto.RawRecordWithId{chWithId},
|
||||
}
|
||||
logMessage := consensusproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(logMessage, fx.spaceId, fx.aclId)
|
||||
|
||||
fx.aclMock.EXPECT().Id().AnyTimes().Return(fx.aclId)
|
||||
_, err := fx.syncHandler.HandleRequest(ctx, fx.senderId, objectMsg)
|
||||
require.Error(t, ErrMessageIsNotRequest, err)
|
||||
})
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package syncacl
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
||||
"github.com/anyproto/any-sync/commonspace/requestmanager"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/consensus/consensusproto"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type SyncClient interface {
|
||||
RequestFactory
|
||||
Broadcast(msg *consensusproto.LogSyncMessage)
|
||||
SendUpdate(peerId string, msg *consensusproto.LogSyncMessage) (err error)
|
||||
QueueRequest(peerId string, msg *consensusproto.LogSyncMessage) (err error)
|
||||
SendRequest(ctx context.Context, peerId string, msg *consensusproto.LogSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error)
|
||||
}
|
||||
|
||||
type syncClient struct {
|
||||
RequestFactory
|
||||
spaceId string
|
||||
requestManager requestmanager.RequestManager
|
||||
peerManager peermanager.PeerManager
|
||||
}
|
||||
|
||||
func NewSyncClient(spaceId string, requestManager requestmanager.RequestManager, peerManager peermanager.PeerManager) SyncClient {
|
||||
return &syncClient{
|
||||
RequestFactory: &requestFactory{},
|
||||
spaceId: spaceId,
|
||||
requestManager: requestManager,
|
||||
peerManager: peerManager,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *syncClient) Broadcast(msg *consensusproto.LogSyncMessage) {
|
||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, msg.Id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = s.peerManager.Broadcast(context.Background(), objMsg)
|
||||
if err != nil {
|
||||
log.Debug("broadcast error", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *syncClient) SendUpdate(peerId string, msg *consensusproto.LogSyncMessage) (err error) {
|
||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, msg.Id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return s.peerManager.SendPeer(context.Background(), peerId, objMsg)
|
||||
}
|
||||
|
||||
func (s *syncClient) SendRequest(ctx context.Context, peerId string, msg *consensusproto.LogSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error) {
|
||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, msg.Id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return s.requestManager.SendRequest(ctx, peerId, objMsg)
|
||||
}
|
||||
|
||||
func (s *syncClient) QueueRequest(peerId string, msg *consensusproto.LogSyncMessage) (err error) {
|
||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, msg.Id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return s.requestManager.QueueRequest(peerId, objMsg)
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package syncobjectgetter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync/synchandler"
|
||||
)
|
||||
|
||||
type SyncObject interface {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package exporter
|
||||
|
||||
import (
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"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/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
)
|
||||
|
||||
type DataConverter interface {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package exporter
|
||||
|
||||
import (
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"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"
|
||||
)
|
||||
|
||||
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, list.NoOpAcceptorVerifier{})
|
||||
aclList, err := list.BuildAclList(params.ListStorage)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ package objecttree
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
)
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ package objecttree
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/util/cidutil"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/util/cidutil"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
)
|
||||
|
||||
|
||||
@@ -14,47 +14,34 @@ type historyTree struct {
|
||||
*objectTree
|
||||
}
|
||||
|
||||
func (h *historyTree) rebuildFromStorage(params HistoryTreeParams) (err error) {
|
||||
err = h.rebuild(params)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
h.aclList.RLock()
|
||||
defer h.aclList.RUnlock()
|
||||
state := h.aclList.AclState()
|
||||
|
||||
return h.readKeysFromAclState(state)
|
||||
}
|
||||
|
||||
func (h *historyTree) rebuild(params HistoryTreeParams) (err error) {
|
||||
var (
|
||||
beforeId = params.BeforeId
|
||||
include = params.IncludeBeforeId
|
||||
full = params.BuildFullTree
|
||||
)
|
||||
h.treeBuilder.Reset()
|
||||
if full {
|
||||
h.tree, err = h.treeBuilder.BuildFull()
|
||||
return
|
||||
}
|
||||
if beforeId == h.Id() && !include {
|
||||
func (h *historyTree) rebuildFromStorage(beforeId string, include bool) (err error) {
|
||||
ot := h.objectTree
|
||||
ot.treeBuilder.Reset()
|
||||
if beforeId == ot.Id() && !include {
|
||||
return ErrLoadBeforeRoot
|
||||
}
|
||||
|
||||
heads := []string{beforeId}
|
||||
if beforeId == "" {
|
||||
heads, err = h.treeStorage.Heads()
|
||||
heads, err = ot.treeStorage.Heads()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else if !include {
|
||||
beforeChange, err := h.treeBuilder.loadChange(beforeId)
|
||||
beforeChange, err := ot.treeBuilder.loadChange(beforeId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
heads = beforeChange.PreviousIds
|
||||
}
|
||||
|
||||
h.tree, err = h.treeBuilder.build(heads, nil, nil)
|
||||
return
|
||||
ot.tree, err = ot.treeBuilder.build(heads, nil, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ot.aclList.RLock()
|
||||
defer ot.aclList.RUnlock()
|
||||
state := ot.aclList.AclState()
|
||||
|
||||
return ot.readKeysFromAclState(state)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anyproto/any-sync/commonspace/object/tree/objecttree (interfaces: ObjectTree)
|
||||
// Source: github.com/anytypeio/any-sync/commonspace/object/tree/objecttree (interfaces: ObjectTree)
|
||||
|
||||
// Package mock_objecttree is a generated GoMock package.
|
||||
package mock_objecttree
|
||||
@@ -9,11 +9,11 @@ import (
|
||||
reflect "reflect"
|
||||
time "time"
|
||||
|
||||
list "github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
objecttree "github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
treechangeproto "github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
treestorage "github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
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"
|
||||
)
|
||||
|
||||
// MockObjectTree is a mock of ObjectTree interface.
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
//go:generate mockgen -destination mock_objecttree/mock_objecttree.go github.com/anyproto/any-sync/commonspace/object/tree/objecttree ObjectTree
|
||||
//go:generate mockgen -destination mock_objecttree/mock_objecttree.go github.com/anytypeio/any-sync/commonspace/object/tree/objecttree ObjectTree
|
||||
package objecttree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
"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/slice"
|
||||
)
|
||||
|
||||
type RWLocker interface {
|
||||
@@ -116,25 +116,13 @@ 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()
|
||||
@@ -199,7 +187,7 @@ func (ot *objectTree) AddContent(ctx context.Context, content SignableChangeCont
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = ot.treeStorage.AddRawChangesSetHeads([]*treechangeproto.RawTreeChangeWithId{rawChange}, []string{objChange.Id})
|
||||
err = ot.treeStorage.TransactionAdd([]*treechangeproto.RawTreeChangeWithId{rawChange}, []string{objChange.Id})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -248,7 +236,9 @@ func (ot *objectTree) prepareBuilderContent(content SignableChangeContent) (cnt
|
||||
pubKey = content.Key.GetPublic()
|
||||
readKeyId string
|
||||
)
|
||||
if !state.Permissions(pubKey).CanWrite() {
|
||||
canWrite := state.HasPermission(pubKey, aclrecordproto.AclUserPermissions_Writer) ||
|
||||
state.HasPermission(pubKey, aclrecordproto.AclUserPermissions_Admin)
|
||||
if !canWrite {
|
||||
err = list.ErrInsufficientPermissions
|
||||
return
|
||||
}
|
||||
@@ -294,11 +284,7 @@ func (ot *objectTree) AddRawChanges(ctx context.Context, changesPayload RawChang
|
||||
addResult.Mode = Rebuild
|
||||
}
|
||||
|
||||
err = ot.treeStorage.AddRawChangesSetHeads(addResult.Added, addResult.Heads)
|
||||
if err != nil {
|
||||
// rolling back all changes made to inmemory state
|
||||
ot.rebuildFromStorage(nil, nil)
|
||||
}
|
||||
err = ot.treeStorage.TransactionAdd(addResult.Added, addResult.Heads)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -361,7 +347,7 @@ func (ot *objectTree) addRawChanges(ctx context.Context, changesPayload RawChang
|
||||
}
|
||||
|
||||
// checks if we need to go to database
|
||||
snapshotNotInTree := func(ch *Change) bool {
|
||||
isOldSnapshot := func(ch *Change) bool {
|
||||
if ch.SnapshotId == ot.tree.RootId() {
|
||||
return false
|
||||
}
|
||||
@@ -376,12 +362,26 @@ func (ot *objectTree) addRawChanges(ctx context.Context, changesPayload RawChang
|
||||
|
||||
shouldRebuildFromStorage := false
|
||||
// checking if we have some changes with different snapshot and then rebuilding
|
||||
for _, ch := range ot.newChangesBuf {
|
||||
if snapshotNotInTree(ch) {
|
||||
for idx, ch := range ot.newChangesBuf {
|
||||
if isOldSnapshot(ch) {
|
||||
var exists bool
|
||||
// checking if it exists in the storage, if yes, then at some point it was added to the tree
|
||||
// thus we don't need to look at this change
|
||||
exists, err = ot.treeStorage.HasChange(ctx, ch.Id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if exists {
|
||||
// marking as nil to delete after
|
||||
ot.newChangesBuf[idx] = nil
|
||||
continue
|
||||
}
|
||||
// we haven't seen the change, and it refers to old snapshot, so we should rebuild
|
||||
shouldRebuildFromStorage = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// discarding all previously seen changes
|
||||
ot.newChangesBuf = slice.DiscardFromSlice(ot.newChangesBuf, func(ch *Change) bool { return ch == nil })
|
||||
|
||||
if shouldRebuildFromStorage {
|
||||
err = ot.rebuildFromStorage(changesPayload.NewHeads, ot.newChangesBuf)
|
||||
@@ -549,8 +549,22 @@ func (ot *objectTree) IterateFrom(id string, convert ChangeConvertFunc, iterate
|
||||
}
|
||||
|
||||
func (ot *objectTree) HasChanges(chs ...string) bool {
|
||||
hasChange := func(s string) bool {
|
||||
_, attachedExists := ot.tree.attached[s]
|
||||
if attachedExists {
|
||||
return attachedExists
|
||||
}
|
||||
|
||||
has, err := ot.treeStorage.HasChange(context.Background(), s)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return has
|
||||
}
|
||||
|
||||
for _, ch := range chs {
|
||||
if _, attachedExists := ot.tree.attached[ch]; !attachedExists {
|
||||
if !hasChange(ch) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,15 @@ package objecttree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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/anytypeio/any-sync/commonspace/object/accountdata"
|
||||
"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/gogo/protobuf/proto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type testTreeContext struct {
|
||||
@@ -66,7 +64,7 @@ func prepareContext(
|
||||
treeStorage := changeCreator.CreateNewTreeStorage("0", aclList.Head().Id)
|
||||
if additionalChanges != nil {
|
||||
payload := additionalChanges(changeCreator)
|
||||
err := treeStorage.AddRawChangesSetHeads(payload.RawChanges, payload.NewHeads)
|
||||
err := treeStorage.TransactionAdd(payload.RawChanges, payload.NewHeads)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
objTree, err := objTreeBuilder(treeStorage, aclList)
|
||||
@@ -124,7 +122,6 @@ func TestObjectTree(t *testing.T) {
|
||||
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()
|
||||
@@ -141,7 +138,6 @@ func TestObjectTree(t *testing.T) {
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -425,99 +421,6 @@ func TestObjectTree(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
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
|
||||
@@ -751,7 +654,7 @@ func TestObjectTree(t *testing.T) {
|
||||
changeCreator.CreateRaw("5", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.CreateRaw("6", aclList.Head().Id, "0", false, "3", "4", "5"),
|
||||
}
|
||||
deps.treeStorage.AddRawChangesSetHeads(rawChanges, []string{"6"})
|
||||
deps.treeStorage.TransactionAdd(rawChanges, []string{"6"})
|
||||
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
|
||||
BeforeId: "6",
|
||||
IncludeBeforeId: false,
|
||||
@@ -771,37 +674,6 @@ 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 := prepareHistoryTreeDeps(aclList)
|
||||
|
||||
@@ -813,7 +685,7 @@ func TestObjectTree(t *testing.T) {
|
||||
changeCreator.CreateRaw("5", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.CreateRaw("6", aclList.Head().Id, "0", false, "3", "4", "5"),
|
||||
}
|
||||
deps.treeStorage.AddRawChangesSetHeads(rawChanges, []string{"6"})
|
||||
deps.treeStorage.TransactionAdd(rawChanges, []string{"6"})
|
||||
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
|
||||
BeforeId: "6",
|
||||
IncludeBeforeId: true,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package objecttree
|
||||
|
||||
import (
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
)
|
||||
|
||||
type ObjectTreeCreatePayload struct {
|
||||
@@ -22,7 +22,6 @@ type HistoryTreeParams struct {
|
||||
AclList list.AclList
|
||||
BeforeId string
|
||||
IncludeBeforeId bool
|
||||
BuildFullTree bool
|
||||
}
|
||||
|
||||
type objectTreeDeps struct {
|
||||
@@ -231,7 +230,7 @@ func buildHistoryTree(deps objectTreeDeps, params HistoryTreeParams) (ht History
|
||||
}
|
||||
|
||||
hTree := &historyTree{objectTree: objTree}
|
||||
err = hTree.rebuildFromStorage(params)
|
||||
err = hTree.rebuildFromStorage(params.BeforeId, params.IncludeBeforeId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ package objecttree
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/util/slice"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anytypeio/any-sync/util/slice"
|
||||
)
|
||||
|
||||
type ObjectTreeValidator interface {
|
||||
@@ -52,18 +52,20 @@ func (v *objectTreeValidator) ValidateNewChanges(tree *Tree, aclList list.AclLis
|
||||
|
||||
func (v *objectTreeValidator) validateChange(tree *Tree, aclList list.AclList, c *Change) (err error) {
|
||||
var (
|
||||
userState list.AclUserState
|
||||
state = aclList.AclState()
|
||||
perm list.AclUserState
|
||||
state = aclList.AclState()
|
||||
)
|
||||
// checking if the user could write
|
||||
userState, err = state.StateAtRecord(c.AclHeadId, c.Identity)
|
||||
perm, err = state.StateAtRecord(c.AclHeadId, c.Identity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !userState.Permissions.CanWrite() {
|
||||
|
||||
if perm.Permissions != aclrecordproto.AclUserPermissions_Writer && perm.Permissions != aclrecordproto.AclUserPermissions_Admin {
|
||||
err = list.ErrInsufficientPermissions
|
||||
return
|
||||
}
|
||||
|
||||
if c.Id == tree.RootId() {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,11 +2,10 @@ 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 {
|
||||
@@ -23,7 +22,6 @@ type rawCacheEntry struct {
|
||||
change *Change
|
||||
rawChange *treechangeproto.RawTreeChangeWithId
|
||||
position int
|
||||
removed bool
|
||||
}
|
||||
|
||||
func newStorageLoader(treeStorage treestorage.TreeStorage, changeBuilder ChangeBuilder) *rawChangeLoader {
|
||||
@@ -128,6 +126,7 @@ func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoi
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
entry.position = -1
|
||||
r.cache[b] = entry
|
||||
existingBreakpoints = append(existingBreakpoints, b)
|
||||
}
|
||||
@@ -136,7 +135,8 @@ func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoi
|
||||
dfs := func(
|
||||
commonSnapshot string,
|
||||
heads []string,
|
||||
shouldVisit func(entry rawCacheEntry, mapExists bool) bool,
|
||||
startCounter int,
|
||||
shouldVisit func(counter int, mapExists bool) bool,
|
||||
visit func(entry rawCacheEntry) rawCacheEntry) bool {
|
||||
|
||||
// resetting stack
|
||||
@@ -150,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, exists) {
|
||||
if !shouldVisit(entry.position, exists) {
|
||||
continue
|
||||
}
|
||||
if id == commonSnapshot {
|
||||
@@ -159,6 +159,7 @@ func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoi
|
||||
}
|
||||
if !exists {
|
||||
entry, err = r.loadEntry(id)
|
||||
entry.position = -1
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -173,7 +174,7 @@ func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoi
|
||||
break
|
||||
}
|
||||
prevEntry, exists := r.cache[prev]
|
||||
if !shouldVisit(prevEntry, exists) {
|
||||
if !shouldVisit(prevEntry.position, exists) {
|
||||
continue
|
||||
}
|
||||
r.idStack = append(r.idStack, prev)
|
||||
@@ -186,8 +187,8 @@ func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoi
|
||||
r.idStack = append(r.idStack, heads...)
|
||||
var buffer []*treechangeproto.RawTreeChangeWithId
|
||||
|
||||
rootVisited := dfs(commonSnapshot, heads,
|
||||
func(_ rawCacheEntry, mapExists bool) bool {
|
||||
rootVisited := dfs(commonSnapshot, heads, 0,
|
||||
func(counter int, mapExists bool) bool {
|
||||
return !mapExists
|
||||
},
|
||||
func(entry rawCacheEntry) rawCacheEntry {
|
||||
@@ -212,13 +213,11 @@ func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoi
|
||||
}
|
||||
|
||||
// marking all visited as nil
|
||||
dfs(commonSnapshot, existingBreakpoints,
|
||||
func(entry rawCacheEntry, mapExists bool) bool {
|
||||
// only going through already loaded changes
|
||||
return mapExists && !entry.removed
|
||||
dfs(commonSnapshot, existingBreakpoints, len(buffer),
|
||||
func(counter int, mapExists bool) bool {
|
||||
return !mapExists || counter < len(buffer)
|
||||
},
|
||||
func(entry rawCacheEntry) rawCacheEntry {
|
||||
entry.removed = true
|
||||
if entry.position != -1 {
|
||||
buffer[entry.position] = nil
|
||||
}
|
||||
@@ -249,7 +248,6 @@ func (r *rawChangeLoader) loadEntry(id string) (entry rawCacheEntry, err error)
|
||||
entry = rawCacheEntry{
|
||||
change: change,
|
||||
rawChange: rawChange,
|
||||
position: -1,
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package objecttree
|
||||
|
||||
import (
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
)
|
||||
|
||||
// SignableChangeContent is a payload to be passed when we are creating change
|
||||
|
||||
@@ -2,9 +2,9 @@ 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"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
libcrypto "github.com/libp2p/go-libp2p/core/crypto"
|
||||
)
|
||||
|
||||
|
||||
@@ -82,7 +82,6 @@ func (t *Tree) AddMergedHead(c *Change) error {
|
||||
}
|
||||
}
|
||||
t.headIds = []string{c.Id}
|
||||
t.lastIteratedHeadId = c.Id
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,10 @@ 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 {
|
||||
@@ -28,17 +26,6 @@ func newSnapshot(id, snapshotId string, prevIds ...string) *Change {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTree_AddMergedHead(t *testing.T) {
|
||||
tr := new(Tree)
|
||||
_, _ = tr.Add(
|
||||
newSnapshot("root", ""),
|
||||
newChange("one", "root", "root"),
|
||||
)
|
||||
require.Equal(t, tr.lastIteratedHeadId, "one")
|
||||
tr.AddMergedHead(newChange("two", "root", "one"))
|
||||
require.Equal(t, tr.lastIteratedHeadId, "two")
|
||||
}
|
||||
|
||||
func TestTree_Add(t *testing.T) {
|
||||
t.Run("add first el", func(t *testing.T) {
|
||||
tr := new(Tree)
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/util/slice"
|
||||
"github.com/anytypeio/any-sync/app/logger"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anytypeio/any-sync/util/slice"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -51,22 +51,6 @@ 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)
|
||||
@@ -121,8 +105,8 @@ func (tb *treeBuilder) buildTree(heads []string, breakpoint string) (err error)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
changes := tb.dfs(heads, breakpoint)
|
||||
tb.tree.AddFast(ch)
|
||||
changes := tb.dfs(heads, breakpoint)
|
||||
tb.tree.AddFast(changes...)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package objecttree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anyproto/any-sync/commonspace/object/tree/synctree (interfaces: SyncTree,ReceiveQueue,HeadNotifiable,SyncClient,RequestFactory,TreeSyncProtocol)
|
||||
// Source: github.com/anytypeio/any-sync/commonspace/object/tree/synctree (interfaces: SyncTree,ReceiveQueue,HeadNotifiable)
|
||||
|
||||
// Package mock_synctree is a generated GoMock package.
|
||||
package mock_synctree
|
||||
@@ -9,13 +9,13 @@ import (
|
||||
reflect "reflect"
|
||||
time "time"
|
||||
|
||||
list "github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
objecttree "github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
updatelistener "github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
|
||||
treechangeproto "github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
treestorage "github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
spacesyncproto "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
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"
|
||||
)
|
||||
|
||||
// MockSyncTree is a mock of SyncTree interface.
|
||||
@@ -186,21 +186,6 @@ func (mr *MockSyncTreeMockRecorder) HandleMessage(arg0, arg1, arg2 interface{})
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleMessage", reflect.TypeOf((*MockSyncTree)(nil).HandleMessage), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// 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()
|
||||
@@ -605,287 +590,3 @@ 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)
|
||||
}
|
||||
@@ -1,362 +0,0 @@
|
||||
package synctree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/util/slice"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEmptyClientGetsFullHistory(t *testing.T) {
|
||||
treeId := "treeId"
|
||||
spaceId := "spaceId"
|
||||
keys, err := accountdata.NewRandom()
|
||||
require.NoError(t, err)
|
||||
aclList, err := list.NewTestDerivedAcl(spaceId, keys)
|
||||
require.NoError(t, err)
|
||||
storage := createStorage(treeId, aclList)
|
||||
changeCreator := objecttree.NewMockChangeCreator()
|
||||
deps := fixtureDeps{
|
||||
aclList: aclList,
|
||||
initStorage: storage.(*treestorage.InMemoryTreeStorage),
|
||||
connectionMap: map[string][]string{
|
||||
"peer1": []string{"peer2"},
|
||||
"peer2": []string{"peer1"},
|
||||
},
|
||||
emptyTrees: []string{"peer2"},
|
||||
treeBuilder: objecttree.BuildTestableTree,
|
||||
}
|
||||
fx := newProtocolFixture(t, spaceId, deps)
|
||||
fx.run(t)
|
||||
fx.handlers["peer1"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
|
||||
NewHeads: nil,
|
||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRaw("1", aclList.Id(), treeId, true, treeId),
|
||||
},
|
||||
})
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
fx.stop()
|
||||
firstHeads := fx.handlers["peer1"].tree().Heads()
|
||||
secondHeads := fx.handlers["peer2"].tree().Heads()
|
||||
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
|
||||
require.Equal(t, []string{"1"}, firstHeads)
|
||||
logMsgs := fx.log.batcher.GetAll()
|
||||
|
||||
var fullResponseMsg msgDescription
|
||||
for _, msg := range logMsgs {
|
||||
descr := msg.description()
|
||||
if descr.name == "FullSyncResponse" {
|
||||
fullResponseMsg = descr
|
||||
}
|
||||
}
|
||||
// that means that we got not only the last snapshot, but also the first one
|
||||
require.Len(t, fullResponseMsg.changes, 2)
|
||||
}
|
||||
|
||||
func TestTreeFuzzyMerge(t *testing.T) {
|
||||
testTreeFuzzyMerge(t, true)
|
||||
testTreeFuzzyMerge(t, false)
|
||||
}
|
||||
|
||||
func testTreeFuzzyMerge(t *testing.T, withData bool) {
|
||||
var (
|
||||
rnd = rand.New(rand.NewSource(time.Now().Unix()))
|
||||
levels = 20
|
||||
perLevel = 20
|
||||
rounds = 10
|
||||
)
|
||||
for i := 0; i < rounds; i++ {
|
||||
testTreeMerge(t, levels, perLevel, withData, func() bool {
|
||||
return true
|
||||
})
|
||||
testTreeMerge(t, levels, perLevel, withData, func() bool {
|
||||
return false
|
||||
})
|
||||
testTreeMerge(t, levels, perLevel, withData, func() bool {
|
||||
return rnd.Intn(10) > 8
|
||||
})
|
||||
levels += 2
|
||||
}
|
||||
}
|
||||
|
||||
func testTreeMerge(t *testing.T, levels, perLevel int, hasData bool, isSnapshot func() bool) {
|
||||
treeId := "treeId"
|
||||
spaceId := "spaceId"
|
||||
keys, err := accountdata.NewRandom()
|
||||
require.NoError(t, err)
|
||||
aclList, err := list.NewTestDerivedAcl(spaceId, keys)
|
||||
storage := createStorage(treeId, aclList)
|
||||
changeCreator := objecttree.NewMockChangeCreator()
|
||||
builder := objecttree.BuildTestableTree
|
||||
if hasData {
|
||||
builder = objecttree.BuildEmptyDataTestableTree
|
||||
}
|
||||
params := genParams{
|
||||
prefix: "peer1",
|
||||
aclId: aclList.Id(),
|
||||
startIdx: 0,
|
||||
levels: levels,
|
||||
perLevel: perLevel,
|
||||
snapshotId: treeId,
|
||||
prevHeads: []string{treeId},
|
||||
isSnapshot: isSnapshot,
|
||||
hasData: hasData,
|
||||
}
|
||||
// generating initial tree
|
||||
initialRes := genChanges(changeCreator, params)
|
||||
err = storage.AddRawChangesSetHeads(initialRes.changes, initialRes.heads)
|
||||
require.NoError(t, err)
|
||||
deps := fixtureDeps{
|
||||
aclList: aclList,
|
||||
initStorage: storage.(*treestorage.InMemoryTreeStorage),
|
||||
connectionMap: map[string][]string{
|
||||
"peer1": []string{"node1"},
|
||||
"peer2": []string{"node1"},
|
||||
"node1": []string{"peer1", "peer2"},
|
||||
},
|
||||
emptyTrees: []string{"peer2", "node1"},
|
||||
treeBuilder: builder,
|
||||
}
|
||||
fx := newProtocolFixture(t, spaceId, deps)
|
||||
fx.run(t)
|
||||
fx.handlers["peer1"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
|
||||
NewHeads: initialRes.heads,
|
||||
RawChanges: initialRes.changes,
|
||||
})
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
firstHeads := fx.handlers["peer1"].tree().Heads()
|
||||
secondHeads := fx.handlers["peer2"].tree().Heads()
|
||||
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
|
||||
params = genParams{
|
||||
prefix: "peer1",
|
||||
aclId: aclList.Id(),
|
||||
startIdx: levels,
|
||||
levels: levels,
|
||||
perLevel: perLevel,
|
||||
|
||||
snapshotId: initialRes.snapshotId,
|
||||
prevHeads: initialRes.heads,
|
||||
isSnapshot: isSnapshot,
|
||||
hasData: hasData,
|
||||
}
|
||||
// generating different additions to the tree for different peers
|
||||
peer1Res := genChanges(changeCreator, params)
|
||||
params.prefix = "peer2"
|
||||
peer2Res := genChanges(changeCreator, params)
|
||||
fx.handlers["peer1"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
|
||||
NewHeads: peer1Res.heads,
|
||||
RawChanges: peer1Res.changes,
|
||||
})
|
||||
fx.handlers["peer2"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
|
||||
NewHeads: peer2Res.heads,
|
||||
RawChanges: peer2Res.changes,
|
||||
})
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
fx.stop()
|
||||
firstTree := fx.handlers["peer1"].tree()
|
||||
secondTree := fx.handlers["peer2"].tree()
|
||||
firstHeads = firstTree.Heads()
|
||||
secondHeads = secondTree.Heads()
|
||||
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
|
||||
require.True(t, slice.UnsortedEquals(firstHeads, append(peer1Res.heads, peer2Res.heads...)))
|
||||
firstStorage := firstTree.Storage().(*treestorage.InMemoryTreeStorage)
|
||||
secondStorage := secondTree.Storage().(*treestorage.InMemoryTreeStorage)
|
||||
require.True(t, firstStorage.Equal(secondStorage))
|
||||
if hasData {
|
||||
for _, ch := range secondStorage.Changes {
|
||||
if ch.Id == treeId {
|
||||
continue
|
||||
}
|
||||
unmarshallRaw := &treechangeproto.RawTreeChange{}
|
||||
proto.Unmarshal(ch.RawChange, unmarshallRaw)
|
||||
treeCh := &treechangeproto.TreeChange{}
|
||||
proto.Unmarshal(unmarshallRaw.Payload, treeCh)
|
||||
require.Equal(t, ch.Id, string(treeCh.ChangesData))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTreeStorageHasExtraChanges(t *testing.T) {
|
||||
var (
|
||||
rnd = rand.New(rand.NewSource(time.Now().Unix()))
|
||||
levels = 20
|
||||
perLevel = 40
|
||||
)
|
||||
|
||||
// simulating cases where one peer has some extra changes saved in storage
|
||||
// and checking that this will not break the sync
|
||||
t.Run("tree storage has extra simple", func(t *testing.T) {
|
||||
testTreeStorageHasExtra(t, levels, perLevel, false, func() bool {
|
||||
return false
|
||||
})
|
||||
testTreeStorageHasExtra(t, levels, perLevel, false, func() bool {
|
||||
return rnd.Intn(10) > 5
|
||||
})
|
||||
testTreeStorageHasExtra(t, levels, perLevel, true, func() bool {
|
||||
return false
|
||||
})
|
||||
testTreeStorageHasExtra(t, levels, perLevel, true, func() bool {
|
||||
return rnd.Intn(10) > 5
|
||||
})
|
||||
})
|
||||
t.Run("tree storage has extra three parts", func(t *testing.T) {
|
||||
testTreeStorageHasExtraThreeParts(t, levels, perLevel, false, func() bool {
|
||||
return false
|
||||
})
|
||||
testTreeStorageHasExtraThreeParts(t, levels, perLevel, false, func() bool {
|
||||
return rnd.Intn(10) > 5
|
||||
})
|
||||
testTreeStorageHasExtraThreeParts(t, levels, perLevel, true, func() bool {
|
||||
return false
|
||||
})
|
||||
testTreeStorageHasExtraThreeParts(t, levels, perLevel, true, func() bool {
|
||||
return rnd.Intn(10) > 5
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func testTreeStorageHasExtra(t *testing.T, levels, perLevel int, hasData bool, isSnapshot func() bool) {
|
||||
treeId := "treeId"
|
||||
spaceId := "spaceId"
|
||||
keys, err := accountdata.NewRandom()
|
||||
require.NoError(t, err)
|
||||
aclList, err := list.NewTestDerivedAcl(spaceId, keys)
|
||||
storage := createStorage(treeId, aclList)
|
||||
changeCreator := objecttree.NewMockChangeCreator()
|
||||
builder := objecttree.BuildTestableTree
|
||||
if hasData {
|
||||
builder = objecttree.BuildEmptyDataTestableTree
|
||||
}
|
||||
params := genParams{
|
||||
prefix: "peer1",
|
||||
aclId: aclList.Id(),
|
||||
startIdx: 0,
|
||||
levels: levels,
|
||||
perLevel: perLevel,
|
||||
snapshotId: treeId,
|
||||
prevHeads: []string{treeId},
|
||||
isSnapshot: isSnapshot,
|
||||
hasData: hasData,
|
||||
}
|
||||
deps := fixtureDeps{
|
||||
aclList: aclList,
|
||||
initStorage: storage.(*treestorage.InMemoryTreeStorage),
|
||||
connectionMap: map[string][]string{
|
||||
"peer1": []string{"peer2"},
|
||||
"peer2": []string{"peer1"},
|
||||
},
|
||||
treeBuilder: builder,
|
||||
}
|
||||
fx := newProtocolFixture(t, spaceId, deps)
|
||||
|
||||
// generating initial tree
|
||||
initialRes := genChanges(changeCreator, params)
|
||||
fx.run(t)
|
||||
|
||||
// adding some changes to store, but without updating heads
|
||||
store := fx.handlers["peer1"].tree().Storage().(*treestorage.InMemoryTreeStorage)
|
||||
oldHeads, _ := store.Heads()
|
||||
store.AddRawChangesSetHeads(initialRes.changes, oldHeads)
|
||||
|
||||
// sending those changes to other peer
|
||||
fx.handlers["peer2"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
|
||||
NewHeads: initialRes.heads,
|
||||
RawChanges: initialRes.changes,
|
||||
})
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
// here we want that the saved changes in storage should not affect the sync protocol
|
||||
firstHeads := fx.handlers["peer1"].tree().Heads()
|
||||
secondHeads := fx.handlers["peer2"].tree().Heads()
|
||||
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
|
||||
require.True(t, slice.UnsortedEquals(initialRes.heads, firstHeads))
|
||||
}
|
||||
|
||||
func testTreeStorageHasExtraThreeParts(t *testing.T, levels, perLevel int, hasData bool, isSnapshot func() bool) {
|
||||
treeId := "treeId"
|
||||
spaceId := "spaceId"
|
||||
keys, err := accountdata.NewRandom()
|
||||
require.NoError(t, err)
|
||||
aclList, err := list.NewTestDerivedAcl(spaceId, keys)
|
||||
storage := createStorage(treeId, aclList)
|
||||
changeCreator := objecttree.NewMockChangeCreator()
|
||||
builder := objecttree.BuildTestableTree
|
||||
if hasData {
|
||||
builder = objecttree.BuildEmptyDataTestableTree
|
||||
}
|
||||
params := genParams{
|
||||
prefix: "peer1",
|
||||
aclId: aclList.Id(),
|
||||
startIdx: 0,
|
||||
levels: levels,
|
||||
perLevel: perLevel,
|
||||
snapshotId: treeId,
|
||||
prevHeads: []string{treeId},
|
||||
isSnapshot: isSnapshot,
|
||||
hasData: hasData,
|
||||
}
|
||||
deps := fixtureDeps{
|
||||
aclList: aclList,
|
||||
initStorage: storage.(*treestorage.InMemoryTreeStorage),
|
||||
connectionMap: map[string][]string{
|
||||
"peer1": []string{"peer2"},
|
||||
"peer2": []string{"peer1"},
|
||||
},
|
||||
treeBuilder: builder,
|
||||
}
|
||||
fx := newProtocolFixture(t, spaceId, deps)
|
||||
|
||||
// generating parts
|
||||
firstPart := genChanges(changeCreator, params)
|
||||
params.startIdx = levels
|
||||
params.snapshotId = firstPart.snapshotId
|
||||
params.prevHeads = firstPart.heads
|
||||
secondPart := genChanges(changeCreator, params)
|
||||
params.startIdx = levels * 2
|
||||
params.snapshotId = secondPart.snapshotId
|
||||
params.prevHeads = secondPart.heads
|
||||
thirdPart := genChanges(changeCreator, params)
|
||||
|
||||
// adding part1 to first peer and saving part2 and part3 in its storage
|
||||
res, _ := fx.handlers["peer1"].tree().AddRawChanges(context.Background(), objecttree.RawChangesPayload{
|
||||
NewHeads: firstPart.heads,
|
||||
RawChanges: firstPart.changes,
|
||||
})
|
||||
require.True(t, slice.UnsortedEquals(res.Heads, firstPart.heads))
|
||||
store := fx.handlers["peer1"].tree().Storage().(*treestorage.InMemoryTreeStorage)
|
||||
oldHeads, _ := store.Heads()
|
||||
store.AddRawChangesSetHeads(secondPart.changes, oldHeads)
|
||||
store.AddRawChangesSetHeads(thirdPart.changes, oldHeads)
|
||||
|
||||
var peer2Initial []*treechangeproto.RawTreeChangeWithId
|
||||
peer2Initial = append(peer2Initial, firstPart.changes...)
|
||||
peer2Initial = append(peer2Initial, secondPart.changes...)
|
||||
|
||||
// adding part1 and part2 to second peer
|
||||
res, _ = fx.handlers["peer2"].tree().AddRawChanges(context.Background(), objecttree.RawChangesPayload{
|
||||
NewHeads: secondPart.heads,
|
||||
RawChanges: peer2Initial,
|
||||
})
|
||||
require.True(t, slice.UnsortedEquals(res.Heads, secondPart.heads))
|
||||
fx.run(t)
|
||||
|
||||
// sending part3 changes to other peer
|
||||
fx.handlers["peer2"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
|
||||
NewHeads: thirdPart.heads,
|
||||
RawChanges: thirdPart.changes,
|
||||
})
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
firstHeads := fx.handlers["peer1"].tree().Heads()
|
||||
secondHeads := fx.handlers["peer2"].tree().Heads()
|
||||
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
|
||||
require.True(t, slice.UnsortedEquals(thirdPart.heads, firstHeads))
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package synctree
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"sync"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
package synctree
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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(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 {
|
||||
RequestFactory
|
||||
spaceId string
|
||||
requestManager requestmanager.RequestManager
|
||||
peerManager peermanager.PeerManager
|
||||
}
|
||||
|
||||
func NewSyncClient(spaceId string, requestManager requestmanager.RequestManager, peerManager peermanager.PeerManager) SyncClient {
|
||||
return &syncClient{
|
||||
RequestFactory: &requestFactory{},
|
||||
spaceId: spaceId,
|
||||
requestManager: requestManager,
|
||||
peerManager: peerManager,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *syncClient) Broadcast(msg *treechangeproto.TreeSyncMessage) {
|
||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, msg.RootChange.Id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = s.peerManager.Broadcast(context.Background(), objMsg)
|
||||
if err != nil {
|
||||
log.Debug("broadcast error", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *syncClient) SendUpdate(peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (err error) {
|
||||
objMsg, err := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, objectId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return s.peerManager.SendPeer(context.Background(), peerId, objMsg)
|
||||
}
|
||||
|
||||
func (s *syncClient) SendRequest(ctx context.Context, peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error) {
|
||||
objMsg, err := spacesyncproto.MarshallSyncMessage(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 := spacesyncproto.MarshallSyncMessage(msg, s.spaceId, objectId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return s.requestManager.QueueRequest(peerId, objMsg)
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
package synctree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anytypeio/any-sync/util/slice"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/stretchr/testify/require"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestEmptyClientGetsFullHistory(t *testing.T) {
|
||||
treeId := "treeId"
|
||||
spaceId := "spaceId"
|
||||
keys, err := accountdata.NewRandom()
|
||||
require.NoError(t, err)
|
||||
aclList, err := list.NewTestDerivedAcl(spaceId, keys)
|
||||
require.NoError(t, err)
|
||||
storage := createStorage(treeId, aclList)
|
||||
changeCreator := objecttree.NewMockChangeCreator()
|
||||
deps := fixtureDeps{
|
||||
aclList: aclList,
|
||||
initStorage: storage.(*treestorage.InMemoryTreeStorage),
|
||||
connectionMap: map[string][]string{
|
||||
"peer1": []string{"peer2"},
|
||||
"peer2": []string{"peer1"},
|
||||
},
|
||||
emptyTrees: []string{"peer2"},
|
||||
treeBuilder: objecttree.BuildTestableTree,
|
||||
}
|
||||
fx := newProtocolFixture(t, spaceId, deps)
|
||||
fx.run(t)
|
||||
fx.handlers["peer1"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
|
||||
NewHeads: nil,
|
||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRaw("1", aclList.Id(), treeId, true, treeId),
|
||||
},
|
||||
})
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
fx.stop()
|
||||
firstHeads := fx.handlers["peer1"].tree().Heads()
|
||||
secondHeads := fx.handlers["peer2"].tree().Heads()
|
||||
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
|
||||
require.Equal(t, []string{"1"}, firstHeads)
|
||||
logMsgs := fx.log.batcher.GetAll()
|
||||
|
||||
var fullResponseMsg msgDescription
|
||||
for _, msg := range logMsgs {
|
||||
descr := msg.description()
|
||||
if descr.name == "FullSyncResponse" {
|
||||
fullResponseMsg = descr
|
||||
}
|
||||
}
|
||||
// that means that we got not only the last snapshot, but also the first one
|
||||
require.Len(t, fullResponseMsg.changes, 2)
|
||||
}
|
||||
|
||||
func TestTreeFuzzyMerge(t *testing.T) {
|
||||
testTreeFuzzyMerge(t, true)
|
||||
testTreeFuzzyMerge(t, false)
|
||||
}
|
||||
|
||||
func testTreeFuzzyMerge(t *testing.T, withData bool) {
|
||||
var (
|
||||
rnd = rand.New(rand.NewSource(time.Now().Unix()))
|
||||
levels = 20
|
||||
perLevel = 20
|
||||
rounds = 10
|
||||
)
|
||||
for i := 0; i < rounds; i++ {
|
||||
testTreeMerge(t, levels, perLevel, withData, func() bool {
|
||||
return true
|
||||
})
|
||||
testTreeMerge(t, levels, perLevel, withData, func() bool {
|
||||
return false
|
||||
})
|
||||
testTreeMerge(t, levels, perLevel, withData, func() bool {
|
||||
return rnd.Intn(10) > 8
|
||||
})
|
||||
levels += 2
|
||||
}
|
||||
}
|
||||
|
||||
func testTreeMerge(t *testing.T, levels, perLevel int, hasData bool, isSnapshot func() bool) {
|
||||
treeId := "treeId"
|
||||
spaceId := "spaceId"
|
||||
keys, err := accountdata.NewRandom()
|
||||
require.NoError(t, err)
|
||||
aclList, err := list.NewTestDerivedAcl(spaceId, keys)
|
||||
storage := createStorage(treeId, aclList)
|
||||
changeCreator := objecttree.NewMockChangeCreator()
|
||||
builder := objecttree.BuildTestableTree
|
||||
if hasData {
|
||||
builder = objecttree.BuildEmptyDataTestableTree
|
||||
}
|
||||
params := genParams{
|
||||
prefix: "peer1",
|
||||
aclId: aclList.Id(),
|
||||
startIdx: 0,
|
||||
levels: levels,
|
||||
perLevel: perLevel,
|
||||
snapshotId: treeId,
|
||||
prevHeads: []string{treeId},
|
||||
isSnapshot: isSnapshot,
|
||||
hasData: hasData,
|
||||
}
|
||||
// generating initial tree
|
||||
initialRes := genChanges(changeCreator, params)
|
||||
err = storage.TransactionAdd(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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:generate mockgen -destination mock_synctree/mock_synctree.go github.com/anyproto/any-sync/commonspace/object/tree/synctree SyncTree,ReceiveQueue,HeadNotifiable,SyncClient,RequestFactory,TreeSyncProtocol
|
||||
//go:generate mockgen -destination mock_synctree/mock_synctree.go github.com/anytypeio/any-sync/commonspace/object/tree/synctree SyncTree,ReceiveQueue,HeadNotifiable
|
||||
package synctree
|
||||
|
||||
import (
|
||||
@@ -6,16 +6,17 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||
"github.com/anyproto/any-sync/net/peer"
|
||||
"github.com/anyproto/any-sync/nodeconf"
|
||||
"github.com/anytypeio/any-sync/app/logger"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/synctree/updatelistener"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync/synchandler"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacestorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/syncstatus"
|
||||
"github.com/anytypeio/any-sync/net/peer"
|
||||
"github.com/anytypeio/any-sync/nodeconf"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -43,7 +44,7 @@ type SyncTree interface {
|
||||
type syncTree struct {
|
||||
objecttree.ObjectTree
|
||||
synchandler.SyncHandler
|
||||
syncClient SyncClient
|
||||
syncClient objectsync.SyncClient
|
||||
syncStatus syncstatus.StatusUpdater
|
||||
notifiable HeadNotifiable
|
||||
listener updatelistener.UpdateListener
|
||||
@@ -59,30 +60,28 @@ type ResponsiblePeersGetter interface {
|
||||
}
|
||||
|
||||
type BuildDeps struct {
|
||||
SpaceId string
|
||||
SyncClient SyncClient
|
||||
Configuration nodeconf.NodeConf
|
||||
HeadNotifiable HeadNotifiable
|
||||
Listener updatelistener.UpdateListener
|
||||
AclList list.AclList
|
||||
SpaceStorage spacestorage.SpaceStorage
|
||||
TreeStorage treestorage.TreeStorage
|
||||
OnClose func(id string)
|
||||
SyncStatus syncstatus.StatusUpdater
|
||||
PeerGetter ResponsiblePeersGetter
|
||||
BuildObjectTree objecttree.BuildObjectTreeFunc
|
||||
SpaceId string
|
||||
SyncClient objectsync.SyncClient
|
||||
Configuration nodeconf.NodeConf
|
||||
HeadNotifiable HeadNotifiable
|
||||
Listener updatelistener.UpdateListener
|
||||
AclList list.AclList
|
||||
SpaceStorage spacestorage.SpaceStorage
|
||||
TreeStorage treestorage.TreeStorage
|
||||
OnClose func(id string)
|
||||
SyncStatus syncstatus.StatusUpdater
|
||||
PeerGetter ResponsiblePeersGetter
|
||||
BuildObjectTree objecttree.BuildObjectTreeFunc
|
||||
WaitTreeRemoteSync bool
|
||||
}
|
||||
|
||||
func BuildSyncTreeOrGetRemote(ctx context.Context, id string, deps BuildDeps) (t SyncTree, err error) {
|
||||
var (
|
||||
remoteGetter = treeRemoteGetter{treeId: id, deps: deps}
|
||||
isRemote bool
|
||||
)
|
||||
deps.TreeStorage, isRemote, err = remoteGetter.getTree(ctx)
|
||||
remoteGetter := treeRemoteGetter{treeId: id, deps: deps}
|
||||
deps.TreeStorage, err = remoteGetter.getTree(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return buildSyncTree(ctx, isRemote, deps)
|
||||
return buildSyncTree(ctx, true, deps)
|
||||
}
|
||||
|
||||
func PutSyncTree(ctx context.Context, payload treestorage.TreeStorageCreatePayload, deps BuildDeps) (t SyncTree, err error) {
|
||||
@@ -93,7 +92,7 @@ func PutSyncTree(ctx context.Context, payload treestorage.TreeStorageCreatePaylo
|
||||
return buildSyncTree(ctx, true, deps)
|
||||
}
|
||||
|
||||
func buildSyncTree(ctx context.Context, sendUpdate bool, deps BuildDeps) (t SyncTree, err error) {
|
||||
func buildSyncTree(ctx context.Context, isFirstBuild bool, deps BuildDeps) (t SyncTree, err error) {
|
||||
objTree, err := deps.BuildObjectTree(deps.TreeStorage, deps.AclList)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -114,10 +113,10 @@ func buildSyncTree(ctx context.Context, sendUpdate bool, deps BuildDeps) (t Sync
|
||||
syncTree.afterBuild()
|
||||
syncTree.Unlock()
|
||||
|
||||
if sendUpdate {
|
||||
if isFirstBuild {
|
||||
headUpdate := syncTree.syncClient.CreateHeadUpdate(t, nil)
|
||||
// send to everybody, because everybody should know that the node or client got new tree
|
||||
syncTree.syncClient.Broadcast(headUpdate)
|
||||
syncTree.syncClient.Broadcast(ctx, headUpdate)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -154,7 +153,7 @@ func (s *syncTree) AddContent(ctx context.Context, content objecttree.SignableCh
|
||||
}
|
||||
s.syncStatus.HeadsChange(s.Id(), res.Heads)
|
||||
headUpdate := s.syncClient.CreateHeadUpdate(s, res.Added)
|
||||
s.syncClient.Broadcast(headUpdate)
|
||||
s.syncClient.Broadcast(ctx, headUpdate)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -181,7 +180,7 @@ func (s *syncTree) AddRawChanges(ctx context.Context, changesPayload objecttree.
|
||||
s.notifiable.UpdateHeads(s.Id(), res.Heads)
|
||||
}
|
||||
headUpdate := s.syncClient.CreateHeadUpdate(s, res.Added)
|
||||
s.syncClient.Broadcast(headUpdate)
|
||||
s.syncClient.Broadcast(ctx, headUpdate)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -205,27 +204,18 @@ func (s *syncTree) Delete() (err error) {
|
||||
}
|
||||
|
||||
func (s *syncTree) TryClose(objectTTL time.Duration) (bool, error) {
|
||||
if !s.TryLock() {
|
||||
return false, nil
|
||||
}
|
||||
log.Debug("closing sync tree", zap.String("id", s.Id()))
|
||||
return true, s.close()
|
||||
return true, s.Close()
|
||||
}
|
||||
|
||||
func (s *syncTree) Close() (err error) {
|
||||
log.Debug("closing sync tree", zap.String("id", s.Id()))
|
||||
s.Lock()
|
||||
return s.close()
|
||||
}
|
||||
|
||||
func (s *syncTree) close() (err error) {
|
||||
defer s.Unlock()
|
||||
defer func() {
|
||||
log.Debug("closed sync tree", zap.Error(err), zap.String("id", s.Id()))
|
||||
}()
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
if s.isClosed {
|
||||
err = ErrSyncTreeClosed
|
||||
return
|
||||
return ErrSyncTreeClosed
|
||||
}
|
||||
s.onClose(s.Id())
|
||||
s.isClosed = true
|
||||
@@ -246,7 +236,7 @@ func (s *syncTree) SyncWithPeer(ctx context.Context, peerId string) (err error)
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
headUpdate := s.syncClient.CreateHeadUpdate(s, nil)
|
||||
return s.syncClient.SendUpdate(peerId, headUpdate.RootChange.Id, headUpdate)
|
||||
return s.syncClient.SendWithReply(ctx, peerId, headUpdate.RootChange.Id, headUpdate, "")
|
||||
}
|
||||
|
||||
func (s *syncTree) afterBuild() {
|
||||
|
||||
@@ -2,23 +2,23 @@ package synctree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/mock_synctree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener/mock_updatelistener"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/objectsync"
|
||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||
"github.com/anyproto/any-sync/nodeconf"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/synctree/updatelistener"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/synctree/updatelistener/mock_updatelistener"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync/mock_objectsync"
|
||||
"github.com/anytypeio/any-sync/commonspace/syncstatus"
|
||||
"github.com/anytypeio/any-sync/nodeconf"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type syncTreeMatcher struct {
|
||||
objTree objecttree.ObjectTree
|
||||
client SyncClient
|
||||
client objectsync.SyncClient
|
||||
listener updatelistener.UpdateListener
|
||||
}
|
||||
|
||||
@@ -34,8 +34,8 @@ func (s syncTreeMatcher) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func syncClientFuncCreator(client SyncClient) func(spaceId string, factory RequestFactory, objectSync objectsync.ObjectSync, configuration nodeconf.NodeConf) SyncClient {
|
||||
return func(spaceId string, factory RequestFactory, objectSync objectsync.ObjectSync, configuration nodeconf.NodeConf) SyncClient {
|
||||
func syncClientFuncCreator(client objectsync.SyncClient) func(spaceId string, factory objectsync.RequestFactory, objectSync objectsync.ObjectSync, configuration nodeconf.NodeConf) objectsync.SyncClient {
|
||||
return func(spaceId string, factory objectsync.RequestFactory, objectSync objectsync.ObjectSync, configuration nodeconf.NodeConf) objectsync.SyncClient {
|
||||
return client
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ func Test_BuildSyncTree(t *testing.T) {
|
||||
defer ctrl.Finish()
|
||||
|
||||
updateListenerMock := mock_updatelistener.NewMockUpdateListener(ctrl)
|
||||
syncClientMock := mock_synctree.NewMockSyncClient(ctrl)
|
||||
syncClientMock := mock_objectsync.NewMockSyncClient(ctrl)
|
||||
objTreeMock := newTestObjMock(mock_objecttree.NewMockObjectTree(ctrl))
|
||||
tr := &syncTree{
|
||||
ObjectTree: objTreeMock,
|
||||
@@ -73,7 +73,7 @@ func Test_BuildSyncTree(t *testing.T) {
|
||||
updateListenerMock.EXPECT().Update(tr)
|
||||
|
||||
syncClientMock.EXPECT().CreateHeadUpdate(gomock.Eq(tr), gomock.Eq(changes)).Return(headUpdate)
|
||||
syncClientMock.EXPECT().Broadcast(gomock.Eq(headUpdate))
|
||||
syncClientMock.EXPECT().Broadcast(gomock.Any(), gomock.Eq(headUpdate))
|
||||
res, err := tr.AddRawChanges(ctx, payload)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedRes, res)
|
||||
@@ -95,7 +95,7 @@ func Test_BuildSyncTree(t *testing.T) {
|
||||
updateListenerMock.EXPECT().Rebuild(tr)
|
||||
|
||||
syncClientMock.EXPECT().CreateHeadUpdate(gomock.Eq(tr), gomock.Eq(changes)).Return(headUpdate)
|
||||
syncClientMock.EXPECT().Broadcast(gomock.Eq(headUpdate))
|
||||
syncClientMock.EXPECT().Broadcast(gomock.Any(), gomock.Eq(headUpdate))
|
||||
res, err := tr.AddRawChanges(ctx, payload)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedRes, res)
|
||||
@@ -133,7 +133,7 @@ func Test_BuildSyncTree(t *testing.T) {
|
||||
Return(expectedRes, nil)
|
||||
|
||||
syncClientMock.EXPECT().CreateHeadUpdate(gomock.Eq(tr), gomock.Eq(changes)).Return(headUpdate)
|
||||
syncClientMock.EXPECT().Broadcast(gomock.Eq(headUpdate))
|
||||
syncClientMock.EXPECT().Broadcast(gomock.Any(), gomock.Eq(headUpdate))
|
||||
res, err := tr.AddContent(ctx, content)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedRes, res)
|
||||
|
||||
@@ -2,142 +2,233 @@ package synctree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||
"github.com/anyproto/any-sync/util/slice"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync/synchandler"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/syncstatus"
|
||||
"github.com/anytypeio/any-sync/util/slice"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMessageIsRequest = errors.New("message is request")
|
||||
ErrMessageIsNotRequest = errors.New("message is not request")
|
||||
ErrMoreThanOneRequest = errors.New("more than one request for same peer")
|
||||
"go.uber.org/zap"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type syncTreeHandler struct {
|
||||
objTree objecttree.ObjectTree
|
||||
syncClient SyncClient
|
||||
syncProtocol TreeSyncProtocol
|
||||
syncStatus syncstatus.StatusUpdater
|
||||
spaceId string
|
||||
|
||||
handlerLock sync.Mutex
|
||||
pendingRequests map[string]struct{}
|
||||
heads []string
|
||||
objTree objecttree.ObjectTree
|
||||
syncClient objectsync.SyncClient
|
||||
syncStatus syncstatus.StatusUpdater
|
||||
handlerLock sync.Mutex
|
||||
spaceId string
|
||||
queue ReceiveQueue
|
||||
}
|
||||
|
||||
const maxQueueSize = 5
|
||||
|
||||
func newSyncTreeHandler(spaceId string, objTree objecttree.ObjectTree, syncClient SyncClient, syncStatus syncstatus.StatusUpdater) synchandler.SyncHandler {
|
||||
func newSyncTreeHandler(spaceId string, objTree objecttree.ObjectTree, syncClient objectsync.SyncClient, syncStatus syncstatus.StatusUpdater) synchandler.SyncHandler {
|
||||
return &syncTreeHandler{
|
||||
objTree: objTree,
|
||||
syncProtocol: newTreeSyncProtocol(spaceId, objTree, syncClient),
|
||||
syncClient: syncClient,
|
||||
syncStatus: syncStatus,
|
||||
spaceId: spaceId,
|
||||
pendingRequests: make(map[string]struct{}),
|
||||
objTree: objTree,
|
||||
syncClient: syncClient,
|
||||
syncStatus: syncStatus,
|
||||
spaceId: spaceId,
|
||||
queue: newReceiveQueue(maxQueueSize),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *syncTreeHandler) HandleRequest(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (response *spacesyncproto.ObjectSyncMessage, err error) {
|
||||
unmarshalled := &treechangeproto.TreeSyncMessage{}
|
||||
err = proto.Unmarshal(request.Payload, unmarshalled)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fullSyncRequest := unmarshalled.GetContent().GetFullSyncRequest()
|
||||
if fullSyncRequest == nil {
|
||||
return nil, ErrMessageIsNotRequest
|
||||
}
|
||||
// setting pending requests
|
||||
s.handlerLock.Lock()
|
||||
_, exists := s.pendingRequests[senderId]
|
||||
if exists {
|
||||
s.handlerLock.Unlock()
|
||||
return nil, ErrMoreThanOneRequest
|
||||
}
|
||||
s.pendingRequests[senderId] = struct{}{}
|
||||
s.handlerLock.Unlock()
|
||||
|
||||
response, err = s.handleRequest(ctx, senderId, fullSyncRequest)
|
||||
|
||||
// removing pending requests
|
||||
s.handlerLock.Lock()
|
||||
delete(s.pendingRequests, senderId)
|
||||
s.handlerLock.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
func (s *syncTreeHandler) handleRequest(ctx context.Context, senderId string, fullSyncRequest *treechangeproto.TreeFullSyncRequest) (response *spacesyncproto.ObjectSyncMessage, err error) {
|
||||
s.objTree.Lock()
|
||||
defer s.objTree.Unlock()
|
||||
treeResp, err := s.syncProtocol.FullSyncRequest(ctx, senderId, fullSyncRequest)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
response, err = spacesyncproto.MarshallSyncMessage(treeResp, s.spaceId, s.objTree.Id())
|
||||
return
|
||||
}
|
||||
|
||||
func (s *syncTreeHandler) HandleMessage(ctx context.Context, senderId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
unmarshalled := &treechangeproto.TreeSyncMessage{}
|
||||
err = proto.Unmarshal(msg.Payload, unmarshalled)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
heads := treechangeproto.GetHeads(unmarshalled)
|
||||
s.syncStatus.HeadsReceive(senderId, msg.ObjectId, heads)
|
||||
s.handlerLock.Lock()
|
||||
// if the update has same heads then returning not to hang on a lock
|
||||
if unmarshalled.GetContent().GetHeadUpdate() != nil && slice.UnsortedEquals(heads, s.heads) {
|
||||
s.handlerLock.Unlock()
|
||||
s.syncStatus.HeadsReceive(senderId, msg.ObjectId, treechangeproto.GetHeads(unmarshalled))
|
||||
|
||||
queueFull := s.queue.AddMessage(senderId, unmarshalled, msg.RequestId)
|
||||
if queueFull {
|
||||
return
|
||||
}
|
||||
s.handlerLock.Unlock()
|
||||
return s.handleMessage(ctx, unmarshalled, senderId)
|
||||
|
||||
return s.handleMessage(ctx, senderId)
|
||||
}
|
||||
|
||||
func (s *syncTreeHandler) handleMessage(ctx context.Context, msg *treechangeproto.TreeSyncMessage, senderId string) (err error) {
|
||||
func (s *syncTreeHandler) handleMessage(ctx context.Context, senderId string) (err error) {
|
||||
s.objTree.Lock()
|
||||
defer s.objTree.Unlock()
|
||||
var (
|
||||
copyHeads = make([]string, 0, len(s.objTree.Heads()))
|
||||
treeId = s.objTree.Id()
|
||||
content = msg.GetContent()
|
||||
)
|
||||
msg, replyId, err := s.queue.GetMessage(senderId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer s.queue.ClearQueue(senderId)
|
||||
|
||||
content := msg.GetContent()
|
||||
switch {
|
||||
case content.GetHeadUpdate() != nil:
|
||||
return s.handleHeadUpdate(ctx, senderId, content.GetHeadUpdate(), replyId)
|
||||
case content.GetFullSyncRequest() != nil:
|
||||
return s.handleFullSyncRequest(ctx, senderId, content.GetFullSyncRequest(), replyId)
|
||||
case content.GetFullSyncResponse() != nil:
|
||||
return s.handleFullSyncResponse(ctx, senderId, content.GetFullSyncResponse())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *syncTreeHandler) handleHeadUpdate(
|
||||
ctx context.Context,
|
||||
senderId string,
|
||||
update *treechangeproto.TreeHeadUpdate,
|
||||
replyId string) (err error) {
|
||||
var (
|
||||
fullRequest *treechangeproto.TreeSyncMessage
|
||||
isEmptyUpdate = len(update.Changes) == 0
|
||||
objTree = s.objTree
|
||||
treeId = objTree.Id()
|
||||
)
|
||||
log := log.With(
|
||||
zap.Strings("update heads", update.Heads),
|
||||
zap.String("treeId", treeId),
|
||||
zap.String("spaceId", s.spaceId),
|
||||
zap.Int("len(update changes)", len(update.Changes)))
|
||||
log.DebugCtx(ctx, "received head update message")
|
||||
|
||||
// getting old heads
|
||||
copyHeads = append(copyHeads, s.objTree.Heads()...)
|
||||
defer func() {
|
||||
// checking if something changed
|
||||
if !slice.UnsortedEquals(copyHeads, s.objTree.Heads()) {
|
||||
s.handlerLock.Lock()
|
||||
defer s.handlerLock.Unlock()
|
||||
s.heads = s.heads[:0]
|
||||
for _, h := range s.objTree.Heads() {
|
||||
s.heads = append(s.heads, h)
|
||||
if err != nil {
|
||||
log.With(zap.Error(err)).Debug("head update finished with error")
|
||||
} else if fullRequest != nil {
|
||||
cnt := fullRequest.Content.GetFullSyncRequest()
|
||||
log = log.With(zap.Strings("request heads", cnt.Heads), zap.Int("len(request changes)", len(cnt.Changes)))
|
||||
log.DebugCtx(ctx, "sending full sync request")
|
||||
} else {
|
||||
if !isEmptyUpdate {
|
||||
log.DebugCtx(ctx, "head update finished correctly")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
switch {
|
||||
case content.GetHeadUpdate() != nil:
|
||||
var syncReq *treechangeproto.TreeSyncMessage
|
||||
syncReq, err = s.syncProtocol.HeadUpdate(ctx, senderId, content.GetHeadUpdate())
|
||||
if err != nil || syncReq == nil {
|
||||
// isEmptyUpdate is sent when the tree is brought up from cache
|
||||
if isEmptyUpdate {
|
||||
headEquals := slice.UnsortedEquals(objTree.Heads(), update.Heads)
|
||||
log.DebugCtx(ctx, "is empty update", zap.String("treeId", objTree.Id()), zap.Bool("headEquals", headEquals))
|
||||
if headEquals {
|
||||
return
|
||||
}
|
||||
return s.syncClient.QueueRequest(senderId, treeId, syncReq)
|
||||
case content.GetFullSyncRequest() != nil:
|
||||
return ErrMessageIsRequest
|
||||
case content.GetFullSyncResponse() != nil:
|
||||
return s.syncProtocol.FullSyncResponse(ctx, senderId, content.GetFullSyncResponse())
|
||||
|
||||
// we need to sync in any case
|
||||
fullRequest, err = s.syncClient.CreateFullSyncRequest(objTree, update.Heads, update.SnapshotPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return s.syncClient.SendWithReply(ctx, senderId, treeId, fullRequest, replyId)
|
||||
}
|
||||
|
||||
if s.alreadyHasHeads(objTree, update.Heads) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = objTree.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
||||
NewHeads: update.Heads,
|
||||
RawChanges: update.Changes,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if s.alreadyHasHeads(objTree, update.Heads) {
|
||||
return
|
||||
}
|
||||
|
||||
fullRequest, err = s.syncClient.CreateFullSyncRequest(objTree, update.Heads, update.SnapshotPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return s.syncClient.SendWithReply(ctx, senderId, treeId, fullRequest, replyId)
|
||||
}
|
||||
|
||||
func (s *syncTreeHandler) handleFullSyncRequest(
|
||||
ctx context.Context,
|
||||
senderId string,
|
||||
request *treechangeproto.TreeFullSyncRequest,
|
||||
replyId string) (err error) {
|
||||
var (
|
||||
fullResponse *treechangeproto.TreeSyncMessage
|
||||
header = s.objTree.Header()
|
||||
objTree = s.objTree
|
||||
treeId = s.objTree.Id()
|
||||
)
|
||||
|
||||
log := log.With(zap.String("senderId", senderId),
|
||||
zap.Strings("request heads", request.Heads),
|
||||
zap.String("treeId", treeId),
|
||||
zap.String("replyId", replyId),
|
||||
zap.String("spaceId", s.spaceId),
|
||||
zap.Int("len(request changes)", len(request.Changes)))
|
||||
log.DebugCtx(ctx, "received full sync request message")
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.With(zap.Error(err)).DebugCtx(ctx, "full sync request finished with error")
|
||||
s.syncClient.SendWithReply(ctx, senderId, treeId, treechangeproto.WrapError(treechangeproto.ErrFullSync, header), replyId)
|
||||
return
|
||||
} else if fullResponse != nil {
|
||||
cnt := fullResponse.Content.GetFullSyncResponse()
|
||||
log = log.With(zap.Strings("response heads", cnt.Heads), zap.Int("len(response changes)", len(cnt.Changes)))
|
||||
log.DebugCtx(ctx, "full sync response sent")
|
||||
}
|
||||
}()
|
||||
|
||||
if len(request.Changes) != 0 && !s.alreadyHasHeads(objTree, request.Heads) {
|
||||
_, err = objTree.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
||||
NewHeads: request.Heads,
|
||||
RawChanges: request.Changes,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
fullResponse, err = s.syncClient.CreateFullSyncResponse(objTree, request.Heads, request.SnapshotPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return s.syncClient.SendWithReply(ctx, senderId, treeId, fullResponse, replyId)
|
||||
}
|
||||
|
||||
func (s *syncTreeHandler) handleFullSyncResponse(
|
||||
ctx context.Context,
|
||||
senderId string,
|
||||
response *treechangeproto.TreeFullSyncResponse) (err error) {
|
||||
var (
|
||||
objTree = s.objTree
|
||||
treeId = s.objTree.Id()
|
||||
)
|
||||
log := log.With(
|
||||
zap.Strings("heads", response.Heads),
|
||||
zap.String("treeId", treeId),
|
||||
zap.String("spaceId", s.spaceId),
|
||||
zap.Int("len(changes)", len(response.Changes)))
|
||||
log.DebugCtx(ctx, "received full sync response message")
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.With(zap.Error(err)).DebugCtx(ctx, "full sync response failed")
|
||||
} else {
|
||||
log.DebugCtx(ctx, "full sync response succeeded")
|
||||
}
|
||||
}()
|
||||
|
||||
if s.alreadyHasHeads(objTree, response.Heads) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = objTree.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
||||
NewHeads: response.Heads,
|
||||
RawChanges: response.Changes,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (s *syncTreeHandler) alreadyHasHeads(t objecttree.ObjectTree, heads []string) bool {
|
||||
return slice.UnsortedEquals(t.Heads(), heads) || t.HasChanges(heads...)
|
||||
}
|
||||
@@ -2,16 +2,20 @@ package synctree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync/mock_objectsync"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/mock_synctree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||
"github.com/anytypeio/any-sync/app/logger"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/syncstatus"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type testObjTreeMock struct {
|
||||
@@ -51,40 +55,31 @@ func (t *testObjTreeMock) TryRLock() bool {
|
||||
|
||||
type syncHandlerFixture struct {
|
||||
ctrl *gomock.Controller
|
||||
syncClientMock *mock_synctree.MockSyncClient
|
||||
syncClientMock *mock_objectsync.MockSyncClient
|
||||
objectTreeMock *testObjTreeMock
|
||||
syncProtocolMock *mock_synctree.MockTreeSyncProtocol
|
||||
spaceId string
|
||||
senderId string
|
||||
treeId string
|
||||
receiveQueueMock ReceiveQueue
|
||||
|
||||
syncHandler *syncTreeHandler
|
||||
}
|
||||
|
||||
func newSyncHandlerFixture(t *testing.T) *syncHandlerFixture {
|
||||
ctrl := gomock.NewController(t)
|
||||
syncClientMock := mock_objectsync.NewMockSyncClient(ctrl)
|
||||
objectTreeMock := newTestObjMock(mock_objecttree.NewMockObjectTree(ctrl))
|
||||
syncClientMock := mock_synctree.NewMockSyncClient(ctrl)
|
||||
syncProtocolMock := mock_synctree.NewMockTreeSyncProtocol(ctrl)
|
||||
spaceId := "spaceId"
|
||||
receiveQueue := newReceiveQueue(5)
|
||||
|
||||
syncHandler := &syncTreeHandler{
|
||||
objTree: objectTreeMock,
|
||||
syncClient: syncClientMock,
|
||||
syncProtocol: syncProtocolMock,
|
||||
spaceId: spaceId,
|
||||
syncStatus: syncstatus.NewNoOpSyncStatus(),
|
||||
pendingRequests: map[string]struct{}{},
|
||||
objTree: objectTreeMock,
|
||||
syncClient: syncClientMock,
|
||||
queue: receiveQueue,
|
||||
syncStatus: syncstatus.NewNoOpSyncStatus(),
|
||||
}
|
||||
return &syncHandlerFixture{
|
||||
ctrl: ctrl,
|
||||
objectTreeMock: objectTreeMock,
|
||||
syncProtocolMock: syncProtocolMock,
|
||||
syncClientMock: syncClientMock,
|
||||
objectTreeMock: objectTreeMock,
|
||||
receiveQueueMock: receiveQueue,
|
||||
syncHandler: syncHandler,
|
||||
spaceId: spaceId,
|
||||
senderId: "senderId",
|
||||
treeId: "treeId",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,149 +87,341 @@ func (fx *syncHandlerFixture) stop() {
|
||||
fx.ctrl.Finish()
|
||||
}
|
||||
|
||||
func TestSyncTreeHandler_HandleMessage(t *testing.T) {
|
||||
func TestSyncHandler_HandleHeadUpdate(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
log = logger.CtxLogger{Logger: zap.NewNop()}
|
||||
fullRequest := &treechangeproto.TreeSyncMessage{
|
||||
Content: &treechangeproto.TreeSyncContentValue{
|
||||
Value: &treechangeproto.TreeSyncContentValue_FullSyncRequest{
|
||||
FullSyncRequest: &treechangeproto.TreeFullSyncRequest{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("handle head update message, heads not equal, request returned", func(t *testing.T) {
|
||||
t.Run("head update non empty all heads added", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
senderId := "senderId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||
Heads: []string{"h3"},
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(treeMsg, "spaceId", treeId)
|
||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
|
||||
syncReq := &treechangeproto.TreeSyncMessage{}
|
||||
fx.syncHandler.heads = []string{"h2"}
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.objectTreeMock.EXPECT().Heads().Times(2).Return([]string{"h2"})
|
||||
fx.objectTreeMock.EXPECT().Heads().Times(2).Return([]string{"h3"})
|
||||
fx.syncProtocolMock.EXPECT().HeadUpdate(ctx, fx.senderId, gomock.Any()).Return(syncReq, nil)
|
||||
fx.syncClientMock.EXPECT().QueueRequest(fx.senderId, fx.treeId, syncReq).Return(nil)
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).Times(2)
|
||||
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(false)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
||||
NewHeads: []string{"h1"},
|
||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
})).
|
||||
Return(objecttree.AddResult{}, nil)
|
||||
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(true)
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []string{"h3"}, fx.syncHandler.heads)
|
||||
})
|
||||
|
||||
t.Run("handle head update message, heads equal", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||
Heads: []string{"h1"},
|
||||
}
|
||||
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(treeMsg, "spaceId", treeId)
|
||||
|
||||
fx.syncHandler.heads = []string{"h1"}
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
||||
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("handle head update message, no sync request returned", func(t *testing.T) {
|
||||
t.Run("head update non empty heads not added", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
senderId := "senderId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||
Heads: []string{"h3"},
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(treeMsg, "spaceId", treeId)
|
||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
|
||||
fx.syncHandler.heads = []string{"h2"}
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.objectTreeMock.EXPECT().Heads().Times(2).Return([]string{"h2"})
|
||||
fx.objectTreeMock.EXPECT().Heads().Times(2).Return([]string{"h3"})
|
||||
fx.syncProtocolMock.EXPECT().HeadUpdate(ctx, fx.senderId, gomock.Any()).Return(nil, nil)
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).AnyTimes()
|
||||
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(false)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
||||
NewHeads: []string{"h1"},
|
||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
})).
|
||||
Return(objecttree.AddResult{}, nil)
|
||||
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(false)
|
||||
fx.syncClientMock.EXPECT().
|
||||
CreateFullSyncRequest(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
||||
Return(fullRequest, nil)
|
||||
fx.syncClientMock.EXPECT().SendWithReply(gomock.Any(), gomock.Eq(senderId), gomock.Eq(treeId), gomock.Eq(fullRequest), gomock.Eq(""))
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
||||
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []string{"h3"}, fx.syncHandler.heads)
|
||||
})
|
||||
|
||||
t.Run("handle full sync request returns error", func(t *testing.T) {
|
||||
t.Run("head update non empty equal heads", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
senderId := "senderId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullRequest := &treechangeproto.TreeFullSyncRequest{
|
||||
Heads: []string{"h3"},
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
treeMsg := treechangeproto.WrapFullRequest(fullRequest, chWithId)
|
||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(treeMsg, "spaceId", treeId)
|
||||
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
|
||||
fx.syncHandler.heads = []string{"h2"}
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.objectTreeMock.EXPECT().Heads().Times(3).Return([]string{"h2"})
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h1"}).AnyTimes()
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
||||
require.Equal(t, err, ErrMessageIsRequest)
|
||||
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("handle full sync response", func(t *testing.T) {
|
||||
t.Run("head update empty", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
senderId := "senderId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||
Heads: []string{"h1"},
|
||||
Changes: nil,
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).AnyTimes()
|
||||
fx.syncClientMock.EXPECT().
|
||||
CreateFullSyncRequest(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
||||
Return(fullRequest, nil)
|
||||
fx.syncClientMock.EXPECT().SendWithReply(gomock.Any(), gomock.Eq(senderId), gomock.Eq(treeId), gomock.Eq(fullRequest), gomock.Eq(""))
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("head update empty equal heads", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
senderId := "senderId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||
Heads: []string{"h1"},
|
||||
Changes: nil,
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h1"}).AnyTimes()
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSyncHandler_HandleFullSyncRequest(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
log = logger.CtxLogger{Logger: zap.NewNop()}
|
||||
fullResponse := &treechangeproto.TreeSyncMessage{
|
||||
Content: &treechangeproto.TreeSyncContentValue{
|
||||
Value: &treechangeproto.TreeSyncContentValue_FullSyncResponse{
|
||||
FullSyncResponse: &treechangeproto.TreeFullSyncResponse{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("full sync request with change", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
senderId := "senderId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
treeMsg := treechangeproto.WrapFullRequest(fullSyncRequest, chWithId)
|
||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||
fx.objectTreeMock.EXPECT().Header().Return(nil)
|
||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).AnyTimes()
|
||||
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(false)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
||||
NewHeads: []string{"h1"},
|
||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
})).
|
||||
Return(objecttree.AddResult{}, nil)
|
||||
fx.syncClientMock.EXPECT().
|
||||
CreateFullSyncResponse(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
||||
Return(fullResponse, nil)
|
||||
fx.syncClientMock.EXPECT().SendWithReply(gomock.Any(), gomock.Eq(senderId), gomock.Eq(treeId), gomock.Eq(fullResponse), gomock.Eq(""))
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("full sync request with change same heads", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
senderId := "senderId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
treeMsg := treechangeproto.WrapFullRequest(fullSyncRequest, chWithId)
|
||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
|
||||
fx.objectTreeMock.EXPECT().
|
||||
Id().AnyTimes().Return(treeId)
|
||||
fx.objectTreeMock.EXPECT().Header().Return(nil)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
Heads().
|
||||
Return([]string{"h1"}).AnyTimes()
|
||||
fx.syncClientMock.EXPECT().
|
||||
CreateFullSyncResponse(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
||||
Return(fullResponse, nil)
|
||||
fx.syncClientMock.EXPECT().SendWithReply(gomock.Any(), gomock.Eq(senderId), gomock.Eq(treeId), gomock.Eq(fullResponse), gomock.Eq(""))
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("full sync request without change but with reply id", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
senderId := "senderId"
|
||||
replyId := "replyId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
||||
Heads: []string{"h1"},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
treeMsg := treechangeproto.WrapFullRequest(fullSyncRequest, chWithId)
|
||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
objectMsg.RequestId = replyId
|
||||
|
||||
fx.objectTreeMock.EXPECT().
|
||||
Id().AnyTimes().Return(treeId)
|
||||
fx.objectTreeMock.EXPECT().Header().Return(nil)
|
||||
fx.syncClientMock.EXPECT().
|
||||
CreateFullSyncResponse(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
||||
Return(fullResponse, nil)
|
||||
fx.syncClientMock.EXPECT().SendWithReply(gomock.Any(), gomock.Eq(senderId), gomock.Eq(treeId), gomock.Eq(fullResponse), gomock.Eq(replyId))
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("full sync request with add raw changes error", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
senderId := "senderId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
treeMsg := treechangeproto.WrapFullRequest(fullSyncRequest, chWithId)
|
||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
|
||||
fx.objectTreeMock.EXPECT().
|
||||
Id().AnyTimes().Return(treeId)
|
||||
fx.objectTreeMock.EXPECT().Header().Return(nil)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
Heads().
|
||||
Return([]string{"h2"})
|
||||
fx.objectTreeMock.EXPECT().
|
||||
HasChanges(gomock.Eq([]string{"h1"})).
|
||||
Return(false)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
||||
NewHeads: []string{"h1"},
|
||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
})).
|
||||
Return(objecttree.AddResult{}, fmt.Errorf(""))
|
||||
fx.syncClientMock.EXPECT().SendWithReply(gomock.Any(), gomock.Eq(senderId), gomock.Eq(treeId), gomock.Any(), gomock.Eq(""))
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSyncHandler_HandleFullSyncResponse(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
log = logger.CtxLogger{Logger: zap.NewNop()}
|
||||
|
||||
t.Run("full sync response with change", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
senderId := "senderId"
|
||||
replyId := "replyId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullSyncResponse := &treechangeproto.TreeFullSyncResponse{
|
||||
Heads: []string{"h3"},
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
treeMsg := treechangeproto.WrapFullResponse(fullSyncResponse, chWithId)
|
||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(treeMsg, "spaceId", treeId)
|
||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, replyId)
|
||||
|
||||
fx.syncHandler.heads = []string{"h2"}
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.objectTreeMock.EXPECT().Heads().Times(2).Return([]string{"h2"})
|
||||
fx.objectTreeMock.EXPECT().Heads().Times(2).Return([]string{"h3"})
|
||||
fx.syncProtocolMock.EXPECT().FullSyncResponse(ctx, fx.senderId, gomock.Any()).Return(nil)
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
Heads().
|
||||
Return([]string{"h2"}).AnyTimes()
|
||||
fx.objectTreeMock.EXPECT().
|
||||
HasChanges(gomock.Eq([]string{"h1"})).
|
||||
Return(false)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
||||
NewHeads: []string{"h1"},
|
||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
})).
|
||||
Return(objecttree.AddResult{}, nil)
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
||||
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSyncTreeHandler_HandleRequest(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("handle request", func(t *testing.T) {
|
||||
t.Run("full sync response with same heads", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
senderId := "senderId"
|
||||
replyId := "replyId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullRequest := &treechangeproto.TreeFullSyncRequest{}
|
||||
treeMsg := treechangeproto.WrapFullRequest(fullRequest, chWithId)
|
||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(treeMsg, "spaceId", treeId)
|
||||
|
||||
syncResp := &treechangeproto.TreeSyncMessage{}
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.syncProtocolMock.EXPECT().FullSyncRequest(ctx, fx.senderId, gomock.Any()).Return(syncResp, nil)
|
||||
|
||||
res, err := fx.syncHandler.HandleRequest(ctx, fx.senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, res)
|
||||
})
|
||||
|
||||
t.Run("handle other message", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullResponse := &treechangeproto.TreeFullSyncResponse{}
|
||||
responseMsg := treechangeproto.WrapFullResponse(fullResponse, chWithId)
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{}
|
||||
headUpdateMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||
for _, msg := range []*treechangeproto.TreeSyncMessage{responseMsg, headUpdateMsg} {
|
||||
objectMsg, _ := spacesyncproto.MarshallSyncMessage(msg, "spaceId", treeId)
|
||||
|
||||
_, err := fx.syncHandler.HandleRequest(ctx, fx.senderId, objectMsg)
|
||||
require.Equal(t, err, ErrMessageIsNotRequest)
|
||||
fullSyncResponse := &treechangeproto.TreeFullSyncResponse{
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
treeMsg := treechangeproto.WrapFullResponse(fullSyncResponse, chWithId)
|
||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, replyId)
|
||||
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
Heads().
|
||||
Return([]string{"h1"}).AnyTimes()
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
@@ -2,19 +2,16 @@ package synctree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"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/spacestorage"
|
||||
"github.com/anyproto/any-sync/net/peer"
|
||||
"fmt"
|
||||
"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/anytypeio/any-sync/commonspace/spacestorage"
|
||||
"github.com/anytypeio/any-sync/net/peer"
|
||||
"github.com/anytypeio/any-sync/net/rpc/rpcerr"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoResponsiblePeers = errors.New("no responsible peers")
|
||||
"time"
|
||||
)
|
||||
|
||||
type treeRemoteGetter struct {
|
||||
@@ -39,7 +36,7 @@ func (t treeRemoteGetter) getPeers(ctx context.Context) (peerIds []string, err e
|
||||
return
|
||||
}
|
||||
if len(respPeers) == 0 {
|
||||
err = ErrNoResponsiblePeers
|
||||
err = fmt.Errorf("no responsible peers")
|
||||
return
|
||||
}
|
||||
for _, p := range respPeers {
|
||||
@@ -50,7 +47,7 @@ func (t treeRemoteGetter) getPeers(ctx context.Context) (peerIds []string, err e
|
||||
|
||||
func (t treeRemoteGetter) treeRequest(ctx context.Context, peerId string) (msg *treechangeproto.TreeSyncMessage, err error) {
|
||||
newTreeRequest := t.deps.SyncClient.CreateNewTreeRequest()
|
||||
resp, err := t.deps.SyncClient.SendRequest(ctx, peerId, t.treeId, newTreeRequest)
|
||||
resp, err := t.deps.SyncClient.SendSync(ctx, peerId, t.treeId, newTreeRequest)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -60,20 +57,45 @@ func (t treeRemoteGetter) treeRequest(ctx context.Context, peerId string) (msg *
|
||||
return
|
||||
}
|
||||
|
||||
func (t treeRemoteGetter) treeRequestLoop(ctx context.Context) (msg *treechangeproto.TreeSyncMessage, err error) {
|
||||
availablePeers, err := t.getPeers(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
func (t treeRemoteGetter) treeRequestLoop(ctx context.Context, wait bool) (msg *treechangeproto.TreeSyncMessage, err error) {
|
||||
peerIdx := 0
|
||||
Loop:
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, fmt.Errorf("waiting for object %s interrupted, context closed", t.treeId)
|
||||
default:
|
||||
break
|
||||
}
|
||||
availablePeers, err := t.getPeers(ctx)
|
||||
if err != nil {
|
||||
if !wait {
|
||||
return nil, err
|
||||
}
|
||||
select {
|
||||
// wait for peers to connect
|
||||
case <-time.After(1 * time.Second):
|
||||
continue Loop
|
||||
case <-ctx.Done():
|
||||
return nil, fmt.Errorf("waiting for object %s interrupted, context closed", t.treeId)
|
||||
}
|
||||
}
|
||||
|
||||
peerIdx = peerIdx % len(availablePeers)
|
||||
msg, err = t.treeRequest(ctx, availablePeers[peerIdx])
|
||||
if err == nil || !wait {
|
||||
return msg, err
|
||||
}
|
||||
peerIdx++
|
||||
}
|
||||
// in future we will try to load from different peers
|
||||
return t.treeRequest(ctx, availablePeers[0])
|
||||
}
|
||||
|
||||
func (t treeRemoteGetter) getTree(ctx context.Context) (treeStorage treestorage.TreeStorage, isRemote bool, err error) {
|
||||
func (t treeRemoteGetter) getTree(ctx context.Context) (treeStorage treestorage.TreeStorage, err error) {
|
||||
treeStorage, err = t.deps.SpaceStorage.TreeStorage(t.treeId)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil && err != treestorage.ErrUnknownTreeId {
|
||||
return
|
||||
}
|
||||
@@ -87,16 +109,22 @@ func (t treeRemoteGetter) getTree(ctx context.Context) (treeStorage treestorage.
|
||||
return
|
||||
}
|
||||
|
||||
isRemote = true
|
||||
resp, err := t.treeRequestLoop(ctx)
|
||||
resp, err := t.treeRequestLoop(ctx, t.deps.WaitTreeRemoteSync)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fullSyncResp := resp.GetContent().GetFullSyncResponse()
|
||||
if fullSyncResp == nil {
|
||||
switch {
|
||||
case resp.GetContent().GetErrorResponse() != nil:
|
||||
errResp := resp.GetContent().GetErrorResponse()
|
||||
err = rpcerr.Err(errResp.ErrCode)
|
||||
return
|
||||
case resp.GetContent().GetFullSyncResponse() == nil:
|
||||
err = treechangeproto.ErrUnexpected
|
||||
return
|
||||
default:
|
||||
break
|
||||
}
|
||||
fullSyncResp := resp.GetContent().GetFullSyncResponse()
|
||||
|
||||
payload := treestorage.TreeStorageCreatePayload{
|
||||
RootRawChange: resp.RootChange,
|
||||
@@ -111,6 +139,5 @@ func (t treeRemoteGetter) getTree(ctx context.Context) (treeStorage treestorage.
|
||||
return
|
||||
}
|
||||
// now we are sure that we can save it to the storage
|
||||
treeStorage, err = t.deps.SpaceStorage.CreateTreeStorage(payload)
|
||||
return
|
||||
return t.deps.SpaceStorage.CreateTreeStorage(payload)
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
package synctree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/mock_synctree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/peermanager/mock_peermanager"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/net/peer"
|
||||
"github.com/anyproto/any-sync/net/peer/mock_peer"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
type treeRemoteGetterFixture struct {
|
||||
ctrl *gomock.Controller
|
||||
|
||||
treeGetter treeRemoteGetter
|
||||
syncClientMock *mock_synctree.MockSyncClient
|
||||
peerGetterMock *mock_peermanager.MockPeerManager
|
||||
}
|
||||
|
||||
func newTreeRemoteGetterFixture(t *testing.T) *treeRemoteGetterFixture {
|
||||
ctrl := gomock.NewController(t)
|
||||
syncClientMock := mock_synctree.NewMockSyncClient(ctrl)
|
||||
peerGetterMock := mock_peermanager.NewMockPeerManager(ctrl)
|
||||
treeGetter := treeRemoteGetter{
|
||||
deps: BuildDeps{
|
||||
SyncClient: syncClientMock,
|
||||
PeerGetter: peerGetterMock,
|
||||
},
|
||||
treeId: "treeId",
|
||||
}
|
||||
return &treeRemoteGetterFixture{
|
||||
ctrl: ctrl,
|
||||
treeGetter: treeGetter,
|
||||
syncClientMock: syncClientMock,
|
||||
peerGetterMock: peerGetterMock,
|
||||
}
|
||||
}
|
||||
|
||||
func (fx *treeRemoteGetterFixture) stop() {
|
||||
fx.ctrl.Finish()
|
||||
}
|
||||
|
||||
func TestTreeRemoteGetter(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
peerId := "peerId"
|
||||
treeRequest := &treechangeproto.TreeSyncMessage{}
|
||||
treeResponse := &treechangeproto.TreeSyncMessage{
|
||||
RootChange: &treechangeproto.RawTreeChangeWithId{Id: "id"},
|
||||
}
|
||||
marshalled, _ := proto.Marshal(treeResponse)
|
||||
objectResponse := &spacesyncproto.ObjectSyncMessage{
|
||||
Payload: marshalled,
|
||||
}
|
||||
|
||||
t.Run("request works", func(t *testing.T) {
|
||||
fx := newTreeRemoteGetterFixture(t)
|
||||
defer fx.stop()
|
||||
mockPeer := mock_peer.NewMockPeer(fx.ctrl)
|
||||
mockPeer.EXPECT().Id().AnyTimes().Return(peerId)
|
||||
fx.peerGetterMock.EXPECT().GetResponsiblePeers(ctx).Return([]peer.Peer{mockPeer}, nil)
|
||||
fx.syncClientMock.EXPECT().CreateNewTreeRequest().Return(treeRequest)
|
||||
fx.syncClientMock.EXPECT().SendRequest(ctx, peerId, fx.treeGetter.treeId, treeRequest).Return(objectResponse, nil)
|
||||
resp, err := fx.treeGetter.treeRequestLoop(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "id", resp.RootChange.Id)
|
||||
})
|
||||
|
||||
t.Run("request fails", func(t *testing.T) {
|
||||
fx := newTreeRemoteGetterFixture(t)
|
||||
defer fx.stop()
|
||||
treeRequest := &treechangeproto.TreeSyncMessage{}
|
||||
mockPeer := mock_peer.NewMockPeer(fx.ctrl)
|
||||
mockPeer.EXPECT().Id().AnyTimes().Return(peerId)
|
||||
fx.peerGetterMock.EXPECT().GetResponsiblePeers(ctx).Return([]peer.Peer{mockPeer}, nil)
|
||||
fx.syncClientMock.EXPECT().CreateNewTreeRequest().Return(treeRequest)
|
||||
fx.syncClientMock.EXPECT().SendRequest(ctx, peerId, fx.treeGetter.treeId, treeRequest).AnyTimes().Return(nil, fmt.Errorf("some"))
|
||||
_, err := fx.treeGetter.treeRequestLoop(ctx)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
package synctree
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/util/slice"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type TreeSyncProtocol interface {
|
||||
HeadUpdate(ctx context.Context, senderId string, update *treechangeproto.TreeHeadUpdate) (request *treechangeproto.TreeSyncMessage, err error)
|
||||
FullSyncRequest(ctx context.Context, senderId string, request *treechangeproto.TreeFullSyncRequest) (response *treechangeproto.TreeSyncMessage, err error)
|
||||
FullSyncResponse(ctx context.Context, senderId string, response *treechangeproto.TreeFullSyncResponse) (err error)
|
||||
}
|
||||
|
||||
type treeSyncProtocol struct {
|
||||
log logger.CtxLogger
|
||||
spaceId string
|
||||
objTree objecttree.ObjectTree
|
||||
reqFactory RequestFactory
|
||||
}
|
||||
|
||||
func newTreeSyncProtocol(spaceId string, objTree objecttree.ObjectTree, reqFactory RequestFactory) *treeSyncProtocol {
|
||||
return &treeSyncProtocol{
|
||||
log: log.With(zap.String("spaceId", spaceId), zap.String("treeId", objTree.Id())),
|
||||
spaceId: spaceId,
|
||||
objTree: objTree,
|
||||
reqFactory: reqFactory,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *treeSyncProtocol) HeadUpdate(ctx context.Context, senderId string, update *treechangeproto.TreeHeadUpdate) (fullRequest *treechangeproto.TreeSyncMessage, err error) {
|
||||
var (
|
||||
isEmptyUpdate = len(update.Changes) == 0
|
||||
objTree = t.objTree
|
||||
)
|
||||
log := t.log.With(
|
||||
zap.String("senderId", senderId),
|
||||
zap.Strings("update heads", update.Heads),
|
||||
zap.Int("len(update changes)", len(update.Changes)))
|
||||
log.DebugCtx(ctx, "received head update message")
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.ErrorCtx(ctx, "head update finished with error", zap.Error(err))
|
||||
} else if fullRequest != nil {
|
||||
cnt := fullRequest.Content.GetFullSyncRequest()
|
||||
log = log.With(zap.Strings("request heads", cnt.Heads), zap.Int("len(request changes)", len(cnt.Changes)))
|
||||
log.DebugCtx(ctx, "returning full sync request")
|
||||
} else {
|
||||
if !isEmptyUpdate {
|
||||
log.DebugCtx(ctx, "head update finished correctly")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// isEmptyUpdate is sent when the tree is brought up from cache
|
||||
if isEmptyUpdate {
|
||||
headEquals := slice.UnsortedEquals(objTree.Heads(), update.Heads)
|
||||
log.DebugCtx(ctx, "is empty update", zap.Bool("headEquals", headEquals))
|
||||
if headEquals {
|
||||
return
|
||||
}
|
||||
|
||||
// we need to sync in any case
|
||||
fullRequest, err = t.reqFactory.CreateFullSyncRequest(objTree, update.Heads, update.SnapshotPath)
|
||||
return
|
||||
}
|
||||
|
||||
if t.hasHeads(objTree, update.Heads) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = objTree.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
||||
NewHeads: update.Heads,
|
||||
RawChanges: update.Changes,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if t.hasHeads(objTree, update.Heads) {
|
||||
return
|
||||
}
|
||||
|
||||
fullRequest, err = t.reqFactory.CreateFullSyncRequest(objTree, update.Heads, update.SnapshotPath)
|
||||
return
|
||||
}
|
||||
|
||||
func (t *treeSyncProtocol) FullSyncRequest(ctx context.Context, senderId string, request *treechangeproto.TreeFullSyncRequest) (fullResponse *treechangeproto.TreeSyncMessage, err error) {
|
||||
var (
|
||||
objTree = t.objTree
|
||||
)
|
||||
log := t.log.With(zap.String("senderId", senderId),
|
||||
zap.Strings("request heads", request.Heads),
|
||||
zap.Int("len(request changes)", len(request.Changes)))
|
||||
log.DebugCtx(ctx, "received full sync request message")
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.ErrorCtx(ctx, "full sync request finished with error", zap.Error(err))
|
||||
} else if fullResponse != nil {
|
||||
cnt := fullResponse.Content.GetFullSyncResponse()
|
||||
log = log.With(zap.Strings("response heads", cnt.Heads), zap.Int("len(response changes)", len(cnt.Changes)))
|
||||
log.DebugCtx(ctx, "full sync response sent")
|
||||
}
|
||||
}()
|
||||
|
||||
if len(request.Changes) != 0 && !t.hasHeads(objTree, request.Heads) {
|
||||
_, err = objTree.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
||||
NewHeads: request.Heads,
|
||||
RawChanges: request.Changes,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
fullResponse, err = t.reqFactory.CreateFullSyncResponse(objTree, request.Heads, request.SnapshotPath)
|
||||
return
|
||||
}
|
||||
|
||||
func (t *treeSyncProtocol) FullSyncResponse(ctx context.Context, senderId string, response *treechangeproto.TreeFullSyncResponse) (err error) {
|
||||
var (
|
||||
objTree = t.objTree
|
||||
)
|
||||
log := log.With(
|
||||
zap.Strings("heads", response.Heads),
|
||||
zap.Int("len(changes)", len(response.Changes)))
|
||||
log.DebugCtx(ctx, "received full sync response message")
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.ErrorCtx(ctx, "full sync response failed", zap.Error(err))
|
||||
} else {
|
||||
log.DebugCtx(ctx, "full sync response succeeded")
|
||||
}
|
||||
}()
|
||||
if t.hasHeads(objTree, response.Heads) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = objTree.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
||||
NewHeads: response.Heads,
|
||||
RawChanges: response.Changes,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (t *treeSyncProtocol) hasHeads(ot objecttree.ObjectTree, heads []string) bool {
|
||||
return slice.UnsortedEquals(ot.Heads(), heads) || ot.HasChanges(heads...)
|
||||
}
|
||||
@@ -1,293 +0,0 @@
|
||||
package synctree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/mock_synctree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type treeSyncProtocolFixture struct {
|
||||
log logger.CtxLogger
|
||||
spaceId string
|
||||
senderId string
|
||||
treeId string
|
||||
objectTreeMock *testObjTreeMock
|
||||
reqFactory *mock_synctree.MockRequestFactory
|
||||
ctrl *gomock.Controller
|
||||
syncProtocol TreeSyncProtocol
|
||||
}
|
||||
|
||||
func newSyncProtocolFixture(t *testing.T) *treeSyncProtocolFixture {
|
||||
ctrl := gomock.NewController(t)
|
||||
objTree := &testObjTreeMock{
|
||||
MockObjectTree: mock_objecttree.NewMockObjectTree(ctrl),
|
||||
}
|
||||
spaceId := "spaceId"
|
||||
reqFactory := mock_synctree.NewMockRequestFactory(ctrl)
|
||||
objTree.EXPECT().Id().Return("treeId")
|
||||
syncProtocol := newTreeSyncProtocol(spaceId, objTree, reqFactory)
|
||||
return &treeSyncProtocolFixture{
|
||||
log: log,
|
||||
spaceId: spaceId,
|
||||
senderId: "senderId",
|
||||
treeId: "treeId",
|
||||
objectTreeMock: objTree,
|
||||
reqFactory: reqFactory,
|
||||
ctrl: ctrl,
|
||||
syncProtocol: syncProtocol,
|
||||
}
|
||||
}
|
||||
|
||||
func (fx *treeSyncProtocolFixture) stop() {
|
||||
fx.ctrl.Finish()
|
||||
}
|
||||
|
||||
func TestTreeSyncProtocol_HeadUpdate(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
fullRequest := &treechangeproto.TreeSyncMessage{
|
||||
Content: &treechangeproto.TreeSyncContentValue{
|
||||
Value: &treechangeproto.TreeSyncContentValue_FullSyncRequest{
|
||||
FullSyncRequest: &treechangeproto.TreeFullSyncRequest{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("head update non empty all heads added", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).Times(2)
|
||||
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(false)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
||||
NewHeads: []string{"h1"},
|
||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
})).
|
||||
Return(objecttree.AddResult{}, nil)
|
||||
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(true)
|
||||
|
||||
res, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, res)
|
||||
})
|
||||
|
||||
t.Run("head update non empty equal heads", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h1"}).AnyTimes()
|
||||
|
||||
res, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, res)
|
||||
})
|
||||
|
||||
t.Run("head update empty", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||
Heads: []string{"h1"},
|
||||
Changes: nil,
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).AnyTimes()
|
||||
fx.reqFactory.EXPECT().
|
||||
CreateFullSyncRequest(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
||||
Return(fullRequest, nil)
|
||||
|
||||
res, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fullRequest, res)
|
||||
})
|
||||
|
||||
t.Run("head update empty equal heads", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||
Heads: []string{"h1"},
|
||||
Changes: nil,
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h1"}).AnyTimes()
|
||||
|
||||
res, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, res)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTreeSyncProtocol_FullSyncRequest(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
fullResponse := &treechangeproto.TreeSyncMessage{
|
||||
Content: &treechangeproto.TreeSyncContentValue{
|
||||
Value: &treechangeproto.TreeSyncContentValue_FullSyncResponse{
|
||||
FullSyncResponse: &treechangeproto.TreeFullSyncResponse{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("full sync request with change", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
|
||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).AnyTimes()
|
||||
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(false)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
||||
NewHeads: []string{"h1"},
|
||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
})).
|
||||
Return(objecttree.AddResult{}, nil)
|
||||
fx.reqFactory.EXPECT().
|
||||
CreateFullSyncResponse(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
||||
Return(fullResponse, nil)
|
||||
|
||||
res, err := fx.syncProtocol.FullSyncRequest(ctx, fx.senderId, fullSyncRequest)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fullResponse, res)
|
||||
})
|
||||
|
||||
t.Run("full sync request with change same heads", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
|
||||
fx.objectTreeMock.EXPECT().
|
||||
Heads().
|
||||
Return([]string{"h1"}).AnyTimes()
|
||||
fx.reqFactory.EXPECT().
|
||||
CreateFullSyncResponse(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
||||
Return(fullResponse, nil)
|
||||
|
||||
res, err := fx.syncProtocol.FullSyncRequest(ctx, fx.senderId, fullSyncRequest)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fullResponse, res)
|
||||
})
|
||||
|
||||
t.Run("full sync request without changes", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
||||
Heads: []string{"h1"},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.reqFactory.EXPECT().
|
||||
CreateFullSyncResponse(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
||||
Return(fullResponse, nil)
|
||||
|
||||
res, err := fx.syncProtocol.FullSyncRequest(ctx, fx.senderId, fullSyncRequest)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fullResponse, res)
|
||||
})
|
||||
|
||||
t.Run("full sync request with change, raw changes error", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
|
||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).AnyTimes()
|
||||
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(false)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
||||
NewHeads: []string{"h1"},
|
||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
})).
|
||||
Return(objecttree.AddResult{}, fmt.Errorf("addRawChanges error"))
|
||||
|
||||
_, err := fx.syncProtocol.FullSyncRequest(ctx, fx.senderId, fullSyncRequest)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTreeSyncProtocol_FullSyncResponse(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("full sync response with change", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullSyncResponse := &treechangeproto.TreeFullSyncResponse{
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
Heads().
|
||||
Return([]string{"h2"}).AnyTimes()
|
||||
fx.objectTreeMock.EXPECT().
|
||||
HasChanges(gomock.Eq([]string{"h1"})).
|
||||
Return(false)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
||||
NewHeads: []string{"h1"},
|
||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
})).
|
||||
Return(objecttree.AddResult{}, nil)
|
||||
|
||||
err := fx.syncProtocol.FullSyncResponse(ctx, fx.senderId, fullSyncResponse)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("full sync response with same heads", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullSyncResponse := &treechangeproto.TreeFullSyncResponse{
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
Heads().
|
||||
Return([]string{"h1"}).AnyTimes()
|
||||
|
||||
err := fx.syncProtocol.FullSyncResponse(ctx, fx.senderId, fullSyncResponse)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener (interfaces: UpdateListener)
|
||||
// Source: github.com/anytypeio/any-sync/commonspace/object/tree/synctree/updatelistener (interfaces: UpdateListener)
|
||||
|
||||
// Package mock_updatelistener is a generated GoMock package.
|
||||
package mock_updatelistener
|
||||
@@ -7,8 +7,8 @@ package mock_updatelistener
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
objecttree "github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
objecttree "github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockUpdateListener is a mock of UpdateListener interface.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//go:generate mockgen -destination mock_updatelistener/mock_updatelistener.go github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener UpdateListener
|
||||
//go:generate mockgen -destination mock_updatelistener/mock_updatelistener.go github.com/anytypeio/any-sync/commonspace/object/tree/synctree/updatelistener UpdateListener
|
||||
package updatelistener
|
||||
|
||||
import (
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
)
|
||||
|
||||
type UpdateListener interface {
|
||||
|
||||
@@ -3,15 +3,15 @@ package synctree
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"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/commonspace/objectsync/synchandler"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||
"github.com/anyproto/any-sync/net/peer"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync/synchandler"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/syncstatus"
|
||||
"github.com/anytypeio/any-sync/net/peer"
|
||||
"github.com/cheggaaa/mb/v3"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -82,124 +82,51 @@ func (m *messageLog) addMessage(msg protocolMsg) {
|
||||
m.batcher.Add(context.Background(), msg)
|
||||
}
|
||||
|
||||
type requestPeerManager struct {
|
||||
peerId string
|
||||
handlers map[string]*testSyncHandler
|
||||
log *messageLog
|
||||
}
|
||||
|
||||
func newRequestPeerManager(peerId string, log *messageLog) *requestPeerManager {
|
||||
return &requestPeerManager{
|
||||
peerId: peerId,
|
||||
handlers: map[string]*testSyncHandler{},
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *requestPeerManager) addHandler(peerId string, handler *testSyncHandler) {
|
||||
r.handlers[peerId] = handler
|
||||
}
|
||||
|
||||
func (r *requestPeerManager) Run(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *requestPeerManager) Close(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *requestPeerManager) SendRequest(ctx context.Context, peerId string, msg *spacesyncproto.ObjectSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error) {
|
||||
panic("should not be called")
|
||||
}
|
||||
|
||||
func (r *requestPeerManager) QueueRequest(peerId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
pMsg := protocolMsg{
|
||||
msg: msg,
|
||||
senderId: r.peerId,
|
||||
receiverId: peerId,
|
||||
}
|
||||
r.log.addMessage(pMsg)
|
||||
return r.handlers[peerId].send(context.Background(), pMsg)
|
||||
}
|
||||
|
||||
func (r *requestPeerManager) Init(a *app.App) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (r *requestPeerManager) Name() (name string) {
|
||||
return
|
||||
}
|
||||
|
||||
func (r *requestPeerManager) SendPeer(ctx context.Context, peerId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
pMsg := protocolMsg{
|
||||
msg: msg,
|
||||
senderId: r.peerId,
|
||||
receiverId: peerId,
|
||||
}
|
||||
r.log.addMessage(pMsg)
|
||||
return r.handlers[peerId].send(context.Background(), pMsg)
|
||||
}
|
||||
|
||||
func (r *requestPeerManager) Broadcast(ctx context.Context, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
for _, handler := range r.handlers {
|
||||
pMsg := protocolMsg{
|
||||
msg: msg,
|
||||
senderId: r.peerId,
|
||||
receiverId: handler.peerId,
|
||||
}
|
||||
r.log.addMessage(pMsg)
|
||||
handler.send(context.Background(), pMsg)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *requestPeerManager) GetResponsiblePeers(ctx context.Context) (peers []peer.Peer, err error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// testSyncHandler is the wrapper around individual tree to test sync protocol
|
||||
type testSyncHandler struct {
|
||||
synchandler.SyncHandler
|
||||
batcher *mb.MB[protocolMsg]
|
||||
peerId string
|
||||
aclList list.AclList
|
||||
log *messageLog
|
||||
syncClient SyncClient
|
||||
builder objecttree.BuildObjectTreeFunc
|
||||
peerManager *requestPeerManager
|
||||
batcher *mb.MB[protocolMsg]
|
||||
peerId string
|
||||
aclList list.AclList
|
||||
log *messageLog
|
||||
syncClient objectsync.SyncClient
|
||||
builder objecttree.BuildObjectTreeFunc
|
||||
}
|
||||
|
||||
// createSyncHandler creates a sync handler when a tree is already created
|
||||
func createSyncHandler(peerId, spaceId string, objTree objecttree.ObjectTree, log *messageLog) *testSyncHandler {
|
||||
peerManager := newRequestPeerManager(peerId, log)
|
||||
syncClient := NewSyncClient(spaceId, peerManager, peerManager)
|
||||
factory := objectsync.NewRequestFactory()
|
||||
syncClient := objectsync.NewSyncClient(spaceId, newTestMessagePool(peerId, log), factory)
|
||||
netTree := &broadcastTree{
|
||||
ObjectTree: objTree,
|
||||
SyncClient: syncClient,
|
||||
}
|
||||
handler := newSyncTreeHandler(spaceId, netTree, syncClient, syncstatus.NewNoOpSyncStatus())
|
||||
return &testSyncHandler{
|
||||
SyncHandler: handler,
|
||||
batcher: mb.New[protocolMsg](0),
|
||||
peerId: peerId,
|
||||
peerManager: peerManager,
|
||||
}
|
||||
return newTestSyncHandler(peerId, handler)
|
||||
}
|
||||
|
||||
// createEmptySyncHandler creates a sync handler when the tree will be provided later (this emulates the situation when we have no tree)
|
||||
func createEmptySyncHandler(peerId, spaceId string, builder objecttree.BuildObjectTreeFunc, aclList list.AclList, log *messageLog) *testSyncHandler {
|
||||
peerManager := newRequestPeerManager(peerId, log)
|
||||
syncClient := NewSyncClient(spaceId, peerManager, peerManager)
|
||||
factory := objectsync.NewRequestFactory()
|
||||
syncClient := objectsync.NewSyncClient(spaceId, newTestMessagePool(peerId, log), factory)
|
||||
|
||||
batcher := mb.New[protocolMsg](0)
|
||||
return &testSyncHandler{
|
||||
batcher: batcher,
|
||||
peerId: peerId,
|
||||
aclList: aclList,
|
||||
log: log,
|
||||
syncClient: syncClient,
|
||||
builder: builder,
|
||||
}
|
||||
}
|
||||
|
||||
func newTestSyncHandler(peerId string, syncHandler synchandler.SyncHandler) *testSyncHandler {
|
||||
batcher := mb.New[protocolMsg](0)
|
||||
return &testSyncHandler{
|
||||
SyncHandler: syncHandler,
|
||||
batcher: batcher,
|
||||
peerId: peerId,
|
||||
aclList: aclList,
|
||||
log: log,
|
||||
syncClient: syncClient,
|
||||
builder: builder,
|
||||
peerManager: peerManager,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,8 +140,13 @@ func (h *testSyncHandler) HandleMessage(ctx context.Context, senderId string, re
|
||||
return
|
||||
}
|
||||
if unmarshalled.Content.GetFullSyncResponse() == nil {
|
||||
newTreeRequest := NewRequestFactory().CreateNewTreeRequest()
|
||||
return h.syncClient.QueueRequest(senderId, request.ObjectId, newTreeRequest)
|
||||
newTreeRequest := objectsync.NewRequestFactory().CreateNewTreeRequest()
|
||||
var objMsg *spacesyncproto.ObjectSyncMessage
|
||||
objMsg, err = objectsync.MarshallTreeMessage(newTreeRequest, request.SpaceId, request.ObjectId, "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return h.manager().SendPeer(context.Background(), senderId, objMsg)
|
||||
}
|
||||
fullSyncResponse := unmarshalled.Content.GetFullSyncResponse()
|
||||
treeStorage, _ := treestorage.NewInMemoryTreeStorage(unmarshalled.RootChange, []string{unmarshalled.RootChange.Id}, nil)
|
||||
@@ -234,13 +166,20 @@ func (h *testSyncHandler) HandleMessage(ctx context.Context, senderId string, re
|
||||
return
|
||||
}
|
||||
h.SyncHandler = newSyncTreeHandler(request.SpaceId, netTree, h.syncClient, syncstatus.NewNoOpSyncStatus())
|
||||
headUpdate := NewRequestFactory().CreateHeadUpdate(netTree, res.Added)
|
||||
h.syncClient.Broadcast(headUpdate)
|
||||
return nil
|
||||
var objMsg *spacesyncproto.ObjectSyncMessage
|
||||
newTreeRequest := objectsync.NewRequestFactory().CreateHeadUpdate(netTree, res.Added)
|
||||
objMsg, err = objectsync.MarshallTreeMessage(newTreeRequest, request.SpaceId, request.ObjectId, "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return h.manager().Broadcast(context.Background(), objMsg)
|
||||
}
|
||||
|
||||
func (h *testSyncHandler) manager() *requestPeerManager {
|
||||
return h.peerManager
|
||||
func (h *testSyncHandler) manager() *testMessagePool {
|
||||
if h.SyncHandler != nil {
|
||||
return h.SyncHandler.(*syncTreeHandler).syncClient.MessagePool().(*testMessagePool)
|
||||
}
|
||||
return h.syncClient.MessagePool().(*testMessagePool)
|
||||
}
|
||||
|
||||
func (h *testSyncHandler) tree() *broadcastTree {
|
||||
@@ -272,28 +211,74 @@ func (h *testSyncHandler) run(ctx context.Context, t *testing.T, wg *sync.WaitGr
|
||||
h.tree().Unlock()
|
||||
continue
|
||||
}
|
||||
if res.description().name == "FullSyncRequest" {
|
||||
resp, err := h.HandleRequest(ctx, res.senderId, res.msg)
|
||||
if err != nil {
|
||||
fmt.Println("error handling request", err.Error())
|
||||
continue
|
||||
}
|
||||
h.peerManager.SendPeer(ctx, res.senderId, resp)
|
||||
} else {
|
||||
err = h.HandleMessage(ctx, res.senderId, res.msg)
|
||||
if err != nil {
|
||||
fmt.Println("error handling message", err.Error())
|
||||
}
|
||||
err = h.HandleMessage(ctx, res.senderId, res.msg)
|
||||
if err != nil {
|
||||
fmt.Println("error handling message", err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// testMessagePool captures all other handlers and sends messages to them
|
||||
type testMessagePool struct {
|
||||
peerId string
|
||||
handlers map[string]*testSyncHandler
|
||||
log *messageLog
|
||||
}
|
||||
|
||||
func newTestMessagePool(peerId string, log *messageLog) *testMessagePool {
|
||||
return &testMessagePool{handlers: map[string]*testSyncHandler{}, peerId: peerId, log: log}
|
||||
}
|
||||
|
||||
func (m *testMessagePool) addHandler(peerId string, handler *testSyncHandler) {
|
||||
m.handlers[peerId] = handler
|
||||
}
|
||||
|
||||
func (m *testMessagePool) SendPeer(ctx context.Context, peerId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
pMsg := protocolMsg{
|
||||
msg: msg,
|
||||
senderId: m.peerId,
|
||||
receiverId: peerId,
|
||||
}
|
||||
m.log.addMessage(pMsg)
|
||||
return m.handlers[peerId].send(context.Background(), pMsg)
|
||||
}
|
||||
|
||||
func (m *testMessagePool) Broadcast(ctx context.Context, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
for _, handler := range m.handlers {
|
||||
pMsg := protocolMsg{
|
||||
msg: msg,
|
||||
senderId: m.peerId,
|
||||
receiverId: handler.peerId,
|
||||
}
|
||||
m.log.addMessage(pMsg)
|
||||
handler.send(context.Background(), pMsg)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *testMessagePool) GetResponsiblePeers(ctx context.Context) (peers []peer.Peer, err error) {
|
||||
panic("should not be called")
|
||||
}
|
||||
|
||||
func (m *testMessagePool) LastUsage() time.Time {
|
||||
panic("should not be called")
|
||||
}
|
||||
|
||||
func (m *testMessagePool) HandleMessage(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
panic("should not be called")
|
||||
}
|
||||
|
||||
func (m *testMessagePool) SendSync(ctx context.Context, peerId string, message *spacesyncproto.ObjectSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error) {
|
||||
panic("should not be called")
|
||||
}
|
||||
|
||||
// broadcastTree is the tree that broadcasts changes to everyone when changes are added
|
||||
// it is a simplified version of SyncTree which is easier to use in the test environment
|
||||
type broadcastTree struct {
|
||||
objecttree.ObjectTree
|
||||
SyncClient
|
||||
objectsync.SyncClient
|
||||
}
|
||||
|
||||
func (b *broadcastTree) AddRawChanges(ctx context.Context, changes objecttree.RawChangesPayload) (objecttree.AddResult, error) {
|
||||
@@ -302,7 +287,7 @@ func (b *broadcastTree) AddRawChanges(ctx context.Context, changes objecttree.Ra
|
||||
return objecttree.AddResult{}, err
|
||||
}
|
||||
upd := b.SyncClient.CreateHeadUpdate(b.ObjectTree, res.Added)
|
||||
b.SyncClient.Broadcast(upd)
|
||||
b.SyncClient.Broadcast(ctx, upd)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package treechangeproto
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/anyproto/any-sync/net/rpc/rpcerr"
|
||||
"github.com/anytypeio/any-sync/net/rpc/rpcerr"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package treechangeproto
|
||||
|
||||
import "github.com/anyproto/any-sync/net/rpc/rpcerr"
|
||||
import "github.com/anytypeio/any-sync/net/rpc/rpcerr"
|
||||
|
||||
func WrapHeadUpdate(update *TreeHeadUpdate, rootChange *RawTreeChangeWithId) *TreeSyncMessage {
|
||||
return &TreeSyncMessage{
|
||||
|
||||
@@ -3,8 +3,8 @@ package treestorage
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/util/slice"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/util/slice"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -13,21 +13,13 @@ type InMemoryTreeStorage struct {
|
||||
root *treechangeproto.RawTreeChangeWithId
|
||||
heads []string
|
||||
Changes map[string]*treechangeproto.RawTreeChangeWithId
|
||||
addErr error
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func (t *InMemoryTreeStorage) SetReturnErrorOnAdd(err error) {
|
||||
t.addErr = err
|
||||
}
|
||||
|
||||
func (t *InMemoryTreeStorage) AddRawChangesSetHeads(changes []*treechangeproto.RawTreeChangeWithId, heads []string) error {
|
||||
func (t *InMemoryTreeStorage) TransactionAdd(changes []*treechangeproto.RawTreeChangeWithId, heads []string) error {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
if t.addErr != nil {
|
||||
return t.addErr
|
||||
}
|
||||
|
||||
for _, ch := range changes {
|
||||
t.Changes[ch.Id] = ch
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anyproto/any-sync/commonspace/object/tree/treestorage (interfaces: TreeStorage)
|
||||
// Source: github.com/anytypeio/any-sync/commonspace/object/tree/treestorage (interfaces: TreeStorage)
|
||||
|
||||
// Package mock_treestorage is a generated GoMock package.
|
||||
package mock_treestorage
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
treechangeproto "github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
treechangeproto "github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockTreeStorage is a mock of TreeStorage interface.
|
||||
@@ -49,20 +49,6 @@ func (mr *MockTreeStorageMockRecorder) AddRawChange(arg0 interface{}) *gomock.Ca
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawChange", reflect.TypeOf((*MockTreeStorage)(nil).AddRawChange), arg0)
|
||||
}
|
||||
|
||||
// AddRawChangesSetHeads mocks base method.
|
||||
func (m *MockTreeStorage) AddRawChangesSetHeads(arg0 []*treechangeproto.RawTreeChangeWithId, arg1 []string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AddRawChangesSetHeads", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddRawChangesSetHeads indicates an expected call of AddRawChangesSetHeads.
|
||||
func (mr *MockTreeStorageMockRecorder) AddRawChangesSetHeads(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawChangesSetHeads", reflect.TypeOf((*MockTreeStorage)(nil).AddRawChangesSetHeads), arg0, arg1)
|
||||
}
|
||||
|
||||
// Delete mocks base method.
|
||||
func (m *MockTreeStorage) Delete() error {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -164,3 +150,17 @@ func (mr *MockTreeStorageMockRecorder) SetHeads(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetHeads", reflect.TypeOf((*MockTreeStorage)(nil).SetHeads), arg0)
|
||||
}
|
||||
|
||||
// TransactionAdd mocks base method.
|
||||
func (m *MockTreeStorage) TransactionAdd(arg0 []*treechangeproto.RawTreeChangeWithId, arg1 []string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "TransactionAdd", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// TransactionAdd indicates an expected call of TransactionAdd.
|
||||
func (mr *MockTreeStorageMockRecorder) TransactionAdd(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TransactionAdd", reflect.TypeOf((*MockTreeStorage)(nil).TransactionAdd), arg0, arg1)
|
||||
}
|
||||
Loaded 100 of 245 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user