forked from TrueCloudLab/frostfs-node
Compare commits
1087 commits
support/v0
...
master
Author | SHA1 | Date | |
---|---|---|---|
b2c63e57ba | |||
445ebcc0e7 | |||
2302e5d342 | |||
a982c3df18 | |||
7f6852bbd2 | |||
26e4f7005c | |||
b21be1abdd | |||
b215817e14 | |||
306f12e6c5 | |||
5521737f0b | |||
e81a58b8da | |||
5048236441 | |||
c516c7c5f4 | |||
c99157f0b2 | |||
07390ad4e3 | |||
8d18fa159e | |||
02454df14a | |||
76ff26039c | |||
47286ebf32 | |||
5cfb758e4e | |||
29fe8c41f3 | |||
137e987a4e | |||
4d5be5ccb5 | |||
fd9128d051 | |||
364f835b7e | |||
c1ec6e33b4 | |||
f871f5cc6c | |||
b62008daca | |||
f13f5d3b0f | |||
a952a406a2 | |||
f04806ccd3 | |||
8088063195 | |||
c8a62ffedd | |||
2393d13e4d | |||
518f3baf41 | |||
5466e88444 | |||
bdfa523487 | |||
78cfb6aea8 | |||
0f75e48138 | |||
7cdae4f660 | |||
1bca8f118f | |||
9133b4389e | |||
1b22801eed | |||
3534d6d05b | |||
3ed3e2715b | |||
66848d3288 | |||
8e11ef46b8 | |||
5ec73fe8a0 | |||
3a2c319b87 | |||
70ab1ebd54 | |||
9c98fa6152 | |||
226e84d782 | |||
1e21733ea5 | |||
523fb3ca51 | |||
74c91eeef5 | |||
cae50ecb21 | |||
20d6132f31 | |||
7b1eda5107 | |||
7c8591f83b | |||
1ab567870a | |||
0b0e5dab24 | |||
c7a7229484 | |||
a26483fc30 | |||
c80b46fad3 | |||
05b508f79a | |||
8a82335b5c | |||
990f9f2d2b | |||
79088baa06 | |||
00aa6d9749 | |||
b8f79f4227 | |||
261d281154 | |||
869518be0a | |||
d4b6ebe7e7 | |||
121f5c4dd8 | |||
9f7c2d8810 | |||
cddc58ace2 | |||
0a9830564f | |||
6950312967 | |||
4f62fded01 | |||
2dbf5c612a | |||
4239f1e817 | |||
7f35f2fb1d | |||
b0d303f3ed | |||
a788c24e6d | |||
4368243bed | |||
00a0045d9a | |||
7f8ccc105b | |||
efb37b0e65 | |||
fe1acf9e9a | |||
559ad58ab1 | |||
c0b86f2d93 | |||
b0cf100427 | |||
58b6224dd8 | |||
12b7cf2533 | |||
dc4d27201b | |||
189dbb01be | |||
f2437f7ae9 | |||
f26233b47a | |||
7e0c5a55de | |||
d5c10612f4 | |||
3a997d1207 | |||
bf082348d4 | |||
994f48f8bb | |||
aca11d7474 | |||
5e229dc248 | |||
4caa934eea | |||
d07afd803c | |||
997ac7cd8d | |||
bd5bf8b1a9 | |||
f3278d76a9 | |||
627b302745 | |||
a0a35ffbec | |||
c1e4130020 | |||
b8c3c2486d | |||
c14c9a023c | |||
d9b93b12c1 | |||
3889e829e6 | |||
10570fc035 | |||
c6af4a3ec8 | |||
58239d1b2c | |||
3c76884182 | |||
f435ab1b26 | |||
aa9f8dce3d | |||
8a81af5a3b | |||
a716db99db | |||
36759f8434 | |||
39879fa868 | |||
c661ba1312 | |||
268adb79cb | |||
429f941cda | |||
382eb8a485 | |||
42696016de | |||
bdecfbc1be | |||
aa23c6a83a | |||
da8f384324 | |||
aeeb8193d2 | |||
d9de9e2bbb | |||
88d50e4c77 | |||
054e3ef3d3 | |||
a54b4472de | |||
7456c8556a | |||
c672f59ab8 | |||
b9b86d2ec8 | |||
4dff9555f1 | |||
806cc13d9f | |||
1daef2ceeb | |||
fe5aa06a75 | |||
91f3745b58 | |||
7654847f79 | |||
a724debb19 | |||
55b82e744b | |||
ae81d6660a | |||
ab2614ec2d | |||
4ea0df77d0 | |||
554ff2c06b | |||
9072772a09 | |||
c4db8e7690 | |||
f8ba60aa0c | |||
d2084ece41 | |||
40b556fc19 | |||
4db2cbc927 | |||
966ad22abf | |||
56f841b022 | |||
ba58144de1 | |||
c9e3c9956e | |||
facd3b2c4b | |||
3fcf56f2fb | |||
96e690883f | |||
322c1dc273 | |||
02b03d9c4f | |||
82cc453be9 | |||
238b8f10a0 | |||
345a1a69a2 | |||
dc3bc08c07 | |||
23be3eb627 | |||
42fb6fb372 | |||
62c2ad4b22 | |||
84ea075587 | |||
354a92ea2c | |||
d3904ec599 | |||
4d9a6c07fb | |||
a1f1d233cc | |||
f2811f8585 | |||
c4e1d8eb07 | |||
10e63537b2 | |||
809e97626b | |||
2e49d7ea7e | |||
f7042c5a6f | |||
127c676786 | |||
e604a3d749 | |||
a8de37c8a2 | |||
5a51b78946 | |||
6407bb5bd1 | |||
5335e7089e | |||
2efe9cc1be | |||
58c8722c81 | |||
baad49990c | |||
0c52186572 | |||
eec97d177e | |||
d15199c5d8 | |||
bc425b5bad | |||
88b6755c5e | |||
ae8be495c8 | |||
376f03a445 | |||
ad87493c41 | |||
21800e9fcc | |||
a5f51add25 | |||
abdb0910cc | |||
4d2af137e9 | |||
b44a8dd46c | |||
20af34ecdb | |||
dd988a5912 | |||
32a9f51586 | |||
e084c47bd6 | |||
779da6ec35 | |||
2685b1d548 | |||
55ce4dc075 | |||
c54fcb297d | |||
26a78aa366 | |||
4ad0ebb32f | |||
c3e23a1448 | |||
6bcba27757 | |||
dca31e8888 | |||
b02a1a34c1 | |||
082370583f | |||
34b5d90441 | |||
6186329aec | |||
c3c0574e3c | |||
8f994163ee | |||
023b90342c | |||
d641cba2fc | |||
33c11be0cf | |||
5b7e4a51b7 | |||
de3d1eb99c | |||
ae322e9f73 | |||
8d589314b5 | |||
7da4306e38 | |||
d3a52ec73a | |||
0e697266c3 | |||
5bbfebba2d | |||
1a0cb0f34a | |||
65c72f3e0b | |||
1e8b4b8a17 | |||
435a581b5e | |||
93c46cfdf0 | |||
1b7b54ba89 | |||
b3695411d9 | |||
ec8a631d31 | |||
9ca63ac8c3 | |||
b8052c794e | |||
35dc64bd7b | |||
05ac9e3637 | |||
7b0fdf0202 | |||
ec8b4fdc48 | |||
ad5f527bd3 | |||
ea32913430 | |||
99bb488ebd | |||
286242cad0 | |||
32c77f3a23 | |||
5ff82ff04f | |||
448b48287c | |||
c2617baf63 | |||
372160d048 | |||
fef172c5b0 | |||
5a4054eeb6 | |||
eed594431f | |||
af82c2865e | |||
b4e72a2dfd | |||
94df541426 | |||
57e7fb5ccf | |||
24dffdac6f | |||
a9d04ba86f | |||
6429975584 | |||
4680087711 | |||
f0355a453e | |||
8b78db74bc | |||
8966dd8e35 | |||
b2487e8cc5 | |||
3b66f98f27 | |||
3e8de14e7d | |||
a0c7045f29 | |||
d8e37a827f | |||
486287c2f7 | |||
397131b0ea | |||
11027945d8 | |||
8a9fc2c372 | |||
b8bcfac531 | |||
24eb988897 | |||
c83e7c875f | |||
e8091101c7 | |||
ec9b738465 | |||
800a685e84 | |||
1420b8b9ea | |||
a476d8285a | |||
70a1081988 | |||
18d8898b00 | |||
61541eaec2 | |||
7da284f3e8 | |||
80481c015c | |||
0754e6e654 | |||
676a3efa79 | |||
c42db4e761 | |||
a65e26878b | |||
fcbf90d31b | |||
7b76527759 | |||
9be5d44a46 | |||
040a623d39 | |||
140d970a95 | |||
2310a5c7ba | |||
e858479a74 | |||
a0d51090a4 | |||
033eaf77e1 | |||
4bbe9cc936 | |||
6eefe9747e | |||
f354b8a270 | |||
c6df6c84ae | |||
b520a3049e | |||
4c248d573e | |||
90e9a85acc | |||
8d16d95376 | |||
26acf5689e | |||
3223402c90 | |||
f9730f090d | |||
0c5b025788 | |||
f437ab8f15 | |||
d01c064674 | |||
d0ab552a90 | |||
33d9ebbe7f | |||
f91cfb36e6 | |||
8a4e250dae | |||
4f413fe86e | |||
cab51c8cbe | |||
73a71a71b0 | |||
f4c71cea65 | |||
64e1383df6 | |||
13b53258b4 | |||
0c866f62c5 | |||
43d263c3d5 | |||
cac4ed93d6 | |||
71a63b8e9c | |||
4bf345225c | |||
b4ce0b0412 | |||
dd3874eff1 | |||
72fedff7ad | |||
ab489265b3 | |||
71889234b7 | |||
40eae22109 | |||
a64dc9ad70 | |||
167a67f0b8 | |||
785d81a68a | |||
4d48377cec | |||
03aa210145 | |||
b5d9f4a285 | |||
e89fa110c7 | |||
af608da952 | |||
56f320dd85 | |||
16a142cd0c | |||
d8ecc69d00 | |||
d5aaec1107 | |||
059e9e88a2 | |||
f54cc0b607 | |||
8318d90ad0 | |||
3ae3c8dfdb | |||
a8526d45e9 | |||
028d4a8058 | |||
01a0c97760 | |||
50caa388b0 | |||
69df0d21c2 | |||
fe01781811 | |||
20b84f183a | |||
69b788a90b | |||
26b305f82b | |||
4449006862 | |||
847732605c | |||
c348ae35b0 | |||
1b364d8cf4 | |||
c8023a9c8d | |||
85deb12f4d | |||
07f155ac77 | |||
71bbeddb64 | |||
fb8fee0c8e | |||
344d6b2ae1 | |||
4887f489a1 | |||
90e9247b69 | |||
4f83ab0fb4 | |||
e68384d4e3 | |||
898f0686b1 | |||
957a43a124 | |||
e69a1e8482 | |||
2541d319de | |||
0c40d98f7a | |||
83d600ed77 | |||
0400153b7d | |||
41ab4d070e | |||
cd92d8a9e7 | |||
9bc1a25c07 | |||
508e2064eb | |||
5b75432ca2 | |||
263c6fdc50 | |||
189a367ef2 | |||
a770b89fd8 | |||
f8c1e0639d | |||
f1f56ef871 | |||
96c9843591 | |||
9562123c49 | |||
63473d0806 | |||
2dd3fc8b7e | |||
55c28fd5f4 | |||
dcdfb6ed41 | |||
c09144ecf1 | |||
74578052f9 | |||
7e9a1f394a | |||
f7c4c07453 | |||
4476a1dbaf | |||
dbf41391b5 | |||
3220c4df9f | |||
faca861451 | |||
f934abed8f | |||
f64322576a | |||
ebcc8afbee | |||
8dcd06c587 | |||
a3e30062df | |||
365a7ca0f4 | |||
802168c0c6 | |||
9c54a24101 | |||
271a56c2ab | |||
bc34fee6a7 | |||
f2e5dead7e | |||
5983617069 | |||
20a489bdb5 | |||
9119199f6e | |||
2613351008 | |||
a1823423cf | |||
2ce43935f9 | |||
d212d908b5 | |||
4503a61997 | |||
53a1b15693 | |||
0ab589dd52 | |||
81718afb39 | |||
656fd7f376 | |||
fb708b3a2d | |||
03ab0ca30f | |||
cf8531ccd7 | |||
4b768fd115 | |||
ff570847a4 | |||
7eb8fa6350 | |||
731bf5d0ee | |||
e3ad3c2965 | |||
35c9b6b26d | |||
|
bf79d06f03 | ||
|
9e56592be3 | ||
483fac03d6 | |||
f7c0b50d70 | |||
e4889e06ba | |||
100b1b5128 | |||
0fc494637f | |||
aabcd8d3e4 | |||
df9e099fa7 | |||
13a7a90101 | |||
6f47c75e43 | |||
0624820909 | |||
869fcbf591 | |||
ab07bad33d | |||
8da6530f41 | |||
079b28fa0f | |||
d4d921dcaf | |||
429a87e83b | |||
|
f604d6bbdc | ||
f989bc52be | |||
61776033c2 | |||
|
14c35d776e | ||
a6ee7a3087 | |||
6055b18362 | |||
02c02974b3 | |||
147ae8728a | |||
c62025c836 | |||
c8c5f14e2e | |||
fe4082799a | |||
945454f60c | |||
4578d00619 | |||
d35e4c389f | |||
969bfb603f | |||
47b0ec33c3 | |||
b480df4985 | |||
|
bcdb0f330d | ||
|
ddcc156ecc | ||
35fdf6f315 | |||
|
a5f118a987 | ||
800eb5e983 | |||
a181c9e434 | |||
90799497d3 | |||
ea10abb42a | |||
1309622b20 | |||
b2ffd7df53 | |||
35ea207df6 | |||
|
ee58b390bb | ||
d02950ad63 | |||
973af12854 | |||
cedd07bbc8 | |||
|
479c5a65e1 | ||
2f6757c828 | |||
872fe90c40 | |||
d1661ae7dc | |||
5f1af84587 | |||
a1b4ba9980 | |||
529d0bc710 | |||
eca5c210dd | |||
b939e4e5c5 | |||
235fe84ea3 | |||
d00b1c0d29 | |||
fb5dcc15d2 | |||
31b4da225a | |||
5010b35466 | |||
686f01bce5 | |||
e89fa7f69f | |||
53693071de | |||
e70f808dc3 | |||
2dbe382b5f | |||
500611f3c8 | |||
3b64dffda2 | |||
22d47376a6 | |||
45438e7b06 | |||
1440450606 | |||
591c4e7d50 | |||
265d2326a0 | |||
30e1b62b67 | |||
8fc082b688 | |||
3bac5a485d | |||
f368ccbdf0 | |||
b2bc1fccbc | |||
a9c4ba62c3 | |||
586f5986bc | |||
f1ea8fec93 | |||
7f49f07255 | |||
8879c6ea4a | |||
8b2aae73c6 | |||
f73ac6e02d | |||
ff25521204 | |||
58f1ba4b51 | |||
daa26f6e9b | |||
291f9e809a | |||
0045f1bcd4 | |||
f856ad7480 | |||
ada081dfd5 | |||
1f4061c0e2 | |||
dfe4ada838 | |||
f07e2d4812 | |||
57718bd6b4 | |||
14f83b8aa9 | |||
563780057d | |||
ef222e2487 | |||
e61aec4a7d | |||
eb7be82e87 | |||
d390f093e0 | |||
b2123bfd1a | |||
ee01275d25 | |||
9d01029733 | |||
299b24b974 | |||
700f39c3f8 | |||
|
dce5924a89 | ||
89530534a1 | |||
c04f6c5e59 | |||
04be9415d9 | |||
ddbc9e255f | |||
c70306b324 | |||
6fef2726b8 | |||
4ade5339da | |||
015d62425b | |||
|
59822f7fb4 | ||
dc2e9d70c7 | |||
|
09938a9841 | ||
|
e9461686b8 | ||
|
6b6f33ed71 | ||
|
4f5f832137 | ||
6c90bb87f1 | |||
|
3d23b08773 | ||
13c8afcb02 | |||
|
20cd080323 | ||
3d43b0f7f9 | |||
a358255c1b | |||
b447ff99aa | |||
7b981bfe97 | |||
f07d4158f5 | |||
d757d881d0 | |||
05c870f39a | |||
160147b05d | |||
|
262c9c2b93 | ||
0b42a00a60 | |||
8466894fdf | |||
|
070154d506 | ||
|
beabed788c | ||
|
b453bb754c | ||
|
8799138fcb | ||
|
960e3c219e | ||
|
560f73ab7e | ||
6121b541b5 | |||
d62c6e4ce6 | |||
200fc8b882 | |||
995db117d0 | |||
8d2f443868 | |||
41eb3129ae | |||
adcfce39cf | |||
299b6a6938 | |||
0c6aeaaf18 | |||
4496999e52 | |||
cffcc7745e | |||
0e31c12e63 | |||
|
d29b13454f | ||
d686ab49e8 | |||
|
f41ad9d419 | ||
|
be4df989e5 | ||
|
96b38f7e86 | ||
2f1beddfd3 | |||
01c0c90a86 | |||
7d39fecc6a | |||
04727ce1d6 | |||
08769f413f | |||
5d2affa5cd | |||
5778980252 | |||
b2ca730547 | |||
0920d848d0 | |||
5af9f58469 | |||
72565a91ef | |||
c4865783fc | |||
6ad5c38225 | |||
c85a0bc866 | |||
6bf11f7cca | |||
2c07f831c7 | |||
93eba19a8e | |||
2ed9fd3f94 | |||
ccf8463e69 | |||
ae86cda58c | |||
6016d78a45 | |||
02831d427b | |||
38ae71cc7d | |||
b689027d57 | |||
0b9622c418 | |||
dbc3811ff4 | |||
cb172e73a6 | |||
c236b54a65 | |||
7ebbfa3358 | |||
469e8a6e59 | |||
e2f13d03d7 | |||
e8d340287f | |||
3dbff0a478 | |||
fe87735073 | |||
d07e40d6fe | |||
775179f823 | |||
e815b19101 | |||
56282edf02 | |||
9027695371 | |||
4ec69cbbf8 | |||
f32f61df87 | |||
8908798f59 | |||
bab11492ad | |||
e21c5bea21 | |||
68a2f36636 | |||
9e2df4b7c7 | |||
ab891517de | |||
c58ab0c369 | |||
89924071cd | |||
6c7b708a98 | |||
b0786d2e5c | |||
f889893216 | |||
91ead04fa4 | |||
1bf21dbb47 | |||
206458c841 | |||
279261ace3 | |||
6f7b6a8813 | |||
d6486d172e | |||
080be5cfcd | |||
23575e1ac0 | |||
9098d0eec0 | |||
760af6b912 | |||
d85703a963 | |||
0b38419fbf | |||
5f2a1531fe | |||
4941926c9d | |||
585415fa92 | |||
9ef790f782 | |||
cd33a57f44 | |||
1f1aed87be | |||
5a66db80c5 | |||
456bc097f7 | |||
3010ca2649 | |||
0739c36a3b | |||
8273a3dfb2 | |||
594b5821ed | |||
ee7468daa7 | |||
49cc23e03c | |||
e85e5382e4 | |||
8e5a0dcf27 | |||
0948a280fa | |||
ece6c820e7 | |||
27bdddc48f | |||
cecea8053a | |||
14d894178e | |||
|
a69c6d1ec9 | ||
|
2bdf7126b8 | ||
|
aa92f977ef | ||
|
f09ee27af9 | ||
|
db5321309d | ||
|
44d5412e10 | ||
ed28ce24cd | |||
1f929fdd57 | |||
dd572825c7 | |||
56161d39b4 | |||
28dc9e2190 | |||
dcd39f8fdd | |||
c94372e6f9 | |||
3bbb516528 | |||
a7c79c773a | |||
8426d25f4b | |||
c1cbe6ff2d | |||
aeb4bbc51e | |||
30c18d46cc | |||
6be2688fb4 | |||
4d160bd4ab | |||
c8a6978563 | |||
85fb9e77c4 | |||
7be5a0fd79 | |||
ab32067152 | |||
9f0bce5c15 | |||
341fe1688f | |||
|
e843e7f090 | ||
|
ba58a77f8c | ||
|
3646723ae3 | ||
|
97e201993b | ||
221203beeb | |||
91717d4b98 | |||
382ecae96a | |||
9e54646248 | |||
bf7d80f44b | |||
5bf1ec348f | |||
93cb9e3a94 | |||
512b72591a | |||
e1b99dacad | |||
92f8810970 | |||
b0fefcb21f | |||
d62b11f5e7 | |||
ecbc211016 | |||
5f7d70c59c | |||
9534ade716 | |||
f2e880465e | |||
04b3d9d068 | |||
1d5f2dd681 | |||
c2cf708e0e | |||
c78e9cc857 | |||
|
3c7ed21f74 | ||
a5ece7889d | |||
|
f3ff9fd251 | ||
|
533e9f8b75 | ||
9ffa0d8fea | |||
d857ffeb2e | |||
|
34329d67ff | ||
9808dec591 | |||
342e571d89 | |||
f111704ceb | |||
|
da8da1c63a | ||
49234b915e | |||
1637a3edce | |||
cbc2efb1d6 | |||
484ac502ca | |||
8014fdb21a | |||
fb13902db9 | |||
3f6b962349 | |||
5368c4207a | |||
47e8c5bf23 | |||
ec2c5d45b4 | |||
7eb9e88f8f | |||
9aeea0b974 | |||
|
9a4f40626c | ||
7a31988a36 | |||
5059dcc19d | |||
6c4a1699ef | |||
|
9cd8f7cea0 | ||
44b86bac5a | |||
481a1ca6f3 | |||
97c36ed3ec | |||
cc8ff015b4 | |||
2dc86058c3 | |||
573d920821 | |||
d64fb887ff | |||
392be818e5 | |||
db3ccd2762 | |||
|
abd21f8099 | ||
|
10c419adf0 | ||
b70caa216b | |||
|
64bde68fb9 | ||
|
f006f3b342 | ||
22be532cbd | |||
724debfdcd | |||
b1c165a93b | |||
ac0a278a05 | |||
b8e93d4c08 | |||
07de839f18 | |||
2886b1581b | |||
8b9e40a848 | |||
b4582239bf | |||
4e244686cf | |||
6cd806f998 | |||
3e6fd4c611 | |||
5ae4446280 | |||
5890cd4d7d | |||
365adb4ebd | |||
bce5827f64 | |||
05471d3827 | |||
8226d49376 | |||
0893689c6a | |||
a4931ea4c7 | |||
861e9ab59a | |||
|
24a540caa8 | ||
6226c3ba86 | |||
f2250a316f | |||
9929dcf50b | |||
7486c02bbc | |||
|
f1f3c80dbf | ||
|
381e363a8b | ||
20de74a505 | |||
79ba34714a | |||
6d4f48f37a | |||
e9f3c24229 | |||
88e3868f47 | |||
6925fb4c59 | |||
c3a7039801 | |||
a1ab25b33e | |||
73bb590cb1 | |||
7eaf159a8b | |||
cb5468abb8 | |||
3d873237d5 | |||
633c5a35de | |||
5f06232d34 | |||
bed5a36235 | |||
87e69b9349 | |||
|
337049b2ce | ||
|
3beef10f89 | ||
5303736acd | |||
22f3c7d080 | |||
2b755ddb12 | |||
0b61a3c961 | |||
bf1e59bb83 | |||
2567f8020e | |||
d1d123d180 | |||
315141dc2c | |||
|
b422ac9f94 | ||
|
95ee905861 | ||
|
07ec51ea60 | ||
|
dbbbef9ddb | ||
351fdd9fa2 | |||
6fd88a036f | |||
2272c55c4d | |||
5cb2c5ae62 | |||
59748b7ae8 | |||
427fe276f2 | |||
c53903ccd0 | |||
e0309e398c | |||
58367e4df6 | |||
236c4af615 | |||
|
40822adb51 | ||
|
ad93d4db7c | ||
|
a6f071d66f | ||
9afe86ba3e | |||
c43b2dbac9 | |||
85cf1f47ac | |||
362f24953a | |||
|
901d62567d | ||
|
269a4e9b50 | ||
3e5bc394b5 | |||
1d3669232e | |||
204cd3a11c | |||
dee4498c1e | |||
abbecf49d6 | |||
ab21d90cfb | |||
5ffa826897 | |||
cb016d53a6 | |||
c761a95eef | |||
f825cfac78 | |||
c6645ef775 | |||
488eece25f | |||
1cedd446bb | |||
1858f11146 | |||
|
73bc1b0b68 | ||
515c60bdf4 | |||
ee24815748 | |||
2b09564355 | |||
5a9d6a09d8 | |||
2540598779 | |||
5d64a354cb | |||
d7e9e2ef9e | |||
d31d8c5335 | |||
406ff1360f | |||
|
89a0266f5e | ||
|
9513f163aa | ||
d65a95a2c6 | |||
46c62be7e8 | |||
072a7d61ab | |||
8078af3424 | |||
e5a40b90b6 | |||
93eb72ef44 | |||
8ee590794f | |||
cff4184cd3 | |||
c72576e72f | |||
87f0e3ea25 | |||
792319a044 | |||
67c97c6804 | |||
25d5995cef | |||
165a600624 | |||
|
64a5294b27 | ||
|
91757329ae | ||
|
cf1a91a758 | ||
|
c33ad3c474 | ||
0d8366f475 | |||
6451f019d2 | |||
|
6efa93be0a | ||
|
ac81c70c09 | ||
9009612a82 | |||
cedbd380f2 | |||
|
1d21b1e3e8 | ||
554b85411f | |||
b0ad1b9ed2 | |||
ba393e3e91 | |||
118783c4cf | |||
3d57f4c961 | |||
d4d2a8c865 | |||
3d1d2ee7b1 | |||
054bc4a727 | |||
9cb4b4cc17 | |||
023396e6a4 | |||
19d180b510 | |||
e355442532 | |||
cc71e6902a | |||
fdb0affc31 | |||
9936b112b8 | |||
f0be0befc5 | |||
1b3374ac7f | |||
d0a0432a51 | |||
4155c1bdff | |||
8f61cc1dcc | |||
55b403e0ee | |||
d8fb9c85eb | |||
6f8dd816fb | |||
26560b6b8d | |||
0272218eb9 | |||
874be99076 | |||
71c64ae253 | |||
|
cfdb53a788 | ||
a2fe912d1a | |||
741482c26f | |||
6f5edac730 | |||
f3caf6acfe | |||
0ace28e43d | |||
c1cf418956 | |||
b4e90cdf51 | |||
f9fcd85363 | |||
06137dbf8e | |||
c299b98afe | |||
de9957e076 | |||
d3054e577a | |||
|
6a4e5e6f0a | ||
|
86a4fba571 | ||
b7a99c757a | |||
e406036629 | |||
|
21717262ec | ||
|
74ec71446f | ||
|
1608fd1c07 | ||
|
eea2892109 | ||
33d279a3f2 | |||
|
04b5ec759b | ||
9e0decd12d | |||
|
e1c3bdbfa6 | ||
|
1044adbe94 | ||
|
2539d466a6 | ||
|
e9ba8931f8 | ||
|
fe7ddfdc6a | ||
|
306609030a | ||
edb1428248 | |||
e5c304536b | |||
3bb5a320d7 | |||
387d1e2977 | |||
|
21c58c92a9 | ||
|
a30310b2ca | ||
b207dc424f | |||
0244f2e5ce | |||
|
a68ff67ed8 | ||
|
73ef5b18c7 | ||
|
b413094704 | ||
|
350eecfa13 | ||
|
d6196c3971 | ||
0b78af467e | |||
|
923f84722a | ||
|
42554a9298 | ||
|
4a49ea0855 | ||
|
857d2dc3f5 | ||
|
63f604e948 | ||
|
6ad2b5d5b8 | ||
9eccf3bbf5 | |||
b0eeb0dfcf | |||
ba74244d1b | |||
b6a40241f8 | |||
|
d54022eacc | ||
|
bd25db5d4a | ||
|
e21c472dc7 | ||
afabd6be91 | |||
|
50d28b72c3 | ||
|
0f41c09207 | ||
|
b8c30b88f6 | ||
|
c01d4ecb50 | ||
|
3d0768a1d3 | ||
|
bc905f169d | ||
|
761e82fecd | ||
|
960a2d0629 | ||
8d15c14be6 | |||
|
51963abce7 | ||
|
7335a52f29 | ||
|
ae7b473768 | ||
8a77b4638a | |||
|
59db66cdb6 | ||
|
6882887bdd | ||
|
c1ea6fd854 | ||
|
b93be8869b | ||
|
cfefebd5b3 | ||
|
6af52c46d8 | ||
51e886dd67 | |||
79130f781e | |||
|
dd76ceadf1 | ||
|
ca3596dd05 | ||
|
816c74d185 | ||
71853348b9 | |||
|
ed4351aab0 | ||
|
dd225906a0 | ||
|
9f7ac6bf9f | ||
98e6dc5ce8 | |||
|
660c38d07e | ||
|
b1025bdb42 | ||
|
fdeea1dfac | ||
|
3d6defd3e8 | ||
|
fa231b8c56 | ||
|
1779664644 | ||
|
b673d9e472 | ||
9a20498f34 | |||
|
426fe97990 | ||
|
634792077e | ||
|
fe09cd9c70 | ||
|
3b61cb4f49 | ||
|
34e8d2ba56 | ||
|
9a039ba582 | ||
|
7ef0303e13 | ||
|
659011c143 | ||
|
597ed18269 | ||
|
d5a14041e0 | ||
|
fd61bdadcb | ||
|
d65604ad30 | ||
|
f2d7e65e39 | ||
|
c85bea15ef | ||
|
f48b1de54b | ||
|
09a59fef56 | ||
|
481b48b942 | ||
|
aadd2ad050 | ||
|
2522d924b9 | ||
|
37f813604f | ||
|
b0e94b6a6b | ||
|
aa478f1def | ||
|
3875fef542 | ||
|
b9fb0d6050 | ||
|
aab398f4f5 | ||
|
a77392e9ce | ||
|
2849e465f9 | ||
|
a3e7365cbd | ||
|
134f2ba02e | ||
|
2ef38cfbc4 | ||
|
a455ec18c3 | ||
|
ff5526038d | ||
|
d8d3588e1b | ||
|
08efe6eb11 | ||
|
777fd32d4f | ||
|
bffb0f894c | ||
|
5cf75404dc | ||
|
b64b14eb54 | ||
|
a56927e3d4 | ||
bb52857b2b | |||
|
1f82c583e3 | ||
|
7daa57d4d2 | ||
51e3810285 | |||
|
98a152256b | ||
|
b6930f2219 | ||
|
148da5fdbb | ||
|
f564430b90 |
1289 changed files with 51883 additions and 45400 deletions
|
@ -1,19 +1,19 @@
|
||||||
FROM golang:1.17 as builder
|
FROM golang:1.21 as builder
|
||||||
ARG BUILD=now
|
ARG BUILD=now
|
||||||
ARG VERSION=dev
|
ARG VERSION=dev
|
||||||
ARG REPO=repository
|
ARG REPO=repository
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY . /src
|
COPY . /src
|
||||||
|
|
||||||
RUN make bin/neofs-adm
|
RUN make bin/frostfs-adm
|
||||||
|
|
||||||
# Executable image
|
# Executable image
|
||||||
FROM alpine AS neofs-adm
|
FROM alpine AS frostfs-adm
|
||||||
RUN apk add --no-cache bash
|
RUN apk add --no-cache bash
|
||||||
|
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
|
|
||||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
COPY --from=builder /src/bin/neofs-adm /bin/neofs-adm
|
COPY --from=builder /src/bin/frostfs-adm /bin/frostfs-adm
|
||||||
|
|
||||||
CMD ["neofs-adm"]
|
CMD ["frostfs-adm"]
|
||||||
|
|
25
.docker/Dockerfile.ci
Normal file
25
.docker/Dockerfile.ci
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
FROM golang:1.21
|
||||||
|
|
||||||
|
WORKDIR /tmp
|
||||||
|
|
||||||
|
# Install apt packages
|
||||||
|
RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||||
|
pip \
|
||||||
|
&& apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Dash → Bash
|
||||||
|
RUN echo "dash dash/sh boolean false" | debconf-set-selections
|
||||||
|
RUN DEBIAN_FRONTEND=noninteractive dpkg-reconfigure dash
|
||||||
|
|
||||||
|
RUN useradd -u 1234 -d /home/ci -m ci
|
||||||
|
USER ci
|
||||||
|
|
||||||
|
ENV PATH="$PATH:/home/ci/.local/bin"
|
||||||
|
|
||||||
|
COPY .pre-commit-config.yaml .
|
||||||
|
|
||||||
|
RUN pip install "pre-commit==3.1.1" \
|
||||||
|
&& git init . \
|
||||||
|
&& pre-commit install-hooks \
|
||||||
|
&& rm -rf /tmp/*
|
|
@ -1,19 +1,19 @@
|
||||||
FROM golang:1.17 as builder
|
FROM golang:1.21 as builder
|
||||||
ARG BUILD=now
|
ARG BUILD=now
|
||||||
ARG VERSION=dev
|
ARG VERSION=dev
|
||||||
ARG REPO=repository
|
ARG REPO=repository
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY . /src
|
COPY . /src
|
||||||
|
|
||||||
RUN make bin/neofs-cli
|
RUN make bin/frostfs-cli
|
||||||
|
|
||||||
# Executable image
|
# Executable image
|
||||||
FROM alpine AS neofs-cli
|
FROM alpine AS frostfs-cli
|
||||||
RUN apk add --no-cache bash
|
RUN apk add --no-cache bash
|
||||||
|
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
|
|
||||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
COPY --from=builder /src/bin/neofs-cli /bin/neofs-cli
|
COPY --from=builder /src/bin/frostfs-cli /bin/frostfs-cli
|
||||||
|
|
||||||
CMD ["neofs-cli"]
|
CMD ["frostfs-cli"]
|
||||||
|
|
|
@ -3,6 +3,6 @@ RUN apk add --no-cache bash ca-certificates
|
||||||
|
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
|
|
||||||
COPY bin/neofs-adm /bin/neofs-adm
|
COPY bin/frostfs-adm /bin/frostfs-adm
|
||||||
|
|
||||||
CMD ["neofs-adm"]
|
CMD ["frostfs-adm"]
|
||||||
|
|
|
@ -3,6 +3,6 @@ RUN apk add --no-cache bash ca-certificates
|
||||||
|
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
|
|
||||||
COPY bin/neofs-cli /bin/neofs-cli
|
COPY bin/frostfs-cli /bin/frostfs-cli
|
||||||
|
|
||||||
CMD ["neofs-cli"]
|
CMD ["frostfs-cli"]
|
||||||
|
|
|
@ -3,6 +3,6 @@ RUN apk add --no-cache bash ca-certificates
|
||||||
|
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
|
|
||||||
COPY bin/neofs-ir /bin/neofs-ir
|
COPY bin/frostfs-ir /bin/frostfs-ir
|
||||||
|
|
||||||
CMD ["neofs-ir"]
|
CMD ["frostfs-ir"]
|
||||||
|
|
|
@ -3,6 +3,6 @@ RUN apk add --no-cache bash ca-certificates
|
||||||
|
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
|
|
||||||
COPY bin/neofs-node /bin/neofs-node
|
COPY bin/frostfs-node /bin/frostfs-node
|
||||||
|
|
||||||
CMD ["neofs-node"]
|
CMD ["frostfs-node"]
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
FROM golang:1.17 as builder
|
FROM golang:1.21 as builder
|
||||||
ARG BUILD=now
|
ARG BUILD=now
|
||||||
ARG VERSION=dev
|
ARG VERSION=dev
|
||||||
ARG REPO=repository
|
ARG REPO=repository
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY . /src
|
COPY . /src
|
||||||
|
|
||||||
RUN make bin/neofs-ir
|
RUN make bin/frostfs-ir
|
||||||
|
|
||||||
# Executable image
|
# Executable image
|
||||||
FROM alpine AS neofs-ir
|
FROM alpine AS frostfs-ir
|
||||||
RUN apk add --no-cache bash
|
RUN apk add --no-cache bash
|
||||||
|
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
|
|
||||||
COPY --from=builder /src/bin/neofs-ir /bin/neofs-ir
|
COPY --from=builder /src/bin/frostfs-ir /bin/frostfs-ir
|
||||||
|
|
||||||
CMD ["neofs-ir"]
|
CMD ["frostfs-ir"]
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
FROM golang:1.17 as builder
|
FROM golang:1.21 as builder
|
||||||
ARG BUILD=now
|
ARG BUILD=now
|
||||||
ARG VERSION=dev
|
ARG VERSION=dev
|
||||||
ARG REPO=repository
|
ARG REPO=repository
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY . /src
|
COPY . /src
|
||||||
|
|
||||||
RUN make bin/neofs-node
|
RUN make bin/frostfs-node
|
||||||
|
|
||||||
# Executable image
|
# Executable image
|
||||||
FROM alpine AS neofs-node
|
FROM alpine AS frostfs-node
|
||||||
RUN apk add --no-cache bash
|
RUN apk add --no-cache bash
|
||||||
|
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
|
|
||||||
COPY --from=builder /src/bin/neofs-node /bin/neofs-node
|
COPY --from=builder /src/bin/frostfs-node /bin/frostfs-node
|
||||||
|
|
||||||
CMD ["neofs-node"]
|
CMD ["frostfs-node"]
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
FROM golang:1.17 as builder
|
|
||||||
ARG BUILD=now
|
|
||||||
ARG VERSION=dev
|
|
||||||
ARG REPO=repository
|
|
||||||
WORKDIR /src
|
|
||||||
COPY . /src
|
|
||||||
|
|
||||||
RUN make bin/neofs-node
|
|
||||||
|
|
||||||
# Executable image
|
|
||||||
FROM alpine AS neofs-node
|
|
||||||
RUN apk add --no-cache bash
|
|
||||||
|
|
||||||
WORKDIR /
|
|
||||||
|
|
||||||
COPY --from=builder /src/bin/neofs-node /bin/neofs-node
|
|
||||||
COPY --from=builder /src/config/testnet/config.yml /config.yml
|
|
||||||
|
|
||||||
CMD ["neofs-node", "--config", "/config.yml"]
|
|
|
@ -5,4 +5,5 @@ docker-compose.yml
|
||||||
Dockerfile
|
Dockerfile
|
||||||
temp
|
temp
|
||||||
.dockerignore
|
.dockerignore
|
||||||
docker
|
docker
|
||||||
|
.cache
|
||||||
|
|
41
.forgejo/workflows/build.yml
Normal file
41
.forgejo/workflows/build.yml
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
name: Build
|
||||||
|
|
||||||
|
on: [pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build Components
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
go_versions: [ '1.20', '1.21' ]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
# Allows to fetch all history for all branches and tags.
|
||||||
|
# Need this for proper versioning.
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: '${{ matrix.go_versions }}'
|
||||||
|
|
||||||
|
- name: Build CLI
|
||||||
|
run: make bin/frostfs-cli
|
||||||
|
- run: bin/frostfs-cli --version
|
||||||
|
|
||||||
|
- name: Build NODE
|
||||||
|
run: make bin/frostfs-node
|
||||||
|
|
||||||
|
- name: Build IR
|
||||||
|
run: make bin/frostfs-ir
|
||||||
|
|
||||||
|
- name: Build ADM
|
||||||
|
run: make bin/frostfs-adm
|
||||||
|
- run: bin/frostfs-adm --version
|
||||||
|
|
||||||
|
- name: Build LENS
|
||||||
|
run: make bin/frostfs-lens
|
||||||
|
- run: bin/frostfs-lens --version
|
21
.forgejo/workflows/dco.yml
Normal file
21
.forgejo/workflows/dco.yml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
name: DCO action
|
||||||
|
on: [pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
dco:
|
||||||
|
name: DCO
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: '1.21'
|
||||||
|
|
||||||
|
- name: Run commit format checker
|
||||||
|
uses: https://git.frostfs.info/TrueCloudLab/dco-go@v2
|
||||||
|
with:
|
||||||
|
from: 'origin/${{ github.event.pull_request.base.ref }}'
|
73
.forgejo/workflows/tests.yml
Normal file
73
.forgejo/workflows/tests.yml
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
name: Tests and linters
|
||||||
|
on: [pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
name: Lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: '1.21'
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Install linters
|
||||||
|
run: make lint-install
|
||||||
|
|
||||||
|
- name: Run linters
|
||||||
|
run: make lint
|
||||||
|
|
||||||
|
tests:
|
||||||
|
name: Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
go_versions: [ '1.20', '1.21' ]
|
||||||
|
fail-fast: false
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: '${{ matrix.go_versions }}'
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: make test
|
||||||
|
|
||||||
|
tests-race:
|
||||||
|
name: Tests with -race
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: '1.21'
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: go test ./... -count=1 -race
|
||||||
|
|
||||||
|
staticcheck:
|
||||||
|
name: Staticcheck
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: '1.21'
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Install staticcheck
|
||||||
|
run: make staticcheck-install
|
||||||
|
|
||||||
|
- name: Run staticcheck
|
||||||
|
run: make staticcheck-run
|
22
.forgejo/workflows/vulncheck.yml
Normal file
22
.forgejo/workflows/vulncheck.yml
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
name: Vulncheck
|
||||||
|
on: [pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
vulncheck:
|
||||||
|
name: Vulncheck
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: '1.21'
|
||||||
|
|
||||||
|
- name: Install govulncheck
|
||||||
|
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||||
|
|
||||||
|
- name: Run govulncheck
|
||||||
|
run: govulncheck ./...
|
1
.gitattributes
vendored
1
.gitattributes
vendored
|
@ -1,2 +1,3 @@
|
||||||
/**/*.pb.go -diff -merge
|
/**/*.pb.go -diff -merge
|
||||||
/**/*.pb.go linguist-generated=true
|
/**/*.pb.go linguist-generated=true
|
||||||
|
/go.sum -diff
|
||||||
|
|
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -1 +0,0 @@
|
||||||
* @alexvanin @carpawell @fyrchik @cthulhu-rider
|
|
16
.github/ISSUE_TEMPLATE/bug_report.md
vendored
16
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -2,7 +2,7 @@
|
||||||
name: Bug report
|
name: Bug report
|
||||||
about: Create a report to help us improve
|
about: Create a report to help us improve
|
||||||
title: ''
|
title: ''
|
||||||
labels: community, triage
|
labels: community, triage, bug
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -18,8 +18,11 @@ assignees: ''
|
||||||
If suggesting a change/improvement, explain the difference from current behavior -->
|
If suggesting a change/improvement, explain the difference from current behavior -->
|
||||||
|
|
||||||
## Possible Solution
|
## Possible Solution
|
||||||
<!-- Not obligatory, but suggest a fix/reason for the bug,
|
<!-- Not obligatory
|
||||||
or ideas how to implement the addition or change -->
|
If no reason/fix/additions for the bug can be suggested,
|
||||||
|
uncomment the following phrase:
|
||||||
|
|
||||||
|
No fix can be suggested by a QA engineer. Further solutions shall be up to developers. -->
|
||||||
|
|
||||||
## Steps to Reproduce (for bugs)
|
## Steps to Reproduce (for bugs)
|
||||||
<!-- Provide a link to a live example, or an unambiguous set of steps
|
<!-- Provide a link to a live example, or an unambiguous set of steps
|
||||||
|
@ -41,10 +44,3 @@ assignees: ''
|
||||||
* Version used:
|
* Version used:
|
||||||
* Server setup and configuration:
|
* Server setup and configuration:
|
||||||
* Operating System and version (`uname -a`):
|
* Operating System and version (`uname -a`):
|
||||||
|
|
||||||
## Don't forget to add labels!
|
|
||||||
- component label (`neofs-adm`, `neofs-storage`, ...)
|
|
||||||
- `goodfirstissue`, `helpwanted` if needed
|
|
||||||
- does this issue belong to an epic?
|
|
||||||
- priority (`P0`-`P4`) if already triaged
|
|
||||||
- quarter label (`202XQY`) if possible
|
|
||||||
|
|
197
.github/logo.svg
vendored
197
.github/logo.svg
vendored
|
@ -1,129 +1,70 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<svg
|
<!-- Generator: Adobe Illustrator 25.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
<svg version="1.1" id="Слой_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
viewBox="0 0 184.2 51.8" style="enable-background:new 0 0 184.2 51.8;" xml:space="preserve">
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
<style type="text/css">
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
.st0{display:none;}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
.st1{display:inline;}
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
.st2{fill:#01E397;}
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
.st3{display:inline;fill:#010032;}
|
||||||
sodipodi:docname="logo_fs.svg"
|
.st4{display:inline;fill:#00E599;}
|
||||||
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
.st5{display:inline;fill:#00AF92;}
|
||||||
id="svg57"
|
.st6{fill:#00C3E5;}
|
||||||
version="1.1"
|
</style>
|
||||||
viewBox="0 0 105 25"
|
<g id="Layer_2">
|
||||||
height="25mm"
|
<g id="Layer_1-2" class="st0">
|
||||||
width="105mm">
|
<g class="st1">
|
||||||
<defs
|
<path class="st2" d="M146.6,18.3v7.2h10.9V29h-10.9v10.7h-4V14.8h18v3.5H146.6z"/>
|
||||||
id="defs51">
|
<path class="st2" d="M180,15.7c1.7,0.9,3,2.2,4,3.8l-3,2.7c-0.6-1.3-1.5-2.4-2.6-3.3c-1.3-0.7-2.8-1-4.3-1
|
||||||
<clipPath
|
c-1.4-0.1-2.8,0.3-4,1.1c-0.9,0.5-1.5,1.5-1.4,2.6c0,1,0.5,1.9,1.4,2.4c1.5,0.8,3.2,1.3,4.9,1.5c1.9,0.3,3.7,0.8,5.4,1.6
|
||||||
clipPathUnits="userSpaceOnUse"
|
c1.2,0.5,2.2,1.3,2.9,2.3c0.6,1,1,2.2,0.9,3.4c0,1.4-0.5,2.7-1.3,3.8c-0.9,1.2-2.1,2.1-3.5,2.6c-1.7,0.6-3.4,0.9-5.2,0.8
|
||||||
id="clipPath434">
|
c-5,0-8.6-1.6-10.7-5l2.9-2.8c0.7,1.4,1.8,2.5,3.1,3.3c1.5,0.7,3.1,1.1,4.7,1c1.5,0.1,2.9-0.2,4.2-0.9c0.9-0.5,1.5-1.5,1.5-2.6
|
||||||
<path
|
c0-0.9-0.5-1.8-1.3-2.2c-1.5-0.7-3.1-1.2-4.8-1.5c-1.9-0.3-3.7-0.8-5.5-1.5c-1.2-0.5-2.2-1.4-3-2.4c-0.6-1-1-2.2-0.9-3.4
|
||||||
d="M 0,0 H 1366 V 768 H 0 Z"
|
c0-1.4,0.4-2.7,1.2-3.8c0.8-1.2,2-2.2,3.3-2.8c1.6-0.7,3.4-1.1,5.2-1C176.1,14.3,178.2,14.8,180,15.7z"/>
|
||||||
id="path432" />
|
</g>
|
||||||
</clipPath>
|
<path class="st3" d="M73.3,16.3c1.9,1.9,2.9,4.5,2.7,7.1v15.9h-4V24.8c0-2.6-0.5-4.5-1.6-5.7c-1.2-1.2-2.8-1.8-4.5-1.7
|
||||||
</defs>
|
c-1.3,0-2.5,0.3-3.7,0.8c-1.2,0.7-2.2,1.7-2.9,2.9c-0.8,1.5-1.1,3.2-1.1,4.9v13.3h-4V15.1l3.6,1.5v1.7c0.8-1.5,2.1-2.6,3.6-3.3
|
||||||
<sodipodi:namedview
|
c1.5-0.8,3.2-1.2,4.9-1.1C68.9,13.8,71.3,14.7,73.3,16.3z"/>
|
||||||
inkscape:window-maximized="0"
|
<path class="st3" d="M104.4,28.3H85.6c0.1,2.2,1,4.3,2.5,5.9c1.5,1.4,3.5,2.2,5.6,2.1c1.6,0.1,3.2-0.2,4.6-0.9
|
||||||
inkscape:window-y="0"
|
c1.1-0.6,2-1.6,2.5-2.8l3.3,1.8c-0.9,1.7-2.3,3.1-4,4c-2,1-4.2,1.5-6.4,1.4c-3.7,0-6.7-1.1-8.8-3.4s-3.2-5.5-3.2-9.6s1-7.2,3-9.5
|
||||||
inkscape:window-x="130"
|
s5-3.4,8.7-3.4c2.1-0.1,4.2,0.5,6.1,1.5c1.6,1,3,2.5,3.8,4.2c0.9,1.8,1.3,3.9,1.3,5.9C104.6,26.4,104.6,27.4,104.4,28.3z
|
||||||
inkscape:window-height="1040"
|
M88.1,19.3c-1.4,1.5-2.2,3.4-2.4,5.5h15.1c-0.2-2-1-3.9-2.3-5.5c-1.4-1.3-3.2-2-5.1-1.9C91.5,17.3,89.6,18,88.1,19.3z"/>
|
||||||
inkscape:window-width="1274"
|
<path class="st3" d="M131,17.3c2.2,2.3,3.2,5.5,3.2,9.5s-1,7.3-3.2,9.6s-5.1,3.4-8.8,3.4s-6.7-1.1-8.9-3.4s-3.2-5.5-3.2-9.6
|
||||||
height="50mm"
|
s1.1-7.2,3.2-9.5s5.1-3.4,8.9-3.4S128.9,15,131,17.3z M116.2,19.9c-1.5,2-2.2,4.4-2.1,6.9c-0.2,2.5,0.6,5,2.1,7
|
||||||
units="mm"
|
c1.5,1.7,3.7,2.7,6,2.6c2.3,0.1,4.4-0.9,5.9-2.6c1.5-2,2.3-4.5,2.1-7c0.1-2.5-0.6-4.9-2.1-6.9c-1.5-1.7-3.6-2.7-5.9-2.6
|
||||||
showgrid="false"
|
C119.9,17.2,117.7,18.2,116.2,19.9z"/>
|
||||||
inkscape:document-rotation="0"
|
<polygon class="st4" points="0,9.1 0,43.7 22.5,51.8 22.5,16.9 46.8,7.9 24.8,0 "/>
|
||||||
inkscape:current-layer="layer1"
|
<polygon class="st5" points="24.3,17.9 24.3,36.8 46.8,44.9 46.8,9.6 "/>
|
||||||
inkscape:document-units="mm"
|
</g>
|
||||||
inkscape:cy="344.49897"
|
<g>
|
||||||
inkscape:cx="468.64708"
|
<g>
|
||||||
inkscape:zoom="0.7"
|
<path class="st6" d="M41.6,17.5H28.2v6.9h10.4v3.3H28.2v10.2h-3.9V14.2h17.2V17.5z"/>
|
||||||
inkscape:pageshadow="2"
|
<path class="st6" d="M45.8,37.9v-18h3.3l0.4,3.2c0.5-1.2,1.2-2.1,2.1-2.7c0.9-0.6,2.1-0.9,3.5-0.9c0.4,0,0.7,0,1.1,0.1
|
||||||
inkscape:pageopacity="0.0"
|
c0.4,0.1,0.7,0.2,0.9,0.3l-0.5,3.4c-0.3-0.1-0.6-0.2-0.9-0.2C55.4,23,54.9,23,54.4,23c-0.7,0-1.5,0.2-2.2,0.6
|
||||||
borderopacity="1.0"
|
c-0.7,0.4-1.3,1-1.8,1.8s-0.7,1.8-0.7,3v9.5H45.8z"/>
|
||||||
bordercolor="#666666"
|
<path class="st6" d="M68.6,19.6c1.8,0,3.3,0.4,4.6,1.1c1.3,0.7,2.4,1.8,3.1,3.2s1.1,3.1,1.1,5c0,1.9-0.4,3.6-1.1,5
|
||||||
pagecolor="#ffffff"
|
c-0.8,1.4-1.8,2.5-3.1,3.2c-1.3,0.7-2.9,1.1-4.6,1.1s-3.3-0.4-4.6-1.1c-1.3-0.7-2.4-1.8-3.2-3.2c-0.8-1.4-1.2-3.1-1.2-5
|
||||||
id="base" />
|
c0-1.9,0.4-3.6,1.2-5s1.8-2.5,3.2-3.2C65.3,19.9,66.8,19.6,68.6,19.6z M68.6,22.6c-1.1,0-2,0.2-2.8,0.7c-0.8,0.5-1.3,1.2-1.7,2.1
|
||||||
<metadata
|
s-0.6,2.1-0.6,3.5c0,1.3,0.2,2.5,0.6,3.4s1,1.7,1.7,2.2s1.7,0.7,2.8,0.7c1.1,0,2-0.2,2.7-0.7c0.7-0.5,1.3-1.2,1.7-2.2
|
||||||
id="metadata54">
|
s0.6-2.1,0.6-3.4c0-1.4-0.2-2.5-0.6-3.5s-1-1.6-1.7-2.1C70.6,22.8,69.6,22.6,68.6,22.6z"/>
|
||||||
<rdf:RDF>
|
<path class="st6" d="M89.2,38.3c-1.8,0-3.4-0.3-4.9-1c-1.5-0.7-2.7-1.7-3.5-3l2.7-2.3c0.5,1,1.3,1.8,2.3,2.4
|
||||||
<cc:Work
|
c1,0.6,2.2,0.9,3.6,0.9c1.1,0,2-0.2,2.6-0.6c0.6-0.4,1-0.9,1-1.6c0-0.5-0.2-0.9-0.5-1.2s-0.9-0.6-1.7-0.8l-3.8-0.8
|
||||||
rdf:about="">
|
c-1.9-0.4-3.3-1-4.1-1.9c-0.8-0.9-1.2-1.9-1.2-3.3c0-1,0.3-1.9,0.9-2.7c0.6-0.8,1.4-1.5,2.5-2s2.5-0.8,4-0.8c1.8,0,3.3,0.3,4.6,1
|
||||||
<dc:format>image/svg+xml</dc:format>
|
c1.3,0.6,2.2,1.5,2.9,2.7l-2.7,2.2c-0.5-1-1.1-1.7-2-2.1c-0.9-0.5-1.8-0.7-2.8-0.7c-0.8,0-1.4,0.1-2,0.3c-0.6,0.2-1,0.5-1.3,0.8
|
||||||
<dc:type
|
c-0.3,0.3-0.4,0.7-0.4,1.2c0,0.5,0.2,0.9,0.5,1.3s1,0.6,1.9,0.8l4.1,0.9c1.7,0.3,2.9,0.9,3.7,1.7c0.7,0.8,1.1,1.8,1.1,2.9
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
c0,1.2-0.3,2.2-0.9,3c-0.6,0.9-1.5,1.6-2.6,2C92.1,38.1,90.7,38.3,89.2,38.3z"/>
|
||||||
<dc:title></dc:title>
|
<path class="st6" d="M112.8,19.9v3H99.3v-3H112.8z M106.6,14.6v17.9c0,0.9,0.2,1.5,0.7,1.9c0.5,0.4,1.1,0.6,1.9,0.6
|
||||||
</cc:Work>
|
c0.6,0,1.2-0.1,1.7-0.3c0.5-0.2,0.9-0.5,1.3-0.8l0.9,2.8c-0.6,0.5-1.2,0.9-2,1.1c-0.8,0.3-1.7,0.4-2.7,0.4c-1,0-2-0.2-2.8-0.5
|
||||||
</rdf:RDF>
|
s-1.5-0.9-2-1.6c-0.5-0.8-0.7-1.7-0.8-3V15.7L106.6,14.6z"/>
|
||||||
</metadata>
|
<path d="M137.9,17.5h-13.3v6.9h10.4v3.3h-10.4v10.2h-3.9V14.2h17.2V17.5z"/>
|
||||||
<g
|
<path d="M150.9,13.8c2.1,0,4,0.4,5.5,1.2c1.6,0.8,2.9,2,4,3.5l-2.6,2.5c-0.9-1.4-1.9-2.4-3.1-3c-1.1-0.6-2.5-0.9-4-0.9
|
||||||
id="layer1"
|
c-1.2,0-2.1,0.2-2.8,0.5c-0.7,0.3-1.3,0.7-1.6,1.2c-0.3,0.5-0.5,1.1-0.5,1.7c0,0.7,0.3,1.4,0.8,1.9c0.5,0.6,1.5,1,2.9,1.3
|
||||||
inkscape:groupmode="layer"
|
l4.8,1.1c2.3,0.5,3.9,1.3,4.9,2.3c1,1,1.4,2.3,1.4,3.9c0,1.5-0.4,2.7-1.2,3.8c-0.8,1.1-1.9,1.9-3.3,2.5s-3.1,0.9-5,0.9
|
||||||
inkscape:label="Layer 1">
|
c-1.7,0-3.2-0.2-4.5-0.6c-1.3-0.4-2.5-1-3.5-1.8c-1-0.7-1.8-1.6-2.5-2.6l2.7-2.7c0.5,0.8,1.1,1.6,1.9,2.2
|
||||||
<g
|
c0.8,0.7,1.7,1.2,2.7,1.5c1,0.4,2.2,0.5,3.4,0.5c1.1,0,2.1-0.1,2.9-0.4c0.8-0.3,1.4-0.7,1.8-1.2c0.4-0.5,0.6-1.1,0.6-1.9
|
||||||
id="g424"
|
c0-0.7-0.2-1.3-0.7-1.8c-0.5-0.5-1.3-0.9-2.6-1.2l-5.2-1.2c-1.4-0.3-2.6-0.8-3.6-1.3c-0.9-0.6-1.6-1.3-2.1-2.1s-0.7-1.8-0.7-2.8
|
||||||
transform="matrix(0.35277777,0,0,-0.35277777,63.946468,10.194047)">
|
c0-1.3,0.4-2.6,1.1-3.7c0.7-1.1,1.8-2,3.2-2.6C147.3,14.1,148.9,13.8,150.9,13.8z"/>
|
||||||
<path
|
</g>
|
||||||
d="m 0,0 v -8.093 h 12.287 v -3.94 H 0 V -24.067 H -4.534 V 3.898 H 15.677 V 0 Z"
|
</g>
|
||||||
style="fill:#00e396;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
</g>
|
||||||
id="path426" />
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
transform="matrix(0.35277777,0,0,-0.35277777,-315.43002,107.34005)"
|
|
||||||
id="g428">
|
|
||||||
<g
|
|
||||||
id="g430"
|
|
||||||
clip-path="url(#clipPath434)">
|
|
||||||
<g
|
|
||||||
id="g436"
|
|
||||||
transform="translate(1112.874,278.2981)">
|
|
||||||
<path
|
|
||||||
d="M 0,0 C 1.822,-0.932 3.354,-2.359 4.597,-4.28 L 1.165,-7.373 c -0.791,1.695 -1.779,2.924 -2.966,3.686 -1.186,0.763 -2.768,1.145 -4.745,1.145 -1.949,0 -3.461,-0.389 -4.534,-1.166 -1.074,-0.777 -1.61,-1.772 -1.61,-2.987 0,-1.13 0.523,-2.027 1.568,-2.69 1.045,-0.664 2.909,-1.236 5.593,-1.716 2.514,-0.452 4.512,-1.024 5.995,-1.716 1.483,-0.693 2.564,-1.554 3.242,-2.585 0.677,-1.031 1.016,-2.309 1.016,-3.834 0,-1.639 -0.466,-3.079 -1.398,-4.322 -0.932,-1.243 -2.239,-2.197 -3.919,-2.86 -1.681,-0.664 -3.623,-0.996 -5.826,-0.996 -5.678,0 -9.689,1.892 -12.033,5.678 l 3.178,3.178 c 0.903,-1.695 2.068,-2.939 3.495,-3.729 1.426,-0.791 3.199,-1.186 5.318,-1.186 2.005,0 3.58,0.345 4.724,1.038 1.144,0.692 1.716,1.674 1.716,2.945 0,1.017 -0.516,1.835 -1.547,2.457 -1.031,0.621 -2.832,1.172 -5.402,1.653 -2.571,0.479 -4.618,1.073 -6.143,1.779 -1.526,0.706 -2.635,1.582 -3.326,2.627 -0.693,1.045 -1.039,2.316 -1.039,3.813 0,1.582 0.438,3.023 1.314,4.322 0.875,1.299 2.14,2.33 3.792,3.093 1.653,0.763 3.58,1.144 5.783,1.144 C -4.018,1.398 -1.822,0.932 0,0"
|
|
||||||
style="fill:#00e396;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
|
||||||
id="path438" />
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
id="g440"
|
|
||||||
transform="translate(993.0239,277.5454)">
|
|
||||||
<path
|
|
||||||
d="m 0,0 c 2.054,-1.831 3.083,-4.465 3.083,-7.902 v -17.935 h -4.484 v 16.366 c 0,2.914 -0.626,5.024 -1.877,6.332 -1.253,1.308 -2.924,1.962 -5.016,1.962 -1.495,0 -2.896,-0.327 -4.204,-0.981 -1.308,-0.654 -2.381,-1.719 -3.222,-3.194 -0.841,-1.477 -1.261,-3.335 -1.261,-5.576 v -14.909 h -4.484 V 1.328 l 4.086,-1.674 0.118,-1.84 c 0.933,1.681 2.222,2.923 3.867,3.727 1.643,0.803 3.493,1.205 5.548,1.205 C -4.671,2.746 -2.055,1.83 0,0"
|
|
||||||
style="fill:#000033;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
|
||||||
id="path442" />
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
id="g444"
|
|
||||||
transform="translate(1027.9968,264.0386)">
|
|
||||||
<path
|
|
||||||
d="m 0,0 h -21.128 c 0.261,-2.84 1.205,-5.044 2.83,-6.613 1.625,-1.57 3.727,-2.355 6.305,-2.355 2.054,0 3.763,0.356 5.128,1.065 1.363,0.71 2.288,1.738 2.774,3.083 l 3.755,-1.961 c -1.121,-1.981 -2.616,-3.495 -4.484,-4.54 -1.868,-1.046 -4.259,-1.569 -7.173,-1.569 -4.223,0 -7.538,1.289 -9.948,3.867 -2.41,2.578 -3.615,6.146 -3.615,10.704 0,4.558 1.149,8.127 3.447,10.705 2.298,2.578 5.557,3.867 9.779,3.867 2.615,0 4.876,-0.58 6.782,-1.738 1.905,-1.158 3.343,-2.728 4.315,-4.707 C -0.262,7.827 0.224,5.605 0.224,3.139 0.224,2.092 0.149,1.046 0,0 m -18.298,10.144 c -1.513,-1.457 -2.438,-3.512 -2.775,-6.165 h 16.982 c -0.3,2.615 -1.159,4.661 -2.578,6.137 -1.42,1.476 -3.307,2.214 -5.661,2.214 -2.466,0 -4.455,-0.728 -5.968,-2.186"
|
|
||||||
style="fill:#000033;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
|
||||||
id="path446" />
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
id="g448"
|
|
||||||
transform="translate(1057.8818,276.4246)">
|
|
||||||
<path
|
|
||||||
d="m 0,0 c 2.41,-2.578 3.615,-6.147 3.615,-10.705 0,-4.558 -1.205,-8.126 -3.615,-10.704 -2.41,-2.578 -5.726,-3.867 -9.948,-3.867 -4.222,0 -7.537,1.289 -9.947,3.867 -2.41,2.578 -3.615,6.146 -3.615,10.704 0,4.558 1.205,8.127 3.615,10.705 2.41,2.578 5.725,3.867 9.947,3.867 C -5.726,3.867 -2.41,2.578 0,0 m -16.617,-2.858 c -1.607,-1.906 -2.41,-4.522 -2.41,-7.847 0,-3.326 0.803,-5.94 2.41,-7.846 1.607,-1.905 3.83,-2.858 6.669,-2.858 2.839,0 5.063,0.953 6.67,2.858 1.606,1.906 2.41,4.52 2.41,7.846 0,3.325 -0.804,5.941 -2.41,7.847 C -4.885,-0.953 -7.109,0 -9.948,0 c -2.839,0 -5.062,-0.953 -6.669,-2.858"
|
|
||||||
style="fill:#000033;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
|
||||||
id="path450" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
id="g452"
|
|
||||||
transform="matrix(0.35277777,0,0,-0.35277777,5.8329581,6.5590171)">
|
|
||||||
<path
|
|
||||||
d="m 0,0 0.001,-38.946 25.286,-9.076 V -8.753 L 52.626,1.321 27.815,10.207 Z"
|
|
||||||
style="fill:#00e599;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
|
||||||
id="path454" />
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
id="g456"
|
|
||||||
transform="matrix(0.35277777,0,0,-0.35277777,15.479008,10.041927)">
|
|
||||||
<path
|
|
||||||
d="M 0,0 V -21.306 L 25.293,-30.364 25.282,9.347 Z"
|
|
||||||
style="fill:#00b091;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
|
||||||
id="path458" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 5.5 KiB |
28
.github/workflows/changelog.yml
vendored
28
.github/workflows/changelog.yml
vendored
|
@ -1,28 +0,0 @@
|
||||||
name: CHANGELOG check
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: Check for updates
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Get changed CHANGELOG
|
|
||||||
id: changelog-diff
|
|
||||||
uses: tj-actions/changed-files@v29
|
|
||||||
with:
|
|
||||||
files: CHANGELOG.md
|
|
||||||
|
|
||||||
- name: Fail if changelog not updated
|
|
||||||
if: steps.changelog-diff.outputs.any_changed == 'false'
|
|
||||||
uses: actions/github-script@v3
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
core.setFailed('CHANGELOG.md has not been updated')
|
|
36
.github/workflows/config-update.yml
vendored
36
.github/workflows/config-update.yml
vendored
|
@ -1,36 +0,0 @@
|
||||||
name: Configuration check
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: config-check
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Get changed config-related files
|
|
||||||
id: config-diff
|
|
||||||
uses: tj-actions/changed-files@v29
|
|
||||||
with:
|
|
||||||
files: |
|
|
||||||
config/**
|
|
||||||
cmd/neofs-node/config/**
|
|
||||||
|
|
||||||
- name: Get changed doc files
|
|
||||||
id: docs-diff
|
|
||||||
uses: tj-actions/changed-files@v29
|
|
||||||
with:
|
|
||||||
files: docs/**
|
|
||||||
|
|
||||||
- name: Fail if config files are changed but the documentation is not updated
|
|
||||||
if: steps.config-diff.outputs.any_changed == 'true' && steps.docs-diff.outputs.any_changed == 'false'
|
|
||||||
uses: actions/github-script@v3
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
core.setFailed('Documentation has not been updated')
|
|
21
.github/workflows/dco.yml
vendored
21
.github/workflows/dco.yml
vendored
|
@ -1,21 +0,0 @@
|
||||||
name: DCO check
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
commits_check_job:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: Commits Check
|
|
||||||
steps:
|
|
||||||
- name: Get PR Commits
|
|
||||||
id: 'get-pr-commits'
|
|
||||||
uses: tim-actions/get-pr-commits@master
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: DCO Check
|
|
||||||
uses: tim-actions/dco@master
|
|
||||||
with:
|
|
||||||
commits: ${{ steps.get-pr-commits.outputs.commits }}
|
|
58
.github/workflows/go.yml
vendored
58
.github/workflows/go.yml
vendored
|
@ -1,58 +0,0 @@
|
||||||
name: neofs-node tests
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
paths-ignore:
|
|
||||||
- '*.md'
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
paths-ignore:
|
|
||||||
- '*.md'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
go: [ '1.17.x', '1.18.x', '1.19.x' ]
|
|
||||||
steps:
|
|
||||||
- name: Setup go
|
|
||||||
uses: actions/setup-go@v3
|
|
||||||
with:
|
|
||||||
go-version: ${{ matrix.go }}
|
|
||||||
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Cache go mod
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: ~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-go-${{ matrix.go }}-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-go-${{ matrix.go }}-
|
|
||||||
|
|
||||||
- name: Run go test
|
|
||||||
run: go test -coverprofile=coverage.txt -covermode=atomic ./...
|
|
||||||
|
|
||||||
- name: Codecov
|
|
||||||
env:
|
|
||||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
|
||||||
run: bash <(curl -s https://codecov.io/bash)
|
|
||||||
|
|
||||||
lint:
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/setup-go@v3
|
|
||||||
with:
|
|
||||||
go-version: 1.19
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: golangci-lint
|
|
||||||
uses: golangci/golangci-lint-action@v3
|
|
||||||
with:
|
|
||||||
version: v1.50.0
|
|
||||||
args: --timeout=5m
|
|
||||||
only-new-issues: true
|
|
20
.gitignore
vendored
20
.gitignore
vendored
|
@ -30,4 +30,22 @@ testfile
|
||||||
.neofs-cli.yml
|
.neofs-cli.yml
|
||||||
|
|
||||||
# debhelpers
|
# debhelpers
|
||||||
**/.debhelper
|
debian/*debhelper*
|
||||||
|
|
||||||
|
# logfiles
|
||||||
|
debian/*.log
|
||||||
|
|
||||||
|
# .substvars
|
||||||
|
debian/*.substvars
|
||||||
|
|
||||||
|
# .bash-completion
|
||||||
|
debian/*.bash-completion
|
||||||
|
|
||||||
|
# Install folders and files
|
||||||
|
debian/frostfs-cli/
|
||||||
|
debian/frostfs-ir/
|
||||||
|
debian/files
|
||||||
|
debian/frostfs-storage/
|
||||||
|
debian/changelog
|
||||||
|
man/
|
||||||
|
debs/
|
||||||
|
|
11
.gitlint
Normal file
11
.gitlint
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[general]
|
||||||
|
fail-without-commits=True
|
||||||
|
regex-style-search=True
|
||||||
|
contrib=CC1
|
||||||
|
|
||||||
|
[title-match-regex]
|
||||||
|
regex=^\[\#[0-9Xx]+\]\s
|
||||||
|
|
||||||
|
[ignore-by-title]
|
||||||
|
regex=^Release(.*)
|
||||||
|
ignore=title-match-regex
|
|
@ -4,7 +4,7 @@
|
||||||
# options for analysis running
|
# options for analysis running
|
||||||
run:
|
run:
|
||||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||||
timeout: 5m
|
timeout: 20m
|
||||||
|
|
||||||
# include test files or not, default is true
|
# include test files or not, default is true
|
||||||
tests: false
|
tests: false
|
||||||
|
@ -24,6 +24,28 @@ linters-settings:
|
||||||
govet:
|
govet:
|
||||||
# report about shadowed variables
|
# report about shadowed variables
|
||||||
check-shadowing: false
|
check-shadowing: false
|
||||||
|
staticcheck:
|
||||||
|
checks: ["all", "-SA1019"] # TODO Enable SA1019 after deprecated warning are fixed.
|
||||||
|
funlen:
|
||||||
|
lines: 80 # default 60
|
||||||
|
statements: 60 # default 40
|
||||||
|
gocognit:
|
||||||
|
min-complexity: 40 # default 30
|
||||||
|
importas:
|
||||||
|
no-unaliased: true
|
||||||
|
no-extra-aliases: false
|
||||||
|
alias:
|
||||||
|
pkg: git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object
|
||||||
|
alias: objectSDK
|
||||||
|
custom:
|
||||||
|
truecloudlab-linters:
|
||||||
|
path: bin/linters/external_linters.so
|
||||||
|
original-url: git.frostfs.info/TrueCloudLab/linters.git
|
||||||
|
settings:
|
||||||
|
noliteral:
|
||||||
|
target-methods : ["reportFlushError", "reportError"]
|
||||||
|
disable-packages: ["codes", "err", "res","exec"]
|
||||||
|
constants-package: "git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
enable:
|
enable:
|
||||||
|
@ -51,6 +73,11 @@ linters:
|
||||||
- predeclared
|
- predeclared
|
||||||
- reassign
|
- reassign
|
||||||
- whitespace
|
- whitespace
|
||||||
|
- containedctx
|
||||||
|
- funlen
|
||||||
|
- gocognit
|
||||||
|
- contextcheck
|
||||||
|
- importas
|
||||||
|
- truecloudlab-linters
|
||||||
disable-all: true
|
disable-all: true
|
||||||
fast: false
|
fast: false
|
||||||
|
|
||||||
|
|
63
.pre-commit-config.yaml
Normal file
63
.pre-commit-config.yaml
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
ci:
|
||||||
|
autofix_prs: false
|
||||||
|
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/jorisroovers/gitlint
|
||||||
|
rev: v0.19.1
|
||||||
|
hooks:
|
||||||
|
- id: gitlint
|
||||||
|
stages: [commit-msg]
|
||||||
|
- id: gitlint-ci
|
||||||
|
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v4.4.0
|
||||||
|
hooks:
|
||||||
|
- id: check-added-large-files
|
||||||
|
- id: check-case-conflict
|
||||||
|
- id: check-executables-have-shebangs
|
||||||
|
- id: check-shebang-scripts-are-executable
|
||||||
|
- id: check-merge-conflict
|
||||||
|
- id: check-json
|
||||||
|
- id: check-xml
|
||||||
|
- id: check-yaml
|
||||||
|
- id: trailing-whitespace
|
||||||
|
args: [--markdown-linebreak-ext=md]
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
exclude: ".key$"
|
||||||
|
|
||||||
|
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||||
|
rev: v0.9.0.5
|
||||||
|
hooks:
|
||||||
|
- id: shellcheck
|
||||||
|
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: make-lint
|
||||||
|
name: Run Make Lint
|
||||||
|
entry: make lint
|
||||||
|
language: system
|
||||||
|
pass_filenames: false
|
||||||
|
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: go-unit-tests
|
||||||
|
name: go unit tests
|
||||||
|
entry: make test
|
||||||
|
pass_filenames: false
|
||||||
|
types: [go]
|
||||||
|
language: system
|
||||||
|
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: gofumpt
|
||||||
|
name: gofumpt
|
||||||
|
entry: make fumpt
|
||||||
|
pass_filenames: false
|
||||||
|
types: [go]
|
||||||
|
language: system
|
||||||
|
|
||||||
|
- repo: https://github.com/TekWizely/pre-commit-golang
|
||||||
|
rev: v1.0.0-rc.1
|
||||||
|
hooks:
|
||||||
|
- id: go-staticcheck-repo-mod
|
||||||
|
- id: go-mod-tidy
|
11
.woodpecker/pre-commit.yml
Normal file
11
.woodpecker/pre-commit.yml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
pipeline:
|
||||||
|
# Kludge for non-root containers under WoodPecker
|
||||||
|
fix-ownership:
|
||||||
|
image: alpine:latest
|
||||||
|
commands: chown -R 1234:1234 .
|
||||||
|
|
||||||
|
pre-commit:
|
||||||
|
image: git.frostfs.info/truecloudlab/frostfs-ci:v0.36
|
||||||
|
commands:
|
||||||
|
- export HOME="$(getent passwd $(id -u) | cut '-d:' -f6)"
|
||||||
|
- pre-commit run --hook-stage manual
|
1559
CHANGELOG.md
1559
CHANGELOG.md
File diff suppressed because it is too large
Load diff
|
@ -3,8 +3,8 @@
|
||||||
First, thank you for contributing! We love and encourage pull requests from
|
First, thank you for contributing! We love and encourage pull requests from
|
||||||
everyone. Please follow the guidelines:
|
everyone. Please follow the guidelines:
|
||||||
|
|
||||||
- Check the open [issues](https://github.com/nspcc-dev/neofs-node/issues) and
|
- Check the open [issues](https://git.frostfs.info/TrueCloudLab/frostfs-node/issues) and
|
||||||
[pull requests](https://github.com/nspcc-dev/neofs-node/pulls) for existing
|
[pull requests](https://git.frostfs.info/TrueCloudLab/frostfs-node/pulls) for existing
|
||||||
discussions.
|
discussions.
|
||||||
|
|
||||||
- Open an issue first, to discuss a new feature or enhancement.
|
- Open an issue first, to discuss a new feature or enhancement.
|
||||||
|
@ -23,23 +23,23 @@ everyone. Please follow the guidelines:
|
||||||
|
|
||||||
## Development Workflow
|
## Development Workflow
|
||||||
|
|
||||||
Start by forking the `neofs-node` repository, make changes in a branch and then
|
Start by forking the `frostfs-node` repository, make changes in a branch and then
|
||||||
send a pull request. We encourage pull requests to discuss code changes. Here
|
send a pull request. We encourage pull requests to discuss code changes. Here
|
||||||
are the steps in details:
|
are the steps in details:
|
||||||
|
|
||||||
### Set up your GitHub Repository
|
### Set up your Forgejo repository
|
||||||
Fork [NeoFS node upstream](https://github.com/nspcc-dev/neofs-node/fork) source
|
Fork [FrostFS node upstream](https://git.frostfs.info/TrueCloudLab/frostfs-node) source
|
||||||
repository to your own personal repository. Copy the URL of your fork (you will
|
repository to your own personal repository. Copy the URL of your fork (you will
|
||||||
need it for the `git clone` command below).
|
need it for the `git clone` command below).
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ git clone https://github.com/nspcc-dev/neofs-node
|
$ git clone https://git.frostfs.info/TrueCloudLab/frostfs-node
|
||||||
```
|
```
|
||||||
|
|
||||||
### Set up git remote as ``upstream``
|
### Set up git remote as ``upstream``
|
||||||
```sh
|
```sh
|
||||||
$ cd neofs-node
|
$ cd frostfs-node
|
||||||
$ git remote add upstream https://github.com/nspcc-dev/neofs-node
|
$ git remote add upstream https://git.frostfs.info/TrueCloudLab/frostfs-node
|
||||||
$ git fetch upstream
|
$ git fetch upstream
|
||||||
$ git merge upstream/master
|
$ git merge upstream/master
|
||||||
...
|
...
|
||||||
|
@ -58,7 +58,7 @@ $ git checkout -b feature/123-something_awesome
|
||||||
After your code changes, make sure
|
After your code changes, make sure
|
||||||
|
|
||||||
- To add test cases for the new code.
|
- To add test cases for the new code.
|
||||||
- To run `make lint`
|
- To run `make lint` and `make staticcheck-run`
|
||||||
- To squash your commits into a single commit or a series of logically separated
|
- To squash your commits into a single commit or a series of logically separated
|
||||||
commits run `git rebase -i`. It's okay to force update your pull request.
|
commits run `git rebase -i`. It's okay to force update your pull request.
|
||||||
- To run `make test` and `make all` completes.
|
- To run `make test` and `make all` completes.
|
||||||
|
@ -79,7 +79,7 @@ Description
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
$ git commit -am '[#123] Add some feature'
|
$ git commit -sam '[#123] Add some feature'
|
||||||
```
|
```
|
||||||
|
|
||||||
### Push to the branch
|
### Push to the branch
|
||||||
|
@ -89,8 +89,8 @@ $ git push origin feature/123-something_awesome
|
||||||
```
|
```
|
||||||
|
|
||||||
### Create a Pull Request
|
### Create a Pull Request
|
||||||
Pull requests can be created via GitHub. Refer to [this
|
Pull requests can be created via Forgejo. Refer to [this
|
||||||
document](https://help.github.com/articles/creating-a-pull-request/) for
|
document](https://docs.codeberg.org/collaborating/pull-requests-and-git-flow/) for
|
||||||
detailed steps on how to create a pull request. After a Pull Request gets peer
|
detailed steps on how to create a pull request. After a Pull Request gets peer
|
||||||
reviewed and approved, it will be merged.
|
reviewed and approved, it will be merged.
|
||||||
|
|
||||||
|
@ -106,7 +106,8 @@ contributors".
|
||||||
To sign your work, just add a line like this at the end of your commit message:
|
To sign your work, just add a line like this at the end of your commit message:
|
||||||
|
|
||||||
```
|
```
|
||||||
Signed-off-by: Samii Sakisaka <samii@nspcc.ru>
|
Signed-off-by: Samii Sakisaka <samii@ivunojikan.co.jp>
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This can easily be done with the `--signoff` option to `git commit`.
|
This can easily be done with the `--signoff` option to `git commit`.
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
# Credits
|
# Credits
|
||||||
|
|
||||||
|
FrostFS continues the development of NeoFS.
|
||||||
|
|
||||||
Initial NeoFS research and development (2018-2020) was done by
|
Initial NeoFS research and development (2018-2020) was done by
|
||||||
[NeoSPCC](https://nspcc.ru) team.
|
[NeoSPCC](https://nspcc.ru) team.
|
||||||
|
|
||||||
|
@ -8,7 +10,7 @@ In alphabetical order:
|
||||||
- Alexey Vanin
|
- Alexey Vanin
|
||||||
- Anastasia Prasolova
|
- Anastasia Prasolova
|
||||||
- Anatoly Bogatyrev
|
- Anatoly Bogatyrev
|
||||||
- Evgeny Kulikov
|
- Evgeny Kulikov
|
||||||
- Evgeny Stratonikov
|
- Evgeny Stratonikov
|
||||||
- Leonard Liubich
|
- Leonard Liubich
|
||||||
- Sergei Liubich
|
- Sergei Liubich
|
||||||
|
|
137
Makefile
Normal file → Executable file
137
Makefile
Normal file → Executable file
|
@ -4,11 +4,20 @@ SHELL = bash
|
||||||
REPO ?= $(shell go list -m)
|
REPO ?= $(shell go list -m)
|
||||||
VERSION ?= $(shell git describe --tags --dirty --match "v*" --always --abbrev=8 2>/dev/null || cat VERSION 2>/dev/null || echo "develop")
|
VERSION ?= $(shell git describe --tags --dirty --match "v*" --always --abbrev=8 2>/dev/null || cat VERSION 2>/dev/null || echo "develop")
|
||||||
|
|
||||||
HUB_IMAGE ?= nspccdev/neofs
|
HUB_IMAGE ?= truecloudlab/frostfs
|
||||||
HUB_TAG ?= "$(shell echo ${VERSION} | sed 's/^v//')"
|
HUB_TAG ?= "$(shell echo ${VERSION} | sed 's/^v//')"
|
||||||
|
|
||||||
GO_VERSION ?= 1.17
|
GO_VERSION ?= 1.21
|
||||||
LINT_VERSION ?= 1.50.0
|
LINT_VERSION ?= 1.54.0
|
||||||
|
TRUECLOUDLAB_LINT_VERSION ?= 0.0.2
|
||||||
|
PROTOC_VERSION ?= 25.0
|
||||||
|
PROTOC_GEN_GO_VERSION ?= $(shell go list -f '{{.Version}}' -m google.golang.org/protobuf)
|
||||||
|
PROTOGEN_FROSTFS_VERSION ?= $(shell go list -f '{{.Version}}' -m git.frostfs.info/TrueCloudLab/frostfs-api-go/v2)
|
||||||
|
PROTOC_OS_VERSION=osx-x86_64
|
||||||
|
ifeq ($(shell uname), Linux)
|
||||||
|
PROTOC_OS_VERSION=linux-x86_64
|
||||||
|
endif
|
||||||
|
STATICCHECK_VERSION ?= 2023.1.6
|
||||||
ARCH = amd64
|
ARCH = amd64
|
||||||
|
|
||||||
BIN = bin
|
BIN = bin
|
||||||
|
@ -16,7 +25,7 @@ RELEASE = release
|
||||||
DIRS = $(BIN) $(RELEASE)
|
DIRS = $(BIN) $(RELEASE)
|
||||||
|
|
||||||
# List of binaries to build.
|
# List of binaries to build.
|
||||||
CMDS = $(notdir $(basename $(wildcard cmd/*)))
|
CMDS = $(notdir $(basename $(wildcard cmd/frostfs-*)))
|
||||||
BINS = $(addprefix $(BIN)/, $(CMDS))
|
BINS = $(addprefix $(BIN)/, $(CMDS))
|
||||||
|
|
||||||
# .deb package versioning
|
# .deb package versioning
|
||||||
|
@ -25,11 +34,21 @@ PKG_VERSION ?= $(shell echo $(VERSION) | sed "s/^v//" | \
|
||||||
sed -E "s/(.*)-(g[a-fA-F0-9]{6,8})(.*)/\1\3~\2/" | \
|
sed -E "s/(.*)-(g[a-fA-F0-9]{6,8})(.*)/\1\3~\2/" | \
|
||||||
sed "s/-/~/")-${OS_RELEASE}
|
sed "s/-/~/")-${OS_RELEASE}
|
||||||
|
|
||||||
.PHONY: help all images dep clean fmts fmt imports test lint docker/lint
|
OUTPUT_LINT_DIR ?= $(abspath $(BIN))/linters
|
||||||
prepare-release debpackage
|
LINT_DIR = $(OUTPUT_LINT_DIR)/golangci-lint-$(LINT_VERSION)-v$(TRUECLOUDLAB_LINT_VERSION)
|
||||||
|
TMP_DIR := .cache
|
||||||
|
PROTOBUF_DIR ?= $(abspath $(BIN))/protobuf
|
||||||
|
PROTOC_DIR ?= $(PROTOBUF_DIR)/protoc-v$(PROTOC_VERSION)
|
||||||
|
PROTOC_GEN_GO_DIR ?= $(PROTOBUF_DIR)/protoc-gen-go-$(PROTOC_GEN_GO_VERSION)
|
||||||
|
PROTOGEN_FROSTFS_DIR ?= $(PROTOBUF_DIR)/protogen-$(PROTOGEN_FROSTFS_VERSION)
|
||||||
|
STATICCHECK_DIR ?= $(abspath $(BIN))/staticcheck
|
||||||
|
STATICCHECK_VERSION_DIR ?= $(STATICCHECK_DIR)/$(STATICCHECK_VERSION)
|
||||||
|
|
||||||
|
.PHONY: help all images dep clean fmts fumpt imports test lint docker/lint
|
||||||
|
prepare-release debpackage pre-commit unpre-commit
|
||||||
|
|
||||||
# To build a specific binary, use it's name prefix with bin/ as a target
|
# To build a specific binary, use it's name prefix with bin/ as a target
|
||||||
# For example `make bin/neofs-node` will build only storage node binary
|
# For example `make bin/frostfs-node` will build only storage node binary
|
||||||
# Just `make` will build all possible binaries
|
# Just `make` will build all possible binaries
|
||||||
all: $(DIRS) $(BINS)
|
all: $(DIRS) $(BINS)
|
||||||
|
|
||||||
|
@ -50,7 +69,7 @@ $(DIRS):
|
||||||
# Prepare binaries and archives for release
|
# Prepare binaries and archives for release
|
||||||
.ONESHELL:
|
.ONESHELL:
|
||||||
prepare-release: docker/all
|
prepare-release: docker/all
|
||||||
@for file in `ls -1 $(BIN)/neofs-*`; do
|
@for file in `ls -1 $(BIN)/frostfs-*`; do
|
||||||
cp $$file $(RELEASE)/`basename $$file`-$(ARCH)
|
cp $$file $(RELEASE)/`basename $$file`-$(ARCH)
|
||||||
strip $(RELEASE)/`basename $$file`-$(ARCH)
|
strip $(RELEASE)/`basename $$file`-$(ARCH)
|
||||||
tar -czf $(RELEASE)/`basename $$file`-$(ARCH).tar.gz $(RELEASE)/`basename $$file`-$(ARCH)
|
tar -czf $(RELEASE)/`basename $$file`-$(ARCH).tar.gz $(RELEASE)/`basename $$file`-$(ARCH)
|
||||||
|
@ -65,28 +84,44 @@ dep:
|
||||||
CGO_ENABLED=0 \
|
CGO_ENABLED=0 \
|
||||||
go mod tidy -v && echo OK
|
go mod tidy -v && echo OK
|
||||||
|
|
||||||
|
# Build export-metrics
|
||||||
|
export-metrics: dep
|
||||||
|
@printf "⇒ Build export-metrics\n"
|
||||||
|
CGO_ENABLED=0 \
|
||||||
|
go build -v -trimpath -o bin/export-metrics ./scripts/export-metrics
|
||||||
|
|
||||||
# Regenerate proto files:
|
# Regenerate proto files:
|
||||||
protoc:
|
protoc:
|
||||||
@GOPRIVATE=github.com/nspcc-dev go mod vendor
|
@if [ ! -d "$(PROTOC_DIR)" ] || [ ! -d "$(PROTOC_GEN_GO_DIR)" ] || [ ! -d "$(PROTOGEN_FROSTFS_DIR)" ]; then \
|
||||||
# Install specific version for protobuf lib
|
make protoc-install; \
|
||||||
@go list -f '{{.Path}}/...@{{.Version}}' -m github.com/golang/protobuf | xargs go install -v
|
fi
|
||||||
@GOBIN=$(abspath $(BIN)) go install -mod=mod -v github.com/nspcc-dev/neofs-api-go/v2/util/protogen
|
@for f in `find . -type f -name '*.proto' -not -path './bin/*'`; do \
|
||||||
# Protoc generate
|
|
||||||
@for f in `find . -type f -name '*.proto' -not -path './vendor/*'`; do \
|
|
||||||
echo "⇒ Processing $$f "; \
|
echo "⇒ Processing $$f "; \
|
||||||
protoc \
|
$(PROTOC_DIR)/bin/protoc \
|
||||||
--proto_path=.:./vendor:/usr/local/include \
|
--proto_path=.:$(PROTOC_DIR)/include:/usr/local/include \
|
||||||
--plugin=protoc-gen-go-neofs=$(BIN)/protogen \
|
--plugin=protoc-gen-go=$(PROTOC_GEN_GO_DIR)/protoc-gen-go \
|
||||||
--go-neofs_out=. --go-neofs_opt=paths=source_relative \
|
--plugin=protoc-gen-go-frostfs=$(PROTOGEN_FROSTFS_DIR)/protogen \
|
||||||
|
--go-frostfs_out=. --go-frostfs_opt=paths=source_relative \
|
||||||
--go_out=. --go_opt=paths=source_relative \
|
--go_out=. --go_opt=paths=source_relative \
|
||||||
--go-grpc_opt=require_unimplemented_servers=false \
|
--go-grpc_opt=require_unimplemented_servers=false \
|
||||||
--go-grpc_out=. --go-grpc_opt=paths=source_relative $$f; \
|
--go-grpc_out=. --go-grpc_opt=paths=source_relative $$f; \
|
||||||
done
|
done
|
||||||
rm -rf vendor
|
|
||||||
|
|
||||||
# Build NeoFS component's docker image
|
protoc-install:
|
||||||
|
@rm -rf $(PROTOBUF_DIR)
|
||||||
|
@mkdir $(PROTOBUF_DIR)
|
||||||
|
@echo "⇒ Installing protoc... "
|
||||||
|
@wget -q -O $(PROTOBUF_DIR)/protoc-$(PROTOC_VERSION).zip 'https://github.com/protocolbuffers/protobuf/releases/download/v$(PROTOC_VERSION)/protoc-$(PROTOC_VERSION)-$(PROTOC_OS_VERSION).zip'
|
||||||
|
@unzip -q -o $(PROTOBUF_DIR)/protoc-$(PROTOC_VERSION).zip -d $(PROTOC_DIR)
|
||||||
|
@rm $(PROTOBUF_DIR)/protoc-$(PROTOC_VERSION).zip
|
||||||
|
@echo "⇒ Installing protoc-gen-go..."
|
||||||
|
@GOBIN=$(PROTOC_GEN_GO_DIR) go install -v google.golang.org/protobuf/...@$(PROTOC_GEN_GO_VERSION)
|
||||||
|
@echo "⇒ Instaling protogen FrostFS plugin..."
|
||||||
|
@GOBIN=$(PROTOGEN_FROSTFS_DIR) go install -mod=mod -v git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/util/protogen@$(PROTOGEN_FROSTFS_VERSION)
|
||||||
|
|
||||||
|
# Build FrostFS component's docker image
|
||||||
image-%:
|
image-%:
|
||||||
@echo "⇒ Build NeoFS $* docker image "
|
@echo "⇒ Build FrostFS $* docker image "
|
||||||
@docker build \
|
@docker build \
|
||||||
--build-arg REPO=$(REPO) \
|
--build-arg REPO=$(REPO) \
|
||||||
--build-arg VERSION=$(VERSION) \
|
--build-arg VERSION=$(VERSION) \
|
||||||
|
@ -95,7 +130,7 @@ image-%:
|
||||||
-t $(HUB_IMAGE)-$*:$(HUB_TAG) .
|
-t $(HUB_IMAGE)-$*:$(HUB_TAG) .
|
||||||
|
|
||||||
# Build all Docker images
|
# Build all Docker images
|
||||||
images: image-storage image-ir image-cli image-adm image-storage-testnet
|
images: image-storage image-ir image-cli image-adm
|
||||||
|
|
||||||
# Build dirty local Docker images
|
# Build dirty local Docker images
|
||||||
dirty-images: image-dirty-storage image-dirty-ir image-dirty-cli image-dirty-adm
|
dirty-images: image-dirty-storage image-dirty-ir image-dirty-cli image-dirty-adm
|
||||||
|
@ -111,26 +146,56 @@ docker/%:
|
||||||
|
|
||||||
|
|
||||||
# Run all code formatters
|
# Run all code formatters
|
||||||
fmts: fmt imports
|
fmts: fumpt imports
|
||||||
|
|
||||||
# Reformat code
|
|
||||||
fmt:
|
|
||||||
@echo "⇒ Processing gofmt check"
|
|
||||||
@gofmt -s -w cmd/ pkg/ misc/
|
|
||||||
|
|
||||||
# Reformat imports
|
# Reformat imports
|
||||||
imports:
|
imports:
|
||||||
@echo "⇒ Processing goimports check"
|
@echo "⇒ Processing goimports check"
|
||||||
@goimports -w cmd/ pkg/ misc/
|
@goimports -w cmd/ pkg/ misc/
|
||||||
|
|
||||||
|
fumpt:
|
||||||
|
@echo "⇒ Processing gofumpt check"
|
||||||
|
@gofumpt -l -w cmd/ pkg/ misc/
|
||||||
|
|
||||||
# Run Unit Test with go test
|
# Run Unit Test with go test
|
||||||
test:
|
test:
|
||||||
@echo "⇒ Running go test"
|
@echo "⇒ Running go test"
|
||||||
@go test ./...
|
@go test ./... -count=1
|
||||||
|
|
||||||
|
pre-commit-run:
|
||||||
|
@pre-commit run -a --hook-stage manual
|
||||||
|
|
||||||
|
# Install linters
|
||||||
|
lint-install:
|
||||||
|
@rm -rf $(OUTPUT_LINT_DIR)
|
||||||
|
@mkdir $(OUTPUT_LINT_DIR)
|
||||||
|
@mkdir -p $(TMP_DIR)
|
||||||
|
@rm -rf $(TMP_DIR)/linters
|
||||||
|
@git -c advice.detachedHead=false clone --branch v$(TRUECLOUDLAB_LINT_VERSION) https://git.frostfs.info/TrueCloudLab/linters.git $(TMP_DIR)/linters
|
||||||
|
@@make -C $(TMP_DIR)/linters lib CGO_ENABLED=1 OUT_DIR=$(OUTPUT_LINT_DIR)
|
||||||
|
@rm -rf $(TMP_DIR)/linters
|
||||||
|
@rmdir $(TMP_DIR) 2>/dev/null || true
|
||||||
|
@CGO_ENABLED=1 GOBIN=$(LINT_DIR) go install github.com/golangci/golangci-lint/cmd/golangci-lint@v$(LINT_VERSION)
|
||||||
|
|
||||||
# Run linters
|
# Run linters
|
||||||
lint:
|
lint:
|
||||||
@golangci-lint --timeout=5m run
|
@if [ ! -d "$(LINT_DIR)" ]; then \
|
||||||
|
make lint-install; \
|
||||||
|
fi
|
||||||
|
$(LINT_DIR)/golangci-lint run
|
||||||
|
|
||||||
|
# Install staticcheck
|
||||||
|
staticcheck-install:
|
||||||
|
@rm -rf $(STATICCHECK_DIR)
|
||||||
|
@mkdir $(STATICCHECK_DIR)
|
||||||
|
@GOBIN=$(STATICCHECK_VERSION_DIR) go install honnef.co/go/tools/cmd/staticcheck@$(STATICCHECK_VERSION)
|
||||||
|
|
||||||
|
# Run staticcheck
|
||||||
|
staticcheck-run:
|
||||||
|
@if [ ! -d "$(STATICCHECK_VERSION_DIR)" ]; then \
|
||||||
|
make staticcheck-install; \
|
||||||
|
fi
|
||||||
|
@$(STATICCHECK_VERSION_DIR)/staticcheck ./...
|
||||||
|
|
||||||
# Run linters in Docker
|
# Run linters in Docker
|
||||||
docker/lint:
|
docker/lint:
|
||||||
|
@ -140,19 +205,27 @@ docker/lint:
|
||||||
--env HOME=/src \
|
--env HOME=/src \
|
||||||
golangci/golangci-lint:v$(LINT_VERSION) bash -c 'cd /src/ && make lint'
|
golangci/golangci-lint:v$(LINT_VERSION) bash -c 'cd /src/ && make lint'
|
||||||
|
|
||||||
|
# Activate pre-commit hooks
|
||||||
|
pre-commit:
|
||||||
|
pre-commit install -t pre-commit -t commit-msg
|
||||||
|
|
||||||
|
# Deactivate pre-commit hooks
|
||||||
|
unpre-commit:
|
||||||
|
pre-commit uninstall -t pre-commit -t commit-msg
|
||||||
|
|
||||||
# Print version
|
# Print version
|
||||||
version:
|
version:
|
||||||
@echo $(VERSION)
|
@echo $(VERSION)
|
||||||
|
|
||||||
|
# Delete built artifacts
|
||||||
clean:
|
clean:
|
||||||
rm -rf vendor
|
|
||||||
rm -rf .cache
|
rm -rf .cache
|
||||||
rm -rf $(BIN)
|
rm -rf $(BIN)
|
||||||
rm -rf $(RELEASE)
|
rm -rf $(RELEASE)
|
||||||
|
|
||||||
# Package for Debian
|
# Package for Debian
|
||||||
debpackage:
|
debpackage:
|
||||||
dch --package neofs-node \
|
dch -b --package frostfs-node \
|
||||||
--controlmaint \
|
--controlmaint \
|
||||||
--newversion $(PKG_VERSION) \
|
--newversion $(PKG_VERSION) \
|
||||||
--distribution $(OS_RELEASE) \
|
--distribution $(OS_RELEASE) \
|
||||||
|
|
41
README.md
41
README.md
|
@ -1,39 +1,40 @@
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="./.github/logo.svg" width="500px" alt="NeoFS">
|
<img src="./.github/logo.svg" width="500px" alt="FrostFS">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://fs.neo.org">NeoFS</a> is a decentralized distributed object storage integrated with the <a href="https://neo.org">NEO Blockchain</a>.
|
<a href="https://frostfs.info">FrostFS</a> is a decentralized distributed object storage integrated with the <a href="https://neo.org">NEO Blockchain</a>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
---
|
---
|
||||||
[![Report](https://goreportcard.com/badge/github.com/nspcc-dev/neofs-node)](https://goreportcard.com/report/github.com/nspcc-dev/neofs-node)
|
[![Report](https://goreportcard.com/badge/github.com/TrueCloudLab/frostfs-node)](https://goreportcard.com/report/github.com/TrueCloudLab/frostfs-node)
|
||||||
![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/nspcc-dev/neofs-node?sort=semver)
|
![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/TrueCloudLab/frostfs-node?sort=semver)
|
||||||
![License](https://img.shields.io/github/license/nspcc-dev/neofs-node.svg?style=popout)
|
![License](https://img.shields.io/github/license/TrueCloudLab/frostfs-node.svg?style=popout)
|
||||||
|
|
||||||
# Overview
|
# Overview
|
||||||
|
|
||||||
NeoFS Nodes are organized in a peer-to-peer network that takes care of storing
|
FrostFS Nodes are organized in a peer-to-peer network that takes care of storing
|
||||||
and distributing user's data. Any Neo user may participate in the network and
|
and distributing user's data. Any Neo user may participate in the network and
|
||||||
get paid for providing storage resources to other users or store their data in
|
get paid for providing storage resources to other users or store their data in
|
||||||
NeoFS and pay a competitive price for it.
|
FrostFS and pay a competitive price for it.
|
||||||
|
|
||||||
Users can reliably store object data in the NeoFS network and have a transparent
|
Users can reliably store object data in the FrostFS network and have a transparent
|
||||||
data placement process due to a decentralized architecture and flexible storage
|
data placement process due to a decentralized architecture and flexible storage
|
||||||
policies. Each node is responsible for executing the storage policies that the
|
policies. Each node is responsible for executing the storage policies that the
|
||||||
users select for geographical location, reliability level, number of nodes, type
|
users select for geographical location, reliability level, number of nodes, type
|
||||||
of disks, capacity, etc. Thus, NeoFS gives full control over data to users.
|
of disks, capacity, etc. Thus, FrostFS gives full control over data to users.
|
||||||
|
|
||||||
Deep [Neo Blockchain](https://neo.org) integration allows NeoFS to be used by
|
Deep [Neo Blockchain](https://neo.org) integration allows FrostFS to be used by
|
||||||
dApps directly from
|
dApps directly from
|
||||||
[NeoVM](https://docs.neo.org/docs/en-us/basic/technology/neovm.html) on the
|
[NeoVM](https://docs.neo.org/docs/en-us/basic/technology/neovm.html) on the
|
||||||
[Smart Contract](https://docs.neo.org/docs/en-us/intro/glossary.html)
|
[Smart Contract](https://docs.neo.org/docs/en-us/intro/glossary.html)
|
||||||
code level. This way dApps are not limited to on-chain storage and can
|
code level. This way dApps are not limited to on-chain storage and can
|
||||||
manipulate large amounts of data without paying a prohibitive price.
|
manipulate large amounts of data without paying a prohibitive price.
|
||||||
|
|
||||||
NeoFS has a native [gRPC API](https://github.com/nspcc-dev/neofs-api) and has
|
FrostFS has a native [gRPC API](https://git.frostfs.info/TrueCloudLab/frostfs-api) and has
|
||||||
protocol gateways for popular protocols such as [AWS
|
protocol gateways for popular protocols such as [AWS
|
||||||
S3](https://github.com/nspcc-dev/neofs-s3-gw),
|
S3](https://github.com/TrueCloudLab/frostfs-s3-gw),
|
||||||
[HTTP](https://github.com/nspcc-dev/neofs-http-gw),
|
[HTTP](https://github.com/TrueCloudLab/frostfs-http-gw),
|
||||||
[FUSE](https://wikipedia.org/wiki/Filesystem_in_Userspace) and
|
[FUSE](https://wikipedia.org/wiki/Filesystem_in_Userspace) and
|
||||||
[sFTP](https://en.wikipedia.org/wiki/SSH_File_Transfer_Protocol) allowing
|
[sFTP](https://en.wikipedia.org/wiki/SSH_File_Transfer_Protocol) allowing
|
||||||
developers to integrate applications without rewriting their code.
|
developers to integrate applications without rewriting their code.
|
||||||
|
@ -43,12 +44,12 @@ developers to integrate applications without rewriting their code.
|
||||||
Now, we only support GNU/Linux on amd64 CPUs with AVX/AVX2 instructions. More
|
Now, we only support GNU/Linux on amd64 CPUs with AVX/AVX2 instructions. More
|
||||||
platforms will be officially supported after release `1.0`.
|
platforms will be officially supported after release `1.0`.
|
||||||
|
|
||||||
The latest version of neofs-node works with neofs-contract
|
The latest version of frostfs-node works with frostfs-contract
|
||||||
[v0.16.0](https://github.com/nspcc-dev/neofs-contract/releases/tag/v0.16.0).
|
[v0.16.0](https://github.com/TrueCloudLab/frostfs-contract/releases/tag/v0.16.0).
|
||||||
|
|
||||||
# Building
|
# Building
|
||||||
|
|
||||||
To make all binaries you need Go 1.17+ and `make`:
|
To make all binaries you need Go 1.20+ and `make`:
|
||||||
```
|
```
|
||||||
make all
|
make all
|
||||||
```
|
```
|
||||||
|
@ -56,7 +57,7 @@ The resulting binaries will appear in `bin/` folder.
|
||||||
|
|
||||||
To make a specific binary use:
|
To make a specific binary use:
|
||||||
```
|
```
|
||||||
make bin/neofs-<name>
|
make bin/frostfs-<name>
|
||||||
```
|
```
|
||||||
See the list of all available commands in the `cmd` folder.
|
See the list of all available commands in the `cmd` folder.
|
||||||
|
|
||||||
|
@ -65,12 +66,12 @@ See the list of all available commands in the `cmd` folder.
|
||||||
Building can also be performed in a container:
|
Building can also be performed in a container:
|
||||||
```
|
```
|
||||||
make docker/all # build all binaries
|
make docker/all # build all binaries
|
||||||
make docker/bin/neofs-<name> # build a specific binary
|
make docker/bin/frostfs-<name> # build a specific binary
|
||||||
```
|
```
|
||||||
|
|
||||||
## Docker images
|
## Docker images
|
||||||
|
|
||||||
To make docker images suitable for use in [neofs-dev-env](https://github.com/nspcc-dev/neofs-dev-env/) use:
|
To make docker images suitable for use in [frostfs-dev-env](https://github.com/TrueCloudLab/frostfs-dev-env/) use:
|
||||||
```
|
```
|
||||||
make images
|
make images
|
||||||
```
|
```
|
||||||
|
@ -85,7 +86,7 @@ the feature/topic you are going to implement.
|
||||||
|
|
||||||
# Credits
|
# Credits
|
||||||
|
|
||||||
NeoFS is maintained by [NeoSPCC](https://nspcc.ru) with the help and
|
FrostFS is maintained by [True Cloud Lab](https://github.com/TrueCloudLab/) with the help and
|
||||||
contributions from community members.
|
contributions from community members.
|
||||||
|
|
||||||
Please see [CREDITS](CREDITS.md) for details.
|
Please see [CREDITS](CREDITS.md) for details.
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
v0.34.0
|
v0.36.0
|
||||||
|
|
|
@ -1,25 +1,24 @@
|
||||||
# NeoFS Admin Tool
|
# FrostFS Admin Tool
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
Admin tool provides an easier way to deploy and maintain private installation
|
Admin tool provides an easier way to deploy and maintain private installation
|
||||||
of NeoFS. Private installation provides a set of N3 consensus nodes, NeoFS
|
of FrostFS. Private installation provides a set of N3 consensus nodes, FrostFS
|
||||||
Alphabet, and Storage nodes. Admin tool generates consensus keys, initializes
|
Alphabet, and Storage nodes. Admin tool generates consensus keys, initializes
|
||||||
the sidechain, and provides functions to update the network and register new
|
the sidechain, and provides functions to update the network and register new
|
||||||
Storage nodes.
|
Storage nodes.
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
To build binary locally, use `make bin/neofs-adm` command.
|
To build binary locally, use `make bin/frostfs-adm` command.
|
||||||
|
|
||||||
For clean build inside a docker container, use `make docker/bin/neofs-adm`.
|
For clean build inside a docker container, use `make docker/bin/frostfs-adm`.
|
||||||
|
|
||||||
Build docker image with `make image-adm`.
|
Build docker image with `make image-adm`.
|
||||||
|
|
||||||
At NeoFS private install deployment, neofs-adm requires compiled NeoFS
|
At FrostFS private install deployment, frostfs-adm requires compiled FrostFS
|
||||||
contracts. Find them in the latest release of
|
contracts. Find them in the latest release of
|
||||||
[neofs-contract repository](https://github.com/nspcc-dev/neofs-contract/releases).
|
[frostfs-contract repository](https://git.frostfs.info/TrueCloudLab/frostfs-contract/releases).
|
||||||
|
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
|
@ -27,7 +26,7 @@ contracts. Find them in the latest release of
|
||||||
|
|
||||||
Config section provides `init` command that creates a configuration file for
|
Config section provides `init` command that creates a configuration file for
|
||||||
private installation deployment and updates. Config file is optional, all
|
private installation deployment and updates. Config file is optional, all
|
||||||
parameters can be passed by arguments or read from standard input (wallet
|
parameters can be passed by arguments or read from standard input (wallet
|
||||||
passwords).
|
passwords).
|
||||||
|
|
||||||
Config example:
|
Config example:
|
||||||
|
@ -35,11 +34,9 @@ Config example:
|
||||||
rpc-endpoint: https://address:port # sidechain RPC node endpoint
|
rpc-endpoint: https://address:port # sidechain RPC node endpoint
|
||||||
alphabet-wallets: /path # path to consensus node / alphabet wallets storage
|
alphabet-wallets: /path # path to consensus node / alphabet wallets storage
|
||||||
network:
|
network:
|
||||||
max_object_size: 67108864 # max size of a single NeoFS object, bytes
|
max_object_size: 67108864 # max size of a single FrostFS object, bytes
|
||||||
epoch_duration: 240 # duration of a NeoFS epoch in blocks, consider block generation frequency in the sidechain
|
epoch_duration: 240 # duration of a FrostFS epoch in blocks, consider block generation frequency in the sidechain
|
||||||
basic_income_rate: 0 # basic income rate, for private consider 0
|
|
||||||
fee:
|
fee:
|
||||||
audit: 0 # network audit fee, for private installation consider 0
|
|
||||||
candidate: 0 # inner ring candidate registration fee, for private installation consider 0
|
candidate: 0 # inner ring candidate registration fee, for private installation consider 0
|
||||||
container: 0 # container creation fee, for private installation consider 0
|
container: 0 # container creation fee, for private installation consider 0
|
||||||
container_alias: 0 # container nice-name registration fee, for private installation consider 0
|
container_alias: 0 # container nice-name registration fee, for private installation consider 0
|
||||||
|
@ -58,24 +55,24 @@ credentials: # passwords for consensus node / alphabet wallets
|
||||||
|
|
||||||
#### Network deployment
|
#### Network deployment
|
||||||
|
|
||||||
- `generate-alphabet` generates a set of wallets for consensus and
|
- `generate-alphabet` generates a set of wallets for consensus and
|
||||||
Alphabet nodes.
|
Alphabet nodes.
|
||||||
|
|
||||||
- `init` initializes the sidechain by deploying smart contracts and
|
- `init` initializes the sidechain by deploying smart contracts and
|
||||||
setting provided NeoFS network configuration.
|
setting provided FrostFS network configuration.
|
||||||
|
|
||||||
- `generate-storage-wallet` generates a wallet for the Storage node that
|
- `generate-storage-wallet` generates a wallet for the Storage node that
|
||||||
is ready for deployment. It also transfers a bit of sidechain GAS, so this
|
is ready for deployment. It also transfers a bit of sidechain GAS, so this
|
||||||
wallet can be used for NeoFS bootstrap.
|
wallet can be used for FrostFS bootstrap.
|
||||||
|
|
||||||
#### Network maintenance
|
#### Network maintenance
|
||||||
|
|
||||||
- `set-config` add/update configuration values in the Netmap contract.
|
- `set-config` add/update configuration values in the Netmap contract.
|
||||||
|
|
||||||
- `force-new-epoch` increments NeoFS epoch number and executes new epoch
|
- `force-new-epoch` increments FrostFS epoch number and executes new epoch
|
||||||
handlers in NeoFS nodes.
|
handlers in FrostFS nodes.
|
||||||
|
|
||||||
- `refill-gas` transfers sidechain GAS to the specified wallet.
|
- `refill-gas` transfers sidechain GAS to the specified wallet.
|
||||||
|
|
||||||
- `update-contracts` updates contracts to a new version.
|
- `update-contracts` updates contracts to a new version.
|
||||||
|
|
||||||
|
@ -87,14 +84,16 @@ info. These commands **do not migrate actual objects**.
|
||||||
- `dump-containers` saves all containers and metadata registered in the container
|
- `dump-containers` saves all containers and metadata registered in the container
|
||||||
contract to a file.
|
contract to a file.
|
||||||
|
|
||||||
- `restore-containers` restores previously saved containers by their repeated registration in
|
- `restore-containers` restores previously saved containers by their repeated registration in
|
||||||
the container contract.
|
the container contract.
|
||||||
|
|
||||||
|
- `list-containers` output all containers ids.
|
||||||
|
|
||||||
#### Network info
|
#### Network info
|
||||||
|
|
||||||
- `dump-config` prints NeoFS network configuration.
|
- `dump-config` prints FrostFS network configuration.
|
||||||
|
|
||||||
- `dump-hashes` prints NeoFS contract addresses stored in NNS.
|
- `dump-hashes` prints FrostFS contract addresses stored in NNS.
|
||||||
|
|
||||||
|
|
||||||
## Private network deployment
|
## Private network deployment
|
|
@ -1,41 +1,40 @@
|
||||||
# Step-by-step private NeoFS deployment
|
# Step-by-step private FrostFS deployment
|
||||||
|
|
||||||
This is a short guide on how to deploy a private NeoFS storage network on bare
|
This is a short guide on how to deploy a private FrostFS storage network on bare
|
||||||
metal without docker images. This guide does not cover details on how to start
|
metal without docker images. This guide does not cover details on how to start
|
||||||
consensus, Alphabet, or Storage nodes. This guide covers only `neofs-adm`
|
consensus, Alphabet, or Storage nodes. This guide covers only `frostfs-adm`
|
||||||
related configuration details.
|
related configuration details.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
To follow this guide you need:
|
To follow this guide you need:
|
||||||
- latest released version of [neo-go](https://github.com/nspcc-dev/neo-go/releases) (v0.97.2 at the moment),
|
- latest released version of [neo-go](https://github.com/nspcc-dev/neo-go/releases) (v0.97.2 at the moment),
|
||||||
- latest released version of [neofs-adm](https://github.com/nspcc-dev/neofs-node/releases) utility (v0.25.1 at the moment),
|
- latest released version of [frostfs-adm](https://github.com/TrueCloudLab/frostfs-node/releases) utility (v0.25.1 at the moment),
|
||||||
- latest released version of compiled [neofs-contract](https://github.com/nspcc-dev/neofs-contract/releases) (v0.11.0 at the moment).
|
- latest released version of compiled [frostfs-contract](https://github.com/TrueCloudLab/frostfs-contract/releases) (v0.11.0 at the moment).
|
||||||
|
|
||||||
## Step 1: Prepare network configuration
|
## Step 1: Prepare network configuration
|
||||||
|
|
||||||
To start a network, you need a set of consensus nodes, the same number of
|
To start a network, you need a set of consensus nodes, the same number of
|
||||||
Alphabet nodes and any number of Storage nodes. While the number of Storage
|
Alphabet nodes and any number of Storage nodes. While the number of Storage
|
||||||
nodes can be scaled almost infinitely, the number of consensus and Alphabet
|
nodes can be scaled almost infinitely, the number of consensus and Alphabet
|
||||||
nodes can't be changed so easily right now. Consider this before going any further.
|
nodes can't be changed so easily right now. Consider this before going any further.
|
||||||
|
Note also that there is an upper limit on the number of alphabet nodes (currently 22).
|
||||||
|
|
||||||
It is easier to use`neofs-adm` with a predefined configuration. First, create
|
It is easier to use`frostfs-adm` with a predefined configuration. First, create
|
||||||
a network configuration file. In this example, there is going to be only one
|
a network configuration file. In this example, there is going to be only one
|
||||||
consensus / Alphabet node in the network.
|
consensus / Alphabet node in the network.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ neofs-adm config init --path foo.network.yml
|
$ frostfs-adm config init --path foo.network.yml
|
||||||
Initial config file saved to foo.network.yml
|
Initial config file saved to foo.network.yml
|
||||||
|
|
||||||
$ cat foo.network.yml
|
$ cat foo.network.yml
|
||||||
rpc-endpoint: https://neo.rpc.node:30333
|
rpc-endpoint: https://neo.rpc.node:30333
|
||||||
alphabet-wallets: /home/user/deploy/alphabet-wallets
|
alphabet-wallets: /home/user/deploy/alphabet-wallets
|
||||||
network:
|
network:
|
||||||
max_object_size: 67108864
|
max_object_size: 67108864
|
||||||
epoch_duration: 240
|
epoch_duration: 240
|
||||||
basic_income_rate: 0
|
|
||||||
fee:
|
fee:
|
||||||
audit: 0
|
|
||||||
candidate: 0
|
candidate: 0
|
||||||
container: 0
|
container: 0
|
||||||
withdraw: 0
|
withdraw: 0
|
||||||
|
@ -43,21 +42,21 @@ credentials:
|
||||||
az: hunter2
|
az: hunter2
|
||||||
```
|
```
|
||||||
|
|
||||||
For private installation, it is recommended to set all **fees** and **basic
|
For private installation, it is recommended to set all **fees** and **basic
|
||||||
income rate** to 0.
|
income rate** to 0.
|
||||||
|
|
||||||
As for **epoch duration**, consider consensus node block generation frequency.
|
As for **epoch duration**, consider consensus node block generation frequency.
|
||||||
With default 15 seconds per block, 240 blocks are going to be a 1-hour epoch.
|
With default 15 seconds per block, 240 blocks are going to be a 1-hour epoch.
|
||||||
|
|
||||||
For **max object size**, 67108864 (64 MiB) or 134217728 (128 MiB) should provide
|
For **max object size**, 67108864 (64 MiB) or 134217728 (128 MiB) should provide
|
||||||
good chunk distribution in most cases.
|
good chunk distribution in most cases.
|
||||||
|
|
||||||
With this config, generate wallets (private keys) of consensus nodes. The same
|
With this config, generate wallets (private keys) of consensus nodes. The same
|
||||||
wallets will be used for Alphabet nodes. Make sure, that dir for alphabet
|
wallets will be used for Alphabet nodes. Make sure, that dir for alphabet
|
||||||
wallets already exists.
|
wallets already exists.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ neofs-adm -c foo.network.yml morph generate-alphabet --size 1
|
$ frostfs-adm -c foo.network.yml morph generate-alphabet --size 1
|
||||||
size: 1
|
size: 1
|
||||||
alphabet-wallets: /home/user/deploy/alphabet-wallets
|
alphabet-wallets: /home/user/deploy/alphabet-wallets
|
||||||
wallet[0]: hunter2
|
wallet[0]: hunter2
|
||||||
|
@ -69,14 +68,14 @@ storage.
|
||||||
## Step 2: Launch consensus nodes
|
## Step 2: Launch consensus nodes
|
||||||
|
|
||||||
Configure blockchain nodes with the generated wallets from the previous step.
|
Configure blockchain nodes with the generated wallets from the previous step.
|
||||||
Config examples can be found in
|
Config examples can be found in
|
||||||
[neo-go repository](https://github.com/nspcc-dev/neo-go/tree/master/config).
|
[neo-go repository](https://github.com/nspcc-dev/neo-go/tree/master/config).
|
||||||
|
|
||||||
Gather public keys from **all** generated wallets. We are interested in the first
|
Gather public keys from **all** generated wallets. We are interested in the first
|
||||||
`simple signature contract` public key.
|
`simple signature contract` public key.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ neo-go wallet dump-keys -w alphabet-wallets/az.json
|
$ neo-go wallet dump-keys -w alphabet-wallets/az.json
|
||||||
NitdS4k4f1Hh5mbLJhAswBK3WC2gQgPN1o (simple signature contract):
|
NitdS4k4f1Hh5mbLJhAswBK3WC2gQgPN1o (simple signature contract):
|
||||||
02c1cc85f9c856dbe2d02017349bcb7b4e5defa78b8056a09b3240ba2a8c078869
|
02c1cc85f9c856dbe2d02017349bcb7b4e5defa78b8056a09b3240ba2a8c078869
|
||||||
|
|
||||||
|
@ -87,10 +86,10 @@ NiMKabp3ddi3xShmLAXhTfbnuWb4cSJT6E (1 out of 1 multisig contract):
|
||||||
02c1cc85f9c856dbe2d02017349bcb7b4e5defa78b8056a09b3240ba2a8c078869
|
02c1cc85f9c856dbe2d02017349bcb7b4e5defa78b8056a09b3240ba2a8c078869
|
||||||
```
|
```
|
||||||
|
|
||||||
Put the list of public keys into `ProtocolConfiguration.StandbyCommittee`
|
Put the list of public keys into `ProtocolConfiguration.StandbyCommittee`
|
||||||
section. Specify the wallet path and the password in `ApplicationConfiguration.P2PNotary`
|
section. Specify the wallet path and the password in `ApplicationConfiguration.P2PNotary`
|
||||||
and `ApplicationConfiguration.UnlockWallet` sections. If config includes
|
and `ApplicationConfiguration.UnlockWallet` sections. If config includes
|
||||||
`ProtocolConfiguration.NativeActivations` section, add notary
|
`ProtocolConfiguration.NativeActivations` section, add notary
|
||||||
contract `Notary: [0]`.
|
contract `Notary: [0]`.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
@ -118,19 +117,19 @@ and possible overload issues.
|
||||||
|
|
||||||
## Step 3: Initialize sidechain
|
## Step 3: Initialize sidechain
|
||||||
|
|
||||||
Use archive with compiled NeoFS contracts to initialize the sidechain.
|
Use archive with compiled FrostFS contracts to initialize the sidechain.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ tar -xzvf neofs-contract-v0.11.0.tar.gz
|
$ tar -xzvf frostfs-contract-v0.11.0.tar.gz
|
||||||
|
|
||||||
$ ./neofs-adm -c foo.network.yml morph init --contracts ./neofs-contract-v0.11.0
|
$ ./frostfs-adm -c foo.network.yml morph init --contracts ./frostfs-contract-v0.11.0
|
||||||
Stage 1: transfer GAS to alphabet nodes.
|
Stage 1: transfer GAS to alphabet nodes.
|
||||||
Waiting for transactions to persist...
|
Waiting for transactions to persist...
|
||||||
Stage 2: set notary and alphabet nodes in designate contract.
|
Stage 2: set notary and alphabet nodes in designate contract.
|
||||||
Waiting for transactions to persist...
|
Waiting for transactions to persist...
|
||||||
Stage 3: deploy NNS contract.
|
Stage 3: deploy NNS contract.
|
||||||
Waiting for transactions to persist...
|
Waiting for transactions to persist...
|
||||||
Stage 4: deploy NeoFS contracts.
|
Stage 4: deploy FrostFS contracts.
|
||||||
Waiting for transactions to persist...
|
Waiting for transactions to persist...
|
||||||
Stage 4.1: Transfer GAS to proxy contract.
|
Stage 4.1: Transfer GAS to proxy contract.
|
||||||
Waiting for transactions to persist...
|
Waiting for transactions to persist...
|
||||||
|
@ -140,21 +139,19 @@ Stage 6: transfer NEO to alphabet contracts.
|
||||||
Waiting for transactions to persist...
|
Waiting for transactions to persist...
|
||||||
Stage 7: set addresses in NNS.
|
Stage 7: set addresses in NNS.
|
||||||
Waiting for transactions to persist...
|
Waiting for transactions to persist...
|
||||||
NNS: Set alphabet0.neofs -> f692dfb4d43a15b464eb51a7041160fb29c44b6a
|
NNS: Set alphabet0.frostfs -> f692dfb4d43a15b464eb51a7041160fb29c44b6a
|
||||||
NNS: Set audit.neofs -> 7df847b993affb3852074345a7c2bd622171ee0d
|
NNS: Set balance.frostfs -> 103519b3067a66307080a66570c0491ee8f68879
|
||||||
NNS: Set balance.neofs -> 103519b3067a66307080a66570c0491ee8f68879
|
NNS: Set container.frostfs -> cae60bdd689d185901e495352d0247752ce50846
|
||||||
NNS: Set container.neofs -> cae60bdd689d185901e495352d0247752ce50846
|
NNS: Set frostfsid.frostfs -> c421fb60a3895865a8f24d197d6a80ef686041d2
|
||||||
NNS: Set neofsid.neofs -> c421fb60a3895865a8f24d197d6a80ef686041d2
|
NNS: Set netmap.frostfs -> 894eb854632f50fb124412ce7951ebc00763525e
|
||||||
NNS: Set netmap.neofs -> 894eb854632f50fb124412ce7951ebc00763525e
|
NNS: Set proxy.frostfs -> ac6e6fe4b373d0ca0ca4969d1e58fa0988724e7d
|
||||||
NNS: Set proxy.neofs -> ac6e6fe4b373d0ca0ca4969d1e58fa0988724e7d
|
|
||||||
NNS: Set reputation.neofs -> 6eda57c9d93d990573646762d1fea327ce41191f
|
|
||||||
Waiting for transactions to persist...
|
Waiting for transactions to persist...
|
||||||
```
|
```
|
||||||
|
|
||||||
## Step 4: Launch Alphabet nodes
|
## Step 4: Launch Alphabet nodes
|
||||||
|
|
||||||
Configure Alphabet nodes with the wallets generated in step 1. For
|
Configure Alphabet nodes with the wallets generated in step 1. For
|
||||||
`morph.validators` use a list of public keys from
|
`morph.validators` use a list of public keys from
|
||||||
`ProtocolConfiguration.StandbyCommittee`.
|
`ProtocolConfiguration.StandbyCommittee`.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
@ -177,11 +174,11 @@ contracts:
|
||||||
Generate a new wallet for a Storage node.
|
Generate a new wallet for a Storage node.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ neofs-adm -c foo.network.yml morph generate-storage-wallet --storage-wallet ./sn01.json --initial-gas 10.0
|
$ frostfs-adm -c foo.network.yml morph generate-storage-wallet --storage-wallet ./sn01.json --initial-gas 10.0
|
||||||
New password >
|
New password >
|
||||||
Waiting for transactions to persist...
|
Waiting for transactions to persist...
|
||||||
|
|
||||||
$ neo-go wallet dump-keys -w sn01.json
|
$ neo-go wallet dump-keys -w sn01.json
|
||||||
Ngr7p8Z9S22XDH6VkUG9oXobv8zZRAWwwv (simple signature contract):
|
Ngr7p8Z9S22XDH6VkUG9oXobv8zZRAWwwv (simple signature contract):
|
||||||
0355eccb72cd46f09a3e5237eaa0f4949cceb5ecfa5a225bd3bb9fd021c4d75b85
|
0355eccb72cd46f09a3e5237eaa0f4949cceb5ecfa5a225bd3bb9fd021c4d75b85
|
||||||
```
|
```
|
||||||
|
@ -196,16 +193,16 @@ node:
|
||||||
password: "foobar"
|
password: "foobar"
|
||||||
```
|
```
|
||||||
|
|
||||||
The storage node will be included in the network map in the next NeoFS epoch. To
|
The storage node will be included in the network map in the next FrostFS epoch. To
|
||||||
speed up this process, you can increment epoch counter immediately.
|
speed up this process, you can increment epoch counter immediately.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ neofs-adm -c foo.network.yml morph force-new-epoch
|
$ frostfs-adm -c foo.network.yml morph force-new-epoch
|
||||||
Current epoch: 8, increase to 9.
|
Current epoch: 8, increase to 9.
|
||||||
Waiting for transactions to persist...
|
Waiting for transactions to persist...
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
After that, NeoFS Storage is ready to work. You can access it directly or
|
After that, FrostFS Storage is ready to work. You can access it directly or
|
||||||
with protocol gates.
|
with protocol gates.
|
14
cmd/frostfs-adm/internal/commonflags/flags.go
Normal file
14
cmd/frostfs-adm/internal/commonflags/flags.go
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package commonflags
|
||||||
|
|
||||||
|
const (
|
||||||
|
ConfigFlag = "config"
|
||||||
|
ConfigFlagShorthand = "c"
|
||||||
|
ConfigFlagUsage = "Config file"
|
||||||
|
|
||||||
|
ConfigDirFlag = "config-dir"
|
||||||
|
ConfigDirFlagUsage = "Config directory"
|
||||||
|
|
||||||
|
Verbose = "verbose"
|
||||||
|
VerboseShorthand = "v"
|
||||||
|
VerboseUsage = "Verbose output"
|
||||||
|
)
|
|
@ -7,8 +7,8 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||||
"github.com/nspcc-dev/neo-go/cli/input"
|
"github.com/nspcc-dev/neo-go/cli/input"
|
||||||
"github.com/nspcc-dev/neofs-node/pkg/innerring"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
@ -18,8 +18,6 @@ type configTemplate struct {
|
||||||
AlphabetDir string
|
AlphabetDir string
|
||||||
MaxObjectSize int
|
MaxObjectSize int
|
||||||
EpochDuration int
|
EpochDuration int
|
||||||
BasicIncomeRate int
|
|
||||||
AuditFee int
|
|
||||||
CandidateFee int
|
CandidateFee int
|
||||||
ContainerFee int
|
ContainerFee int
|
||||||
ContainerAliasFee int
|
ContainerAliasFee int
|
||||||
|
@ -33,33 +31,31 @@ alphabet-wallets: {{ .AlphabetDir}}
|
||||||
network:
|
network:
|
||||||
max_object_size: {{ .MaxObjectSize}}
|
max_object_size: {{ .MaxObjectSize}}
|
||||||
epoch_duration: {{ .EpochDuration}}
|
epoch_duration: {{ .EpochDuration}}
|
||||||
basic_income_rate: {{ .BasicIncomeRate}}
|
|
||||||
homomorphic_hash_disabled: {{ .HomomorphicHashDisabled}}
|
homomorphic_hash_disabled: {{ .HomomorphicHashDisabled}}
|
||||||
fee:
|
fee:
|
||||||
audit: {{ .AuditFee}}
|
|
||||||
candidate: {{ .CandidateFee}}
|
candidate: {{ .CandidateFee}}
|
||||||
container: {{ .ContainerFee}}
|
container: {{ .ContainerFee}}
|
||||||
container_alias: {{ .ContainerAliasFee }}
|
container_alias: {{ .ContainerAliasFee }}
|
||||||
withdraw: {{ .WithdrawFee}}
|
withdraw: {{ .WithdrawFee}}
|
||||||
# if credentials section is omitted, then neofs-adm will require manual password input
|
# if credentials section is omitted, then frostfs-adm will require manual password input
|
||||||
credentials:
|
credentials:
|
||||||
contract: password # wallet for contract group signature{{ range.Glagolitics}}
|
contract: password # wallet for contract group signature{{ range.Glagolitics}}
|
||||||
{{.}}: password{{end}}
|
{{.}}: password{{end}}
|
||||||
`
|
`
|
||||||
|
|
||||||
func initConfig(cmd *cobra.Command, args []string) error {
|
func initConfig(cmd *cobra.Command, _ []string) error {
|
||||||
configPath, err := readConfigPathFromArgs(cmd)
|
configPath, err := readConfigPathFromArgs(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
pathDir := filepath.Dir(configPath)
|
pathDir := filepath.Dir(configPath)
|
||||||
err = os.MkdirAll(pathDir, 0700)
|
err = os.MkdirAll(pathDir, 0o700)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create dir %s: %w", pathDir, err)
|
return fmt.Errorf("create dir %s: %w", pathDir, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.OpenFile(configPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_SYNC, 0600)
|
f, err := os.OpenFile(configPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_SYNC, 0o600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("open %s: %w", configPath, err)
|
return fmt.Errorf("open %s: %w", configPath, err)
|
||||||
}
|
}
|
||||||
|
@ -99,7 +95,7 @@ func defaultConfigPath() (string, error) {
|
||||||
return "", fmt.Errorf("getting home dir path: %w", err)
|
return "", fmt.Errorf("getting home dir path: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return filepath.Join(home, ".neofs", "adm", "config.yml"), nil
|
return filepath.Join(home, ".frostfs", "adm", "config.yml"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateConfigExample builds .yml representation of the config file. It is
|
// generateConfigExample builds .yml representation of the config file. It is
|
||||||
|
@ -111,9 +107,7 @@ func generateConfigExample(appDir string, credSize int) (string, error) {
|
||||||
Endpoint: "https://neo.rpc.node:30333",
|
Endpoint: "https://neo.rpc.node:30333",
|
||||||
MaxObjectSize: 67108864, // 64 MiB
|
MaxObjectSize: 67108864, // 64 MiB
|
||||||
EpochDuration: 240, // 1 hour with 15s per block
|
EpochDuration: 240, // 1 hour with 15s per block
|
||||||
BasicIncomeRate: 1_0000_0000, // 0.0001 GAS per GiB (Fixed12)
|
|
||||||
HomomorphicHashDisabled: false, // object homomorphic hash is enabled
|
HomomorphicHashDisabled: false, // object homomorphic hash is enabled
|
||||||
AuditFee: 1_0000, // 0.00000001 GAS per audit (Fixed12)
|
|
||||||
CandidateFee: 100_0000_0000, // 100.0 GAS (Fixed8)
|
CandidateFee: 100_0000_0000, // 100.0 GAS (Fixed8)
|
||||||
ContainerFee: 1000, // 0.000000001 * 7 GAS per container (Fixed12)
|
ContainerFee: 1000, // 0.000000001 * 7 GAS per container (Fixed12)
|
||||||
ContainerAliasFee: 500, // ContainerFee / 2
|
ContainerAliasFee: 500, // ContainerFee / 2
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-node/pkg/innerring"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -13,7 +13,7 @@ import (
|
||||||
func TestGenerateConfigExample(t *testing.T) {
|
func TestGenerateConfigExample(t *testing.T) {
|
||||||
const (
|
const (
|
||||||
n = 10
|
n = 10
|
||||||
appDir = "/home/example/.neofs"
|
appDir = "/home/example/.frostfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
configText, err := generateConfigExample(appDir, n)
|
configText, err := generateConfigExample(appDir, n)
|
||||||
|
@ -28,8 +28,6 @@ func TestGenerateConfigExample(t *testing.T) {
|
||||||
require.Equal(t, filepath.Join(appDir, "alphabet-wallets"), v.GetString("alphabet-wallets"))
|
require.Equal(t, filepath.Join(appDir, "alphabet-wallets"), v.GetString("alphabet-wallets"))
|
||||||
require.Equal(t, 67108864, v.GetInt("network.max_object_size"))
|
require.Equal(t, 67108864, v.GetInt("network.max_object_size"))
|
||||||
require.Equal(t, 240, v.GetInt("network.epoch_duration"))
|
require.Equal(t, 240, v.GetInt("network.epoch_duration"))
|
||||||
require.Equal(t, 100000000, v.GetInt("network.basic_income_rate"))
|
|
||||||
require.Equal(t, 10000, v.GetInt("network.fee.audit"))
|
|
||||||
require.Equal(t, 10000000000, v.GetInt("network.fee.candidate"))
|
require.Equal(t, 10000000000, v.GetInt("network.fee.candidate"))
|
||||||
require.Equal(t, 1000, v.GetInt("network.fee.container"))
|
require.Equal(t, 1000, v.GetInt("network.fee.container"))
|
||||||
require.Equal(t, 100000000, v.GetInt("network.fee.withdraw"))
|
require.Equal(t, 100000000, v.GetInt("network.fee.withdraw"))
|
|
@ -10,14 +10,14 @@ var (
|
||||||
// RootCmd is a root command of config section.
|
// RootCmd is a root command of config section.
|
||||||
RootCmd = &cobra.Command{
|
RootCmd = &cobra.Command{
|
||||||
Use: "config",
|
Use: "config",
|
||||||
Short: "Section for neofs-adm config related commands",
|
Short: "Section for frostfs-adm config related commands",
|
||||||
}
|
}
|
||||||
|
|
||||||
initCmd = &cobra.Command{
|
initCmd = &cobra.Command{
|
||||||
Use: "init",
|
Use: "init",
|
||||||
Short: "Initialize basic neofs-adm configuration file",
|
Short: "Initialize basic frostfs-adm configuration file",
|
||||||
Example: `neofs-adm config init
|
Example: `frostfs-adm config init
|
||||||
neofs-adm config init --path .config/neofs-adm.yml`,
|
frostfs-adm config init --path .config/frostfs-adm.yml`,
|
||||||
RunE: initConfig,
|
RunE: initConfig,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -25,5 +25,5 @@ neofs-adm config init --path .config/neofs-adm.yml`,
|
||||||
func init() {
|
func init() {
|
||||||
RootCmd.AddCommand(initCmd)
|
RootCmd.AddCommand(initCmd)
|
||||||
|
|
||||||
initCmd.Flags().String(configPathFlag, "", "Path to config (default ~/.neofs/adm/config.yml)")
|
initCmd.Flags().String(configPathFlag, "", "Path to config (default ~/.frostfs/adm/config.yml)")
|
||||||
}
|
}
|
244
cmd/frostfs-adm/internal/modules/morph/balance.go
Normal file
244
cmd/frostfs-adm/internal/modules/morph/balance.go
Normal file
|
@ -0,0 +1,244 @@
|
||||||
|
package morph
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/elliptic"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/rolemgmt"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
type accBalancePair struct {
|
||||||
|
scriptHash util.Uint160
|
||||||
|
balance *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
dumpBalancesStorageFlag = "storage"
|
||||||
|
dumpBalancesAlphabetFlag = "alphabet"
|
||||||
|
dumpBalancesProxyFlag = "proxy"
|
||||||
|
dumpBalancesUseScriptHashFlag = "script-hash"
|
||||||
|
)
|
||||||
|
|
||||||
|
func dumpBalances(cmd *cobra.Command, _ []string) error {
|
||||||
|
var (
|
||||||
|
dumpStorage, _ = cmd.Flags().GetBool(dumpBalancesStorageFlag)
|
||||||
|
dumpAlphabet, _ = cmd.Flags().GetBool(dumpBalancesAlphabetFlag)
|
||||||
|
dumpProxy, _ = cmd.Flags().GetBool(dumpBalancesProxyFlag)
|
||||||
|
nnsCs *state.Contract
|
||||||
|
nmHash util.Uint160
|
||||||
|
)
|
||||||
|
|
||||||
|
c, err := getN3Client(viper.GetViper())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
inv := invoker.New(c, nil)
|
||||||
|
|
||||||
|
if dumpStorage || dumpAlphabet || dumpProxy {
|
||||||
|
r := management.NewReader(inv)
|
||||||
|
nnsCs, err = r.GetContractByID(1)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't get NNS contract info: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nmHash, err = nnsResolveHash(inv, nnsCs.Hash, netmapContract+".frostfs")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
irList, err := fetchIRNodes(c, rolemgmt.Hash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fetchBalances(inv, gas.Hash, irList); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
printBalances(cmd, "Inner ring nodes balances:", irList)
|
||||||
|
|
||||||
|
if dumpStorage {
|
||||||
|
if err := printStorageNodeBalances(cmd, inv, nmHash); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dumpProxy {
|
||||||
|
if err := printProxyContractBalance(cmd, inv, nnsCs.Hash); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dumpAlphabet {
|
||||||
|
if err := printAlphabetContractBalances(cmd, c, inv, len(irList), nnsCs.Hash); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func printStorageNodeBalances(cmd *cobra.Command, inv *invoker.Invoker, nmHash util.Uint160) error {
|
||||||
|
arr, err := unwrap.Array(inv.Call(nmHash, "netmap"))
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("can't fetch the list of storage nodes")
|
||||||
|
}
|
||||||
|
|
||||||
|
snList := make([]accBalancePair, len(arr))
|
||||||
|
for i := range arr {
|
||||||
|
node, ok := arr[i].Value().([]stackitem.Item)
|
||||||
|
if !ok || len(node) == 0 {
|
||||||
|
return errors.New("can't parse the list of storage nodes")
|
||||||
|
}
|
||||||
|
bs, err := node[0].TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("can't parse the list of storage nodes")
|
||||||
|
}
|
||||||
|
var ni netmap.NodeInfo
|
||||||
|
if err := ni.Unmarshal(bs); err != nil {
|
||||||
|
return fmt.Errorf("can't parse the list of storage nodes: %w", err)
|
||||||
|
}
|
||||||
|
pub, err := keys.NewPublicKeyFromBytes(ni.PublicKey(), elliptic.P256())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't parse storage node public key: %w", err)
|
||||||
|
}
|
||||||
|
snList[i].scriptHash = pub.GetScriptHash()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fetchBalances(inv, gas.Hash, snList); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
printBalances(cmd, "\nStorage node balances:", snList)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func printProxyContractBalance(cmd *cobra.Command, inv *invoker.Invoker, nnsHash util.Uint160) error {
|
||||||
|
h, err := nnsResolveHash(inv, nnsHash, proxyContract+".frostfs")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't get hash of the proxy contract: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyList := []accBalancePair{{scriptHash: h}}
|
||||||
|
if err := fetchBalances(inv, gas.Hash, proxyList); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
printBalances(cmd, "\nProxy contract balance:", proxyList)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func printAlphabetContractBalances(cmd *cobra.Command, c Client, inv *invoker.Invoker, count int, nnsHash util.Uint160) error {
|
||||||
|
alphaList := make([]accBalancePair, count)
|
||||||
|
|
||||||
|
w := io.NewBufBinWriter()
|
||||||
|
for i := range alphaList {
|
||||||
|
emit.AppCall(w.BinWriter, nnsHash, "resolve", callflag.ReadOnly,
|
||||||
|
getAlphabetNNSDomain(i),
|
||||||
|
int64(nns.TXT))
|
||||||
|
}
|
||||||
|
if w.Err != nil {
|
||||||
|
panic(w.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
alphaRes, err := c.InvokeScript(w.Bytes(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't fetch info from NNS: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range alphaList {
|
||||||
|
h, err := parseNNSResolveResult(alphaRes.Stack[i])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't fetch the alphabet contract #%d hash: %w", i, err)
|
||||||
|
}
|
||||||
|
alphaList[i].scriptHash = h
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fetchBalances(inv, gas.Hash, alphaList); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
printBalances(cmd, "\nAlphabet contracts balances:", alphaList)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchIRNodes(c Client, desigHash util.Uint160) ([]accBalancePair, error) {
|
||||||
|
inv := invoker.New(c, nil)
|
||||||
|
|
||||||
|
height, err := c.GetBlockCount()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't get block height: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
arr, err := getDesignatedByRole(inv, desigHash, noderoles.NeoFSAlphabet, height)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("can't fetch list of IR nodes from the netmap contract")
|
||||||
|
}
|
||||||
|
|
||||||
|
irList := make([]accBalancePair, len(arr))
|
||||||
|
for i := range arr {
|
||||||
|
irList[i].scriptHash = arr[i].GetScriptHash()
|
||||||
|
}
|
||||||
|
return irList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func printBalances(cmd *cobra.Command, prefix string, accounts []accBalancePair) {
|
||||||
|
useScriptHash, _ := cmd.Flags().GetBool(dumpBalancesUseScriptHashFlag)
|
||||||
|
|
||||||
|
cmd.Println(prefix)
|
||||||
|
for i := range accounts {
|
||||||
|
var addr string
|
||||||
|
if useScriptHash {
|
||||||
|
addr = accounts[i].scriptHash.StringLE()
|
||||||
|
} else {
|
||||||
|
addr = address.Uint160ToString(accounts[i].scriptHash)
|
||||||
|
}
|
||||||
|
cmd.Printf("%s: %s\n", addr, fixedn.ToString(accounts[i].balance, 8))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchBalances(c *invoker.Invoker, gasHash util.Uint160, accounts []accBalancePair) error {
|
||||||
|
w := io.NewBufBinWriter()
|
||||||
|
for i := range accounts {
|
||||||
|
emit.AppCall(w.BinWriter, gasHash, "balanceOf", callflag.ReadStates, accounts[i].scriptHash)
|
||||||
|
}
|
||||||
|
if w.Err != nil {
|
||||||
|
panic(w.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.Run(w.Bytes())
|
||||||
|
if err != nil || res.State != vmstate.Halt.String() || len(res.Stack) != len(accounts) {
|
||||||
|
return errors.New("can't fetch account balances")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range accounts {
|
||||||
|
bal, err := res.Stack[i].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't parse account balance: %w", err)
|
||||||
|
}
|
||||||
|
accounts[i].balance = bal
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -10,12 +10,13 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
@ -29,13 +30,14 @@ func dumpNetworkConfig(cmd *cobra.Command, _ []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
inv := invoker.New(c, nil)
|
inv := invoker.New(c, nil)
|
||||||
|
r := management.NewReader(inv)
|
||||||
|
|
||||||
cs, err := c.GetContractStateByID(1)
|
cs, err := r.GetContractByID(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't get NNS contract info: %w", err)
|
return fmt.Errorf("can't get NNS contract info: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
nmHash, err := nnsResolveHash(inv, cs.Hash, netmapContract+".neofs")
|
nmHash, err := nnsResolveHash(inv, cs.Hash, netmapContract+".frostfs")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -48,41 +50,24 @@ func dumpNetworkConfig(cmd *cobra.Command, _ []string) error {
|
||||||
buf := bytes.NewBuffer(nil)
|
buf := bytes.NewBuffer(nil)
|
||||||
tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
|
tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
|
||||||
|
|
||||||
for _, param := range arr {
|
m, err := parseConfigFromNetmapContract(arr)
|
||||||
tuple, ok := param.Value().([]stackitem.Item)
|
if err != nil {
|
||||||
if !ok || len(tuple) != 2 {
|
return err
|
||||||
return errors.New("invalid ListConfig response from netmap contract")
|
}
|
||||||
}
|
for k, v := range m {
|
||||||
|
switch k {
|
||||||
k, err := tuple[0].TryBytes()
|
case netmap.ContainerFeeConfig, netmap.ContainerAliasFeeConfig,
|
||||||
if err != nil {
|
netmap.EpochDurationConfig, netmap.IrCandidateFeeConfig,
|
||||||
return errors.New("invalid config key from netmap contract")
|
netmap.MaxObjectSizeConfig, netmap.WithdrawFeeConfig:
|
||||||
}
|
|
||||||
|
|
||||||
v, err := tuple[1].TryBytes()
|
|
||||||
if err != nil {
|
|
||||||
return invalidConfigValueErr(k)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch string(k) {
|
|
||||||
case netmapAuditFeeKey, netmapBasicIncomeRateKey,
|
|
||||||
netmapContainerFeeKey, netmapContainerAliasFeeKey,
|
|
||||||
netmapEigenTrustIterationsKey,
|
|
||||||
netmapEpochKey, netmapInnerRingCandidateFeeKey,
|
|
||||||
netmapMaxObjectSizeKey, netmapWithdrawFeeKey:
|
|
||||||
nbuf := make([]byte, 8)
|
nbuf := make([]byte, 8)
|
||||||
copy(nbuf[:], v)
|
copy(nbuf[:], v)
|
||||||
n := binary.LittleEndian.Uint64(nbuf)
|
n := binary.LittleEndian.Uint64(nbuf)
|
||||||
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%d (int)\n", k, n)))
|
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%d (int)\n", k, n)))
|
||||||
case netmapEigenTrustAlphaKey:
|
case netmap.HomomorphicHashingDisabledKey, netmap.MaintenanceModeAllowedConfig:
|
||||||
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%s (str)\n", k, v)))
|
if len(v) == 0 || len(v) > 1 {
|
||||||
case netmapHomomorphicHashDisabledKey, netmapMaintenanceAllowedKey:
|
|
||||||
vBool, err := tuple[1].TryBool()
|
|
||||||
if err != nil {
|
|
||||||
return invalidConfigValueErr(k)
|
return invalidConfigValueErr(k)
|
||||||
}
|
}
|
||||||
|
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%t (bool)\n", k, v[0] == 1)))
|
||||||
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%t (bool)\n", k, vBool)))
|
|
||||||
default:
|
default:
|
||||||
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%s (hex)\n", k, hex.EncodeToString(v))))
|
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%s (hex)\n", k, hex.EncodeToString(v))))
|
||||||
}
|
}
|
||||||
|
@ -104,12 +89,13 @@ func setConfigCmd(cmd *cobra.Command, args []string) error {
|
||||||
return fmt.Errorf("can't initialize context: %w", err)
|
return fmt.Errorf("can't initialize context: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cs, err := wCtx.Client.GetContractStateByID(1)
|
r := management.NewReader(wCtx.ReadOnlyInvoker)
|
||||||
|
cs, err := r.GetContractByID(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't get NNS contract info: %w", err)
|
return fmt.Errorf("can't get NNS contract info: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
nmHash, err := nnsResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, netmapContract+".neofs")
|
nmHash, err := nnsResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, netmapContract+".frostfs")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -140,35 +126,24 @@ func setConfigCmd(cmd *cobra.Command, args []string) error {
|
||||||
return wCtx.awaitTx()
|
return wCtx.awaitTx()
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseConfigPair(kvStr string, force bool) (key string, val interface{}, err error) {
|
func parseConfigPair(kvStr string, force bool) (key string, val any, err error) {
|
||||||
kv := strings.SplitN(kvStr, "=", 2)
|
k, v, found := strings.Cut(kvStr, "=")
|
||||||
if len(kv) != 2 {
|
if !found {
|
||||||
return "", nil, fmt.Errorf("invalid parameter format: must be 'key=val', got: %s", kvStr)
|
return "", nil, fmt.Errorf("invalid parameter format: must be 'key=val', got: %s", kvStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
key = kv[0]
|
key = k
|
||||||
valRaw := kv[1]
|
valRaw := v
|
||||||
|
|
||||||
switch key {
|
switch key {
|
||||||
case netmapAuditFeeKey, netmapBasicIncomeRateKey,
|
case netmap.ContainerFeeConfig, netmap.ContainerAliasFeeConfig,
|
||||||
netmapContainerFeeKey, netmapContainerAliasFeeKey,
|
netmap.EpochDurationConfig, netmap.IrCandidateFeeConfig,
|
||||||
netmapEigenTrustIterationsKey,
|
netmap.MaxObjectSizeConfig, netmap.WithdrawFeeConfig:
|
||||||
netmapEpochKey, netmapInnerRingCandidateFeeKey,
|
|
||||||
netmapMaxObjectSizeKey, netmapWithdrawFeeKey:
|
|
||||||
val, err = strconv.ParseInt(valRaw, 10, 64)
|
val, err = strconv.ParseInt(valRaw, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("could not parse %s's value '%s' as int: %w", key, valRaw, err)
|
err = fmt.Errorf("could not parse %s's value '%s' as int: %w", key, valRaw, err)
|
||||||
}
|
}
|
||||||
case netmapEigenTrustAlphaKey:
|
case netmap.HomomorphicHashingDisabledKey, netmap.MaintenanceModeAllowedConfig:
|
||||||
// just check that it could
|
|
||||||
// be parsed correctly
|
|
||||||
_, err = strconv.ParseFloat(kv[1], 64)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("could not parse %s's value '%s' as float: %w", key, valRaw, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
val = valRaw
|
|
||||||
case netmapHomomorphicHashDisabledKey, netmapMaintenanceAllowedKey:
|
|
||||||
val, err = strconv.ParseBool(valRaw)
|
val, err = strconv.ParseBool(valRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("could not parse %s's value '%s' as bool: %w", key, valRaw, err)
|
err = fmt.Errorf("could not parse %s's value '%s' as bool: %w", key, valRaw, err)
|
||||||
|
@ -187,6 +162,6 @@ func parseConfigPair(kvStr string, force bool) (key string, val interface{}, err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func invalidConfigValueErr(key []byte) error {
|
func invalidConfigValueErr(key string) error {
|
||||||
return fmt.Errorf("invalid %s config value from netmap contract", key)
|
return fmt.Errorf("invalid %s config value from netmap contract", key)
|
||||||
}
|
}
|
|
@ -7,21 +7,66 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errInvalidContainerResponse = errors.New("invalid response from container contract")
|
var errInvalidContainerResponse = errors.New("invalid response from container contract")
|
||||||
|
|
||||||
|
func getContainerContractHash(cmd *cobra.Command, inv *invoker.Invoker) (util.Uint160, error) {
|
||||||
|
s, err := cmd.Flags().GetString(containerContractFlag)
|
||||||
|
var ch util.Uint160
|
||||||
|
if err == nil {
|
||||||
|
ch, err = util.Uint160DecodeStringLE(s)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
r := management.NewReader(inv)
|
||||||
|
nnsCs, err := r.GetContractByID(1)
|
||||||
|
if err != nil {
|
||||||
|
return util.Uint160{}, fmt.Errorf("can't get NNS contract state: %w", err)
|
||||||
|
}
|
||||||
|
ch, err = nnsResolveHash(inv, nnsCs.Hash, containerContract+".frostfs")
|
||||||
|
if err != nil {
|
||||||
|
return util.Uint160{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ch, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func iterateContainerList(inv *invoker.Invoker, ch util.Uint160, f func([]byte) error) error {
|
||||||
|
sid, r, err := unwrap.SessionIterator(inv.Call(ch, "containersOf", ""))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
|
||||||
|
}
|
||||||
|
// Nothing bad, except live session on the server, do not report to the user.
|
||||||
|
defer func() { _ = inv.TerminateSession(sid) }()
|
||||||
|
|
||||||
|
items, err := inv.TraverseIterator(sid, &r, 0)
|
||||||
|
for err == nil && len(items) != 0 {
|
||||||
|
for j := range items {
|
||||||
|
b, err := items[j].TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
|
||||||
|
}
|
||||||
|
if err := f(b); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items, err = inv.TraverseIterator(sid, &r, 0)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func dumpContainers(cmd *cobra.Command, _ []string) error {
|
func dumpContainers(cmd *cobra.Command, _ []string) error {
|
||||||
filename, err := cmd.Flags().GetString(containerDumpFlag)
|
filename, err := cmd.Flags().GetString(containerDumpFlag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -35,26 +80,9 @@ func dumpContainers(cmd *cobra.Command, _ []string) error {
|
||||||
|
|
||||||
inv := invoker.New(c, nil)
|
inv := invoker.New(c, nil)
|
||||||
|
|
||||||
nnsCs, err := c.GetContractStateByID(1)
|
ch, err := getContainerContractHash(cmd, inv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't get NNS contract state: %w", err)
|
return fmt.Errorf("unable to get contaract hash: %w", err)
|
||||||
}
|
|
||||||
|
|
||||||
var ch util.Uint160
|
|
||||||
s, err := cmd.Flags().GetString(containerContractFlag)
|
|
||||||
if err == nil {
|
|
||||||
ch, err = util.Uint160DecodeStringLE(s)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
ch, err = nnsResolveHash(inv, nnsCs.Hash, containerContract+".neofs")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cids, err := unwrap.ArrayOfBytes(inv.Call(ch, "list", ""))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isOK, err := getCIDFilterFunc(cmd)
|
isOK, err := getCIDFilterFunc(cmd)
|
||||||
|
@ -62,46 +90,100 @@ func dumpContainers(cmd *cobra.Command, _ []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var containers []*Container
|
f, err := os.OpenFile(filename, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0o660)
|
||||||
bw := io.NewBufBinWriter()
|
|
||||||
for _, id := range cids {
|
|
||||||
if !isOK(id) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
bw.Reset()
|
|
||||||
emit.AppCall(bw.BinWriter, ch, "get", callflag.All, id)
|
|
||||||
emit.AppCall(bw.BinWriter, ch, "eACL", callflag.All, id)
|
|
||||||
res, err := inv.Run(bw.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't get container info: %w", err)
|
|
||||||
}
|
|
||||||
if len(res.Stack) != 2 {
|
|
||||||
return fmt.Errorf("%w: expected 2 items on stack", errInvalidContainerResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
cnt := new(Container)
|
|
||||||
err = cnt.FromStackItem(res.Stack[0])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ea := new(EACL)
|
|
||||||
err = ea.FromStackItem(res.Stack[1])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
|
|
||||||
}
|
|
||||||
if len(ea.Value) != 0 {
|
|
||||||
cnt.EACL = ea
|
|
||||||
}
|
|
||||||
|
|
||||||
containers = append(containers, cnt)
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := json.Marshal(containers)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return os.WriteFile(filename, out, 0o660)
|
defer f.Close()
|
||||||
|
|
||||||
|
_, err = f.Write([]byte{'['})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
written := 0
|
||||||
|
enc := json.NewEncoder(f)
|
||||||
|
bw := io.NewBufBinWriter()
|
||||||
|
iterErr := iterateContainerList(inv, ch, func(id []byte) error {
|
||||||
|
if !isOK(id) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cnt, err := dumpSingleContainer(bw, ch, inv, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writing directly to the file is ok, because json.Encoder does no internal buffering.
|
||||||
|
if written != 0 {
|
||||||
|
_, err = f.Write([]byte{','})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
written++
|
||||||
|
return enc.Encode(cnt)
|
||||||
|
})
|
||||||
|
if iterErr != nil {
|
||||||
|
return iterErr
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = f.Write([]byte{']'})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func dumpSingleContainer(bw *io.BufBinWriter, ch util.Uint160, inv *invoker.Invoker, id []byte) (*Container, error) {
|
||||||
|
bw.Reset()
|
||||||
|
emit.AppCall(bw.BinWriter, ch, "get", callflag.All, id)
|
||||||
|
emit.AppCall(bw.BinWriter, ch, "eACL", callflag.All, id)
|
||||||
|
res, err := inv.Run(bw.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't get container info: %w", err)
|
||||||
|
}
|
||||||
|
if len(res.Stack) != 2 {
|
||||||
|
return nil, fmt.Errorf("%w: expected 2 items on stack", errInvalidContainerResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
cnt := new(Container)
|
||||||
|
err = cnt.FromStackItem(res.Stack[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ea := new(EACL)
|
||||||
|
err = ea.FromStackItem(res.Stack[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
|
||||||
|
}
|
||||||
|
if len(ea.Value) != 0 {
|
||||||
|
cnt.EACL = ea
|
||||||
|
}
|
||||||
|
return cnt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func listContainers(cmd *cobra.Command, _ []string) error {
|
||||||
|
c, err := getN3Client(viper.GetViper())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't create N3 client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
inv := invoker.New(c, nil)
|
||||||
|
|
||||||
|
ch, err := getContainerContractHash(cmd, inv)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to get contaract hash: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return iterateContainerList(inv, ch, func(id []byte) error {
|
||||||
|
var idCnr cid.ID
|
||||||
|
err = idCnr.Decode(id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to decode container id: %w", err)
|
||||||
|
}
|
||||||
|
cmd.Println(idCnr)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func restoreContainers(cmd *cobra.Command, _ []string) error {
|
func restoreContainers(cmd *cobra.Command, _ []string) error {
|
||||||
|
@ -116,25 +198,14 @@ func restoreContainers(cmd *cobra.Command, _ []string) error {
|
||||||
}
|
}
|
||||||
defer wCtx.close()
|
defer wCtx.close()
|
||||||
|
|
||||||
nnsCs, err := wCtx.Client.GetContractStateByID(1)
|
containers, err := parseContainers(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't get NNS contract state: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ch, err := nnsResolveHash(wCtx.ReadOnlyInvoker, nnsCs.Hash, containerContract+".neofs")
|
ch, err := fetchContainerContractHash(wCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't fetch container contract hash: %w", err)
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
data, err := os.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't read dump file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var containers []Container
|
|
||||||
err = json.Unmarshal(data, &containers)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't parse dump file: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isOK, err := getCIDFilterFunc(cmd)
|
isOK, err := getCIDFilterFunc(cmd)
|
||||||
|
@ -142,6 +213,15 @@ func restoreContainers(cmd *cobra.Command, _ []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = restoreOrPutContainers(containers, isOK, cmd, wCtx, ch)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return wCtx.awaitTx()
|
||||||
|
}
|
||||||
|
|
||||||
|
func restoreOrPutContainers(containers []Container, isOK func([]byte) bool, cmd *cobra.Command, wCtx *initializeContext, ch util.Uint160) error {
|
||||||
bw := io.NewBufBinWriter()
|
bw := io.NewBufBinWriter()
|
||||||
for _, cnt := range containers {
|
for _, cnt := range containers {
|
||||||
hv := hash.Sha256(cnt.Value)
|
hv := hash.Sha256(cnt.Value)
|
||||||
|
@ -149,33 +229,18 @@ func restoreContainers(cmd *cobra.Command, _ []string) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
bw.Reset()
|
bw.Reset()
|
||||||
emit.AppCall(bw.BinWriter, ch, "get", callflag.All, hv.BytesBE())
|
restored, err := isContainerRestored(cmd, wCtx, ch, bw, hv)
|
||||||
res, err := wCtx.Client.InvokeScript(bw.Bytes(), nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't check if container is already restored: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
if len(res.Stack) == 0 {
|
if restored {
|
||||||
return errors.New("empty stack")
|
|
||||||
}
|
|
||||||
|
|
||||||
old := new(Container)
|
|
||||||
if err := old.FromStackItem(res.Stack[0]); err != nil {
|
|
||||||
return fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
|
|
||||||
}
|
|
||||||
if len(old.Value) != 0 {
|
|
||||||
var id cid.ID
|
|
||||||
id.SetSHA256(hv)
|
|
||||||
cmd.Printf("Container %s is already deployed.\n", id)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
bw.Reset()
|
bw.Reset()
|
||||||
emit.AppCall(bw.BinWriter, ch, "put", callflag.All,
|
|
||||||
cnt.Value, cnt.Signature, cnt.PublicKey, cnt.Token)
|
putContainer(bw, ch, cnt)
|
||||||
if ea := cnt.EACL; ea != nil {
|
|
||||||
emit.AppCall(bw.BinWriter, ch, "setEACL", callflag.All,
|
|
||||||
ea.Value, ea.Signature, ea.PublicKey, ea.Token)
|
|
||||||
}
|
|
||||||
if bw.Err != nil {
|
if bw.Err != nil {
|
||||||
panic(bw.Err)
|
panic(bw.Err)
|
||||||
}
|
}
|
||||||
|
@ -184,8 +249,68 @@ func restoreContainers(cmd *cobra.Command, _ []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return wCtx.awaitTx()
|
func putContainer(bw *io.BufBinWriter, ch util.Uint160, cnt Container) {
|
||||||
|
emit.AppCall(bw.BinWriter, ch, "put", callflag.All,
|
||||||
|
cnt.Value, cnt.Signature, cnt.PublicKey, cnt.Token)
|
||||||
|
if ea := cnt.EACL; ea != nil {
|
||||||
|
emit.AppCall(bw.BinWriter, ch, "setEACL", callflag.All,
|
||||||
|
ea.Value, ea.Signature, ea.PublicKey, ea.Token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isContainerRestored(cmd *cobra.Command, wCtx *initializeContext, containerHash util.Uint160, bw *io.BufBinWriter, hashValue util.Uint256) (bool, error) {
|
||||||
|
emit.AppCall(bw.BinWriter, containerHash, "get", callflag.All, hashValue.BytesBE())
|
||||||
|
res, err := wCtx.Client.InvokeScript(bw.Bytes(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("can't check if container is already restored: %w", err)
|
||||||
|
}
|
||||||
|
if len(res.Stack) == 0 {
|
||||||
|
return false, errors.New("empty stack")
|
||||||
|
}
|
||||||
|
|
||||||
|
old := new(Container)
|
||||||
|
if err := old.FromStackItem(res.Stack[0]); err != nil {
|
||||||
|
return false, fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
|
||||||
|
}
|
||||||
|
if len(old.Value) != 0 {
|
||||||
|
var id cid.ID
|
||||||
|
id.SetSHA256(hashValue)
|
||||||
|
cmd.Printf("Container %s is already deployed.\n", id)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseContainers(filename string) ([]Container, error) {
|
||||||
|
data, err := os.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't read dump file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var containers []Container
|
||||||
|
err = json.Unmarshal(data, &containers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't parse dump file: %w", err)
|
||||||
|
}
|
||||||
|
return containers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchContainerContractHash(wCtx *initializeContext) (util.Uint160, error) {
|
||||||
|
r := management.NewReader(wCtx.ReadOnlyInvoker)
|
||||||
|
nnsCs, err := r.GetContractByID(1)
|
||||||
|
if err != nil {
|
||||||
|
return util.Uint160{}, fmt.Errorf("can't get NNS contract state: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ch, err := nnsResolveHash(wCtx.ReadOnlyInvoker, nnsCs.Hash, containerContract+".frostfs")
|
||||||
|
if err != nil {
|
||||||
|
return util.Uint160{}, fmt.Errorf("can't fetch container contract hash: %w", err)
|
||||||
|
}
|
||||||
|
return ch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Container represents container struct in contract storage.
|
// Container represents container struct in contract storage.
|
|
@ -6,16 +6,16 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||||
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/services/rpcsrv/params"
|
"github.com/nspcc-dev/neo-go/pkg/services/rpcsrv/params"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
"github.com/nspcc-dev/neofs-contract/nns"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
@ -35,7 +35,7 @@ Optionally, arguments can be provided to be passed to a contract's _deploy funct
|
||||||
The syntax is the same as for 'neo-go contract testinvokefunction' command.
|
The syntax is the same as for 'neo-go contract testinvokefunction' command.
|
||||||
Compiled contract file name must contain '_contract.nef' suffix.
|
Compiled contract file name must contain '_contract.nef' suffix.
|
||||||
Contract's manifest file name must be 'config.json'.
|
Contract's manifest file name must be 'config.json'.
|
||||||
NNS name is taken by stripping '_contract.nef' from the NEF file (similar to neofs contracts).`,
|
NNS name is taken by stripping '_contract.nef' from the NEF file (similar to frostfs contracts).`,
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||||
|
@ -54,7 +54,7 @@ func init() {
|
||||||
_ = deployCmd.MarkFlagFilename(contractPathFlag)
|
_ = deployCmd.MarkFlagFilename(contractPathFlag)
|
||||||
|
|
||||||
ff.Bool(updateFlag, false, "Update an existing contract")
|
ff.Bool(updateFlag, false, "Update an existing contract")
|
||||||
ff.String(customZoneFlag, "neofs", "Custom zone for NNS")
|
ff.String(customZoneFlag, "frostfs", "Custom zone for NNS")
|
||||||
}
|
}
|
||||||
|
|
||||||
func deployContractCmd(cmd *cobra.Command, args []string) error {
|
func deployContractCmd(cmd *cobra.Command, args []string) error {
|
||||||
|
@ -76,12 +76,13 @@ func deployContractCmd(cmd *cobra.Command, args []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
nnsCs, err := c.Client.GetContractStateByID(1)
|
r := management.NewReader(c.ReadOnlyInvoker)
|
||||||
|
nnsCs, err := r.GetContractByID(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't fetch NNS contract state: %w", err)
|
return fmt.Errorf("can't fetch NNS contract state: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
callHash := c.nativeHash(nativenames.Management)
|
callHash := management.Hash
|
||||||
method := deployMethodName
|
method := deployMethodName
|
||||||
zone, _ := cmd.Flags().GetString(customZoneFlag)
|
zone, _ := cmd.Flags().GetString(customZoneFlag)
|
||||||
domain := ctrName + "." + zone
|
domain := ctrName + "." + zone
|
||||||
|
@ -100,80 +101,88 @@ func deployContractCmd(cmd *cobra.Command, args []string) error {
|
||||||
cs.Manifest.Name)
|
cs.Manifest.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
w := io.NewBufBinWriter()
|
writer := io.NewBufBinWriter()
|
||||||
if err := emitDeploymentArguments(w.BinWriter, args); err != nil {
|
if err := emitDeploymentArguments(writer.BinWriter, args); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
emit.Bytes(w.BinWriter, cs.RawManifest)
|
emit.Bytes(writer.BinWriter, cs.RawManifest)
|
||||||
emit.Bytes(w.BinWriter, cs.RawNEF)
|
emit.Bytes(writer.BinWriter, cs.RawNEF)
|
||||||
emit.Int(w.BinWriter, 3)
|
emit.Int(writer.BinWriter, 3)
|
||||||
emit.Opcodes(w.BinWriter, opcode.PACK)
|
emit.Opcodes(writer.BinWriter, opcode.PACK)
|
||||||
emit.AppCallNoArgs(w.BinWriter, callHash, method, callflag.All)
|
emit.AppCallNoArgs(writer.BinWriter, callHash, method, callflag.All)
|
||||||
emit.Opcodes(w.BinWriter, opcode.DROP) // contract state on stack
|
emit.Opcodes(writer.BinWriter, opcode.DROP) // contract state on stack
|
||||||
if !isUpdate {
|
if !isUpdate {
|
||||||
bw := io.NewBufBinWriter()
|
err := registerNNS(nnsCs, c, zone, domain, cs, writer)
|
||||||
emit.Instruction(bw.BinWriter, opcode.INITSSLOT, []byte{1})
|
|
||||||
emit.AppCall(bw.BinWriter, nnsCs.Hash, "getPrice", callflag.All)
|
|
||||||
emit.Opcodes(bw.BinWriter, opcode.STSFLD0)
|
|
||||||
emit.AppCall(bw.BinWriter, nnsCs.Hash, "setPrice", callflag.All, 1)
|
|
||||||
|
|
||||||
start := bw.Len()
|
|
||||||
needRecord := false
|
|
||||||
|
|
||||||
ok, err := c.nnsRootRegistered(nnsCs.Hash, zone)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if !ok {
|
|
||||||
needRecord = true
|
|
||||||
|
|
||||||
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
|
|
||||||
zone, c.CommitteeAcc.Contract.ScriptHash(),
|
|
||||||
"ops@nspcc.ru", int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
|
|
||||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
|
||||||
|
|
||||||
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
|
|
||||||
domain, c.CommitteeAcc.Contract.ScriptHash(),
|
|
||||||
"ops@nspcc.ru", int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
|
|
||||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
|
||||||
} else {
|
|
||||||
s, ok, err := c.nnsRegisterDomainScript(nnsCs.Hash, cs.Hash, domain)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
needRecord = !ok
|
|
||||||
if len(s) != 0 {
|
|
||||||
bw.WriteBytes(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if needRecord {
|
|
||||||
emit.AppCall(bw.BinWriter, nnsCs.Hash, "deleteRecords", callflag.All, domain, int64(nns.TXT))
|
|
||||||
emit.AppCall(bw.BinWriter, nnsCs.Hash, "addRecord", callflag.All,
|
|
||||||
domain, int64(nns.TXT), address.Uint160ToString(cs.Hash))
|
|
||||||
}
|
|
||||||
|
|
||||||
if bw.Err != nil {
|
|
||||||
panic(fmt.Errorf("BUG: can't create deployment script: %w", w.Err))
|
|
||||||
} else if bw.Len() != start {
|
|
||||||
w.WriteBytes(bw.Bytes())
|
|
||||||
emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.PUSH1, opcode.PACK)
|
|
||||||
emit.AppCallNoArgs(w.BinWriter, nnsCs.Hash, "setPrice", callflag.All)
|
|
||||||
|
|
||||||
if needRecord {
|
|
||||||
c.Command.Printf("NNS: Set %s -> %s\n", domain, cs.Hash.StringLE())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if w.Err != nil {
|
if writer.Err != nil {
|
||||||
panic(fmt.Errorf("BUG: can't create deployment script: %w", w.Err))
|
panic(fmt.Errorf("BUG: can't create deployment script: %w", writer.Err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.sendCommitteeTx(w.Bytes(), false); err != nil {
|
if err := c.sendCommitteeTx(writer.Bytes(), false); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return c.awaitTx()
|
return c.awaitTx()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func registerNNS(nnsCs *state.Contract, c *initializeContext, zone string, domain string, cs *contractState, writer *io.BufBinWriter) error {
|
||||||
|
bw := io.NewBufBinWriter()
|
||||||
|
emit.Instruction(bw.BinWriter, opcode.INITSSLOT, []byte{1})
|
||||||
|
emit.AppCall(bw.BinWriter, nnsCs.Hash, "getPrice", callflag.All)
|
||||||
|
emit.Opcodes(bw.BinWriter, opcode.STSFLD0)
|
||||||
|
emit.AppCall(bw.BinWriter, nnsCs.Hash, "setPrice", callflag.All, 1)
|
||||||
|
|
||||||
|
start := bw.Len()
|
||||||
|
needRecord := false
|
||||||
|
|
||||||
|
ok, err := c.nnsRootRegistered(nnsCs.Hash, zone)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if !ok {
|
||||||
|
needRecord = true
|
||||||
|
|
||||||
|
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
|
||||||
|
zone, c.CommitteeAcc.Contract.ScriptHash(),
|
||||||
|
frostfsOpsEmail, int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
|
||||||
|
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||||
|
|
||||||
|
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
|
||||||
|
domain, c.CommitteeAcc.Contract.ScriptHash(),
|
||||||
|
frostfsOpsEmail, int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
|
||||||
|
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||||
|
} else {
|
||||||
|
s, ok, err := c.nnsRegisterDomainScript(nnsCs.Hash, cs.Hash, domain)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
needRecord = !ok
|
||||||
|
if len(s) != 0 {
|
||||||
|
bw.WriteBytes(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if needRecord {
|
||||||
|
emit.AppCall(bw.BinWriter, nnsCs.Hash, "deleteRecords", callflag.All, domain, int64(nns.TXT))
|
||||||
|
emit.AppCall(bw.BinWriter, nnsCs.Hash, "addRecord", callflag.All,
|
||||||
|
domain, int64(nns.TXT), address.Uint160ToString(cs.Hash))
|
||||||
|
}
|
||||||
|
|
||||||
|
if bw.Err != nil {
|
||||||
|
panic(fmt.Errorf("BUG: can't create deployment script: %w", writer.Err))
|
||||||
|
} else if bw.Len() != start {
|
||||||
|
writer.WriteBytes(bw.Bytes())
|
||||||
|
emit.Opcodes(writer.BinWriter, opcode.LDSFLD0, opcode.PUSH1, opcode.PACK)
|
||||||
|
emit.AppCallNoArgs(writer.BinWriter, nnsCs.Hash, "setPrice", callflag.All)
|
||||||
|
|
||||||
|
if needRecord {
|
||||||
|
c.Command.Printf("NNS: Set %s -> %s\n", domain, cs.Hash.StringLE())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func emitDeploymentArguments(w *io.BinWriter, args []string) error {
|
func emitDeploymentArguments(w *io.BinWriter, args []string) error {
|
||||||
_, ps, err := cmdargs.ParseParams(args, true)
|
_, ps, err := cmdargs.ParseParams(args, true)
|
||||||
if err != nil {
|
if err != nil {
|
|
@ -2,12 +2,16 @@ package morph
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||||
|
morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
@ -15,7 +19,6 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||||
"github.com/nspcc-dev/neofs-contract/nns"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
@ -34,7 +37,8 @@ func dumpContractHashes(cmd *cobra.Command, _ []string) error {
|
||||||
return fmt.Errorf("can't create N3 client: %w", err)
|
return fmt.Errorf("can't create N3 client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cs, err := c.GetContractStateByID(1)
|
r := management.NewReader(invoker.New(c, nil))
|
||||||
|
cs, err := r.GetContractByID(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -83,7 +87,7 @@ func dumpContractHashes(cmd *cobra.Command, _ []string) error {
|
||||||
for _, ctrName := range contractList {
|
for _, ctrName := range contractList {
|
||||||
bw.Reset()
|
bw.Reset()
|
||||||
emit.AppCall(bw.BinWriter, cs.Hash, "resolve", callflag.ReadOnly,
|
emit.AppCall(bw.BinWriter, cs.Hash, "resolve", callflag.ReadOnly,
|
||||||
ctrName+".neofs", int64(nns.TXT))
|
ctrName+".frostfs", int64(nns.TXT))
|
||||||
|
|
||||||
res, err := c.InvokeScript(bw.Bytes(), nil)
|
res, err := c.InvokeScript(bw.Bytes(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -107,31 +111,30 @@ func dumpContractHashes(cmd *cobra.Command, _ []string) error {
|
||||||
|
|
||||||
func dumpCustomZoneHashes(cmd *cobra.Command, nnsHash util.Uint160, zone string, c Client) error {
|
func dumpCustomZoneHashes(cmd *cobra.Command, nnsHash util.Uint160, zone string, c Client) error {
|
||||||
const nnsMaxTokens = 100
|
const nnsMaxTokens = 100
|
||||||
inv := invoker.New(c, nil)
|
|
||||||
|
|
||||||
arr, err := unwrap.Array(inv.CallAndExpandIterator(nnsHash, "tokens", nnsMaxTokens))
|
inv := invoker.New(c, nil)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't get a list of NNS domains: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.HasPrefix(zone, ".") {
|
if !strings.HasPrefix(zone, ".") {
|
||||||
zone = "." + zone
|
zone = "." + zone
|
||||||
}
|
}
|
||||||
|
|
||||||
var infos []contractDumpInfo
|
var infos []contractDumpInfo
|
||||||
for i := range arr {
|
processItem := func(item stackitem.Item) {
|
||||||
bs, err := arr[i].TryBytes()
|
bs, err := item.TryBytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
cmd.PrintErrf("Invalid NNS record: %v\n", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.HasSuffix(bs, []byte(zone)) {
|
if !bytes.HasSuffix(bs, []byte(zone)) || bytes.HasPrefix(bs, []byte(morphClient.NNSGroupKeyName)) {
|
||||||
continue
|
// Related https://github.com/nspcc-dev/neofs-contract/issues/316.
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h, err := nnsResolveHash(inv, nnsHash, string(bs))
|
h, err := nnsResolveHash(inv, nnsHash, string(bs))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
cmd.PrintErrf("Could not resolve name %s: %v\n", string(bs), err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
infos = append(infos, contractDumpInfo{
|
infos = append(infos, contractDumpInfo{
|
||||||
|
@ -140,6 +143,39 @@ func dumpCustomZoneHashes(cmd *cobra.Command, nnsHash util.Uint160, zone string,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sessionID, iter, err := unwrap.SessionIterator(inv.Call(nnsHash, "tokens"))
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, unwrap.ErrNoSessionID) {
|
||||||
|
items, err := unwrap.Array(inv.CallAndExpandIterator(nnsHash, "tokens", nnsMaxTokens))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't get a list of NNS domains: %w", err)
|
||||||
|
}
|
||||||
|
if len(items) == nnsMaxTokens {
|
||||||
|
cmd.PrintErrln("Provided RPC endpoint doesn't support sessions, some hashes might be lost.")
|
||||||
|
}
|
||||||
|
for i := range items {
|
||||||
|
processItem(items[i])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
defer func() {
|
||||||
|
_ = inv.TerminateSession(sessionID)
|
||||||
|
}()
|
||||||
|
|
||||||
|
items, err := inv.TraverseIterator(sessionID, &iter, nnsMaxTokens)
|
||||||
|
for err == nil && len(items) != 0 {
|
||||||
|
for i := range items {
|
||||||
|
processItem(items[i])
|
||||||
|
}
|
||||||
|
items, err = inv.TraverseIterator(sessionID, &iter, nnsMaxTokens)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error during NNS domains iteration: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fillContractVersion(cmd, c, infos)
|
fillContractVersion(cmd, c, infos)
|
||||||
printContractInfo(cmd, infos)
|
printContractInfo(cmd, infos)
|
||||||
|
|
|
@ -3,8 +3,10 @@ package morph
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
@ -13,18 +15,19 @@ import (
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
func forceNewEpochCmd(cmd *cobra.Command, args []string) error {
|
func forceNewEpochCmd(cmd *cobra.Command, _ []string) error {
|
||||||
wCtx, err := newInitializeContext(cmd, viper.GetViper())
|
wCtx, err := newInitializeContext(cmd, viper.GetViper())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't to initialize context: %w", err)
|
return fmt.Errorf("can't to initialize context: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cs, err := wCtx.Client.GetContractStateByID(1)
|
r := management.NewReader(wCtx.ReadOnlyInvoker)
|
||||||
|
cs, err := r.GetContractByID(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't get NNS contract info: %w", err)
|
return fmt.Errorf("can't get NNS contract info: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
nmHash, err := nnsResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, netmapContract+".neofs")
|
nmHash, err := nnsResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, netmapContract+".frostfs")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -38,7 +41,14 @@ func forceNewEpochCmd(cmd *cobra.Command, args []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return wCtx.awaitTx()
|
if err := wCtx.awaitTx(); err != nil {
|
||||||
|
if strings.Contains(err.Error(), "invalid epoch") {
|
||||||
|
cmd.Println("Epoch has already ticked.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func emitNewEpochCall(bw *io.BufBinWriter, wCtx *initializeContext, nmHash util.Uint160) error {
|
func emitNewEpochCall(bw *io.BufBinWriter, wCtx *initializeContext, nmHash util.Uint160) error {
|
42
cmd/frostfs-adm/internal/modules/morph/frostfsid_util.go
Normal file
42
cmd/frostfs-adm/internal/modules/morph/frostfsid_util.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package morph
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
// neo-go doesn't support []util.Uint160 type:
|
||||||
|
// https://github.com/nspcc-dev/neo-go/blob/v0.103.0/pkg/smartcontract/parameter.go#L262
|
||||||
|
// Thus, return []smartcontract.Parameter.
|
||||||
|
func getFrostfsIDAuthorizedKeys(v *viper.Viper, defaultOwner util.Uint160) ([]smartcontract.Parameter, error) {
|
||||||
|
var res []smartcontract.Parameter
|
||||||
|
res = append(res, smartcontract.Parameter{Type: smartcontract.Hash160Type, Value: defaultOwner})
|
||||||
|
|
||||||
|
ks := v.GetStringSlice(frostfsIDAuthorizedKeysConfigKey)
|
||||||
|
for i := range ks {
|
||||||
|
h, err := address.StringToUint160(ks[i])
|
||||||
|
if err == nil {
|
||||||
|
res = append(res, smartcontract.Parameter{Type: smartcontract.Hash160Type, Value: h})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
h, err = util.Uint160DecodeStringLE(ks[i])
|
||||||
|
if err == nil {
|
||||||
|
res = append(res, smartcontract.Parameter{Type: smartcontract.Hash160Type, Value: h})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pk, err := keys.NewPublicKeyFromString(ks[i])
|
||||||
|
if err == nil {
|
||||||
|
res = append(res, smartcontract.Parameter{Type: smartcontract.Hash160Type, Value: pk.GetScriptHash()})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("frostfsid: #%d item in authorized_keys is invalid: '%s'", i, ks[i])
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
package morph
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFrostfsIDConfig(t *testing.T) {
|
||||||
|
pks := make([]*keys.PrivateKey, 4)
|
||||||
|
for i := range pks {
|
||||||
|
pk, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
pks[i] = pk
|
||||||
|
}
|
||||||
|
|
||||||
|
v := viper.New()
|
||||||
|
v.Set("frostfsid.authorized_keys", []string{
|
||||||
|
pks[0].GetScriptHash().StringLE(),
|
||||||
|
address.Uint160ToString(pks[1].GetScriptHash()),
|
||||||
|
hex.EncodeToString(pks[2].PublicKey().UncompressedBytes()),
|
||||||
|
hex.EncodeToString(pks[3].PublicKey().Bytes()),
|
||||||
|
})
|
||||||
|
|
||||||
|
comm := util.Uint160{1, 2, 3}
|
||||||
|
actual, err := getFrostfsIDAuthorizedKeys(v, comm)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, len(pks)+1, len(actual))
|
||||||
|
require.Equal(t, smartcontract.Hash160Type, actual[0].Type)
|
||||||
|
require.Equal(t, comm, actual[0].Value)
|
||||||
|
for i := range pks {
|
||||||
|
require.Equal(t, smartcontract.Hash160Type, actual[i+1].Type)
|
||||||
|
require.Equal(t, pks[i].GetScriptHash(), actual[i+1].Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("bad key", func(t *testing.T) {
|
||||||
|
v := viper.New()
|
||||||
|
v.Set("frostfsid.authorized_keys", []string{"abc"})
|
||||||
|
|
||||||
|
_, err := getFrostfsIDAuthorizedKeys(v, comm)
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
|
@ -6,21 +6,22 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-adm/internal/modules/config"
|
|
||||||
"github.com/nspcc-dev/neofs-node/pkg/innerring"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -29,7 +30,7 @@ const (
|
||||||
consensusAccountName = "consensus"
|
consensusAccountName = "consensus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func generateAlphabetCreds(cmd *cobra.Command, args []string) error {
|
func generateAlphabetCreds(cmd *cobra.Command, _ []string) error {
|
||||||
// alphabet size is not part of the config
|
// alphabet size is not part of the config
|
||||||
size, err := cmd.Flags().GetUint(alphabetSizeFlag)
|
size, err := cmd.Flags().GetUint(alphabetSizeFlag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -38,6 +39,9 @@ func generateAlphabetCreds(cmd *cobra.Command, args []string) error {
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
return errors.New("size must be > 0")
|
return errors.New("size must be > 0")
|
||||||
}
|
}
|
||||||
|
if size > maxAlphabetNodes {
|
||||||
|
return ErrTooManyAlphabetNodes
|
||||||
|
}
|
||||||
|
|
||||||
v := viper.GetViper()
|
v := viper.GetViper()
|
||||||
walletDir := config.ResolveHomePath(viper.GetString(alphabetWalletsFlag))
|
walletDir := config.ResolveHomePath(viper.GetString(alphabetWalletsFlag))
|
||||||
|
@ -72,7 +76,7 @@ func initializeWallets(v *viper.Viper, walletDir string, size int) ([]string, er
|
||||||
}
|
}
|
||||||
|
|
||||||
p := filepath.Join(walletDir, innerring.GlagoliticLetter(i).String()+".json")
|
p := filepath.Join(walletDir, innerring.GlagoliticLetter(i).String()+".json")
|
||||||
f, err := os.OpenFile(p, os.O_CREATE, 0644)
|
f, err := os.OpenFile(p, os.O_CREATE, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't create wallet file: %w", err)
|
return nil, fmt.Errorf("can't create wallet file: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -92,28 +96,31 @@ func initializeWallets(v *viper.Viper, walletDir string, size int) ([]string, er
|
||||||
pubs[i] = w.Accounts[0].PrivateKey().PublicKey()
|
pubs[i] = w.Accounts[0].PrivateKey().PublicKey()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errG errgroup.Group
|
||||||
|
|
||||||
// Create committee account with N/2+1 multi-signature.
|
// Create committee account with N/2+1 multi-signature.
|
||||||
majCount := smartcontract.GetMajorityHonestNodeCount(size)
|
majCount := smartcontract.GetMajorityHonestNodeCount(size)
|
||||||
for i, w := range wallets {
|
|
||||||
if err := addMultisigAccount(w, majCount, committeeAccountName, passwords[i], pubs); err != nil {
|
|
||||||
return nil, fmt.Errorf("can't create committee account: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create consensus account with 2*N/3+1 multi-signature.
|
// Create consensus account with 2*N/3+1 multi-signature.
|
||||||
bftCount := smartcontract.GetDefaultHonestNodeCount(size)
|
bftCount := smartcontract.GetDefaultHonestNodeCount(size)
|
||||||
for i, w := range wallets {
|
for i := range wallets {
|
||||||
if err := addMultisigAccount(w, bftCount, consensusAccountName, passwords[i], pubs); err != nil {
|
i := i
|
||||||
return nil, fmt.Errorf("can't create consensus account: %w", err)
|
ps := pubs.Copy()
|
||||||
}
|
errG.Go(func() error {
|
||||||
|
if err := addMultisigAccount(wallets[i], majCount, committeeAccountName, passwords[i], ps); err != nil {
|
||||||
|
return fmt.Errorf("can't create committee account: %w", err)
|
||||||
|
}
|
||||||
|
if err := addMultisigAccount(wallets[i], bftCount, consensusAccountName, passwords[i], ps); err != nil {
|
||||||
|
return fmt.Errorf("can't create consentus account: %w", err)
|
||||||
|
}
|
||||||
|
if err := wallets[i].SavePretty(); err != nil {
|
||||||
|
return fmt.Errorf("can't save wallet: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
if err := errG.Wait(); err != nil {
|
||||||
for _, w := range wallets {
|
return nil, err
|
||||||
if err := w.SavePretty(); err != nil {
|
|
||||||
return nil, fmt.Errorf("can't save wallet: %w", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return passwords, nil
|
return passwords, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,10 +205,8 @@ func refillGas(cmd *cobra.Command, gasFlag string, createWallet bool) (err error
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
gasHash := wCtx.nativeHash(nativenames.Gas)
|
|
||||||
|
|
||||||
bw := io.NewBufBinWriter()
|
bw := io.NewBufBinWriter()
|
||||||
emit.AppCall(bw.BinWriter, gasHash, "transfer", callflag.All,
|
emit.AppCall(bw.BinWriter, gas.Hash, "transfer", callflag.All,
|
||||||
wCtx.CommitteeAcc.Contract.ScriptHash(), gasReceiver, int64(gasAmount), nil)
|
wCtx.CommitteeAcc.Contract.ScriptHash(), gasReceiver, int64(gasAmount), nil)
|
||||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||||
if bw.Err != nil {
|
if bw.Err != nil {
|
|
@ -7,13 +7,14 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||||
"github.com/nspcc-dev/neo-go/cli/input"
|
"github.com/nspcc-dev/neo-go/cli/input"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
"github.com/nspcc-dev/neofs-node/pkg/innerring"
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
|
@ -71,24 +72,32 @@ func TestGenerateAlphabet(t *testing.T) {
|
||||||
buf.WriteString(testContractPassword + "\r")
|
buf.WriteString(testContractPassword + "\r")
|
||||||
require.NoError(t, generateAlphabetCreds(generateAlphabetCmd, nil))
|
require.NoError(t, generateAlphabetCreds(generateAlphabetCmd, nil))
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
for i := uint64(0); i < size; i++ {
|
for i := uint64(0); i < size; i++ {
|
||||||
p := filepath.Join(walletDir, innerring.GlagoliticLetter(i).String()+".json")
|
i := i
|
||||||
w, err := wallet.NewWalletFromFile(p)
|
wg.Add(1)
|
||||||
require.NoError(t, err, "wallet doesn't exist")
|
go func() {
|
||||||
require.Equal(t, 3, len(w.Accounts), "not all accounts were created")
|
defer wg.Done()
|
||||||
for _, a := range w.Accounts {
|
p := filepath.Join(walletDir, innerring.GlagoliticLetter(i).String()+".json")
|
||||||
err := a.Decrypt(strconv.FormatUint(i, 10), keys.NEP2ScryptParams())
|
w, err := wallet.NewWalletFromFile(p)
|
||||||
require.NoError(t, err, "can't decrypt account")
|
require.NoError(t, err, "wallet doesn't exist")
|
||||||
switch a.Label {
|
require.Equal(t, 3, len(w.Accounts), "not all accounts were created")
|
||||||
case consensusAccountName:
|
|
||||||
require.Equal(t, smartcontract.GetDefaultHonestNodeCount(size), len(a.Contract.Parameters))
|
for _, a := range w.Accounts {
|
||||||
case committeeAccountName:
|
err := a.Decrypt(strconv.FormatUint(i, 10), keys.NEP2ScryptParams())
|
||||||
require.Equal(t, smartcontract.GetMajorityHonestNodeCount(size), len(a.Contract.Parameters))
|
require.NoError(t, err, "can't decrypt account")
|
||||||
default:
|
switch a.Label {
|
||||||
require.Equal(t, singleAccountName, a.Label)
|
case consensusAccountName:
|
||||||
|
require.Equal(t, smartcontract.GetDefaultHonestNodeCount(size), len(a.Contract.Parameters))
|
||||||
|
case committeeAccountName:
|
||||||
|
require.Equal(t, smartcontract.GetMajorityHonestNodeCount(size), len(a.Contract.Parameters))
|
||||||
|
default:
|
||||||
|
require.Equal(t, singleAccountName, a.Label)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}()
|
||||||
}
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
t.Run("check contract group wallet", func(t *testing.T) {
|
t.Run("check contract group wallet", func(t *testing.T) {
|
||||||
p := filepath.Join(walletDir, contractWalletFilename)
|
p := filepath.Join(walletDir, contractWalletFilename)
|
|
@ -6,11 +6,11 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-adm/internal/modules/config"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
|
@ -7,22 +7,31 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||||
|
morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-adm/internal/modules/config"
|
|
||||||
"github.com/nspcc-dev/neofs-node/pkg/innerring"
|
|
||||||
morphClient "github.com/nspcc-dev/neofs-node/pkg/morph/client"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// maxAlphabetNodes is the maximum number of candidates allowed, which is currently limited by the size
|
||||||
|
// of the invocation script.
|
||||||
|
// See: https://github.com/nspcc-dev/neo-go/blob/740488f7f35e367eaa99a71c0a609c315fe2b0fc/pkg/core/transaction/witness.go#L10
|
||||||
|
maxAlphabetNodes = 22
|
||||||
|
)
|
||||||
|
|
||||||
type cache struct {
|
type cache struct {
|
||||||
nnsCs *state.Contract
|
nnsCs *state.Contract
|
||||||
groupKey *keys.PublicKey
|
groupKey *keys.PublicKey
|
||||||
|
@ -43,10 +52,11 @@ type initializeContext struct {
|
||||||
Contracts map[string]*contractState
|
Contracts map[string]*contractState
|
||||||
Command *cobra.Command
|
Command *cobra.Command
|
||||||
ContractPath string
|
ContractPath string
|
||||||
Natives map[string]util.Uint160
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func initializeSideChainCmd(cmd *cobra.Command, args []string) error {
|
var ErrTooManyAlphabetNodes = fmt.Errorf("too many alphabet nodes (maximum allowed is %d)", maxAlphabetNodes)
|
||||||
|
|
||||||
|
func initializeSideChainCmd(cmd *cobra.Command, _ []string) error {
|
||||||
initCtx, err := newInitializeContext(cmd, viper.GetViper())
|
initCtx, err := newInitializeContext(cmd, viper.GetViper())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("initialization error: %w", err)
|
return fmt.Errorf("initialization error: %w", err)
|
||||||
|
@ -92,11 +102,7 @@ func initializeSideChainCmd(cmd *cobra.Command, args []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Println("Stage 7: set addresses in NNS.")
|
cmd.Println("Stage 7: set addresses in NNS.")
|
||||||
if err := initCtx.setNNS(); err != nil {
|
return initCtx.setNNS()
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *initializeContext) close() {
|
func (c *initializeContext) close() {
|
||||||
|
@ -116,25 +122,21 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var w *wallet.Wallet
|
if len(wallets) > maxAlphabetNodes {
|
||||||
if cmd.Name() != "deploy" {
|
return nil, ErrTooManyAlphabetNodes
|
||||||
w, err = openContractWallet(v, cmd, walletDir)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var c Client
|
needContracts := cmd.Name() == "update-contracts" || cmd.Name() == "init"
|
||||||
if v.GetString(localDumpFlag) != "" {
|
|
||||||
if v.GetString(endpointFlag) != "" {
|
var w *wallet.Wallet
|
||||||
return nil, fmt.Errorf("`%s` and `%s` flags are mutually exclusive", endpointFlag, localDumpFlag)
|
w, err = getWallet(cmd, v, needContracts, walletDir)
|
||||||
}
|
|
||||||
c, err = newLocalClient(cmd, v, wallets)
|
|
||||||
} else {
|
|
||||||
c, err = getN3Client(v)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't create N3 client: %w", err)
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := createClient(cmd, v, wallets)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
committeeAcc, err := getWalletAccount(wallets[0], committeeAccountName)
|
committeeAcc, err := getWalletAccount(wallets[0], committeeAccountName)
|
||||||
|
@ -147,37 +149,22 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex
|
||||||
return nil, fmt.Errorf("can't find consensus account: %w", err)
|
return nil, fmt.Errorf("can't find consensus account: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ctrPath string
|
if err := validateInit(cmd); err != nil {
|
||||||
if cmd.Name() == "init" {
|
return nil, err
|
||||||
if viper.GetInt64(epochDurationInitFlag) <= 0 {
|
|
||||||
return nil, fmt.Errorf("epoch duration must be positive")
|
|
||||||
}
|
|
||||||
|
|
||||||
if viper.GetInt64(maxObjectSizeInitFlag) <= 0 {
|
|
||||||
return nil, fmt.Errorf("max object size must be positive")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
needContracts := cmd.Name() == "update-contracts" || cmd.Name() == "init"
|
ctrPath, err := getContractsPath(cmd, needContracts)
|
||||||
if needContracts {
|
|
||||||
ctrPath, err = cmd.Flags().GetString(contractsInitFlag)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid contracts path: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nativeHashes, err := getNativeHashes(c)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
accounts := make([]*wallet.Account, len(wallets))
|
if err := checkNotaryEnabled(c); err != nil {
|
||||||
for i, w := range wallets {
|
return nil, err
|
||||||
acc, err := getWalletAccount(w, singleAccountName)
|
}
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("wallet %s is invalid (no single account): %w", w.Path(), err)
|
accounts, err := createWalletAccounts(wallets)
|
||||||
}
|
if err != nil {
|
||||||
accounts[i] = acc
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cliCtx, err := defaultClientContext(c, committeeAcc)
|
cliCtx, err := defaultClientContext(c, committeeAcc)
|
||||||
|
@ -195,7 +182,6 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex
|
||||||
Command: cmd,
|
Command: cmd,
|
||||||
Contracts: make(map[string]*contractState),
|
Contracts: make(map[string]*contractState),
|
||||||
ContractPath: ctrPath,
|
ContractPath: ctrPath,
|
||||||
Natives: nativeHashes,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if needContracts {
|
if needContracts {
|
||||||
|
@ -208,8 +194,67 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex
|
||||||
return initCtx, nil
|
return initCtx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *initializeContext) nativeHash(name string) util.Uint160 {
|
func validateInit(cmd *cobra.Command) error {
|
||||||
return c.Natives[name]
|
if cmd.Name() != "init" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if viper.GetInt64(epochDurationInitFlag) <= 0 {
|
||||||
|
return fmt.Errorf("epoch duration must be positive")
|
||||||
|
}
|
||||||
|
|
||||||
|
if viper.GetInt64(maxObjectSizeInitFlag) <= 0 {
|
||||||
|
return fmt.Errorf("max object size must be positive")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet) (Client, error) {
|
||||||
|
var c Client
|
||||||
|
var err error
|
||||||
|
if ldf := cmd.Flags().Lookup(localDumpFlag); ldf != nil && ldf.Changed {
|
||||||
|
if cmd.Flags().Changed(endpointFlag) {
|
||||||
|
return nil, fmt.Errorf("`%s` and `%s` flags are mutually exclusive", endpointFlag, localDumpFlag)
|
||||||
|
}
|
||||||
|
c, err = newLocalClient(cmd, v, wallets, ldf.Value.String())
|
||||||
|
} else {
|
||||||
|
c, err = getN3Client(v)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't create N3 client: %w", err)
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getWallet(cmd *cobra.Command, v *viper.Viper, needContracts bool, walletDir string) (*wallet.Wallet, error) {
|
||||||
|
if !needContracts {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return openContractWallet(v, cmd, walletDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getContractsPath(cmd *cobra.Command, needContracts bool) (string, error) {
|
||||||
|
if !needContracts {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrPath, err := cmd.Flags().GetString(contractsInitFlag)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("invalid contracts path: %w", err)
|
||||||
|
}
|
||||||
|
return ctrPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createWalletAccounts(wallets []*wallet.Wallet) ([]*wallet.Account, error) {
|
||||||
|
accounts := make([]*wallet.Account, len(wallets))
|
||||||
|
for i, w := range wallets {
|
||||||
|
acc, err := getWalletAccount(w, singleAccountName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("wallet %s is invalid (no single account): %w", w.Path(), err)
|
||||||
|
}
|
||||||
|
accounts[i] = acc
|
||||||
|
}
|
||||||
|
return accounts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func openAlphabetWallets(v *viper.Viper, walletDir string) ([]*wallet.Wallet, error) {
|
func openAlphabetWallets(v *viper.Viper, walletDir string) ([]*wallet.Wallet, error) {
|
||||||
|
@ -269,7 +314,8 @@ func (c *initializeContext) nnsContractState() (*state.Contract, error) {
|
||||||
return c.nnsCs, nil
|
return c.nnsCs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cs, err := c.Client.GetContractStateByID(1)
|
r := management.NewReader(c.ReadOnlyInvoker)
|
||||||
|
cs, err := r.GetContractByID(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -331,36 +377,18 @@ func (c *clientContext) awaitTx(cmd *cobra.Command) error {
|
||||||
func awaitTx(cmd *cobra.Command, c Client, txs []hashVUBPair) error {
|
func awaitTx(cmd *cobra.Command, c Client, txs []hashVUBPair) error {
|
||||||
cmd.Println("Waiting for transactions to persist...")
|
cmd.Println("Waiting for transactions to persist...")
|
||||||
|
|
||||||
const pollInterval = time.Second
|
|
||||||
|
|
||||||
tick := time.NewTicker(pollInterval)
|
|
||||||
defer tick.Stop()
|
|
||||||
|
|
||||||
at := trigger.Application
|
at := trigger.Application
|
||||||
|
|
||||||
var retErr error
|
var retErr error
|
||||||
|
|
||||||
currBlock, err := c.GetBlockCount()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't fetch current block height: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
loop:
|
loop:
|
||||||
for i := range txs {
|
for i := range txs {
|
||||||
res, err := c.GetApplicationLog(txs[i].hash, &at)
|
var it int
|
||||||
if err == nil {
|
var pollInterval time.Duration
|
||||||
if retErr == nil && len(res.Executions) > 0 && res.Executions[0].VMState != vmstate.Halt {
|
var pollIntervalChanged bool
|
||||||
retErr = fmt.Errorf("tx %d persisted in %s state: %s",
|
for {
|
||||||
i, res.Executions[0].VMState, res.Executions[0].FaultException)
|
|
||||||
}
|
|
||||||
continue loop
|
|
||||||
}
|
|
||||||
if txs[i].vub < currBlock {
|
|
||||||
return fmt.Errorf("tx was not persisted: vub=%d, height=%d", txs[i].vub, currBlock)
|
|
||||||
}
|
|
||||||
for range tick.C {
|
|
||||||
// We must fetch current height before application log, to avoid race condition.
|
// We must fetch current height before application log, to avoid race condition.
|
||||||
currBlock, err = c.GetBlockCount()
|
currBlock, err := c.GetBlockCount()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't fetch current block height: %w", err)
|
return fmt.Errorf("can't fetch current block height: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -375,12 +403,43 @@ loop:
|
||||||
if txs[i].vub < currBlock {
|
if txs[i].vub < currBlock {
|
||||||
return fmt.Errorf("tx was not persisted: vub=%d, height=%d", txs[i].vub, currBlock)
|
return fmt.Errorf("tx was not persisted: vub=%d, height=%d", txs[i].vub, currBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pollInterval, pollIntervalChanged = nextPollInterval(it, pollInterval)
|
||||||
|
if pollIntervalChanged && viper.GetBool(commonflags.Verbose) {
|
||||||
|
cmd.Printf("Pool interval to check transaction persistence changed: %s\n", pollInterval.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
timer := time.NewTimer(pollInterval)
|
||||||
|
select {
|
||||||
|
case <-cmd.Context().Done():
|
||||||
|
return cmd.Context().Err()
|
||||||
|
case <-timer.C:
|
||||||
|
}
|
||||||
|
|
||||||
|
it++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return retErr
|
return retErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func nextPollInterval(it int, previous time.Duration) (time.Duration, bool) {
|
||||||
|
const minPollInterval = 1 * time.Second
|
||||||
|
const maxPollInterval = 16 * time.Second
|
||||||
|
const changeAfter = 5
|
||||||
|
if it == 0 {
|
||||||
|
return minPollInterval, true
|
||||||
|
}
|
||||||
|
if it%changeAfter != 0 {
|
||||||
|
return previous, false
|
||||||
|
}
|
||||||
|
nextInterval := previous * 2
|
||||||
|
if nextInterval > maxPollInterval {
|
||||||
|
return maxPollInterval, previous != maxPollInterval
|
||||||
|
}
|
||||||
|
return nextInterval, true
|
||||||
|
}
|
||||||
|
|
||||||
// sendCommitteeTx creates transaction from script, signs it by committee nodes and sends it to RPC.
|
// sendCommitteeTx creates transaction from script, signs it by committee nodes and sends it to RPC.
|
||||||
// If tryGroup is false, global scope is used for the signer (useful when
|
// If tryGroup is false, global scope is used for the signer (useful when
|
||||||
// working with native contracts).
|
// working with native contracts).
|
||||||
|
@ -450,10 +509,10 @@ func getWalletAccount(w *wallet.Wallet, typ string) (*wallet.Account, error) {
|
||||||
return nil, fmt.Errorf("account for '%s' not found", typ)
|
return nil, fmt.Errorf("account for '%s' not found", typ)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNativeHashes(c Client) (map[string]util.Uint160, error) {
|
func checkNotaryEnabled(c Client) error {
|
||||||
ns, err := c.GetNativeContracts()
|
ns, err := c.GetNativeContracts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't get native contract hashes: %w", err)
|
return fmt.Errorf("can't get native contract hashes: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
notaryEnabled := false
|
notaryEnabled := false
|
||||||
|
@ -465,7 +524,7 @@ func getNativeHashes(c Client) (map[string]util.Uint160, error) {
|
||||||
nativeHashes[ns[i].Manifest.Name] = ns[i].Hash
|
nativeHashes[ns[i].Manifest.Name] = ns[i].Hash
|
||||||
}
|
}
|
||||||
if !notaryEnabled {
|
if !notaryEnabled {
|
||||||
return nil, errors.New("notary contract must be enabled")
|
return errors.New("notary contract must be enabled")
|
||||||
}
|
}
|
||||||
return nativeHashes, nil
|
return nil
|
||||||
}
|
}
|
|
@ -12,13 +12,17 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||||
|
morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
io2 "github.com/nspcc-dev/neo-go/pkg/io"
|
io2 "github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
|
@ -26,64 +30,52 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/nspcc-dev/neofs-contract/nns"
|
|
||||||
"github.com/nspcc-dev/neofs-node/pkg/innerring"
|
|
||||||
morphClient "github.com/nspcc-dev/neofs-node/pkg/morph/client"
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
nnsContract = "nns"
|
nnsContract = "nns"
|
||||||
neofsContract = "neofs" // not deployed in side-chain.
|
frostfsContract = "frostfs" // not deployed in side-chain.
|
||||||
processingContract = "processing" // not deployed in side-chain.
|
processingContract = "processing" // not deployed in side-chain.
|
||||||
alphabetContract = "alphabet"
|
alphabetContract = "alphabet"
|
||||||
auditContract = "audit"
|
|
||||||
balanceContract = "balance"
|
balanceContract = "balance"
|
||||||
containerContract = "container"
|
containerContract = "container"
|
||||||
neofsIDContract = "neofsid"
|
frostfsIDContract = "frostfsid"
|
||||||
netmapContract = "netmap"
|
netmapContract = "netmap"
|
||||||
|
policyContract = "policy"
|
||||||
proxyContract = "proxy"
|
proxyContract = "proxy"
|
||||||
reputationContract = "reputation"
|
|
||||||
subnetContract = "subnet"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const frostfsIDAuthorizedKeysConfigKey = "frostfsid.authorized_keys"
|
||||||
netmapEpochKey = "EpochDuration"
|
|
||||||
netmapMaxObjectSizeKey = "MaxObjectSize"
|
|
||||||
netmapAuditFeeKey = "AuditFee"
|
|
||||||
netmapContainerFeeKey = "ContainerFee"
|
|
||||||
netmapContainerAliasFeeKey = "ContainerAliasFee"
|
|
||||||
netmapEigenTrustIterationsKey = "EigenTrustIterations"
|
|
||||||
netmapEigenTrustAlphaKey = "EigenTrustAlpha"
|
|
||||||
netmapBasicIncomeRateKey = "BasicIncomeRate"
|
|
||||||
netmapInnerRingCandidateFeeKey = "InnerRingCandidateFee"
|
|
||||||
netmapWithdrawFeeKey = "WithdrawFee"
|
|
||||||
netmapHomomorphicHashDisabledKey = "HomomorphicHashingDisabled"
|
|
||||||
netmapMaintenanceAllowedKey = "MaintenanceModeAllowed"
|
|
||||||
|
|
||||||
defaultEigenTrustIterations = 4
|
|
||||||
defaultEigenTrustAlpha = "0.1"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
contractList = []string{
|
contractList = []string{
|
||||||
auditContract,
|
|
||||||
balanceContract,
|
balanceContract,
|
||||||
containerContract,
|
containerContract,
|
||||||
neofsIDContract,
|
frostfsIDContract,
|
||||||
netmapContract,
|
netmapContract,
|
||||||
|
policyContract,
|
||||||
proxyContract,
|
proxyContract,
|
||||||
reputationContract,
|
|
||||||
subnetContract,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fullContractList = append([]string{
|
fullContractList = append([]string{
|
||||||
neofsContract,
|
frostfsContract,
|
||||||
processingContract,
|
processingContract,
|
||||||
nnsContract,
|
nnsContract,
|
||||||
alphabetContract,
|
alphabetContract,
|
||||||
}, contractList...)
|
}, contractList...)
|
||||||
|
|
||||||
|
netmapConfigKeys = []string{
|
||||||
|
netmap.EpochDurationConfig,
|
||||||
|
netmap.MaxObjectSizeConfig,
|
||||||
|
netmap.ContainerFeeConfig,
|
||||||
|
netmap.ContainerAliasFeeConfig,
|
||||||
|
netmap.IrCandidateFeeConfig,
|
||||||
|
netmap.WithdrawFeeConfig,
|
||||||
|
netmap.HomomorphicHashingDisabledKey,
|
||||||
|
netmap.MaintenanceModeAllowedConfig,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
type contractState struct {
|
type contractState struct {
|
||||||
|
@ -104,9 +96,16 @@ func (c *initializeContext) deployNNS(method string) error {
|
||||||
h := cs.Hash
|
h := cs.Hash
|
||||||
|
|
||||||
nnsCs, err := c.nnsContractState()
|
nnsCs, err := c.nnsContractState()
|
||||||
if err == nil {
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if nnsCs != nil {
|
||||||
if nnsCs.NEF.Checksum == cs.NEF.Checksum {
|
if nnsCs.NEF.Checksum == cs.NEF.Checksum {
|
||||||
c.Command.Println("NNS contract is already deployed.")
|
if method == deployMethodName {
|
||||||
|
c.Command.Println("NNS contract is already deployed.")
|
||||||
|
} else {
|
||||||
|
c.Command.Println("NNS contract is already updated.")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
h = nnsCs.Hash
|
h = nnsCs.Hash
|
||||||
|
@ -118,28 +117,13 @@ func (c *initializeContext) deployNNS(method string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
params := getContractDeployParameters(cs, nil)
|
params := getContractDeployParameters(cs, nil)
|
||||||
signer := transaction.Signer{
|
|
||||||
Account: c.CommitteeAcc.Contract.ScriptHash(),
|
|
||||||
Scopes: transaction.CalledByEntry,
|
|
||||||
}
|
|
||||||
|
|
||||||
invokeHash := c.nativeHash(nativenames.Management)
|
invokeHash := management.Hash
|
||||||
if method == updateMethodName {
|
if method == updateMethodName {
|
||||||
invokeHash = nnsCs.Hash
|
invokeHash = nnsCs.Hash
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := invokeFunction(c.Client, invokeHash, method, params, []transaction.Signer{signer})
|
tx, err := c.CommitteeAct.MakeCall(invokeHash, method, params...)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't deploy NNS contract: %w", err)
|
|
||||||
}
|
|
||||||
if res.State != vmstate.Halt.String() {
|
|
||||||
return fmt.Errorf("can't deploy NNS contract: %s", res.FaultException)
|
|
||||||
}
|
|
||||||
|
|
||||||
tx, err := c.Client.CreateTxFromScript(res.Script, c.CommitteeAcc, res.GasConsumed, 0, []rpcclient.SignerAccount{{
|
|
||||||
Signer: signer,
|
|
||||||
Account: c.CommitteeAcc,
|
|
||||||
}})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create deploy tx for %s: %w", nnsContract, err)
|
return fmt.Errorf("failed to create deploy tx for %s: %w", nnsContract, err)
|
||||||
}
|
}
|
||||||
|
@ -152,7 +136,6 @@ func (c *initializeContext) deployNNS(method string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *initializeContext) updateContracts() error {
|
func (c *initializeContext) updateContracts() error {
|
||||||
mgmtHash := c.nativeHash(nativenames.Management)
|
|
||||||
alphaCs := c.getContract(alphabetContract)
|
alphaCs := c.getContract(alphabetContract)
|
||||||
|
|
||||||
nnsCs, err := c.nnsContractState()
|
nnsCs, err := c.nnsContractState()
|
||||||
|
@ -163,8 +146,6 @@ func (c *initializeContext) updateContracts() error {
|
||||||
|
|
||||||
w := io2.NewBufBinWriter()
|
w := io2.NewBufBinWriter()
|
||||||
|
|
||||||
var keysParam []interface{}
|
|
||||||
|
|
||||||
// Update script size for a single-node committee is close to the maximum allowed size of 65535.
|
// Update script size for a single-node committee is close to the maximum allowed size of 65535.
|
||||||
// Because of this we want to reuse alphabet contract NEF and manifest for different updates.
|
// Because of this we want to reuse alphabet contract NEF and manifest for different updates.
|
||||||
// The generated script is as following.
|
// The generated script is as following.
|
||||||
|
@ -178,39 +159,36 @@ func (c *initializeContext) updateContracts() error {
|
||||||
emit.Bytes(w.BinWriter, alphaCs.RawNEF)
|
emit.Bytes(w.BinWriter, alphaCs.RawNEF)
|
||||||
emit.Opcodes(w.BinWriter, opcode.STSFLD0)
|
emit.Opcodes(w.BinWriter, opcode.STSFLD0)
|
||||||
|
|
||||||
baseGroups := alphaCs.Manifest.Groups
|
keysParam, err := c.deployAlphabetAccounts(nnsHash, w, alphaCs)
|
||||||
|
if err != nil {
|
||||||
// alphabet contracts should be deployed by individual nodes to get different hashes.
|
|
||||||
for i, acc := range c.Accounts {
|
|
||||||
ctrHash, err := nnsResolveHash(c.ReadOnlyInvoker, nnsHash, getAlphabetNNSDomain(i))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't resolve hash for contract update: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
keysParam = append(keysParam, acc.PrivateKey().PublicKey().Bytes())
|
|
||||||
|
|
||||||
params := c.getAlphabetDeployItems(i, len(c.Wallets))
|
|
||||||
emit.Array(w.BinWriter, params...)
|
|
||||||
|
|
||||||
alphaCs.Manifest.Groups = baseGroups
|
|
||||||
err = c.addManifestGroup(ctrHash, alphaCs)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't sign manifest group: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
emit.Bytes(w.BinWriter, alphaCs.RawManifest)
|
|
||||||
emit.Opcodes(w.BinWriter, opcode.LDSFLD0)
|
|
||||||
emit.Int(w.BinWriter, 3)
|
|
||||||
emit.Opcodes(w.BinWriter, opcode.PACK)
|
|
||||||
emit.AppCallNoArgs(w.BinWriter, ctrHash, updateMethodName, callflag.All)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.sendCommitteeTx(w.Bytes(), false); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Reset()
|
w.Reset()
|
||||||
|
|
||||||
|
if err = c.deployOrUpdateContracts(w, nnsHash, keysParam); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
groupKey := c.ContractWallet.Accounts[0].PrivateKey().PublicKey()
|
||||||
|
_, _, err = c.emitUpdateNNSGroupScript(w, nnsHash, groupKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.Command.Printf("NNS: Set %s -> %s\n", morphClient.NNSGroupKeyName, hex.EncodeToString(groupKey.Bytes()))
|
||||||
|
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.LDSFLD0)
|
||||||
|
emit.Int(w.BinWriter, 1)
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.PACK)
|
||||||
|
emit.AppCallNoArgs(w.BinWriter, nnsHash, "setPrice", callflag.All)
|
||||||
|
|
||||||
|
if err := c.sendCommitteeTx(w.Bytes(), false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.awaitTx()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *initializeContext) deployOrUpdateContracts(w *io2.BufBinWriter, nnsHash util.Uint160, keysParam []any) error {
|
||||||
emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1})
|
emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1})
|
||||||
emit.AppCall(w.BinWriter, nnsHash, "getPrice", callflag.All)
|
emit.AppCall(w.BinWriter, nnsHash, "getPrice", callflag.All)
|
||||||
emit.Opcodes(w.BinWriter, opcode.STSFLD0)
|
emit.Opcodes(w.BinWriter, opcode.STSFLD0)
|
||||||
|
@ -220,7 +198,7 @@ func (c *initializeContext) updateContracts() error {
|
||||||
cs := c.getContract(ctrName)
|
cs := c.getContract(ctrName)
|
||||||
|
|
||||||
method := updateMethodName
|
method := updateMethodName
|
||||||
ctrHash, err := nnsResolveHash(c.ReadOnlyInvoker, nnsHash, ctrName+".neofs")
|
ctrHash, err := nnsResolveHash(c.ReadOnlyInvoker, nnsHash, ctrName+".frostfs")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errMissingNNSRecord) {
|
if errors.Is(err, errMissingNNSRecord) {
|
||||||
// if contract not found we deploy it instead of update
|
// if contract not found we deploy it instead of update
|
||||||
|
@ -235,22 +213,26 @@ func (c *initializeContext) updateContracts() error {
|
||||||
return fmt.Errorf("can't sign manifest group: %v", err)
|
return fmt.Errorf("can't sign manifest group: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
invokeHash := mgmtHash
|
invokeHash := management.Hash
|
||||||
if method == updateMethodName {
|
if method == updateMethodName {
|
||||||
invokeHash = ctrHash
|
invokeHash = ctrHash
|
||||||
}
|
}
|
||||||
|
|
||||||
params := getContractDeployParameters(cs, c.getContractDeployData(ctrName, keysParam))
|
params := getContractDeployParameters(cs, c.getContractDeployData(ctrName, keysParam, updateMethodName))
|
||||||
res, err := c.CommitteeAct.MakeCall(invokeHash, method, params...)
|
res, err := c.CommitteeAct.MakeCall(invokeHash, method, params...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("deploy contract: %w", err)
|
if method != updateMethodName || !strings.Contains(err.Error(), common.ErrAlreadyUpdated) {
|
||||||
|
return fmt.Errorf("deploy contract: %w", err)
|
||||||
|
}
|
||||||
|
c.Command.Printf("%s contract is already updated.\n", ctrName)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteBytes(res.Script)
|
w.WriteBytes(res.Script)
|
||||||
|
|
||||||
if method == deployMethodName {
|
if method == deployMethodName {
|
||||||
// same actions are done in initializeContext.setNNS, can be unified
|
// same actions are done in initializeContext.setNNS, can be unified
|
||||||
domain := ctrName + ".neofs"
|
domain := ctrName + ".frostfs"
|
||||||
script, ok, err := c.nnsRegisterDomainScript(nnsHash, cs.Hash, domain)
|
script, ok, err := c.nnsRegisterDomainScript(nnsHash, cs.Hash, domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -266,28 +248,52 @@ func (c *initializeContext) updateContracts() error {
|
||||||
c.Command.Printf("NNS: Set %s -> %s\n", domain, cs.Hash.StringLE())
|
c.Command.Printf("NNS: Set %s -> %s\n", domain, cs.Hash.StringLE())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
groupKey := c.ContractWallet.Accounts[0].PrivateKey().PublicKey()
|
func (c *initializeContext) deployAlphabetAccounts(nnsHash util.Uint160, w *io2.BufBinWriter, alphaCs *contractState) ([]any, error) {
|
||||||
_, _, err = c.emitUpdateNNSGroupScript(w, nnsHash, groupKey)
|
var keysParam []any
|
||||||
if err != nil {
|
|
||||||
return err
|
baseGroups := alphaCs.Manifest.Groups
|
||||||
|
|
||||||
|
// alphabet contracts should be deployed by individual nodes to get different hashes.
|
||||||
|
for i, acc := range c.Accounts {
|
||||||
|
ctrHash, err := nnsResolveHash(c.ReadOnlyInvoker, nnsHash, getAlphabetNNSDomain(i))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't resolve hash for contract update: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
keysParam = append(keysParam, acc.PrivateKey().PublicKey().Bytes())
|
||||||
|
|
||||||
|
params := c.getAlphabetDeployItems(i, len(c.Wallets))
|
||||||
|
emit.Array(w.BinWriter, params...)
|
||||||
|
|
||||||
|
alphaCs.Manifest.Groups = baseGroups
|
||||||
|
err = c.addManifestGroup(ctrHash, alphaCs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't sign manifest group: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
emit.Bytes(w.BinWriter, alphaCs.RawManifest)
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.LDSFLD0)
|
||||||
|
emit.Int(w.BinWriter, 3)
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.PACK)
|
||||||
|
emit.AppCallNoArgs(w.BinWriter, ctrHash, updateMethodName, callflag.All)
|
||||||
}
|
}
|
||||||
c.Command.Printf("NNS: Set %s -> %s\n", morphClient.NNSGroupKeyName, hex.EncodeToString(groupKey.Bytes()))
|
|
||||||
|
|
||||||
emit.Opcodes(w.BinWriter, opcode.LDSFLD0)
|
|
||||||
emit.AppCallNoArgs(w.BinWriter, nnsHash, "setPrice", callflag.All)
|
|
||||||
|
|
||||||
if err := c.sendCommitteeTx(w.Bytes(), false); err != nil {
|
if err := c.sendCommitteeTx(w.Bytes(), false); err != nil {
|
||||||
return err
|
if !strings.Contains(err.Error(), common.ErrAlreadyUpdated) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.Command.Println("Alphabet contracts are already updated.")
|
||||||
}
|
}
|
||||||
return c.awaitTx()
|
|
||||||
|
return keysParam, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *initializeContext) deployContracts() error {
|
func (c *initializeContext) deployContracts() error {
|
||||||
mgmtHash := c.nativeHash(nativenames.Management)
|
|
||||||
alphaCs := c.getContract(alphabetContract)
|
alphaCs := c.getContract(alphabetContract)
|
||||||
|
|
||||||
var keysParam []interface{}
|
var keysParam []any
|
||||||
|
|
||||||
baseGroups := alphaCs.Manifest.Groups
|
baseGroups := alphaCs.Manifest.Groups
|
||||||
|
|
||||||
|
@ -313,7 +319,7 @@ func (c *initializeContext) deployContracts() error {
|
||||||
return fmt.Errorf("could not create actor: %w", err)
|
return fmt.Errorf("could not create actor: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
txHash, vub, err := act.SendCall(mgmtHash, deployMethodName, params...)
|
txHash, vub, err := act.SendCall(management.Hash, deployMethodName, params...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't deploy alphabet #%d contract: %w", i, err)
|
return fmt.Errorf("can't deploy alphabet #%d contract: %w", i, err)
|
||||||
}
|
}
|
||||||
|
@ -335,8 +341,8 @@ func (c *initializeContext) deployContracts() error {
|
||||||
return fmt.Errorf("can't sign manifest group: %v", err)
|
return fmt.Errorf("can't sign manifest group: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
params := getContractDeployParameters(cs, c.getContractDeployData(ctrName, keysParam))
|
params := getContractDeployParameters(cs, c.getContractDeployData(ctrName, keysParam, deployMethodName))
|
||||||
res, err := c.CommitteeAct.MakeCall(mgmtHash, deployMethodName, params...)
|
res, err := c.CommitteeAct.MakeCall(management.Hash, deployMethodName, params...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't deploy %s contract: %w", ctrName, err)
|
return fmt.Errorf("can't deploy %s contract: %w", ctrName, err)
|
||||||
}
|
}
|
||||||
|
@ -350,8 +356,9 @@ func (c *initializeContext) deployContracts() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *initializeContext) isUpdated(ctrHash util.Uint160, cs *contractState) bool {
|
func (c *initializeContext) isUpdated(ctrHash util.Uint160, cs *contractState) bool {
|
||||||
realCs, err := c.Client.GetContractStateByHash(ctrHash)
|
r := management.NewReader(c.ReadOnlyInvoker)
|
||||||
return err == nil && realCs.NEF.Checksum == cs.NEF.Checksum
|
realCs, err := r.GetContract(ctrHash)
|
||||||
|
return err == nil && realCs != nil && realCs.NEF.Checksum == cs.NEF.Checksum
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *initializeContext) getContract(ctrName string) *contractState {
|
func (c *initializeContext) getContract(ctrName string) *contractState {
|
||||||
|
@ -381,11 +388,9 @@ func (c *initializeContext) readContracts(names []string) error {
|
||||||
} else {
|
} else {
|
||||||
var r io.ReadCloser
|
var r io.ReadCloser
|
||||||
if c.ContractPath == "" {
|
if c.ContractPath == "" {
|
||||||
c.Command.Println("Contracts flag is missing, latest release will be fetched from Github.")
|
return errors.New("contracts flag is missing")
|
||||||
r, err = downloadContractsFromGithub(c.Command)
|
|
||||||
} else {
|
|
||||||
r, err = os.Open(c.ContractPath)
|
|
||||||
}
|
}
|
||||||
|
r, err = os.Open(c.ContractPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't open contracts archive: %w", err)
|
return fmt.Errorf("can't open contracts archive: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -498,25 +503,22 @@ func readContractsFromArchive(file io.Reader, names []string) (map[string]*contr
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getContractDeployParameters(cs *contractState, deployData []interface{}) []interface{} {
|
func getContractDeployParameters(cs *contractState, deployData []any) []any {
|
||||||
return []interface{}{cs.RawNEF, cs.RawManifest, deployData}
|
return []any{cs.RawNEF, cs.RawManifest, deployData}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *initializeContext) getContractDeployData(ctrName string, keysParam []interface{}) []interface{} {
|
func (c *initializeContext) getContractDeployData(ctrName string, keysParam []any, method string) []any {
|
||||||
items := make([]interface{}, 1, 6)
|
items := make([]any, 0, 6)
|
||||||
items[0] = false // notaryDisabled is false
|
|
||||||
|
|
||||||
switch ctrName {
|
switch ctrName {
|
||||||
case neofsContract:
|
case frostfsContract:
|
||||||
items = append(items,
|
items = append(items,
|
||||||
c.Contracts[processingContract].Hash,
|
c.Contracts[processingContract].Hash,
|
||||||
keysParam,
|
keysParam,
|
||||||
smartcontract.Parameter{})
|
smartcontract.Parameter{})
|
||||||
case processingContract:
|
case processingContract:
|
||||||
items = append(items, c.Contracts[neofsContract].Hash)
|
items = append(items, c.Contracts[frostfsContract].Hash)
|
||||||
return items[1:] // no notary info
|
return items[1:] // no notary info
|
||||||
case auditContract:
|
|
||||||
items = append(items, c.Contracts[netmapContract].Hash)
|
|
||||||
case balanceContract:
|
case balanceContract:
|
||||||
items = append(items,
|
items = append(items,
|
||||||
c.Contracts[netmapContract].Hash,
|
c.Contracts[netmapContract].Hash,
|
||||||
|
@ -524,35 +526,50 @@ func (c *initializeContext) getContractDeployData(ctrName string, keysParam []in
|
||||||
case containerContract:
|
case containerContract:
|
||||||
// In case if NNS is updated multiple times, we can't calculate
|
// In case if NNS is updated multiple times, we can't calculate
|
||||||
// it's actual hash based on local data, thus query chain.
|
// it's actual hash based on local data, thus query chain.
|
||||||
nnsCs, err := c.Client.GetContractStateByID(1)
|
r := management.NewReader(c.ReadOnlyInvoker)
|
||||||
|
nnsCs, err := r.GetContractByID(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("NNS is not yet deployed")
|
panic("NNS is not yet deployed")
|
||||||
}
|
}
|
||||||
items = append(items,
|
items = append(items,
|
||||||
c.Contracts[netmapContract].Hash,
|
c.Contracts[netmapContract].Hash,
|
||||||
c.Contracts[balanceContract].Hash,
|
c.Contracts[balanceContract].Hash,
|
||||||
c.Contracts[neofsIDContract].Hash,
|
c.Contracts[frostfsIDContract].Hash,
|
||||||
nnsCs.Hash,
|
nnsCs.Hash,
|
||||||
"container")
|
"container")
|
||||||
case neofsIDContract:
|
case frostfsIDContract:
|
||||||
items = append(items,
|
hs, err := getFrostfsIDAuthorizedKeys(viper.GetViper(), c.CommitteeAcc.PublicKey().GetScriptHash())
|
||||||
c.Contracts[netmapContract].Hash,
|
if err != nil {
|
||||||
c.Contracts[containerContract].Hash)
|
panic(err)
|
||||||
case netmapContract:
|
|
||||||
configParam := []interface{}{
|
|
||||||
netmapEpochKey, viper.GetInt64(epochDurationInitFlag),
|
|
||||||
netmapMaxObjectSizeKey, viper.GetInt64(maxObjectSizeInitFlag),
|
|
||||||
netmapAuditFeeKey, viper.GetInt64(auditFeeInitFlag),
|
|
||||||
netmapContainerFeeKey, viper.GetInt64(containerFeeInitFlag),
|
|
||||||
netmapContainerAliasFeeKey, viper.GetInt64(containerAliasFeeInitFlag),
|
|
||||||
netmapEigenTrustIterationsKey, int64(defaultEigenTrustIterations),
|
|
||||||
netmapEigenTrustAlphaKey, defaultEigenTrustAlpha,
|
|
||||||
netmapBasicIncomeRateKey, viper.GetInt64(incomeRateInitFlag),
|
|
||||||
netmapInnerRingCandidateFeeKey, viper.GetInt64(candidateFeeInitFlag),
|
|
||||||
netmapWithdrawFeeKey, viper.GetInt64(withdrawFeeInitFlag),
|
|
||||||
netmapHomomorphicHashDisabledKey, viper.GetBool(homomorphicHashDisabledInitFlag),
|
|
||||||
netmapMaintenanceAllowedKey, viper.GetBool(maintenanceModeAllowedInitFlag),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
items = append(items, hs)
|
||||||
|
case netmapContract:
|
||||||
|
md := getDefaultNetmapContractConfigMap()
|
||||||
|
if method == updateMethodName {
|
||||||
|
arr, err := c.getNetConfigFromNetmapContract()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
m, err := parseConfigFromNetmapContract(arr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
for k, v := range m {
|
||||||
|
for _, key := range netmapConfigKeys {
|
||||||
|
if k == key {
|
||||||
|
md[k] = v
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var configParam []any
|
||||||
|
for k, v := range md {
|
||||||
|
configParam = append(configParam, k, v)
|
||||||
|
}
|
||||||
|
|
||||||
items = append(items,
|
items = append(items,
|
||||||
c.Contracts[balanceContract].Hash,
|
c.Contracts[balanceContract].Hash,
|
||||||
c.Contracts[containerContract].Hash,
|
c.Contracts[containerContract].Hash,
|
||||||
|
@ -560,21 +577,37 @@ func (c *initializeContext) getContractDeployData(ctrName string, keysParam []in
|
||||||
configParam)
|
configParam)
|
||||||
case proxyContract:
|
case proxyContract:
|
||||||
items = nil
|
items = nil
|
||||||
case reputationContract:
|
case policyContract:
|
||||||
case subnetContract:
|
items = []any{nil}
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("invalid contract name: %s", ctrName))
|
panic(fmt.Sprintf("invalid contract name: %s", ctrName))
|
||||||
}
|
}
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *initializeContext) getAlphabetDeployItems(i, n int) []interface{} {
|
func (c *initializeContext) getNetConfigFromNetmapContract() ([]stackitem.Item, error) {
|
||||||
items := make([]interface{}, 6)
|
r := management.NewReader(c.ReadOnlyInvoker)
|
||||||
items[0] = false
|
cs, err := r.GetContractByID(1)
|
||||||
items[1] = c.Contracts[netmapContract].Hash
|
if err != nil {
|
||||||
items[2] = c.Contracts[proxyContract].Hash
|
return nil, fmt.Errorf("NNS is not yet deployed: %w", err)
|
||||||
items[3] = innerring.GlagoliticLetter(i).String()
|
}
|
||||||
items[4] = int64(i)
|
nmHash, err := nnsResolveHash(c.ReadOnlyInvoker, cs.Hash, netmapContract+".frostfs")
|
||||||
items[5] = int64(n)
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't get netmap contract hash: %w", err)
|
||||||
|
}
|
||||||
|
arr, err := unwrap.Array(c.ReadOnlyInvoker.Call(nmHash, "listConfig"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't fetch list of network config keys from the netmap contract")
|
||||||
|
}
|
||||||
|
return arr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *initializeContext) getAlphabetDeployItems(i, n int) []any {
|
||||||
|
items := make([]any, 5)
|
||||||
|
items[0] = c.Contracts[netmapContract].Hash
|
||||||
|
items[1] = c.Contracts[proxyContract].Hash
|
||||||
|
items[2] = innerring.GlagoliticLetter(i).String()
|
||||||
|
items[3] = int64(i)
|
||||||
|
items[4] = int64(n)
|
||||||
return items
|
return items
|
||||||
}
|
}
|
|
@ -7,12 +7,16 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||||
|
morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||||
|
nnsClient "github.com/nspcc-dev/neo-go/pkg/rpcclient/nns"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
@ -20,26 +24,27 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||||
"github.com/nspcc-dev/neofs-contract/nns"
|
|
||||||
morphClient "github.com/nspcc-dev/neofs-node/pkg/morph/client"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultExpirationTime = 10 * 365 * 24 * time.Hour / time.Second
|
const defaultExpirationTime = 10 * 365 * 24 * time.Hour / time.Second
|
||||||
|
|
||||||
|
const frostfsOpsEmail = "ops@frostfs.info"
|
||||||
|
|
||||||
func (c *initializeContext) setNNS() error {
|
func (c *initializeContext) setNNS() error {
|
||||||
nnsCs, err := c.Client.GetContractStateByID(1)
|
r := management.NewReader(c.ReadOnlyInvoker)
|
||||||
|
nnsCs, err := r.GetContractByID(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ok, err := c.nnsRootRegistered(nnsCs.Hash, "neofs")
|
ok, err := c.nnsRootRegistered(nnsCs.Hash, "frostfs")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if !ok {
|
} else if !ok {
|
||||||
bw := io.NewBufBinWriter()
|
bw := io.NewBufBinWriter()
|
||||||
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
|
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
|
||||||
"neofs", c.CommitteeAcc.Contract.ScriptHash(),
|
"frostfs", c.CommitteeAcc.Contract.ScriptHash(),
|
||||||
"ops@nspcc.ru", int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
|
frostfsOpsEmail, int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
|
||||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||||
if err := c.sendCommitteeTx(bw.Bytes(), true); err != nil {
|
if err := c.sendCommitteeTx(bw.Bytes(), true); err != nil {
|
||||||
return fmt.Errorf("can't add domain root to NNS: %w", err)
|
return fmt.Errorf("can't add domain root to NNS: %w", err)
|
||||||
|
@ -63,7 +68,7 @@ func (c *initializeContext) setNNS() error {
|
||||||
for _, ctrName := range contractList {
|
for _, ctrName := range contractList {
|
||||||
cs := c.getContract(ctrName)
|
cs := c.getContract(ctrName)
|
||||||
|
|
||||||
domain := ctrName + ".neofs"
|
domain := ctrName + ".frostfs"
|
||||||
if err := c.nnsRegisterDomain(nnsCs.Hash, cs.Hash, domain); err != nil {
|
if err := c.nnsRegisterDomain(nnsCs.Hash, cs.Hash, domain); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -82,13 +87,13 @@ func (c *initializeContext) setNNS() error {
|
||||||
|
|
||||||
func (c *initializeContext) updateNNSGroup(nnsHash util.Uint160, pub *keys.PublicKey) error {
|
func (c *initializeContext) updateNNSGroup(nnsHash util.Uint160, pub *keys.PublicKey) error {
|
||||||
bw := io.NewBufBinWriter()
|
bw := io.NewBufBinWriter()
|
||||||
needUpdate, needRegister, err := c.emitUpdateNNSGroupScript(bw, nnsHash, pub)
|
keyAlreadyAdded, domainRegCodeEmitted, err := c.emitUpdateNNSGroupScript(bw, nnsHash, pub)
|
||||||
if !needUpdate || err != nil {
|
if keyAlreadyAdded || err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
script := bw.Bytes()
|
script := bw.Bytes()
|
||||||
if needRegister {
|
if domainRegCodeEmitted {
|
||||||
w := io.NewBufBinWriter()
|
w := io.NewBufBinWriter()
|
||||||
emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1})
|
emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1})
|
||||||
wrapRegisterScriptWithPrice(w, nnsHash, script)
|
wrapRegisterScriptWithPrice(w, nnsHash, script)
|
||||||
|
@ -121,19 +126,19 @@ func (c *initializeContext) emitUpdateNNSGroupScript(bw *io.BufBinWriter, nnsHas
|
||||||
if isAvail {
|
if isAvail {
|
||||||
emit.AppCall(bw.BinWriter, nnsHash, "register", callflag.All,
|
emit.AppCall(bw.BinWriter, nnsHash, "register", callflag.All,
|
||||||
morphClient.NNSGroupKeyName, c.CommitteeAcc.Contract.ScriptHash(),
|
morphClient.NNSGroupKeyName, c.CommitteeAcc.Contract.ScriptHash(),
|
||||||
"ops@nspcc.ru", int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
|
frostfsOpsEmail, int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
|
||||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||||
}
|
}
|
||||||
|
|
||||||
emit.AppCall(bw.BinWriter, nnsHash, "deleteRecords", callflag.All, "group.neofs", int64(nns.TXT))
|
emit.AppCall(bw.BinWriter, nnsHash, "deleteRecords", callflag.All, "group.frostfs", int64(nns.TXT))
|
||||||
emit.AppCall(bw.BinWriter, nnsHash, "addRecord", callflag.All,
|
emit.AppCall(bw.BinWriter, nnsHash, "addRecord", callflag.All,
|
||||||
"group.neofs", int64(nns.TXT), hex.EncodeToString(pub.Bytes()))
|
"group.frostfs", int64(nns.TXT), hex.EncodeToString(pub.Bytes()))
|
||||||
|
|
||||||
return false, isAvail, nil
|
return false, isAvail, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAlphabetNNSDomain(i int) string {
|
func getAlphabetNNSDomain(i int) string {
|
||||||
return alphabetContract + strconv.FormatUint(uint64(i), 10) + ".neofs"
|
return alphabetContract + strconv.FormatUint(uint64(i), 10) + ".frostfs"
|
||||||
}
|
}
|
||||||
|
|
||||||
// wrapRegisterScriptWithPrice wraps a given script with `getPrice`/`setPrice` calls for NNS.
|
// wrapRegisterScriptWithPrice wraps a given script with `getPrice`/`setPrice` calls for NNS.
|
||||||
|
@ -169,7 +174,7 @@ func (c *initializeContext) nnsRegisterDomainScript(nnsHash, expectedHash util.U
|
||||||
bw := io.NewBufBinWriter()
|
bw := io.NewBufBinWriter()
|
||||||
emit.AppCall(bw.BinWriter, nnsHash, "register", callflag.All,
|
emit.AppCall(bw.BinWriter, nnsHash, "register", callflag.All,
|
||||||
domain, c.CommitteeAcc.Contract.ScriptHash(),
|
domain, c.CommitteeAcc.Contract.ScriptHash(),
|
||||||
"ops@nspcc.ru", int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
|
frostfsOpsEmail, int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
|
||||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||||
|
|
||||||
if bw.Err != nil {
|
if bw.Err != nil {
|
||||||
|
@ -228,20 +233,27 @@ func nnsResolve(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (stac
|
||||||
}
|
}
|
||||||
|
|
||||||
func nnsResolveKey(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (*keys.PublicKey, error) {
|
func nnsResolveKey(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (*keys.PublicKey, error) {
|
||||||
item, err := nnsResolve(inv, nnsHash, domain)
|
res, err := nnsResolve(inv, nnsHash, domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
v, ok := item.Value().(stackitem.Null)
|
if _, ok := res.Value().(stackitem.Null); ok {
|
||||||
if ok {
|
|
||||||
return nil, errors.New("NNS record is missing")
|
return nil, errors.New("NNS record is missing")
|
||||||
}
|
}
|
||||||
bs, err := v.TryBytes()
|
arr, ok := res.Value().([]stackitem.Item)
|
||||||
if err != nil {
|
if !ok {
|
||||||
return nil, errors.New("malformed response")
|
return nil, errors.New("API of the NNS contract method `resolve` has changed")
|
||||||
}
|
}
|
||||||
|
for i := range arr {
|
||||||
|
var bs []byte
|
||||||
|
bs, err = arr[i].TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
return keys.NewPublicKeyFromString(string(bs))
|
return keys.NewPublicKeyFromString(string(bs))
|
||||||
|
}
|
||||||
|
return nil, errors.New("no valid keys are found")
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseNNSResolveResult parses the result of resolving NNS record.
|
// parseNNSResolveResult parses the result of resolving NNS record.
|
||||||
|
@ -261,6 +273,8 @@ func parseNNSResolveResult(res stackitem.Item) (util.Uint160, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We support several formats for hash encoding, this logic should be maintained in sync
|
||||||
|
// with nnsResolve from pkg/morph/client/nns.go
|
||||||
h, err := util.Uint160DecodeStringLE(string(bs))
|
h, err := util.Uint160DecodeStringLE(string(bs))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return h, nil
|
return h, nil
|
||||||
|
@ -275,11 +289,13 @@ func parseNNSResolveResult(res stackitem.Item) (util.Uint160, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func nnsIsAvailable(c Client, nnsHash util.Uint160, name string) (bool, error) {
|
func nnsIsAvailable(c Client, nnsHash util.Uint160, name string) (bool, error) {
|
||||||
switch ct := c.(type) {
|
switch c.(type) {
|
||||||
case *rpcclient.Client:
|
case *rpcclient.Client:
|
||||||
return ct.NNSIsAvailable(nnsHash, name)
|
inv := invoker.New(c, nil)
|
||||||
|
reader := nnsClient.NewReader(inv, nnsHash)
|
||||||
|
return reader.IsAvailable(name)
|
||||||
default:
|
default:
|
||||||
b, err := unwrap.Bool(invokeFunction(c, nnsHash, "isAvailable", []interface{}{name}, nil))
|
b, err := unwrap.Bool(invokeFunction(c, nnsHash, "isAvailable", []any{name}, nil))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("`isAvailable`: invalid response: %w", err)
|
return false, fmt.Errorf("`isAvailable`: invalid response: %w", err)
|
||||||
}
|
}
|
|
@ -3,13 +3,17 @@ package morph
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
@ -18,53 +22,48 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// initialAlphabetNEOAmount represents the total amount of GAS distributed between alphabet nodes.
|
// initialAlphabetNEOAmount represents the total amount of GAS distributed between alphabet nodes.
|
||||||
const initialAlphabetNEOAmount = native.NEOTotalSupply
|
const (
|
||||||
|
initialAlphabetNEOAmount = native.NEOTotalSupply
|
||||||
func (c *initializeContext) registerCandidates() error {
|
registerBatchSize = transaction.MaxAttributes - 1
|
||||||
neoHash := c.nativeHash(nativenames.Neo)
|
)
|
||||||
|
|
||||||
cc, err := unwrap.Array(c.ReadOnlyInvoker.Call(neoHash, "getCandidates"))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("`getCandidates`: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cc) > 0 {
|
|
||||||
c.Command.Println("Candidates are already registered.")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
|
func (c *initializeContext) registerCandidateRange(start, end int) error {
|
||||||
regPrice, err := c.getCandidateRegisterPrice()
|
regPrice, err := c.getCandidateRegisterPrice()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't fetch registration price: %w", err)
|
return fmt.Errorf("can't fetch registration price: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
w := io.NewBufBinWriter()
|
w := io.NewBufBinWriter()
|
||||||
emit.AppCall(w.BinWriter, neoHash, "setRegisterPrice", callflag.States, 1)
|
emit.AppCall(w.BinWriter, neo.Hash, "setRegisterPrice", callflag.States, 1)
|
||||||
for _, acc := range c.Accounts {
|
for _, acc := range c.Accounts[start:end] {
|
||||||
emit.AppCall(w.BinWriter, neoHash, "registerCandidate", callflag.States, acc.PrivateKey().PublicKey().Bytes())
|
emit.AppCall(w.BinWriter, neo.Hash, "registerCandidate", callflag.States, acc.PrivateKey().PublicKey().Bytes())
|
||||||
emit.Opcodes(w.BinWriter, opcode.ASSERT)
|
emit.Opcodes(w.BinWriter, opcode.ASSERT)
|
||||||
}
|
}
|
||||||
emit.AppCall(w.BinWriter, neoHash, "setRegisterPrice", callflag.States, regPrice)
|
emit.AppCall(w.BinWriter, neo.Hash, "setRegisterPrice", callflag.States, regPrice)
|
||||||
if w.Err != nil {
|
if w.Err != nil {
|
||||||
panic(fmt.Sprintf("BUG: %v", w.Err))
|
panic(fmt.Sprintf("BUG: %v", w.Err))
|
||||||
}
|
}
|
||||||
|
|
||||||
signers := []rpcclient.SignerAccount{{
|
signers := []actor.SignerAccount{{
|
||||||
Signer: c.getSigner(false, c.CommitteeAcc),
|
Signer: c.getSigner(false, c.CommitteeAcc),
|
||||||
Account: c.CommitteeAcc,
|
Account: c.CommitteeAcc,
|
||||||
}}
|
}}
|
||||||
for i := range c.Accounts {
|
for _, acc := range c.Accounts[start:end] {
|
||||||
signers = append(signers, rpcclient.SignerAccount{
|
signers = append(signers, actor.SignerAccount{
|
||||||
Signer: transaction.Signer{
|
Signer: transaction.Signer{
|
||||||
Account: c.Accounts[i].Contract.ScriptHash(),
|
Account: acc.Contract.ScriptHash(),
|
||||||
Scopes: transaction.CustomContracts,
|
Scopes: transaction.CustomContracts,
|
||||||
AllowedContracts: []util.Uint160{neoHash},
|
AllowedContracts: []util.Uint160{neo.Hash},
|
||||||
},
|
},
|
||||||
Account: c.Accounts[i],
|
Account: acc,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
tx, err := c.Client.CreateTxFromScript(w.Bytes(), c.CommitteeAcc, -1, 0, signers)
|
act, err := actor.New(c.Client, signers)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't create actor: %w", err)
|
||||||
|
}
|
||||||
|
tx, err := act.MakeRun(w.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't create tx: %w", err)
|
return fmt.Errorf("can't create tx: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -73,8 +72,8 @@ func (c *initializeContext) registerCandidates() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
network := c.CommitteeAct.GetNetwork()
|
network := c.CommitteeAct.GetNetwork()
|
||||||
for i := range c.Accounts {
|
for _, acc := range c.Accounts[start:end] {
|
||||||
if err := c.Accounts[i].SignTx(network, tx); err != nil {
|
if err := acc.SignTx(network, tx); err != nil {
|
||||||
return fmt.Errorf("can't sign a transaction: %w", err)
|
return fmt.Errorf("can't sign a transaction: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,8 +81,41 @@ func (c *initializeContext) registerCandidates() error {
|
||||||
return c.sendTx(tx, c.Command, true)
|
return c.sendTx(tx, c.Command, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *initializeContext) registerCandidates() error {
|
||||||
|
cc, err := unwrap.Array(c.ReadOnlyInvoker.Call(neo.Hash, "getCandidates"))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("`getCandidates`: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
need := len(c.Accounts)
|
||||||
|
have := len(cc)
|
||||||
|
|
||||||
|
if need == have {
|
||||||
|
c.Command.Println("Candidates are already registered.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register candidates in batches in order to overcome the signers amount limit.
|
||||||
|
// See: https://github.com/nspcc-dev/neo-go/blob/master/pkg/core/transaction/transaction.go#L27
|
||||||
|
for i := 0; i < need; i += registerBatchSize {
|
||||||
|
start, end := i, i+registerBatchSize
|
||||||
|
if end > need {
|
||||||
|
end = need
|
||||||
|
}
|
||||||
|
// This check is sound because transactions are accepted/rejected atomically.
|
||||||
|
if have >= end {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := c.registerCandidateRange(start, end); err != nil {
|
||||||
|
return fmt.Errorf("registering candidates %d..%d: %q", start, end-1, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *initializeContext) transferNEOToAlphabetContracts() error {
|
func (c *initializeContext) transferNEOToAlphabetContracts() error {
|
||||||
neoHash := c.nativeHash(nativenames.Neo)
|
neoHash := neo.Hash
|
||||||
|
|
||||||
ok, err := c.transferNEOFinished(neoHash)
|
ok, err := c.transferNEOFinished(neoHash)
|
||||||
if ok || err != nil {
|
if ok || err != nil {
|
||||||
|
@ -109,18 +141,21 @@ func (c *initializeContext) transferNEOToAlphabetContracts() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *initializeContext) transferNEOFinished(neoHash util.Uint160) (bool, error) {
|
func (c *initializeContext) transferNEOFinished(neoHash util.Uint160) (bool, error) {
|
||||||
bal, err := c.Client.NEP17BalanceOf(neoHash, c.CommitteeAcc.Contract.ScriptHash())
|
r := nep17.NewReader(c.ReadOnlyInvoker, neoHash)
|
||||||
return bal < native.NEOTotalSupply, err
|
bal, err := r.BalanceOf(c.CommitteeAcc.Contract.ScriptHash())
|
||||||
|
return bal.Cmp(big.NewInt(native.NEOTotalSupply)) == -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var errGetPriceInvalid = errors.New("`getRegisterPrice`: invalid response")
|
var errGetPriceInvalid = errors.New("`getRegisterPrice`: invalid response")
|
||||||
|
|
||||||
func (c *initializeContext) getCandidateRegisterPrice() (int64, error) {
|
func (c *initializeContext) getCandidateRegisterPrice() (int64, error) {
|
||||||
switch ct := c.Client.(type) {
|
switch c.Client.(type) {
|
||||||
case *rpcclient.Client:
|
case *rpcclient.Client:
|
||||||
return ct.GetCandidateRegisterPrice()
|
inv := invoker.New(c.Client, nil)
|
||||||
|
reader := neo.NewReader(inv)
|
||||||
|
return reader.GetRegisterPrice()
|
||||||
default:
|
default:
|
||||||
neoHash := c.nativeHash(nativenames.Neo)
|
neoHash := neo.Hash
|
||||||
res, err := invokeFunction(c.Client, neoHash, "getRegisterPrice", nil, nil)
|
res, err := invokeFunction(c.Client, neoHash, "getRegisterPrice", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
|
@ -1,9 +1,9 @@
|
||||||
package morph
|
package morph
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/rolemgmt"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
)
|
)
|
||||||
|
@ -16,17 +16,15 @@ func (c *initializeContext) setNotaryAndAlphabetNodes() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
designateHash := c.nativeHash(nativenames.Designation)
|
var pubs []any
|
||||||
|
|
||||||
var pubs []interface{}
|
|
||||||
for _, acc := range c.Accounts {
|
for _, acc := range c.Accounts {
|
||||||
pubs = append(pubs, acc.PrivateKey().PublicKey().Bytes())
|
pubs = append(pubs, acc.PrivateKey().PublicKey().Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
w := io.NewBufBinWriter()
|
w := io.NewBufBinWriter()
|
||||||
emit.AppCall(w.BinWriter, designateHash, "designateAsRole",
|
emit.AppCall(w.BinWriter, rolemgmt.Hash, "designateAsRole",
|
||||||
callflag.States|callflag.AllowNotify, int64(noderoles.P2PNotary), pubs)
|
callflag.States|callflag.AllowNotify, int64(noderoles.P2PNotary), pubs)
|
||||||
emit.AppCall(w.BinWriter, designateHash, "designateAsRole",
|
emit.AppCall(w.BinWriter, rolemgmt.Hash, "designateAsRole",
|
||||||
callflag.States|callflag.AllowNotify, int64(noderoles.NeoFSAlphabet), pubs)
|
callflag.States|callflag.AllowNotify, int64(noderoles.NeoFSAlphabet), pubs)
|
||||||
|
|
||||||
if err := c.sendCommitteeTx(w.Bytes(), false); err != nil {
|
if err := c.sendCommitteeTx(w.Bytes(), false); err != nil {
|
||||||
|
@ -42,7 +40,6 @@ func (c *initializeContext) setRolesFinished() (bool, error) {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
h := c.nativeHash(nativenames.Designation)
|
pubs, err := getDesignatedByRole(c.ReadOnlyInvoker, rolemgmt.Hash, noderoles.NeoFSAlphabet, height)
|
||||||
pubs, err := getDesignatedByRole(c.ReadOnlyInvoker, h, noderoles.NeoFSAlphabet, height)
|
|
||||||
return len(pubs) == len(c.Wallets), err
|
return len(pubs) == len(c.Wallets), err
|
||||||
}
|
}
|
182
cmd/frostfs-adm/internal/modules/morph/initialize_test.go
Normal file
182
cmd/frostfs-adm/internal/modules/morph/initialize_test.go
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
package morph
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
contractsPath = "../../../../../../contract/frostfs-contract-v0.18.0.tar.gz"
|
||||||
|
protoFileName = "proto.yml"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInitialize(t *testing.T) {
|
||||||
|
// This test needs frostfs-contract tarball, so it is skipped by default.
|
||||||
|
// It is here for performing local testing after the changes.
|
||||||
|
t.Skip()
|
||||||
|
|
||||||
|
t.Run("1 nodes", func(t *testing.T) {
|
||||||
|
testInitialize(t, 1)
|
||||||
|
})
|
||||||
|
t.Run("4 nodes", func(t *testing.T) {
|
||||||
|
testInitialize(t, 4)
|
||||||
|
})
|
||||||
|
t.Run("7 nodes", func(t *testing.T) {
|
||||||
|
testInitialize(t, 7)
|
||||||
|
})
|
||||||
|
t.Run("16 nodes", func(t *testing.T) {
|
||||||
|
testInitialize(t, 16)
|
||||||
|
})
|
||||||
|
t.Run("max nodes", func(t *testing.T) {
|
||||||
|
testInitialize(t, maxAlphabetNodes)
|
||||||
|
})
|
||||||
|
t.Run("too many nodes", func(t *testing.T) {
|
||||||
|
require.ErrorIs(t, generateTestData(t, t.TempDir(), maxAlphabetNodes+1), ErrTooManyAlphabetNodes)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInitialize(t *testing.T, committeeSize int) {
|
||||||
|
testdataDir := t.TempDir()
|
||||||
|
v := viper.GetViper()
|
||||||
|
|
||||||
|
require.NoError(t, generateTestData(t, testdataDir, committeeSize))
|
||||||
|
v.Set(protoConfigPath, filepath.Join(testdataDir, protoFileName))
|
||||||
|
|
||||||
|
// Set to the path or remove the next statement to download from the network.
|
||||||
|
require.NoError(t, initCmd.Flags().Set(contractsInitFlag, contractsPath))
|
||||||
|
|
||||||
|
dumpPath := filepath.Join(testdataDir, "out")
|
||||||
|
require.NoError(t, initCmd.Flags().Set(localDumpFlag, dumpPath))
|
||||||
|
v.Set(alphabetWalletsFlag, testdataDir)
|
||||||
|
v.Set(epochDurationInitFlag, 1)
|
||||||
|
v.Set(maxObjectSizeInitFlag, 1024)
|
||||||
|
|
||||||
|
setTestCredentials(v, committeeSize)
|
||||||
|
require.NoError(t, initializeSideChainCmd(initCmd, nil))
|
||||||
|
|
||||||
|
t.Run("force-new-epoch", func(t *testing.T) {
|
||||||
|
require.NoError(t, forceNewEpoch.Flags().Set(localDumpFlag, dumpPath))
|
||||||
|
require.NoError(t, forceNewEpochCmd(forceNewEpoch, nil))
|
||||||
|
})
|
||||||
|
t.Run("set-config", func(t *testing.T) {
|
||||||
|
require.NoError(t, setConfig.Flags().Set(localDumpFlag, dumpPath))
|
||||||
|
require.NoError(t, setConfigCmd(setConfig, []string{"MaintenanceModeAllowed=true"}))
|
||||||
|
})
|
||||||
|
t.Run("set-policy", func(t *testing.T) {
|
||||||
|
require.NoError(t, setPolicy.Flags().Set(localDumpFlag, dumpPath))
|
||||||
|
require.NoError(t, setPolicyCmd(setPolicy, []string{"ExecFeeFactor=1"}))
|
||||||
|
})
|
||||||
|
t.Run("remove-node", func(t *testing.T) {
|
||||||
|
pk, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
pub := hex.EncodeToString(pk.PublicKey().Bytes())
|
||||||
|
require.NoError(t, removeNodes.Flags().Set(localDumpFlag, dumpPath))
|
||||||
|
require.NoError(t, removeNodesCmd(removeNodes, []string{pub}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateTestData(t *testing.T, dir string, size int) error {
|
||||||
|
v := viper.GetViper()
|
||||||
|
v.Set(alphabetWalletsFlag, dir)
|
||||||
|
|
||||||
|
sizeStr := strconv.FormatUint(uint64(size), 10)
|
||||||
|
if err := generateAlphabetCmd.Flags().Set(alphabetSizeFlag, sizeStr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
setTestCredentials(v, size)
|
||||||
|
if err := generateAlphabetCreds(generateAlphabetCmd, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var pubs []string
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
p := filepath.Join(dir, innerring.GlagoliticLetter(i).String()+".json")
|
||||||
|
w, err := wallet.NewWalletFromFile(p)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("wallet doesn't exist: %w", err)
|
||||||
|
}
|
||||||
|
for _, acc := range w.Accounts {
|
||||||
|
if acc.Label == singleAccountName {
|
||||||
|
pub, ok := vm.ParseSignatureContract(acc.Contract.Script)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("could not parse signature script for %s", acc.Address)
|
||||||
|
}
|
||||||
|
pubs = append(pubs, hex.EncodeToString(pub))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := config.Config{}
|
||||||
|
cfg.ProtocolConfiguration.Magic = 12345
|
||||||
|
cfg.ProtocolConfiguration.ValidatorsCount = uint32(size)
|
||||||
|
cfg.ProtocolConfiguration.TimePerBlock = time.Second
|
||||||
|
cfg.ProtocolConfiguration.StandbyCommittee = pubs // sorted by glagolic letters
|
||||||
|
cfg.ProtocolConfiguration.P2PSigExtensions = true
|
||||||
|
cfg.ProtocolConfiguration.VerifyTransactions = true
|
||||||
|
data, err := yaml.Marshal(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
protoPath := filepath.Join(dir, protoFileName)
|
||||||
|
return os.WriteFile(protoPath, data, os.ModePerm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setTestCredentials(v *viper.Viper, size int) {
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
v.Set("credentials."+innerring.GlagoliticLetter(i).String(), strconv.FormatUint(uint64(i), 10))
|
||||||
|
}
|
||||||
|
v.Set("credentials.contract", testContractPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNextPollInterval(t *testing.T) {
|
||||||
|
var pollInterval time.Duration
|
||||||
|
var iteration int
|
||||||
|
|
||||||
|
pollInterval, hasChanged := nextPollInterval(iteration, pollInterval)
|
||||||
|
require.True(t, hasChanged)
|
||||||
|
require.Equal(t, time.Second, pollInterval)
|
||||||
|
|
||||||
|
iteration = 4
|
||||||
|
pollInterval, hasChanged = nextPollInterval(iteration, pollInterval)
|
||||||
|
require.False(t, hasChanged)
|
||||||
|
require.Equal(t, time.Second, pollInterval)
|
||||||
|
|
||||||
|
iteration = 5
|
||||||
|
pollInterval, hasChanged = nextPollInterval(iteration, pollInterval)
|
||||||
|
require.True(t, hasChanged)
|
||||||
|
require.Equal(t, 2*time.Second, pollInterval)
|
||||||
|
|
||||||
|
iteration = 10
|
||||||
|
pollInterval, hasChanged = nextPollInterval(iteration, pollInterval)
|
||||||
|
require.True(t, hasChanged)
|
||||||
|
require.Equal(t, 4*time.Second, pollInterval)
|
||||||
|
|
||||||
|
iteration = 20
|
||||||
|
pollInterval = 32 * time.Second
|
||||||
|
pollInterval, hasChanged = nextPollInterval(iteration, pollInterval)
|
||||||
|
require.True(t, hasChanged) // from 32s to 16s
|
||||||
|
require.Equal(t, 16*time.Second, pollInterval)
|
||||||
|
|
||||||
|
pollInterval = 16 * time.Second
|
||||||
|
pollInterval, hasChanged = nextPollInterval(iteration, pollInterval)
|
||||||
|
require.False(t, hasChanged)
|
||||||
|
require.Equal(t, 16*time.Second, pollInterval)
|
||||||
|
}
|
|
@ -2,14 +2,18 @@ package morph
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
scContext "github.com/nspcc-dev/neo-go/pkg/smartcontract/context"
|
scContext "github.com/nspcc-dev/neo-go/pkg/smartcontract/context"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
|
@ -32,15 +36,12 @@ func (c *initializeContext) transferFunds() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
gasHash := c.nativeHash(nativenames.Gas)
|
var transfers []transferTarget
|
||||||
neoHash := c.nativeHash(nativenames.Neo)
|
|
||||||
|
|
||||||
var transfers []rpcclient.TransferTarget
|
|
||||||
for _, acc := range c.Accounts {
|
for _, acc := range c.Accounts {
|
||||||
to := acc.Contract.ScriptHash()
|
to := acc.Contract.ScriptHash()
|
||||||
transfers = append(transfers,
|
transfers = append(transfers,
|
||||||
rpcclient.TransferTarget{
|
transferTarget{
|
||||||
Token: gasHash,
|
Token: gas.Hash,
|
||||||
Address: to,
|
Address: to,
|
||||||
Amount: initialAlphabetGASAmount,
|
Amount: initialAlphabetGASAmount,
|
||||||
},
|
},
|
||||||
|
@ -49,25 +50,19 @@ func (c *initializeContext) transferFunds() error {
|
||||||
|
|
||||||
// It is convenient to have all funds at the committee account.
|
// It is convenient to have all funds at the committee account.
|
||||||
transfers = append(transfers,
|
transfers = append(transfers,
|
||||||
rpcclient.TransferTarget{
|
transferTarget{
|
||||||
Token: gasHash,
|
Token: gas.Hash,
|
||||||
Address: c.CommitteeAcc.Contract.ScriptHash(),
|
Address: c.CommitteeAcc.Contract.ScriptHash(),
|
||||||
Amount: (gasInitialTotalSupply - initialAlphabetGASAmount*int64(len(c.Wallets))) / 2,
|
Amount: (gasInitialTotalSupply - initialAlphabetGASAmount*int64(len(c.Wallets))) / 2,
|
||||||
},
|
},
|
||||||
rpcclient.TransferTarget{
|
transferTarget{
|
||||||
Token: neoHash,
|
Token: neo.Hash,
|
||||||
Address: c.CommitteeAcc.Contract.ScriptHash(),
|
Address: c.CommitteeAcc.Contract.ScriptHash(),
|
||||||
Amount: native.NEOTotalSupply,
|
Amount: native.NEOTotalSupply,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
tx, err := createNEP17MultiTransferTx(c.Client, c.ConsensusAcc, 0, transfers, []rpcclient.SignerAccount{{
|
tx, err := createNEP17MultiTransferTx(c.Client, c.ConsensusAcc, transfers)
|
||||||
Signer: transaction.Signer{
|
|
||||||
Account: c.ConsensusAcc.Contract.ScriptHash(),
|
|
||||||
Scopes: transaction.CalledByEntry,
|
|
||||||
},
|
|
||||||
Account: c.ConsensusAcc,
|
|
||||||
}})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't create transfer transaction: %w", err)
|
return fmt.Errorf("can't create transfer transaction: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -80,11 +75,11 @@ func (c *initializeContext) transferFunds() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *initializeContext) transferFundsFinished() (bool, error) {
|
func (c *initializeContext) transferFundsFinished() (bool, error) {
|
||||||
gasHash := c.nativeHash(nativenames.Gas)
|
|
||||||
acc := c.Accounts[0]
|
acc := c.Accounts[0]
|
||||||
|
|
||||||
res, err := c.Client.NEP17BalanceOf(gasHash, acc.Contract.ScriptHash())
|
r := nep17.NewReader(c.ReadOnlyInvoker, gas.Hash)
|
||||||
return res > initialAlphabetGASAmount/2, err
|
res, err := r.BalanceOf(acc.Contract.ScriptHash())
|
||||||
|
return res.Cmp(big.NewInt(initialAlphabetGASAmount/2)) == 1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *initializeContext) multiSignAndSend(tx *transaction.Transaction, accType string) error {
|
func (c *initializeContext) multiSignAndSend(tx *transaction.Transaction, accType string) error {
|
||||||
|
@ -96,12 +91,13 @@ func (c *initializeContext) multiSignAndSend(tx *transaction.Transaction, accTyp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *initializeContext) multiSign(tx *transaction.Transaction, accType string) error {
|
func (c *initializeContext) multiSign(tx *transaction.Transaction, accType string) error {
|
||||||
network, err := c.Client.GetNetwork()
|
version, err := c.Client.GetVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// error appears only if client
|
// error appears only if client
|
||||||
// has not been initialized
|
// has not been initialized
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
network := version.Protocol.Network
|
||||||
|
|
||||||
// Use parameter context to avoid dealing with signature order.
|
// Use parameter context to avoid dealing with signature order.
|
||||||
pc := scContext.NewParameterContext("", network, tx)
|
pc := scContext.NewParameterContext("", network, tx)
|
||||||
|
@ -147,19 +143,19 @@ func (c *initializeContext) multiSign(tx *transaction.Transaction, accType strin
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *initializeContext) transferGASToProxy() error {
|
func (c *initializeContext) transferGASToProxy() error {
|
||||||
gasHash := c.nativeHash(nativenames.Gas)
|
|
||||||
proxyCs := c.getContract(proxyContract)
|
proxyCs := c.getContract(proxyContract)
|
||||||
|
|
||||||
bal, err := c.Client.NEP17BalanceOf(gasHash, proxyCs.Hash)
|
r := nep17.NewReader(c.ReadOnlyInvoker, gas.Hash)
|
||||||
if err != nil || bal > 0 {
|
bal, err := r.BalanceOf(proxyCs.Hash)
|
||||||
|
if err != nil || bal.Sign() > 0 {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tx, err := createNEP17MultiTransferTx(c.Client, c.CommitteeAcc, 0, []rpcclient.TransferTarget{{
|
tx, err := createNEP17MultiTransferTx(c.Client, c.CommitteeAcc, []transferTarget{{
|
||||||
Token: gasHash,
|
Token: gas.Hash,
|
||||||
Address: proxyCs.Hash,
|
Address: proxyCs.Hash,
|
||||||
Amount: initialProxyGASAmount,
|
Amount: initialProxyGASAmount,
|
||||||
}}, nil)
|
}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -171,8 +167,14 @@ func (c *initializeContext) transferGASToProxy() error {
|
||||||
return c.awaitTx()
|
return c.awaitTx()
|
||||||
}
|
}
|
||||||
|
|
||||||
func createNEP17MultiTransferTx(c Client, acc *wallet.Account, netFee int64,
|
type transferTarget struct {
|
||||||
recipients []rpcclient.TransferTarget, cosigners []rpcclient.SignerAccount) (*transaction.Transaction, error) {
|
Token util.Uint160
|
||||||
|
Address util.Uint160
|
||||||
|
Amount int64
|
||||||
|
Data any
|
||||||
|
}
|
||||||
|
|
||||||
|
func createNEP17MultiTransferTx(c Client, acc *wallet.Account, recipients []transferTarget) (*transaction.Transaction, error) {
|
||||||
from := acc.Contract.ScriptHash()
|
from := acc.Contract.ScriptHash()
|
||||||
|
|
||||||
w := io.NewBufBinWriter()
|
w := io.NewBufBinWriter()
|
||||||
|
@ -184,11 +186,18 @@ func createNEP17MultiTransferTx(c Client, acc *wallet.Account, netFee int64,
|
||||||
if w.Err != nil {
|
if w.Err != nil {
|
||||||
return nil, fmt.Errorf("failed to create transfer script: %w", w.Err)
|
return nil, fmt.Errorf("failed to create transfer script: %w", w.Err)
|
||||||
}
|
}
|
||||||
return c.CreateTxFromScript(w.Bytes(), acc, -1, netFee, append([]rpcclient.SignerAccount{{
|
|
||||||
|
signers := []actor.SignerAccount{{
|
||||||
Signer: transaction.Signer{
|
Signer: transaction.Signer{
|
||||||
Account: from,
|
Account: acc.Contract.ScriptHash(),
|
||||||
Scopes: transaction.CalledByEntry,
|
Scopes: transaction.CalledByEntry,
|
||||||
},
|
},
|
||||||
Account: acc,
|
Account: acc,
|
||||||
}}, cosigners...))
|
}}
|
||||||
|
|
||||||
|
act, err := actor.New(c, signers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't create actor: %w", err)
|
||||||
|
}
|
||||||
|
return act.MakeRun(w.Bytes())
|
||||||
}
|
}
|
|
@ -1,41 +1,37 @@
|
||||||
package morph
|
package morph
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core"
|
"github.com/nspcc-dev/neo-go/pkg/core"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/chaindump"
|
"github.com/nspcc-dev/neo-go/pkg/core/chaindump"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/fee"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
@ -50,18 +46,18 @@ type localClient struct {
|
||||||
maxGasInvoke int64
|
maxGasInvoke int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func newLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet) (*localClient, error) {
|
func newLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet, dumpPath string) (*localClient, error) {
|
||||||
cfg, err := config.LoadFile(v.GetString(protoConfigPath))
|
cfg, err := config.LoadFile(v.GetString(protoConfigPath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
bc, err := core.NewBlockchain(storage.NewMemoryStore(), cfg.ProtocolConfiguration, zap.NewNop())
|
bc, err := core.NewBlockchain(storage.NewMemoryStore(), cfg.Blockchain(), zap.NewNop())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
m := smartcontract.GetDefaultHonestNodeCount(cfg.ProtocolConfiguration.ValidatorsCount)
|
m := smartcontract.GetDefaultHonestNodeCount(int(cfg.ProtocolConfiguration.ValidatorsCount))
|
||||||
accounts := make([]*wallet.Account, len(wallets))
|
accounts := make([]*wallet.Account, len(wallets))
|
||||||
for i := range accounts {
|
for i := range accounts {
|
||||||
accounts[i], err = getWalletAccount(wallets[i], consensusAccountName)
|
accounts[i], err = getWalletAccount(wallets[i], consensusAccountName)
|
||||||
|
@ -81,16 +77,13 @@ func newLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet
|
||||||
return indexMap[string(pi)] < indexMap[string(pj)]
|
return indexMap[string(pi)] < indexMap[string(pj)]
|
||||||
})
|
})
|
||||||
sort.Slice(accounts[:cfg.ProtocolConfiguration.ValidatorsCount], func(i, j int) bool {
|
sort.Slice(accounts[:cfg.ProtocolConfiguration.ValidatorsCount], func(i, j int) bool {
|
||||||
pi := accounts[i].PrivateKey().PublicKey().Bytes()
|
return accounts[i].PublicKey().Cmp(accounts[j].PublicKey()) == -1
|
||||||
pj := accounts[j].PrivateKey().PublicKey().Bytes()
|
|
||||||
return bytes.Compare(pi, pj) == -1
|
|
||||||
})
|
})
|
||||||
|
|
||||||
go bc.Run()
|
go bc.Run()
|
||||||
|
|
||||||
dumpPath := v.GetString(localDumpFlag)
|
|
||||||
if cmd.Name() != "init" {
|
if cmd.Name() != "init" {
|
||||||
f, err := os.OpenFile(dumpPath, os.O_RDONLY, 0600)
|
f, err := os.OpenFile(dumpPath, os.O_RDONLY, 0o600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't open local dump: %w", err)
|
return nil, fmt.Errorf("can't open local dump: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -121,29 +114,10 @@ func (l *localClient) GetBlockCount() (uint32, error) {
|
||||||
return l.bc.BlockHeight(), nil
|
return l.bc.BlockHeight(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *localClient) GetContractStateByID(id int32) (*state.Contract, error) {
|
|
||||||
h, err := l.bc.GetContractScriptHash(id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return l.GetContractStateByHash(h)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *localClient) GetContractStateByHash(h util.Uint160) (*state.Contract, error) {
|
|
||||||
if cs := l.bc.GetContractState(h); cs != nil {
|
|
||||||
return cs, nil
|
|
||||||
}
|
|
||||||
return nil, storage.ErrKeyNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *localClient) GetNativeContracts() ([]state.NativeContract, error) {
|
func (l *localClient) GetNativeContracts() ([]state.NativeContract, error) {
|
||||||
return l.bc.GetNatives(), nil
|
return l.bc.GetNatives(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *localClient) GetNetwork() (netmode.Magic, error) {
|
|
||||||
return l.bc.GetConfig().Magic, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *localClient) GetApplicationLog(h util.Uint256, t *trigger.Type) (*result.ApplicationLog, error) {
|
func (l *localClient) GetApplicationLog(h util.Uint256, t *trigger.Type) (*result.ApplicationLog, error) {
|
||||||
aer, err := l.bc.GetAppExecResults(h, *t)
|
aer, err := l.bc.GetAppExecResults(h, *t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -154,34 +128,6 @@ func (l *localClient) GetApplicationLog(h util.Uint256, t *trigger.Type) (*resul
|
||||||
return &a, nil
|
return &a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *localClient) CreateTxFromScript(script []byte, acc *wallet.Account, sysFee int64, netFee int64, cosigners []rpcclient.SignerAccount) (*transaction.Transaction, error) {
|
|
||||||
signers, accounts, err := getSigners(acc, cosigners)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to construct tx signers: %w", err)
|
|
||||||
}
|
|
||||||
if sysFee < 0 {
|
|
||||||
res, err := l.InvokeScript(script, signers)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("can't add system fee to transaction: %w", err)
|
|
||||||
}
|
|
||||||
if res.State != "HALT" {
|
|
||||||
return nil, fmt.Errorf("can't add system fee to transaction: bad vm state: %s due to an error: %s", res.State, res.FaultException)
|
|
||||||
}
|
|
||||||
sysFee = res.GasConsumed
|
|
||||||
}
|
|
||||||
|
|
||||||
tx := transaction.New(script, sysFee)
|
|
||||||
tx.Signers = signers
|
|
||||||
tx.ValidUntilBlock = l.bc.BlockHeight() + 2
|
|
||||||
|
|
||||||
err = l.AddNetworkFee(tx, netFee, accounts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to add network fee: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return tx, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *localClient) GetCommittee() (keys.PublicKeys, error) {
|
func (l *localClient) GetCommittee() (keys.PublicKeys, error) {
|
||||||
// not used by `morph init` command
|
// not used by `morph init` command
|
||||||
panic("unexpected call")
|
panic("unexpected call")
|
||||||
|
@ -191,7 +137,7 @@ func (l *localClient) GetCommittee() (keys.PublicKeys, error) {
|
||||||
func (l *localClient) InvokeFunction(h util.Uint160, method string, sPrm []smartcontract.Parameter, ss []transaction.Signer) (*result.Invoke, error) {
|
func (l *localClient) InvokeFunction(h util.Uint160, method string, sPrm []smartcontract.Parameter, ss []transaction.Signer) (*result.Invoke, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
pp := make([]interface{}, len(sPrm))
|
pp := make([]any, len(sPrm))
|
||||||
for i, p := range sPrm {
|
for i, p := range sPrm {
|
||||||
pp[i], err = smartcontract.ExpandParameterToEmitable(p)
|
pp[i], err = smartcontract.ExpandParameterToEmitable(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -202,21 +148,6 @@ func (l *localClient) InvokeFunction(h util.Uint160, method string, sPrm []smart
|
||||||
return invokeFunction(l, h, method, pp, ss)
|
return invokeFunction(l, h, method, pp, ss)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *localClient) CalculateNotaryFee(_ uint8) (int64, error) {
|
|
||||||
// not used by `morph init` command
|
|
||||||
panic("unexpected call")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *localClient) SignAndPushP2PNotaryRequest(_ *transaction.Transaction, _ []byte, _ int64, _ int64, _ uint32, _ *wallet.Account) (*payload.P2PNotaryRequest, error) {
|
|
||||||
// not used by `morph init` command
|
|
||||||
panic("unexpected call")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *localClient) SignAndPushInvocationTx(_ []byte, _ *wallet.Account, _ int64, _ fixedn.Fixed8, _ []rpcclient.SignerAccount) (util.Uint256, error) {
|
|
||||||
// not used by `morph init` command
|
|
||||||
panic("unexpected call")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *localClient) TerminateSession(_ uuid.UUID) (bool, error) {
|
func (l *localClient) TerminateSession(_ uuid.UUID) (bool, error) {
|
||||||
// not used by `morph init` command
|
// not used by `morph init` command
|
||||||
panic("unexpected call")
|
panic("unexpected call")
|
||||||
|
@ -229,120 +160,103 @@ func (l *localClient) TraverseIterator(_, _ uuid.UUID, _ int) ([]stackitem.Item,
|
||||||
|
|
||||||
// GetVersion return default version.
|
// GetVersion return default version.
|
||||||
func (l *localClient) GetVersion() (*result.Version, error) {
|
func (l *localClient) GetVersion() (*result.Version, error) {
|
||||||
return &result.Version{}, nil
|
c := l.bc.GetConfig()
|
||||||
|
return &result.Version{
|
||||||
|
Protocol: result.Protocol{
|
||||||
|
AddressVersion: address.NEO3Prefix,
|
||||||
|
Network: c.Magic,
|
||||||
|
MillisecondsPerBlock: int(c.TimePerBlock / time.Millisecond),
|
||||||
|
MaxTraceableBlocks: c.MaxTraceableBlocks,
|
||||||
|
MaxValidUntilBlockIncrement: c.MaxValidUntilBlockIncrement,
|
||||||
|
MaxTransactionsPerBlock: c.MaxTransactionsPerBlock,
|
||||||
|
MemoryPoolMaxTransactions: c.MemPoolSize,
|
||||||
|
ValidatorsCount: byte(c.ValidatorsCount),
|
||||||
|
InitialGasDistribution: c.InitialGASSupply,
|
||||||
|
CommitteeHistory: c.CommitteeHistory,
|
||||||
|
P2PSigExtensions: c.P2PSigExtensions,
|
||||||
|
StateRootInHeader: c.StateRootInHeader,
|
||||||
|
ValidatorsHistory: c.ValidatorsHistory,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *localClient) InvokeContractVerify(contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
|
func (l *localClient) InvokeContractVerify(util.Uint160, []smartcontract.Parameter, []transaction.Signer, ...transaction.Witness) (*result.Invoke, error) {
|
||||||
// not used by `morph init` command
|
// not used by `morph init` command
|
||||||
panic("unexpected call")
|
panic("unexpected call")
|
||||||
}
|
}
|
||||||
|
|
||||||
// CalculateNetworkFee calculates network fee for the given transaction.
|
// CalculateNetworkFee calculates network fee for the given transaction.
|
||||||
// Copied from neo-go with minor corrections (no need to support non-notary mode):
|
// Copied from neo-go with minor corrections (no need to support non-notary mode):
|
||||||
// https://github.com/nspcc-dev/neo-go/blob/v0.99.2/pkg/services/rpcsrv/server.go#L744
|
// https://github.com/nspcc-dev/neo-go/blob/v0.103.0/pkg/services/rpcsrv/server.go#L911
|
||||||
func (l *localClient) CalculateNetworkFee(tx *transaction.Transaction) (int64, error) {
|
func (l *localClient) CalculateNetworkFee(tx *transaction.Transaction) (int64, error) {
|
||||||
hashablePart, err := tx.EncodeHashableFields()
|
// Avoid setting hash for this tx: server code doesn't touch client transaction.
|
||||||
if err != nil {
|
data := tx.Bytes()
|
||||||
return 0, fmt.Errorf("failed to compute tx size: %w", err)
|
tx, err := transaction.NewTransactionFromBytes(data)
|
||||||
}
|
|
||||||
|
|
||||||
size := len(hashablePart) + io.GetVarSize(len(tx.Signers))
|
|
||||||
ef := l.bc.GetBaseExecFee()
|
|
||||||
|
|
||||||
var netFee int64
|
|
||||||
for i, signer := range tx.Signers {
|
|
||||||
var verificationScript []byte
|
|
||||||
for _, w := range tx.Scripts {
|
|
||||||
if w.VerificationScript != nil && hash.Hash160(w.VerificationScript).Equals(signer.Account) {
|
|
||||||
verificationScript = w.VerificationScript
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if verificationScript == nil {
|
|
||||||
gasConsumed, err := l.bc.VerifyWitness(signer.Account, tx, &tx.Scripts[i], l.maxGasInvoke)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("invalid signature: %w", err)
|
|
||||||
}
|
|
||||||
netFee += gasConsumed
|
|
||||||
size += io.GetVarSize([]byte{}) + io.GetVarSize(tx.Scripts[i].InvocationScript)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fee, sizeDelta := fee.Calculate(ef, verificationScript)
|
|
||||||
netFee += fee
|
|
||||||
size += sizeDelta
|
|
||||||
}
|
|
||||||
|
|
||||||
fee := l.bc.FeePerByte()
|
|
||||||
netFee += int64(size) * fee
|
|
||||||
|
|
||||||
return netFee, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddNetworkFee adds network fee for each witness script and optional extra
|
|
||||||
// network fee to transaction. `accs` is an array signer's accounts.
|
|
||||||
// Copied from neo-go with minor corrections (no need to support contract signers):
|
|
||||||
// https://github.com/nspcc-dev/neo-go/blob/6ff11baa1b9e4c71ef0d1de43b92a8c541ca732c/pkg/rpc/client/rpc.go#L960
|
|
||||||
func (l *localClient) AddNetworkFee(tx *transaction.Transaction, extraFee int64, accs ...*wallet.Account) error {
|
|
||||||
if len(tx.Signers) != len(accs) {
|
|
||||||
return errors.New("number of signers must match number of scripts")
|
|
||||||
}
|
|
||||||
|
|
||||||
size := io.GetVarSize(tx)
|
|
||||||
ef := l.bc.GetBaseExecFee()
|
|
||||||
for i := range tx.Signers {
|
|
||||||
netFee, sizeDelta := fee.Calculate(ef, accs[i].Contract.Script)
|
|
||||||
tx.NetworkFee += netFee
|
|
||||||
size += sizeDelta
|
|
||||||
}
|
|
||||||
|
|
||||||
tx.NetworkFee += int64(size)*l.bc.FeePerByte() + extraFee
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getSigners returns an array of transaction signers and corresponding accounts from
|
|
||||||
// given sender and cosigners. If cosigners list already contains sender, the sender
|
|
||||||
// will be placed at the start of the list.
|
|
||||||
// Copied from neo-go with minor corrections:
|
|
||||||
// https://github.com/nspcc-dev/neo-go/blob/6ff11baa1b9e4c71ef0d1de43b92a8c541ca732c/pkg/rpc/client/rpc.go#L735
|
|
||||||
func getSigners(sender *wallet.Account, cosigners []rpcclient.SignerAccount) ([]transaction.Signer, []*wallet.Account, error) {
|
|
||||||
var (
|
|
||||||
signers []transaction.Signer
|
|
||||||
accounts []*wallet.Account
|
|
||||||
)
|
|
||||||
|
|
||||||
from := sender.Contract.ScriptHash()
|
|
||||||
s := transaction.Signer{
|
|
||||||
Account: from,
|
|
||||||
Scopes: transaction.None,
|
|
||||||
}
|
|
||||||
for _, c := range cosigners {
|
|
||||||
if c.Signer.Account == from {
|
|
||||||
s = c.Signer
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
signers = append(signers, c.Signer)
|
|
||||||
accounts = append(accounts, c.Account)
|
|
||||||
}
|
|
||||||
signers = append([]transaction.Signer{s}, signers...)
|
|
||||||
accounts = append([]*wallet.Account{sender}, accounts...)
|
|
||||||
return signers, accounts, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *localClient) NEP17BalanceOf(h util.Uint160, acc util.Uint160) (int64, error) {
|
|
||||||
res, err := invokeFunction(l, h, "balanceOf", []interface{}{acc}, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
if res.State != vmstate.Halt.String() || len(res.Stack) == 0 {
|
|
||||||
return 0, fmt.Errorf("`balance`: invalid response (empty: %t): %s",
|
hashablePart, err := tx.EncodeHashableFields()
|
||||||
len(res.Stack) == 0, res.FaultException)
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
}
|
}
|
||||||
bi, err := res.Stack[0].TryInteger()
|
size := len(hashablePart) + io.GetVarSize(len(tx.Signers))
|
||||||
if err != nil || !bi.IsInt64() {
|
var (
|
||||||
return 0, fmt.Errorf("`balance`: invalid response")
|
netFee int64
|
||||||
|
// Verification GAS cost can't exceed this policy.
|
||||||
|
gasLimit = l.bc.GetMaxVerificationGAS()
|
||||||
|
)
|
||||||
|
for i, signer := range tx.Signers {
|
||||||
|
w := tx.Scripts[i]
|
||||||
|
if len(w.InvocationScript) == 0 { // No invocation provided, try to infer one.
|
||||||
|
var paramz []manifest.Parameter
|
||||||
|
if len(w.VerificationScript) == 0 { // Contract-based verification
|
||||||
|
cs := l.bc.GetContractState(signer.Account)
|
||||||
|
if cs == nil {
|
||||||
|
return 0, fmt.Errorf("signer %d has no verification script and no deployed contract", i)
|
||||||
|
}
|
||||||
|
md := cs.Manifest.ABI.GetMethod(manifest.MethodVerify, -1)
|
||||||
|
if md == nil || md.ReturnType != smartcontract.BoolType {
|
||||||
|
return 0, fmt.Errorf("signer %d has no verify method in deployed contract", i)
|
||||||
|
}
|
||||||
|
paramz = md.Parameters // Might as well have none params and it's OK.
|
||||||
|
} else { // Regular signature verification.
|
||||||
|
if vm.IsSignatureContract(w.VerificationScript) {
|
||||||
|
paramz = []manifest.Parameter{{Type: smartcontract.SignatureType}}
|
||||||
|
} else if nSigs, _, ok := vm.ParseMultiSigContract(w.VerificationScript); ok {
|
||||||
|
paramz = make([]manifest.Parameter, nSigs)
|
||||||
|
for j := 0; j < nSigs; j++ {
|
||||||
|
paramz[j] = manifest.Parameter{Type: smartcontract.SignatureType}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inv := io.NewBufBinWriter()
|
||||||
|
for _, p := range paramz {
|
||||||
|
p.Type.EncodeDefaultValue(inv.BinWriter)
|
||||||
|
}
|
||||||
|
if inv.Err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to create dummy invocation script (signer %d): %s", i, inv.Err.Error())
|
||||||
|
}
|
||||||
|
w.InvocationScript = inv.Bytes()
|
||||||
|
}
|
||||||
|
gasConsumed, err := l.bc.VerifyWitness(signer.Account, tx, &w, gasLimit)
|
||||||
|
if err != nil && !errors.Is(err, core.ErrInvalidSignature) {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
gasLimit -= gasConsumed
|
||||||
|
netFee += gasConsumed
|
||||||
|
size += io.GetVarSize(w.VerificationScript) + io.GetVarSize(w.InvocationScript)
|
||||||
}
|
}
|
||||||
return bi.Int64(), nil
|
if l.bc.P2PSigExtensionsEnabled() {
|
||||||
|
attrs := tx.GetAttributes(transaction.NotaryAssistedT)
|
||||||
|
if len(attrs) != 0 {
|
||||||
|
na := attrs[0].Value.(*transaction.NotaryAssisted)
|
||||||
|
netFee += (int64(na.NKeys) + 1) * l.bc.GetNotaryServiceFeePerKey()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fee := l.bc.FeePerByte()
|
||||||
|
netFee += int64(size) * fee
|
||||||
|
return netFee, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *localClient) InvokeScript(script []byte, signers []transaction.Signer) (*result.Invoke, error) {
|
func (l *localClient) InvokeScript(script []byte, signers []transaction.Signer) (*result.Invoke, error) {
|
||||||
|
@ -435,7 +349,7 @@ func (l *localClient) putTransactions() error {
|
||||||
return l.bc.AddBlock(b)
|
return l.bc.AddBlock(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func invokeFunction(c Client, h util.Uint160, method string, parameters []interface{}, signers []transaction.Signer) (*result.Invoke, error) {
|
func invokeFunction(c Client, h util.Uint160, method string, parameters []any, signers []transaction.Signer) (*result.Invoke, error) {
|
||||||
w := io.NewBufBinWriter()
|
w := io.NewBufBinWriter()
|
||||||
emit.Array(w.BinWriter, parameters...)
|
emit.Array(w.BinWriter, parameters...)
|
||||||
emit.AppCallNoArgs(w.BinWriter, h, method, callflag.All)
|
emit.AppCallNoArgs(w.BinWriter, h, method, callflag.All)
|
|
@ -6,13 +6,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||||
|
@ -29,21 +26,12 @@ type Client interface {
|
||||||
invoker.RPCInvoke
|
invoker.RPCInvoke
|
||||||
|
|
||||||
GetBlockCount() (uint32, error)
|
GetBlockCount() (uint32, error)
|
||||||
GetContractStateByID(int32) (*state.Contract, error)
|
|
||||||
GetContractStateByHash(util.Uint160) (*state.Contract, error)
|
|
||||||
GetNativeContracts() ([]state.NativeContract, error)
|
GetNativeContracts() ([]state.NativeContract, error)
|
||||||
GetNetwork() (netmode.Magic, error)
|
|
||||||
GetApplicationLog(util.Uint256, *trigger.Type) (*result.ApplicationLog, error)
|
GetApplicationLog(util.Uint256, *trigger.Type) (*result.ApplicationLog, error)
|
||||||
GetVersion() (*result.Version, error)
|
GetVersion() (*result.Version, error)
|
||||||
CreateTxFromScript([]byte, *wallet.Account, int64, int64, []rpcclient.SignerAccount) (*transaction.Transaction, error)
|
|
||||||
NEP17BalanceOf(util.Uint160, util.Uint160) (int64, error)
|
|
||||||
SendRawTransaction(*transaction.Transaction) (util.Uint256, error)
|
SendRawTransaction(*transaction.Transaction) (util.Uint256, error)
|
||||||
GetCommittee() (keys.PublicKeys, error)
|
GetCommittee() (keys.PublicKeys, error)
|
||||||
CalculateNotaryFee(uint8) (int64, error)
|
|
||||||
CalculateNetworkFee(tx *transaction.Transaction) (int64, error)
|
CalculateNetworkFee(tx *transaction.Transaction) (int64, error)
|
||||||
AddNetworkFee(*transaction.Transaction, int64, ...*wallet.Account) error
|
|
||||||
SignAndPushInvocationTx([]byte, *wallet.Account, int64, fixedn.Fixed8, []rpcclient.SignerAccount) (util.Uint256, error)
|
|
||||||
SignAndPushP2PNotaryRequest(*transaction.Transaction, []byte, int64, int64, uint32, *wallet.Account) (*payload.P2PNotaryRequest, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type hashVUBPair struct {
|
type hashVUBPair struct {
|
31
cmd/frostfs-adm/internal/modules/morph/netmap_candidates.go
Normal file
31
cmd/frostfs-adm/internal/modules/morph/netmap_candidates.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package morph
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||||
|
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
func listNetmapCandidatesNodes(cmd *cobra.Command, _ []string) {
|
||||||
|
c, err := getN3Client(viper.GetViper())
|
||||||
|
commonCmd.ExitOnErr(cmd, "can't create N3 client: %w", err)
|
||||||
|
|
||||||
|
inv := invoker.New(c, nil)
|
||||||
|
r := management.NewReader(inv)
|
||||||
|
|
||||||
|
cs, err := r.GetContractByID(1)
|
||||||
|
commonCmd.ExitOnErr(cmd, "can't get NNS contract info: %w", err)
|
||||||
|
|
||||||
|
nmHash, err := nnsResolveHash(inv, cs.Hash, netmapContract+".frostfs")
|
||||||
|
commonCmd.ExitOnErr(cmd, "can't get netmap contract hash: %w", err)
|
||||||
|
|
||||||
|
res, err := inv.Call(nmHash, "netmapCandidates")
|
||||||
|
commonCmd.ExitOnErr(cmd, "can't fetch list of network config keys from the netmap contract", err)
|
||||||
|
nm, err := netmap.DecodeNetMap(res.Stack)
|
||||||
|
commonCmd.ExitOnErr(cmd, "unable to decode netmap: %w", err)
|
||||||
|
commonCmd.PrettyPrintNetMap(cmd, *nm, !viper.GetBool(commonflags.Verbose))
|
||||||
|
}
|
44
cmd/frostfs-adm/internal/modules/morph/netmap_util.go
Normal file
44
cmd/frostfs-adm/internal/modules/morph/netmap_util.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package morph
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getDefaultNetmapContractConfigMap() map[string]any {
|
||||||
|
m := make(map[string]any)
|
||||||
|
m[netmap.EpochDurationConfig] = viper.GetInt64(epochDurationInitFlag)
|
||||||
|
m[netmap.MaxObjectSizeConfig] = viper.GetInt64(maxObjectSizeInitFlag)
|
||||||
|
m[netmap.ContainerFeeConfig] = viper.GetInt64(containerFeeInitFlag)
|
||||||
|
m[netmap.ContainerAliasFeeConfig] = viper.GetInt64(containerAliasFeeInitFlag)
|
||||||
|
m[netmap.IrCandidateFeeConfig] = viper.GetInt64(candidateFeeInitFlag)
|
||||||
|
m[netmap.WithdrawFeeConfig] = viper.GetInt64(withdrawFeeInitFlag)
|
||||||
|
m[netmap.HomomorphicHashingDisabledKey] = viper.GetBool(homomorphicHashDisabledInitFlag)
|
||||||
|
m[netmap.MaintenanceModeAllowedConfig] = viper.GetBool(maintenanceModeAllowedInitFlag)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseConfigFromNetmapContract(arr []stackitem.Item) (map[string][]byte, error) {
|
||||||
|
m := make(map[string][]byte, len(arr))
|
||||||
|
for _, param := range arr {
|
||||||
|
tuple, ok := param.Value().([]stackitem.Item)
|
||||||
|
if !ok || len(tuple) != 2 {
|
||||||
|
return nil, errors.New("invalid ListConfig response from netmap contract")
|
||||||
|
}
|
||||||
|
|
||||||
|
k, err := tuple[0].TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("invalid config key from netmap contract")
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := tuple[1].TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, invalidConfigValueErr(string(k))
|
||||||
|
}
|
||||||
|
m[string(k)] = v
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
|
@ -6,12 +6,15 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/cli/input"
|
"github.com/nspcc-dev/neo-go/cli/input"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/notary"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
@ -22,16 +25,9 @@ import (
|
||||||
const defaultNotaryDepositLifetime = 5760
|
const defaultNotaryDepositLifetime = 5760
|
||||||
|
|
||||||
func depositNotary(cmd *cobra.Command, _ []string) error {
|
func depositNotary(cmd *cobra.Command, _ []string) error {
|
||||||
p, err := cmd.Flags().GetString(storageWalletFlag)
|
w, err := openWallet(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if p == "" {
|
|
||||||
return fmt.Errorf("missing wallet path (use '--%s <out.json>')", storageWalletFlag)
|
|
||||||
}
|
|
||||||
|
|
||||||
w, err := wallet.NewWalletFromFile(p)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't open wallet: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
accHash := w.GetChangeAddress()
|
accHash := w.GetChangeAddress()
|
||||||
|
@ -79,23 +75,17 @@ func depositNotary(cmd *cobra.Command, _ []string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return transferGas(cmd, acc, accHash, gasAmount, till)
|
||||||
|
}
|
||||||
|
|
||||||
|
func transferGas(cmd *cobra.Command, acc *wallet.Account, accHash util.Uint160, gasAmount fixedn.Fixed8, till int64) error {
|
||||||
c, err := getN3Client(viper.GetViper())
|
c, err := getN3Client(viper.GetViper())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
nhs, err := getNativeHashes(c)
|
if err := checkNotaryEnabled(c); err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return fmt.Errorf("can't get native contract hashes: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
gasHash, ok := nhs[nativenames.Gas]
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("can't retrieve %s contract hash", nativenames.Gas)
|
|
||||||
}
|
|
||||||
notaryHash, ok := nhs[nativenames.Notary]
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("can't retrieve %s contract hash", nativenames.Notary)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
height, err := c.GetBlockCount()
|
height, err := c.GetBlockCount()
|
||||||
|
@ -114,13 +104,13 @@ func depositNotary(cmd *cobra.Command, _ []string) error {
|
||||||
return fmt.Errorf("could not create actor: %w", err)
|
return fmt.Errorf("could not create actor: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
gas := nep17.New(act, gasHash)
|
gasActor := nep17.New(act, gas.Hash)
|
||||||
|
|
||||||
txHash, vub, err := gas.Transfer(
|
txHash, vub, err := gasActor.Transfer(
|
||||||
accHash,
|
accHash,
|
||||||
notaryHash,
|
notary.Hash,
|
||||||
big.NewInt(int64(gasAmount)),
|
big.NewInt(int64(gasAmount)),
|
||||||
[]interface{}{nil, int64(height) + till},
|
[]any{nil, int64(height) + till},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not send tx: %w", err)
|
return fmt.Errorf("could not send tx: %w", err)
|
||||||
|
@ -128,3 +118,18 @@ func depositNotary(cmd *cobra.Command, _ []string) error {
|
||||||
|
|
||||||
return awaitTx(cmd, c, []hashVUBPair{{hash: txHash, vub: vub}})
|
return awaitTx(cmd, c, []hashVUBPair{{hash: txHash, vub: vub}})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func openWallet(cmd *cobra.Command) (*wallet.Wallet, error) {
|
||||||
|
p, err := cmd.Flags().GetString(storageWalletFlag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if p == "" {
|
||||||
|
return nil, fmt.Errorf("missing wallet path (use '--%s <out.json>')", storageWalletFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
w, err := wallet.NewWalletFromFile(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't open wallet: %v", err)
|
||||||
|
}
|
||||||
|
return w, nil
|
||||||
|
}
|
87
cmd/frostfs-adm/internal/modules/morph/policy.go
Normal file
87
cmd/frostfs-adm/internal/modules/morph/policy.go
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
package morph
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"text/tabwriter"
|
||||||
|
|
||||||
|
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/policy"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
execFeeParam = "ExecFeeFactor"
|
||||||
|
storagePriceParam = "StoragePrice"
|
||||||
|
setFeeParam = "FeePerByte"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setPolicyCmd(cmd *cobra.Command, args []string) error {
|
||||||
|
wCtx, err := newInitializeContext(cmd, viper.GetViper())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't to initialize context: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bw := io.NewBufBinWriter()
|
||||||
|
for i := range args {
|
||||||
|
k, v, found := strings.Cut(args[i], "=")
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf("invalid parameter format, must be Parameter=Value")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch k {
|
||||||
|
case execFeeParam, storagePriceParam, setFeeParam:
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("parameter must be one of %s, %s and %s", execFeeParam, storagePriceParam, setFeeParam)
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := strconv.ParseUint(v, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't parse parameter value '%s': %w", args[1], err)
|
||||||
|
}
|
||||||
|
|
||||||
|
emit.AppCall(bw.BinWriter, policy.Hash, "set"+k, callflag.All, int64(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := wCtx.sendCommitteeTx(bw.Bytes(), false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return wCtx.awaitTx()
|
||||||
|
}
|
||||||
|
|
||||||
|
func dumpPolicyCmd(cmd *cobra.Command, _ []string) error {
|
||||||
|
c, err := getN3Client(viper.GetViper())
|
||||||
|
commonCmd.ExitOnErr(cmd, "can't create N3 client:", err)
|
||||||
|
|
||||||
|
inv := invoker.New(c, nil)
|
||||||
|
policyContract := policy.NewReader(inv)
|
||||||
|
|
||||||
|
execFee, err := policyContract.GetExecFeeFactor()
|
||||||
|
commonCmd.ExitOnErr(cmd, "can't get execution fee factor:", err)
|
||||||
|
|
||||||
|
feePerByte, err := policyContract.GetFeePerByte()
|
||||||
|
commonCmd.ExitOnErr(cmd, "can't get fee per byte:", err)
|
||||||
|
|
||||||
|
storagePrice, err := policyContract.GetStoragePrice()
|
||||||
|
commonCmd.ExitOnErr(cmd, "can't get storage price:", err)
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
|
||||||
|
|
||||||
|
_, _ = tw.Write([]byte(fmt.Sprintf("Execution Fee Factor:\t%d (int)\n", execFee)))
|
||||||
|
_, _ = tw.Write([]byte(fmt.Sprintf("Fee Per Byte:\t%d (int)\n", feePerByte)))
|
||||||
|
_, _ = tw.Write([]byte(fmt.Sprintf("Storage Price:\t%d (int)\n", storagePrice)))
|
||||||
|
|
||||||
|
_ = tw.Flush()
|
||||||
|
cmd.Print(buf.String())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -4,11 +4,12 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
netmapcontract "git.frostfs.info/TrueCloudLab/frostfs-contract/netmap"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
netmapcontract "github.com/nspcc-dev/neofs-contract/netmap"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
@ -33,12 +34,13 @@ func removeNodesCmd(cmd *cobra.Command, args []string) error {
|
||||||
}
|
}
|
||||||
defer wCtx.close()
|
defer wCtx.close()
|
||||||
|
|
||||||
cs, err := wCtx.Client.GetContractStateByID(1)
|
r := management.NewReader(wCtx.ReadOnlyInvoker)
|
||||||
|
cs, err := r.GetContractByID(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't get NNS contract info: %w", err)
|
return fmt.Errorf("can't get NNS contract info: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
nmHash, err := nnsResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, netmapContract+".neofs")
|
nmHash, err := nnsResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, netmapContract+".frostfs")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
||||||
}
|
}
|
|
@ -18,10 +18,6 @@ const (
|
||||||
maxObjectSizeCLIFlag = "max-object-size"
|
maxObjectSizeCLIFlag = "max-object-size"
|
||||||
epochDurationInitFlag = "network.epoch_duration"
|
epochDurationInitFlag = "network.epoch_duration"
|
||||||
epochDurationCLIFlag = "epoch-duration"
|
epochDurationCLIFlag = "epoch-duration"
|
||||||
incomeRateInitFlag = "network.basic_income_rate"
|
|
||||||
incomeRateCLIFlag = "basic-income-rate"
|
|
||||||
auditFeeInitFlag = "network.fee.audit"
|
|
||||||
auditFeeCLIFlag = "audit-fee"
|
|
||||||
containerFeeInitFlag = "network.fee.container"
|
containerFeeInitFlag = "network.fee.container"
|
||||||
containerAliasFeeInitFlag = "network.fee.container_alias"
|
containerAliasFeeInitFlag = "network.fee.container_alias"
|
||||||
containerFeeCLIFlag = "container-fee"
|
containerFeeCLIFlag = "container-fee"
|
||||||
|
@ -69,15 +65,12 @@ var (
|
||||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||||
_ = viper.BindPFlag(epochDurationInitFlag, cmd.Flags().Lookup(epochDurationCLIFlag))
|
_ = viper.BindPFlag(epochDurationInitFlag, cmd.Flags().Lookup(epochDurationCLIFlag))
|
||||||
_ = viper.BindPFlag(maxObjectSizeInitFlag, cmd.Flags().Lookup(maxObjectSizeCLIFlag))
|
_ = viper.BindPFlag(maxObjectSizeInitFlag, cmd.Flags().Lookup(maxObjectSizeCLIFlag))
|
||||||
_ = viper.BindPFlag(incomeRateInitFlag, cmd.Flags().Lookup(incomeRateCLIFlag))
|
|
||||||
_ = viper.BindPFlag(homomorphicHashDisabledInitFlag, cmd.Flags().Lookup(homomorphicHashDisabledCLIFlag))
|
_ = viper.BindPFlag(homomorphicHashDisabledInitFlag, cmd.Flags().Lookup(homomorphicHashDisabledCLIFlag))
|
||||||
_ = viper.BindPFlag(auditFeeInitFlag, cmd.Flags().Lookup(auditFeeCLIFlag))
|
|
||||||
_ = viper.BindPFlag(candidateFeeInitFlag, cmd.Flags().Lookup(candidateFeeCLIFlag))
|
_ = viper.BindPFlag(candidateFeeInitFlag, cmd.Flags().Lookup(candidateFeeCLIFlag))
|
||||||
_ = viper.BindPFlag(containerFeeInitFlag, cmd.Flags().Lookup(containerFeeCLIFlag))
|
_ = viper.BindPFlag(containerFeeInitFlag, cmd.Flags().Lookup(containerFeeCLIFlag))
|
||||||
_ = viper.BindPFlag(containerAliasFeeInitFlag, cmd.Flags().Lookup(containerAliasFeeCLIFlag))
|
_ = viper.BindPFlag(containerAliasFeeInitFlag, cmd.Flags().Lookup(containerAliasFeeCLIFlag))
|
||||||
_ = viper.BindPFlag(withdrawFeeInitFlag, cmd.Flags().Lookup(withdrawFeeCLIFlag))
|
_ = viper.BindPFlag(withdrawFeeInitFlag, cmd.Flags().Lookup(withdrawFeeCLIFlag))
|
||||||
_ = viper.BindPFlag(protoConfigPath, cmd.Flags().Lookup(protoConfigPath))
|
_ = viper.BindPFlag(protoConfigPath, cmd.Flags().Lookup(protoConfigPath))
|
||||||
_ = viper.BindPFlag(localDumpFlag, cmd.Flags().Lookup(localDumpFlag))
|
|
||||||
},
|
},
|
||||||
RunE: initializeSideChainCmd,
|
RunE: initializeSideChainCmd,
|
||||||
}
|
}
|
||||||
|
@ -108,7 +101,7 @@ var (
|
||||||
|
|
||||||
forceNewEpoch = &cobra.Command{
|
forceNewEpoch = &cobra.Command{
|
||||||
Use: "force-new-epoch",
|
Use: "force-new-epoch",
|
||||||
Short: "Create new NeoFS epoch event in the side chain",
|
Short: "Create new FrostFS epoch event in the side chain",
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||||
|
@ -130,7 +123,7 @@ var (
|
||||||
setConfig = &cobra.Command{
|
setConfig = &cobra.Command{
|
||||||
Use: "set-config key1=val1 [key2=val2 ...]",
|
Use: "set-config key1=val1 [key2=val2 ...]",
|
||||||
DisableFlagsInUseLine: true,
|
DisableFlagsInUseLine: true,
|
||||||
Short: "Add/update global config value in the NeoFS network",
|
Short: "Add/update global config value in the FrostFS network",
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||||
|
@ -153,6 +146,15 @@ var (
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dumpPolicy = &cobra.Command{
|
||||||
|
Use: "dump-policy",
|
||||||
|
Short: "Dump FrostFS policy",
|
||||||
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
|
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||||
|
},
|
||||||
|
RunE: dumpPolicyCmd,
|
||||||
|
}
|
||||||
|
|
||||||
dumpContractHashesCmd = &cobra.Command{
|
dumpContractHashesCmd = &cobra.Command{
|
||||||
Use: "dump-hashes",
|
Use: "dump-hashes",
|
||||||
Short: "Dump deployed contract hashes",
|
Short: "Dump deployed contract hashes",
|
||||||
|
@ -164,7 +166,7 @@ var (
|
||||||
|
|
||||||
dumpNetworkConfigCmd = &cobra.Command{
|
dumpNetworkConfigCmd = &cobra.Command{
|
||||||
Use: "dump-config",
|
Use: "dump-config",
|
||||||
Short: "Dump NeoFS network config",
|
Short: "Dump FrostFS network config",
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||||
},
|
},
|
||||||
|
@ -182,7 +184,7 @@ var (
|
||||||
|
|
||||||
updateContractsCmd = &cobra.Command{
|
updateContractsCmd = &cobra.Command{
|
||||||
Use: "update-contracts",
|
Use: "update-contracts",
|
||||||
Short: "Update NeoFS contracts",
|
Short: "Update FrostFS contracts",
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||||
|
@ -192,7 +194,7 @@ var (
|
||||||
|
|
||||||
dumpContainersCmd = &cobra.Command{
|
dumpContainersCmd = &cobra.Command{
|
||||||
Use: "dump-containers",
|
Use: "dump-containers",
|
||||||
Short: "Dump NeoFS containers to file",
|
Short: "Dump FrostFS containers to file",
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||||
},
|
},
|
||||||
|
@ -201,7 +203,7 @@ var (
|
||||||
|
|
||||||
restoreContainersCmd = &cobra.Command{
|
restoreContainersCmd = &cobra.Command{
|
||||||
Use: "restore-containers",
|
Use: "restore-containers",
|
||||||
Short: "Restore NeoFS containers from file",
|
Short: "Restore FrostFS containers from file",
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||||
|
@ -209,6 +211,15 @@ var (
|
||||||
RunE: restoreContainers,
|
RunE: restoreContainers,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listContainersCmd = &cobra.Command{
|
||||||
|
Use: "list-containers",
|
||||||
|
Short: "List FrostFS containers",
|
||||||
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
|
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||||
|
},
|
||||||
|
RunE: listContainers,
|
||||||
|
}
|
||||||
|
|
||||||
depositNotaryCmd = &cobra.Command{
|
depositNotaryCmd = &cobra.Command{
|
||||||
Use: "deposit-notary",
|
Use: "deposit-notary",
|
||||||
Short: "Deposit GAS for notary service",
|
Short: "Deposit GAS for notary service",
|
||||||
|
@ -217,93 +228,46 @@ var (
|
||||||
},
|
},
|
||||||
RunE: depositNotary,
|
RunE: depositNotary,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
netmapCandidatesCmd = &cobra.Command{
|
||||||
|
Use: "netmap-candidates",
|
||||||
|
Short: "List netmap candidates nodes",
|
||||||
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
|
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||||
|
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||||
|
},
|
||||||
|
Run: listNetmapCandidatesNodes,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
RootCmd.AddCommand(generateAlphabetCmd)
|
initGenerateAlphabetCmd()
|
||||||
generateAlphabetCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
|
initInitCmd()
|
||||||
generateAlphabetCmd.Flags().Uint(alphabetSizeFlag, 7, "Amount of alphabet wallets to generate")
|
initDeployCmd()
|
||||||
|
initGenerateStorageCmd()
|
||||||
|
initForceNewEpochCmd()
|
||||||
|
initRemoveNodesCmd()
|
||||||
|
initSetPolicyCmd()
|
||||||
|
initDumpPolicyCmd()
|
||||||
|
initDumpContractHashesCmd()
|
||||||
|
initDumpNetworkConfigCmd()
|
||||||
|
initSetConfigCmd()
|
||||||
|
initDumpBalancesCmd()
|
||||||
|
initUpdateContractsCmd()
|
||||||
|
initDumpContainersCmd()
|
||||||
|
initRestoreContainersCmd()
|
||||||
|
initListContainersCmd()
|
||||||
|
initRefillGasCmd()
|
||||||
|
initDepositoryNotaryCmd()
|
||||||
|
initNetmapCandidatesCmd()
|
||||||
|
}
|
||||||
|
|
||||||
RootCmd.AddCommand(initCmd)
|
func initNetmapCandidatesCmd() {
|
||||||
initCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
|
RootCmd.AddCommand(netmapCandidatesCmd)
|
||||||
initCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
netmapCandidatesCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
||||||
initCmd.Flags().String(contractsInitFlag, "", "Path to archive with compiled NeoFS contracts (default fetched from latest github release)")
|
}
|
||||||
initCmd.Flags().Uint(epochDurationCLIFlag, 240, "Amount of side chain blocks in one NeoFS epoch")
|
|
||||||
initCmd.Flags().Uint(maxObjectSizeCLIFlag, 67108864, "Max single object size in bytes")
|
|
||||||
initCmd.Flags().Bool(homomorphicHashDisabledCLIFlag, false, "Disable object homomorphic hashing")
|
|
||||||
// Defaults are taken from neo-preodolenie.
|
|
||||||
initCmd.Flags().Uint64(containerFeeCLIFlag, 1000, "Container registration fee")
|
|
||||||
initCmd.Flags().Uint64(containerAliasFeeCLIFlag, 500, "Container alias fee")
|
|
||||||
initCmd.Flags().String(protoConfigPath, "", "Path to the consensus node configuration")
|
|
||||||
initCmd.Flags().String(localDumpFlag, "", "Path to the blocks dump file")
|
|
||||||
|
|
||||||
RootCmd.AddCommand(deployCmd)
|
|
||||||
|
|
||||||
RootCmd.AddCommand(generateStorageCmd)
|
|
||||||
generateStorageCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
|
|
||||||
generateStorageCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
|
||||||
generateStorageCmd.Flags().String(storageWalletFlag, "", "Path to new storage node wallet")
|
|
||||||
generateStorageCmd.Flags().String(storageGasCLIFlag, "", "Initial amount of GAS to transfer")
|
|
||||||
generateStorageCmd.Flags().StringP(storageWalletLabelFlag, "l", "", "Wallet label")
|
|
||||||
|
|
||||||
RootCmd.AddCommand(forceNewEpoch)
|
|
||||||
forceNewEpoch.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
|
|
||||||
forceNewEpoch.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
|
||||||
|
|
||||||
RootCmd.AddCommand(removeNodes)
|
|
||||||
removeNodes.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
|
|
||||||
removeNodes.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
|
||||||
|
|
||||||
RootCmd.AddCommand(setPolicy)
|
|
||||||
setPolicy.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
|
|
||||||
setPolicy.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
|
||||||
|
|
||||||
RootCmd.AddCommand(dumpContractHashesCmd)
|
|
||||||
dumpContractHashesCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
|
||||||
dumpContractHashesCmd.Flags().String(customZoneFlag, "", "Custom zone to search.")
|
|
||||||
|
|
||||||
RootCmd.AddCommand(dumpNetworkConfigCmd)
|
|
||||||
dumpNetworkConfigCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
|
||||||
|
|
||||||
RootCmd.AddCommand(setConfig)
|
|
||||||
setConfig.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
|
|
||||||
setConfig.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
|
||||||
setConfig.Flags().Bool(forceConfigSet, false, "Force setting not well-known configuration key")
|
|
||||||
|
|
||||||
RootCmd.AddCommand(dumpBalancesCmd)
|
|
||||||
dumpBalancesCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
|
||||||
dumpBalancesCmd.Flags().BoolP(dumpBalancesStorageFlag, "s", false, "Dump balances of storage nodes from the current netmap")
|
|
||||||
dumpBalancesCmd.Flags().BoolP(dumpBalancesAlphabetFlag, "a", false, "Dump balances of alphabet contracts")
|
|
||||||
dumpBalancesCmd.Flags().BoolP(dumpBalancesProxyFlag, "p", false, "Dump balances of the proxy contract")
|
|
||||||
dumpBalancesCmd.Flags().Bool(dumpBalancesUseScriptHashFlag, false, "Use script-hash format for addresses")
|
|
||||||
|
|
||||||
RootCmd.AddCommand(updateContractsCmd)
|
|
||||||
updateContractsCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
|
|
||||||
updateContractsCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
|
||||||
updateContractsCmd.Flags().String(contractsInitFlag, "", "Path to archive with compiled NeoFS contracts (default fetched from latest github release)")
|
|
||||||
|
|
||||||
RootCmd.AddCommand(dumpContainersCmd)
|
|
||||||
dumpContainersCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
|
||||||
dumpContainersCmd.Flags().String(containerDumpFlag, "", "File where to save dumped containers")
|
|
||||||
dumpContainersCmd.Flags().String(containerContractFlag, "", "Container contract hash (for networks without NNS)")
|
|
||||||
dumpContainersCmd.Flags().StringSlice(containerIDsFlag, nil, "Containers to dump")
|
|
||||||
|
|
||||||
RootCmd.AddCommand(restoreContainersCmd)
|
|
||||||
restoreContainersCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
|
|
||||||
restoreContainersCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
|
||||||
restoreContainersCmd.Flags().String(containerDumpFlag, "", "File to restore containers from")
|
|
||||||
restoreContainersCmd.Flags().StringSlice(containerIDsFlag, nil, "Containers to restore")
|
|
||||||
|
|
||||||
RootCmd.AddCommand(refillGasCmd)
|
|
||||||
refillGasCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
|
|
||||||
refillGasCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
|
||||||
refillGasCmd.Flags().String(storageWalletFlag, "", "Path to storage node wallet")
|
|
||||||
refillGasCmd.Flags().String(walletAddressFlag, "", "Address of wallet")
|
|
||||||
refillGasCmd.Flags().String(refillGasAmountFlag, "", "Additional amount of GAS to transfer")
|
|
||||||
refillGasCmd.MarkFlagsMutuallyExclusive(walletAddressFlag, storageWalletFlag)
|
|
||||||
|
|
||||||
RootCmd.AddCommand(cmdSubnet)
|
|
||||||
|
|
||||||
|
func initDepositoryNotaryCmd() {
|
||||||
RootCmd.AddCommand(depositNotaryCmd)
|
RootCmd.AddCommand(depositNotaryCmd)
|
||||||
depositNotaryCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
depositNotaryCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
||||||
depositNotaryCmd.Flags().String(storageWalletFlag, "", "Path to storage node wallet")
|
depositNotaryCmd.Flags().String(storageWalletFlag, "", "Path to storage node wallet")
|
||||||
|
@ -311,3 +275,132 @@ func init() {
|
||||||
depositNotaryCmd.Flags().String(refillGasAmountFlag, "", "Amount of GAS to deposit")
|
depositNotaryCmd.Flags().String(refillGasAmountFlag, "", "Amount of GAS to deposit")
|
||||||
depositNotaryCmd.Flags().String(notaryDepositTillFlag, "", "Notary deposit duration in blocks")
|
depositNotaryCmd.Flags().String(notaryDepositTillFlag, "", "Notary deposit duration in blocks")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initRefillGasCmd() {
|
||||||
|
RootCmd.AddCommand(refillGasCmd)
|
||||||
|
refillGasCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
|
||||||
|
refillGasCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
||||||
|
refillGasCmd.Flags().String(storageWalletFlag, "", "Path to storage node wallet")
|
||||||
|
refillGasCmd.Flags().String(walletAddressFlag, "", "Address of wallet")
|
||||||
|
refillGasCmd.Flags().String(refillGasAmountFlag, "", "Additional amount of GAS to transfer")
|
||||||
|
refillGasCmd.MarkFlagsMutuallyExclusive(walletAddressFlag, storageWalletFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func initListContainersCmd() {
|
||||||
|
RootCmd.AddCommand(listContainersCmd)
|
||||||
|
listContainersCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
||||||
|
listContainersCmd.Flags().String(containerContractFlag, "", "Container contract hash (for networks without NNS)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func initRestoreContainersCmd() {
|
||||||
|
RootCmd.AddCommand(restoreContainersCmd)
|
||||||
|
restoreContainersCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
|
||||||
|
restoreContainersCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
||||||
|
restoreContainersCmd.Flags().String(containerDumpFlag, "", "File to restore containers from")
|
||||||
|
restoreContainersCmd.Flags().StringSlice(containerIDsFlag, nil, "Containers to restore")
|
||||||
|
}
|
||||||
|
|
||||||
|
func initDumpContainersCmd() {
|
||||||
|
RootCmd.AddCommand(dumpContainersCmd)
|
||||||
|
dumpContainersCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
||||||
|
dumpContainersCmd.Flags().String(containerDumpFlag, "", "File where to save dumped containers")
|
||||||
|
dumpContainersCmd.Flags().String(containerContractFlag, "", "Container contract hash (for networks without NNS)")
|
||||||
|
dumpContainersCmd.Flags().StringSlice(containerIDsFlag, nil, "Containers to dump")
|
||||||
|
}
|
||||||
|
|
||||||
|
func initUpdateContractsCmd() {
|
||||||
|
RootCmd.AddCommand(updateContractsCmd)
|
||||||
|
updateContractsCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
|
||||||
|
updateContractsCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
||||||
|
updateContractsCmd.Flags().String(contractsInitFlag, "", "Path to archive with compiled FrostFS contracts")
|
||||||
|
_ = updateContractsCmd.MarkFlagRequired(contractsInitFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func initDumpBalancesCmd() {
|
||||||
|
RootCmd.AddCommand(dumpBalancesCmd)
|
||||||
|
dumpBalancesCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
||||||
|
dumpBalancesCmd.Flags().BoolP(dumpBalancesStorageFlag, "s", false, "Dump balances of storage nodes from the current netmap")
|
||||||
|
dumpBalancesCmd.Flags().BoolP(dumpBalancesAlphabetFlag, "a", false, "Dump balances of alphabet contracts")
|
||||||
|
dumpBalancesCmd.Flags().BoolP(dumpBalancesProxyFlag, "p", false, "Dump balances of the proxy contract")
|
||||||
|
dumpBalancesCmd.Flags().Bool(dumpBalancesUseScriptHashFlag, false, "Use script-hash format for addresses")
|
||||||
|
}
|
||||||
|
|
||||||
|
func initSetConfigCmd() {
|
||||||
|
RootCmd.AddCommand(setConfig)
|
||||||
|
setConfig.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
|
||||||
|
setConfig.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
||||||
|
setConfig.Flags().Bool(forceConfigSet, false, "Force setting not well-known configuration key")
|
||||||
|
setConfig.Flags().String(localDumpFlag, "", "Path to the blocks dump file")
|
||||||
|
}
|
||||||
|
|
||||||
|
func initDumpNetworkConfigCmd() {
|
||||||
|
RootCmd.AddCommand(dumpNetworkConfigCmd)
|
||||||
|
dumpNetworkConfigCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
||||||
|
}
|
||||||
|
|
||||||
|
func initDumpContractHashesCmd() {
|
||||||
|
RootCmd.AddCommand(dumpContractHashesCmd)
|
||||||
|
dumpContractHashesCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
||||||
|
dumpContractHashesCmd.Flags().String(customZoneFlag, "", "Custom zone to search.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func initSetPolicyCmd() {
|
||||||
|
RootCmd.AddCommand(setPolicy)
|
||||||
|
setPolicy.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
|
||||||
|
setPolicy.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
||||||
|
setPolicy.Flags().String(localDumpFlag, "", "Path to the blocks dump file")
|
||||||
|
}
|
||||||
|
|
||||||
|
func initDumpPolicyCmd() {
|
||||||
|
RootCmd.AddCommand(dumpPolicy)
|
||||||
|
dumpPolicy.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
||||||
|
}
|
||||||
|
|
||||||
|
func initRemoveNodesCmd() {
|
||||||
|
RootCmd.AddCommand(removeNodes)
|
||||||
|
removeNodes.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
|
||||||
|
removeNodes.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
||||||
|
removeNodes.Flags().String(localDumpFlag, "", "Path to the blocks dump file")
|
||||||
|
}
|
||||||
|
|
||||||
|
func initForceNewEpochCmd() {
|
||||||
|
RootCmd.AddCommand(forceNewEpoch)
|
||||||
|
forceNewEpoch.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
|
||||||
|
forceNewEpoch.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
||||||
|
forceNewEpoch.Flags().String(localDumpFlag, "", "Path to the blocks dump file")
|
||||||
|
}
|
||||||
|
|
||||||
|
func initGenerateStorageCmd() {
|
||||||
|
RootCmd.AddCommand(generateStorageCmd)
|
||||||
|
generateStorageCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
|
||||||
|
generateStorageCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
||||||
|
generateStorageCmd.Flags().String(storageWalletFlag, "", "Path to new storage node wallet")
|
||||||
|
generateStorageCmd.Flags().String(storageGasCLIFlag, "", "Initial amount of GAS to transfer")
|
||||||
|
generateStorageCmd.Flags().StringP(storageWalletLabelFlag, "l", "", "Wallet label")
|
||||||
|
}
|
||||||
|
|
||||||
|
func initInitCmd() {
|
||||||
|
RootCmd.AddCommand(initCmd)
|
||||||
|
initCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
|
||||||
|
initCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
||||||
|
initCmd.Flags().String(contractsInitFlag, "", "Path to archive with compiled FrostFS contracts")
|
||||||
|
_ = initCmd.MarkFlagRequired(contractsInitFlag)
|
||||||
|
initCmd.Flags().Uint(epochDurationCLIFlag, 240, "Amount of side chain blocks in one FrostFS epoch")
|
||||||
|
initCmd.Flags().Uint(maxObjectSizeCLIFlag, 67108864, "Max single object size in bytes")
|
||||||
|
initCmd.Flags().Bool(homomorphicHashDisabledCLIFlag, false, "Disable object homomorphic hashing")
|
||||||
|
// Defaults are taken from neo-preodolenie.
|
||||||
|
initCmd.Flags().Uint64(containerFeeCLIFlag, 1000, "Container registration fee")
|
||||||
|
initCmd.Flags().Uint64(containerAliasFeeCLIFlag, 500, "Container alias fee")
|
||||||
|
initCmd.Flags().String(protoConfigPath, "", "Path to the consensus node configuration")
|
||||||
|
initCmd.Flags().String(localDumpFlag, "", "Path to the blocks dump file")
|
||||||
|
}
|
||||||
|
|
||||||
|
func initGenerateAlphabetCmd() {
|
||||||
|
RootCmd.AddCommand(generateAlphabetCmd)
|
||||||
|
generateAlphabetCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
|
||||||
|
generateAlphabetCmd.Flags().Uint(alphabetSizeFlag, 7, "Amount of alphabet wallets to generate")
|
||||||
|
}
|
||||||
|
|
||||||
|
func initDeployCmd() {
|
||||||
|
RootCmd.AddCommand(deployCmd)
|
||||||
|
}
|
83
cmd/frostfs-adm/internal/modules/root.go
Normal file
83
cmd/frostfs-adm/internal/modules/root.go
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
package modules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/storagecfg"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/misc"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/autocomplete"
|
||||||
|
utilConfig "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/config"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/gendoc"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
var rootCmd = &cobra.Command{
|
||||||
|
Use: "frostfs-adm",
|
||||||
|
Short: "FrostFS Administrative Tool",
|
||||||
|
Long: `FrostFS Administrative Tool provides functions to setup and
|
||||||
|
manage FrostFS network deployment.`,
|
||||||
|
RunE: entryPoint,
|
||||||
|
SilenceUsage: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cobra.OnInitialize(func() { initConfig(rootCmd) })
|
||||||
|
// we need to init viper config to bind viper and cobra configurations for
|
||||||
|
// rpc endpoint, alphabet wallet dir, key credentials, etc.
|
||||||
|
|
||||||
|
// use stdout as default output for cmd.Print()
|
||||||
|
rootCmd.SetOut(os.Stdout)
|
||||||
|
|
||||||
|
rootCmd.PersistentFlags().StringP(commonflags.ConfigFlag, commonflags.ConfigFlagShorthand, "", commonflags.ConfigFlagUsage)
|
||||||
|
rootCmd.PersistentFlags().String(commonflags.ConfigDirFlag, "", commonflags.ConfigDirFlagUsage)
|
||||||
|
rootCmd.PersistentFlags().BoolP(commonflags.Verbose, commonflags.VerboseShorthand, false, commonflags.VerboseUsage)
|
||||||
|
_ = viper.BindPFlag(commonflags.Verbose, rootCmd.PersistentFlags().Lookup(commonflags.Verbose))
|
||||||
|
rootCmd.Flags().Bool("version", false, "Application version")
|
||||||
|
|
||||||
|
rootCmd.AddCommand(config.RootCmd)
|
||||||
|
rootCmd.AddCommand(morph.RootCmd)
|
||||||
|
rootCmd.AddCommand(storagecfg.RootCmd)
|
||||||
|
|
||||||
|
rootCmd.AddCommand(autocomplete.Command("frostfs-adm"))
|
||||||
|
rootCmd.AddCommand(gendoc.Command(rootCmd, gendoc.Options{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Execute() error {
|
||||||
|
return rootCmd.Execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
func entryPoint(cmd *cobra.Command, _ []string) error {
|
||||||
|
printVersion, _ := cmd.Flags().GetBool("version")
|
||||||
|
if printVersion {
|
||||||
|
cmd.Print(misc.BuildInfo("FrostFS Adm"))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd.Usage()
|
||||||
|
}
|
||||||
|
|
||||||
|
func initConfig(cmd *cobra.Command) {
|
||||||
|
configFile, err := cmd.Flags().GetString(commonflags.ConfigFlag)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if configFile != "" {
|
||||||
|
viper.SetConfigType("yml")
|
||||||
|
viper.SetConfigFile(configFile)
|
||||||
|
_ = viper.ReadInConfig() // if config file is set but unavailable, ignore it
|
||||||
|
}
|
||||||
|
|
||||||
|
configDir, err := cmd.Flags().GetString(commonflags.ConfigDirFlag)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if configDir != "" {
|
||||||
|
_ = utilConfig.ReadConfigDir(viper.GetViper(), configDir) // if config files cannot be read, ignore it
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,9 +12,6 @@ node:
|
||||||
- {{ .AnnouncedAddress }}
|
- {{ .AnnouncedAddress }}
|
||||||
attribute_0: UN-LOCODE:{{ .Attribute.Locode }}
|
attribute_0: UN-LOCODE:{{ .Attribute.Locode }}
|
||||||
relay: {{ .Relay }} # start Storage node in relay mode without bootstrapping into the Network map
|
relay: {{ .Relay }} # start Storage node in relay mode without bootstrapping into the Network map
|
||||||
subnet:
|
|
||||||
exit_zero: false # toggle entrance to zero subnet (overrides corresponding attribute and occurrence in entries)
|
|
||||||
entries: [] # list of IDs of subnets to enter in a text format of NeoFS API protocol (overrides corresponding attributes)
|
|
||||||
|
|
||||||
grpc:
|
grpc:
|
||||||
num: 1 # total number of listener endpoints
|
num: 1 # total number of listener endpoints
|
|
@ -16,20 +16,20 @@ import (
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
netutil "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network"
|
||||||
"github.com/chzyer/readline"
|
"github.com/chzyer/readline"
|
||||||
"github.com/nspcc-dev/neo-go/cli/flags"
|
"github.com/nspcc-dev/neo-go/cli/flags"
|
||||||
"github.com/nspcc-dev/neo-go/cli/input"
|
"github.com/nspcc-dev/neo-go/cli/input"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
netutil "github.com/nspcc-dev/neofs-node/pkg/network"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
@ -80,17 +80,9 @@ type config struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func storageConfig(cmd *cobra.Command, args []string) {
|
func storageConfig(cmd *cobra.Command, args []string) {
|
||||||
var outPath string
|
outPath := getOutputPath(args)
|
||||||
if len(args) != 0 {
|
|
||||||
outPath = args[0]
|
|
||||||
} else {
|
|
||||||
outPath = getPath("File to write config at [./config.yml]: ")
|
|
||||||
if outPath == "" {
|
|
||||||
outPath = "./config.yml"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
historyPath := filepath.Join(os.TempDir(), "neofs-adm.history")
|
historyPath := filepath.Join(os.TempDir(), "frostfs-adm.history")
|
||||||
readline.SetHistoryPath(historyPath)
|
readline.SetHistoryPath(historyPath)
|
||||||
|
|
||||||
var c config
|
var c config
|
||||||
|
@ -103,14 +95,7 @@ func storageConfig(cmd *cobra.Command, args []string) {
|
||||||
w, err := wallet.NewWalletFromFile(c.Wallet.Path)
|
w, err := wallet.NewWalletFromFile(c.Wallet.Path)
|
||||||
fatalOnErr(err)
|
fatalOnErr(err)
|
||||||
|
|
||||||
c.Wallet.Account, _ = cmd.Flags().GetString(accountFlag)
|
fillWalletAccount(cmd, &c, w)
|
||||||
if c.Wallet.Account == "" {
|
|
||||||
addr := address.Uint160ToString(w.GetChangeAddress())
|
|
||||||
c.Wallet.Account = getWalletAccount(w, fmt.Sprintf("Wallet account [%s]: ", addr))
|
|
||||||
if c.Wallet.Account == "" {
|
|
||||||
c.Wallet.Account = addr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
accH, err := flags.ParseAddress(c.Wallet.Account)
|
accH, err := flags.ParseAddress(c.Wallet.Account)
|
||||||
fatalOnErr(err)
|
fatalOnErr(err)
|
||||||
|
@ -128,32 +113,51 @@ func storageConfig(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
c.AuthorizedKeys = append(c.AuthorizedKeys, hex.EncodeToString(acc.PrivateKey().PublicKey().Bytes()))
|
c.AuthorizedKeys = append(c.AuthorizedKeys, hex.EncodeToString(acc.PrivateKey().PublicKey().Bytes()))
|
||||||
|
|
||||||
var network string
|
network := readNetwork(cmd)
|
||||||
for {
|
|
||||||
network = getString("Choose network [mainnet]/testnet: ")
|
|
||||||
switch network {
|
|
||||||
case "":
|
|
||||||
network = "mainnet"
|
|
||||||
case "testnet", "mainnet":
|
|
||||||
default:
|
|
||||||
cmd.Println(`Network must be either "mainnet" or "testnet"`)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
c.MorphRPC = n3config[network].MorphRPC
|
c.MorphRPC = n3config[network].MorphRPC
|
||||||
|
|
||||||
depositGas(cmd, acc, network)
|
depositGas(cmd, acc, network)
|
||||||
|
|
||||||
c.Attribute.Locode = getString("UN-LOCODE attribute in [XX YYY] format: ")
|
c.Attribute.Locode = getString("UN-LOCODE attribute in [XX YYY] format: ")
|
||||||
|
|
||||||
|
endpoint := getDefaultEndpoint(cmd, &c)
|
||||||
|
c.Endpoint = getString(fmt.Sprintf("Listening address [%s]: ", endpoint))
|
||||||
|
if c.Endpoint == "" {
|
||||||
|
c.Endpoint = endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ControlEndpoint = getString(fmt.Sprintf("Listening address (control endpoint) [%s]: ", defaultControlEndpoint))
|
||||||
|
if c.ControlEndpoint == "" {
|
||||||
|
c.ControlEndpoint = defaultControlEndpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
c.TLSCert = getPath("TLS Certificate (optional): ")
|
||||||
|
if c.TLSCert != "" {
|
||||||
|
c.TLSKey = getPath("TLS Key: ")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Relay = getConfirmation(false, "Use node as a relay? yes/[no]: ")
|
||||||
|
if !c.Relay {
|
||||||
|
p := getPath("Path to the storage directory (all available storage will be used): ")
|
||||||
|
c.BlobstorPath = filepath.Join(p, "blob")
|
||||||
|
c.MetabasePath = filepath.Join(p, "meta")
|
||||||
|
}
|
||||||
|
|
||||||
|
out := applyTemplate(c)
|
||||||
|
fatalOnErr(os.WriteFile(outPath, out, 0o644))
|
||||||
|
|
||||||
|
cmd.Println("Node is ready for work! Run `frostfs-node -config " + outPath + "`")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultEndpoint(cmd *cobra.Command, c *config) string {
|
||||||
var addr, port string
|
var addr, port string
|
||||||
for {
|
for {
|
||||||
c.AnnouncedAddress = getString("Publicly announced address: ")
|
c.AnnouncedAddress = getString("Publicly announced address: ")
|
||||||
validator := netutil.Address{}
|
validator := netutil.Address{}
|
||||||
err := validator.FromString(c.AnnouncedAddress)
|
err := validator.FromString(c.AnnouncedAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmd.Println("Incorrect address format. See https://github.com/nspcc-dev/neofs-node/blob/master/pkg/network/address.go for details.")
|
cmd.Println("Incorrect address format. See https://git.frostfs.info/TrueCloudLab/frostfs-node/src/branch/master/pkg/network/address.go for details.")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
uriAddr, err := url.Parse(validator.URIAddr())
|
uriAddr, err := url.Parse(validator.URIAddr())
|
||||||
|
@ -182,34 +186,46 @@ func storageConfig(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
return net.JoinHostPort(defaultDataEndpoint, port)
|
||||||
|
}
|
||||||
|
|
||||||
defaultAddr := net.JoinHostPort(defaultDataEndpoint, port)
|
func fillWalletAccount(cmd *cobra.Command, c *config, w *wallet.Wallet) {
|
||||||
c.Endpoint = getString(fmt.Sprintf("Listening address [%s]: ", defaultAddr))
|
c.Wallet.Account, _ = cmd.Flags().GetString(accountFlag)
|
||||||
if c.Endpoint == "" {
|
if c.Wallet.Account == "" {
|
||||||
c.Endpoint = defaultAddr
|
addr := address.Uint160ToString(w.GetChangeAddress())
|
||||||
|
c.Wallet.Account = getWalletAccount(w, fmt.Sprintf("Wallet account [%s]: ", addr))
|
||||||
|
if c.Wallet.Account == "" {
|
||||||
|
c.Wallet.Account = addr
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
c.ControlEndpoint = getString(fmt.Sprintf("Listening address (control endpoint) [%s]: ", defaultControlEndpoint))
|
func readNetwork(cmd *cobra.Command) string {
|
||||||
if c.ControlEndpoint == "" {
|
var network string
|
||||||
c.ControlEndpoint = defaultControlEndpoint
|
for {
|
||||||
|
network = getString("Choose network [mainnet]/testnet: ")
|
||||||
|
switch network {
|
||||||
|
case "":
|
||||||
|
network = "mainnet"
|
||||||
|
case "testnet", "mainnet":
|
||||||
|
default:
|
||||||
|
cmd.Println(`Network must be either "mainnet" or "testnet"`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
return network
|
||||||
|
}
|
||||||
|
|
||||||
c.TLSCert = getPath("TLS Certificate (optional): ")
|
func getOutputPath(args []string) string {
|
||||||
if c.TLSCert != "" {
|
if len(args) != 0 {
|
||||||
c.TLSKey = getPath("TLS Key: ")
|
return args[0]
|
||||||
}
|
}
|
||||||
|
outPath := getPath("File to write config at [./config.yml]: ")
|
||||||
c.Relay = getConfirmation(false, "Use node as a relay? yes/[no]: ")
|
if outPath == "" {
|
||||||
if !c.Relay {
|
outPath = "./config.yml"
|
||||||
p := getPath("Path to the storage directory (all available storage will be used): ")
|
|
||||||
c.BlobstorPath = filepath.Join(p, "blob")
|
|
||||||
c.MetabasePath = filepath.Join(p, "meta")
|
|
||||||
}
|
}
|
||||||
|
return outPath
|
||||||
out := applyTemplate(c)
|
|
||||||
fatalOnErr(os.WriteFile(outPath, out, 0644))
|
|
||||||
|
|
||||||
cmd.Println("Node is ready for work! Run `neofs-node -config " + outPath + "`")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getWalletAccount(w *wallet.Wallet, prompt string) string {
|
func getWalletAccount(w *wallet.Wallet, prompt string) string {
|
||||||
|
@ -349,17 +365,12 @@ func depositGas(cmd *cobra.Command, acc *wallet.Account, network string) {
|
||||||
mainClient := initClient(n3config[network].RPC)
|
mainClient := initClient(n3config[network].RPC)
|
||||||
neofsHash, _ := util.Uint160DecodeStringLE(n3config[network].NeoFSContract)
|
neofsHash, _ := util.Uint160DecodeStringLE(n3config[network].NeoFSContract)
|
||||||
|
|
||||||
gasHash, err := mainClient.GetNativeContractHash(nativenames.Gas)
|
|
||||||
if err != nil {
|
|
||||||
fatalOnErr(fmt.Errorf("gas contract hash: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
mainActor, err := actor.NewSimple(mainClient, acc)
|
mainActor, err := actor.NewSimple(mainClient, acc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalOnErr(fmt.Errorf("creating actor over main chain client: %w", err))
|
fatalOnErr(fmt.Errorf("creating actor over main chain client: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
mainGas := nep17.New(mainActor, gasHash)
|
mainGas := nep17.New(mainActor, gas.Hash)
|
||||||
|
|
||||||
txHash, _, err := mainGas.Transfer(accSH, neofsHash, amount, nil)
|
txHash, _, err := mainGas.Transfer(accSH, neofsHash, amount, nil)
|
||||||
if err != nil {
|
if err != nil {
|
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-adm/internal/modules"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
|
@ -1,8 +1,8 @@
|
||||||
# How NeoFS CLI uses session mechanism of the NeoFS
|
# How FrostFS CLI uses session mechanism of the FrostFS
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
NeoFS sessions implement a mechanism for issuing a power of attorney by one
|
FrostFS sessions implement a mechanism for issuing a power of attorney by one
|
||||||
party to another. A trusted party can provide a so-called session token as
|
party to another. A trusted party can provide a so-called session token as
|
||||||
proof of the right to act on behalf of another member of the network. The
|
proof of the right to act on behalf of another member of the network. The
|
||||||
client of operations carried out with such a token will be the user who opened
|
client of operations carried out with such a token will be the user who opened
|
||||||
|
@ -15,7 +15,7 @@ attached session token is treated as performed by the original client.
|
||||||
|
|
||||||
## Types
|
## Types
|
||||||
|
|
||||||
NeoFS CLI supports two ways to execute operation within a session depending on
|
FrostFS CLI supports two ways to execute operation within a session depending on
|
||||||
whether the user of the command application is an original user (1) or a trusted
|
whether the user of the command application is an original user (1) or a trusted
|
||||||
one (2).
|
one (2).
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ to the trusted server. At the moment, the approach is applicable only to
|
||||||
creating objects.
|
creating objects.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ neofs-cli session create --rpc-endpoint <server_ip> --out ./blank_token
|
$ frostfs-cli session create --rpc-endpoint <server_ip> --out ./blank_token
|
||||||
```
|
```
|
||||||
After this example command remote node holds session private key while its
|
After this example command remote node holds session private key while its
|
||||||
public part is written into the session token encoded into the output file.
|
public part is written into the session token encoded into the output file.
|
||||||
|
@ -49,7 +49,7 @@ original client, and the CLI uses it only for reading. Ready token MUST have:
|
||||||
|
|
||||||
To sign the session token, exec:
|
To sign the session token, exec:
|
||||||
```shell
|
```shell
|
||||||
$ neofs-cli --wallet <client_wallet> util sign session-token --from ./blank_token --to ./token
|
$ frostfs-cli --wallet <client_wallet> util sign session-token --from ./blank_token --to ./token
|
||||||
```
|
```
|
||||||
Once the token is signed, it MUST NOT be modified.
|
Once the token is signed, it MUST NOT be modified.
|
||||||
|
|
33
cmd/frostfs-cli/docs/storage-node-xheaders.md
Normal file
33
cmd/frostfs-cli/docs/storage-node-xheaders.md
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# Extended headers
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Extended headers are used for request/response. They may contain any
|
||||||
|
user-defined headers to be interpreted on application level. Key name must be a
|
||||||
|
unique valid UTF-8 string. Value can't be empty. Requests or Responses with
|
||||||
|
duplicated header names or headers with empty values are considered invalid.
|
||||||
|
|
||||||
|
## Existing headers
|
||||||
|
|
||||||
|
There are some "well-known" headers starting with `__SYSTEM__` prefix that
|
||||||
|
affect system behaviour. For backward compatibility, the same set of
|
||||||
|
"well-known" headers may also use `__NEOFS__` prefix:
|
||||||
|
|
||||||
|
* `__SYSTEM__NETMAP_EPOCH` - netmap epoch to use for object placement calculation. The `value` is string
|
||||||
|
encoded `uint64` in decimal presentation. If set to '0' or omitted, the
|
||||||
|
current epoch only will be used.
|
||||||
|
* `__SYSTEM__NETMAP_LOOKUP_DEPTH` - if object can't be found using current epoch's netmap, this header limits
|
||||||
|
how many past epochs the node can look up through. Depth is applied to a current epoch or the value
|
||||||
|
of `__SYSTEM__NETMAP_EPOCH` attribute. The `value` is string encoded `uint64` in decimal presentation.
|
||||||
|
If set to '0' or not set, only the current epoch is used.
|
||||||
|
|
||||||
|
## `frostfs-cli` commands with `--xhdr`
|
||||||
|
|
||||||
|
List of commands with support of extended headers:
|
||||||
|
* `container list-objects`
|
||||||
|
* `object delete/get/hash/head/lock/put/range/search`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```shell
|
||||||
|
$ frostfs-cli object put -r s01.frostfs.devenv:8080 -w wallet.json --cid CID --file FILE --xhdr "__SYSTEM__NETMAP_EPOCH=777"
|
||||||
|
```
|
|
@ -6,16 +6,19 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/accounting"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/accounting"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/client"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum"
|
||||||
containerSDK "github.com/nspcc-dev/neofs-sdk-go/container"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
containerSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/eacl"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/object"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||||
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
|
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/version"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BalanceOfPrm groups parameters of BalanceOf operation.
|
// BalanceOfPrm groups parameters of BalanceOf operation.
|
||||||
|
@ -34,11 +37,11 @@ func (x BalanceOfRes) Balance() accounting.Decimal {
|
||||||
return x.cliRes.Amount()
|
return x.cliRes.Amount()
|
||||||
}
|
}
|
||||||
|
|
||||||
// BalanceOf requests the current balance of a NeoFS user.
|
// BalanceOf requests the current balance of a FrostFS user.
|
||||||
//
|
//
|
||||||
// Returns any error which prevented the operation from completing correctly in error return.
|
// Returns any error which prevented the operation from completing correctly in error return.
|
||||||
func BalanceOf(prm BalanceOfPrm) (res BalanceOfRes, err error) {
|
func BalanceOf(ctx context.Context, prm BalanceOfPrm) (res BalanceOfRes, err error) {
|
||||||
res.cliRes, err = prm.cli.BalanceGet(context.Background(), prm.PrmBalanceGet)
|
res.cliRes, err = prm.cli.BalanceGet(ctx, prm.PrmBalanceGet)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -59,19 +62,29 @@ func (x ListContainersRes) IDList() []cid.ID {
|
||||||
return x.cliRes.Containers()
|
return x.cliRes.Containers()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListContainers requests a list of NeoFS user's containers.
|
// ListContainers requests a list of FrostFS user's containers.
|
||||||
//
|
//
|
||||||
// Returns any error which prevented the operation from completing correctly in error return.
|
// Returns any error which prevented the operation from completing correctly in error return.
|
||||||
func ListContainers(prm ListContainersPrm) (res ListContainersRes, err error) {
|
func ListContainers(ctx context.Context, prm ListContainersPrm) (res ListContainersRes, err error) {
|
||||||
res.cliRes, err = prm.cli.ContainerList(context.Background(), prm.PrmContainerList)
|
res.cliRes, err = prm.cli.ContainerList(ctx, prm.PrmContainerList)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SortedIDList returns sorted list of identifiers of user's containers.
|
||||||
|
func (x ListContainersRes) SortedIDList() []cid.ID {
|
||||||
|
list := x.cliRes.Containers()
|
||||||
|
sort.Slice(list, func(i, j int) bool {
|
||||||
|
lhs, rhs := list[i].EncodeToString(), list[j].EncodeToString()
|
||||||
|
return strings.Compare(lhs, rhs) < 0
|
||||||
|
})
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
// PutContainerPrm groups parameters of PutContainer operation.
|
// PutContainerPrm groups parameters of PutContainer operation.
|
||||||
type PutContainerPrm struct {
|
type PutContainerPrm struct {
|
||||||
commonPrm
|
Client *client.Client
|
||||||
client.PrmContainerPut
|
ClientParams client.PrmContainerPut
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutContainerRes groups the resulting values of PutContainer operation.
|
// PutContainerRes groups the resulting values of PutContainer operation.
|
||||||
|
@ -84,7 +97,7 @@ func (x PutContainerRes) ID() cid.ID {
|
||||||
return x.cnr
|
return x.cnr
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutContainer sends a request to save the container in NeoFS.
|
// PutContainer sends a request to save the container in FrostFS.
|
||||||
//
|
//
|
||||||
// Operation is asynchronous and not guaranteed even in the absence of errors.
|
// Operation is asynchronous and not guaranteed even in the absence of errors.
|
||||||
// The required time is also not predictable.
|
// The required time is also not predictable.
|
||||||
|
@ -92,8 +105,8 @@ func (x PutContainerRes) ID() cid.ID {
|
||||||
// Success can be verified by reading by identifier.
|
// Success can be verified by reading by identifier.
|
||||||
//
|
//
|
||||||
// Returns any error which prevented the operation from completing correctly in error return.
|
// Returns any error which prevented the operation from completing correctly in error return.
|
||||||
func PutContainer(prm PutContainerPrm) (res PutContainerRes, err error) {
|
func PutContainer(ctx context.Context, prm PutContainerPrm) (res PutContainerRes, err error) {
|
||||||
cliRes, err := prm.cli.ContainerPut(context.Background(), prm.PrmContainerPut)
|
cliRes, err := prm.Client.ContainerPut(ctx, prm.ClientParams)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
res.cnr = cliRes.ID()
|
res.cnr = cliRes.ID()
|
||||||
}
|
}
|
||||||
|
@ -103,13 +116,15 @@ func PutContainer(prm PutContainerPrm) (res PutContainerRes, err error) {
|
||||||
|
|
||||||
// GetContainerPrm groups parameters of GetContainer operation.
|
// GetContainerPrm groups parameters of GetContainer operation.
|
||||||
type GetContainerPrm struct {
|
type GetContainerPrm struct {
|
||||||
commonPrm
|
Client *client.Client
|
||||||
cliPrm client.PrmContainerGet
|
ClientParams client.PrmContainerGet
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetContainer sets identifier of the container to be read.
|
// SetContainer sets identifier of the container to be read.
|
||||||
|
//
|
||||||
|
// Deprecated: Use GetContainerPrm.ClientParams.ContainerID instead.
|
||||||
func (x *GetContainerPrm) SetContainer(id cid.ID) {
|
func (x *GetContainerPrm) SetContainer(id cid.ID) {
|
||||||
x.cliPrm.SetContainer(id)
|
x.ClientParams.ContainerID = &id
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetContainerRes groups the resulting values of GetContainer operation.
|
// GetContainerRes groups the resulting values of GetContainer operation.
|
||||||
|
@ -122,25 +137,28 @@ func (x GetContainerRes) Container() containerSDK.Container {
|
||||||
return x.cliRes.Container()
|
return x.cliRes.Container()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetContainer reads a container from NeoFS by ID.
|
// GetContainer reads a container from FrostFS by ID.
|
||||||
//
|
//
|
||||||
// Returns any error which prevented the operation from completing correctly in error return.
|
// Returns any error which prevented the operation from completing correctly in error return.
|
||||||
func GetContainer(prm GetContainerPrm) (res GetContainerRes, err error) {
|
func GetContainer(ctx context.Context, prm GetContainerPrm) (res GetContainerRes, err error) {
|
||||||
res.cliRes, err = prm.cli.ContainerGet(context.Background(), prm.cliPrm)
|
res.cliRes, err = prm.Client.ContainerGet(ctx, prm.ClientParams)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsACLExtendable checks if ACL of the container referenced by the given identifier
|
// IsACLExtendable checks if ACL of the container referenced by the given identifier
|
||||||
// can be extended. Client connection MUST BE correctly established in advance.
|
// can be extended. Client connection MUST BE correctly established in advance.
|
||||||
func IsACLExtendable(c *client.Client, cnr cid.ID) (bool, error) {
|
func IsACLExtendable(ctx context.Context, c *client.Client, cnr cid.ID) (bool, error) {
|
||||||
var prm GetContainerPrm
|
prm := GetContainerPrm{
|
||||||
prm.SetClient(c)
|
Client: c,
|
||||||
prm.SetContainer(cnr)
|
ClientParams: client.PrmContainerGet{
|
||||||
|
ContainerID: &cnr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
res, err := GetContainer(prm)
|
res, err := GetContainer(ctx, prm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("get container from the NeoFS: %w", err)
|
return false, fmt.Errorf("get container from the FrostFS: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.Container().BasicACL().Extendable(), nil
|
return res.Container().BasicACL().Extendable(), nil
|
||||||
|
@ -148,14 +166,14 @@ func IsACLExtendable(c *client.Client, cnr cid.ID) (bool, error) {
|
||||||
|
|
||||||
// DeleteContainerPrm groups parameters of DeleteContainerPrm operation.
|
// DeleteContainerPrm groups parameters of DeleteContainerPrm operation.
|
||||||
type DeleteContainerPrm struct {
|
type DeleteContainerPrm struct {
|
||||||
commonPrm
|
Client *client.Client
|
||||||
client.PrmContainerDelete
|
ClientParams client.PrmContainerDelete
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteContainerRes groups the resulting values of DeleteContainer operation.
|
// DeleteContainerRes groups the resulting values of DeleteContainer operation.
|
||||||
type DeleteContainerRes struct{}
|
type DeleteContainerRes struct{}
|
||||||
|
|
||||||
// DeleteContainer sends a request to remove a container from NeoFS by ID.
|
// DeleteContainer sends a request to remove a container from FrostFS by ID.
|
||||||
//
|
//
|
||||||
// Operation is asynchronous and not guaranteed even in the absence of errors.
|
// Operation is asynchronous and not guaranteed even in the absence of errors.
|
||||||
// The required time is also not predictable.
|
// The required time is also not predictable.
|
||||||
|
@ -163,16 +181,16 @@ type DeleteContainerRes struct{}
|
||||||
// Success can be verified by reading by identifier.
|
// Success can be verified by reading by identifier.
|
||||||
//
|
//
|
||||||
// Returns any error which prevented the operation from completing correctly in error return.
|
// Returns any error which prevented the operation from completing correctly in error return.
|
||||||
func DeleteContainer(prm DeleteContainerPrm) (res DeleteContainerRes, err error) {
|
func DeleteContainer(ctx context.Context, prm DeleteContainerPrm) (res DeleteContainerRes, err error) {
|
||||||
_, err = prm.cli.ContainerDelete(context.Background(), prm.PrmContainerDelete)
|
_, err = prm.Client.ContainerDelete(ctx, prm.ClientParams)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// EACLPrm groups parameters of EACL operation.
|
// EACLPrm groups parameters of EACL operation.
|
||||||
type EACLPrm struct {
|
type EACLPrm struct {
|
||||||
commonPrm
|
Client *client.Client
|
||||||
client.PrmContainerEACL
|
ClientParams client.PrmContainerEACL
|
||||||
}
|
}
|
||||||
|
|
||||||
// EACLRes groups the resulting values of EACL operation.
|
// EACLRes groups the resulting values of EACL operation.
|
||||||
|
@ -185,25 +203,25 @@ func (x EACLRes) EACL() eacl.Table {
|
||||||
return x.cliRes.Table()
|
return x.cliRes.Table()
|
||||||
}
|
}
|
||||||
|
|
||||||
// EACL reads eACL table from NeoFS by container ID.
|
// EACL reads eACL table from FrostFS by container ID.
|
||||||
//
|
//
|
||||||
// Returns any error which prevented the operation from completing correctly in error return.
|
// Returns any error which prevented the operation from completing correctly in error return.
|
||||||
func EACL(prm EACLPrm) (res EACLRes, err error) {
|
func EACL(ctx context.Context, prm EACLPrm) (res EACLRes, err error) {
|
||||||
res.cliRes, err = prm.cli.ContainerEACL(context.Background(), prm.PrmContainerEACL)
|
res.cliRes, err = prm.Client.ContainerEACL(ctx, prm.ClientParams)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetEACLPrm groups parameters of SetEACL operation.
|
// SetEACLPrm groups parameters of SetEACL operation.
|
||||||
type SetEACLPrm struct {
|
type SetEACLPrm struct {
|
||||||
commonPrm
|
Client *client.Client
|
||||||
client.PrmContainerSetEACL
|
ClientParams client.PrmContainerSetEACL
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetEACLRes groups the resulting values of SetEACL operation.
|
// SetEACLRes groups the resulting values of SetEACL operation.
|
||||||
type SetEACLRes struct{}
|
type SetEACLRes struct{}
|
||||||
|
|
||||||
// SetEACL requests to save an eACL table in NeoFS.
|
// SetEACL requests to save an eACL table in FrostFS.
|
||||||
//
|
//
|
||||||
// Operation is asynchronous and no guaranteed even in the absence of errors.
|
// Operation is asynchronous and no guaranteed even in the absence of errors.
|
||||||
// The required time is also not predictable.
|
// The required time is also not predictable.
|
||||||
|
@ -211,16 +229,16 @@ type SetEACLRes struct{}
|
||||||
// Success can be verified by reading by container identifier.
|
// Success can be verified by reading by container identifier.
|
||||||
//
|
//
|
||||||
// Returns any error which prevented the operation from completing correctly in error return.
|
// Returns any error which prevented the operation from completing correctly in error return.
|
||||||
func SetEACL(prm SetEACLPrm) (res SetEACLRes, err error) {
|
func SetEACL(ctx context.Context, prm SetEACLPrm) (res SetEACLRes, err error) {
|
||||||
_, err = prm.cli.ContainerSetEACL(context.Background(), prm.PrmContainerSetEACL)
|
_, err = prm.Client.ContainerSetEACL(ctx, prm.ClientParams)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetworkInfoPrm groups parameters of NetworkInfo operation.
|
// NetworkInfoPrm groups parameters of NetworkInfo operation.
|
||||||
type NetworkInfoPrm struct {
|
type NetworkInfoPrm struct {
|
||||||
commonPrm
|
Client *client.Client
|
||||||
client.PrmNetworkInfo
|
ClientParams client.PrmNetworkInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetworkInfoRes groups the resulting values of NetworkInfo operation.
|
// NetworkInfoRes groups the resulting values of NetworkInfo operation.
|
||||||
|
@ -228,24 +246,24 @@ type NetworkInfoRes struct {
|
||||||
cliRes *client.ResNetworkInfo
|
cliRes *client.ResNetworkInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetworkInfo returns structured information about the NeoFS network.
|
// NetworkInfo returns structured information about the FrostFS network.
|
||||||
func (x NetworkInfoRes) NetworkInfo() netmap.NetworkInfo {
|
func (x NetworkInfoRes) NetworkInfo() netmap.NetworkInfo {
|
||||||
return x.cliRes.Info()
|
return x.cliRes.Info()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetworkInfo reads information about the NeoFS network.
|
// NetworkInfo reads information about the FrostFS network.
|
||||||
//
|
//
|
||||||
// Returns any error which prevented the operation from completing correctly in error return.
|
// Returns any error which prevented the operation from completing correctly in error return.
|
||||||
func NetworkInfo(prm NetworkInfoPrm) (res NetworkInfoRes, err error) {
|
func NetworkInfo(ctx context.Context, prm NetworkInfoPrm) (res NetworkInfoRes, err error) {
|
||||||
res.cliRes, err = prm.cli.NetworkInfo(context.Background(), prm.PrmNetworkInfo)
|
res.cliRes, err = prm.Client.NetworkInfo(ctx, prm.ClientParams)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeInfoPrm groups parameters of NodeInfo operation.
|
// NodeInfoPrm groups parameters of NodeInfo operation.
|
||||||
type NodeInfoPrm struct {
|
type NodeInfoPrm struct {
|
||||||
commonPrm
|
Client *client.Client
|
||||||
client.PrmEndpointInfo
|
ClientParams client.PrmEndpointInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeInfoRes groups the resulting values of NodeInfo operation.
|
// NodeInfoRes groups the resulting values of NodeInfo operation.
|
||||||
|
@ -258,16 +276,16 @@ func (x NodeInfoRes) NodeInfo() netmap.NodeInfo {
|
||||||
return x.cliRes.NodeInfo()
|
return x.cliRes.NodeInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
// LatestVersion returns the latest NeoFS API version in use.
|
// LatestVersion returns the latest FrostFS API version in use.
|
||||||
func (x NodeInfoRes) LatestVersion() version.Version {
|
func (x NodeInfoRes) LatestVersion() version.Version {
|
||||||
return x.cliRes.LatestVersion()
|
return x.cliRes.LatestVersion()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeInfo requests information about the remote server from NeoFS netmap.
|
// NodeInfo requests information about the remote server from FrostFS netmap.
|
||||||
//
|
//
|
||||||
// Returns any error which prevented the operation from completing correctly in error return.
|
// Returns any error which prevented the operation from completing correctly in error return.
|
||||||
func NodeInfo(prm NodeInfoPrm) (res NodeInfoRes, err error) {
|
func NodeInfo(ctx context.Context, prm NodeInfoPrm) (res NodeInfoRes, err error) {
|
||||||
res.cliRes, err = prm.cli.EndpointInfo(context.Background(), prm.PrmEndpointInfo)
|
res.cliRes, err = prm.Client.EndpointInfo(ctx, prm.ClientParams)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -282,7 +300,7 @@ type NetMapSnapshotRes struct {
|
||||||
cliRes *client.ResNetMapSnapshot
|
cliRes *client.ResNetMapSnapshot
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetMap returns current local snapshot of the NeoFS network map.
|
// NetMap returns current local snapshot of the FrostFS network map.
|
||||||
func (x NetMapSnapshotRes) NetMap() netmap.NetMap {
|
func (x NetMapSnapshotRes) NetMap() netmap.NetMap {
|
||||||
return x.cliRes.NetMap()
|
return x.cliRes.NetMap()
|
||||||
}
|
}
|
||||||
|
@ -290,8 +308,8 @@ func (x NetMapSnapshotRes) NetMap() netmap.NetMap {
|
||||||
// NetMapSnapshot requests current network view of the remote server.
|
// NetMapSnapshot requests current network view of the remote server.
|
||||||
//
|
//
|
||||||
// Returns any error which prevented the operation from completing correctly in error return.
|
// Returns any error which prevented the operation from completing correctly in error return.
|
||||||
func NetMapSnapshot(prm NetMapSnapshotPrm) (res NetMapSnapshotRes, err error) {
|
func NetMapSnapshot(ctx context.Context, prm NetMapSnapshotPrm) (res NetMapSnapshotRes, err error) {
|
||||||
res.cliRes, err = prm.cli.NetMapSnapshot(context.Background(), client.PrmNetMapSnapshot{})
|
res.cliRes, err = prm.cli.NetMapSnapshot(ctx, client.PrmNetMapSnapshot{})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,8 +337,8 @@ func (x CreateSessionRes) SessionKey() []byte {
|
||||||
// CreateSession opens a new unlimited session with the remote node.
|
// CreateSession opens a new unlimited session with the remote node.
|
||||||
//
|
//
|
||||||
// Returns any error which prevented the operation from completing correctly in error return.
|
// Returns any error which prevented the operation from completing correctly in error return.
|
||||||
func CreateSession(prm CreateSessionPrm) (res CreateSessionRes, err error) {
|
func CreateSession(ctx context.Context, prm CreateSessionPrm) (res CreateSessionRes, err error) {
|
||||||
res.cliRes, err = prm.cli.SessionCreate(context.Background(), prm.PrmSessionCreate)
|
res.cliRes, err = prm.cli.SessionCreate(ctx, prm.PrmSessionCreate)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -329,15 +347,19 @@ func CreateSession(prm CreateSessionPrm) (res CreateSessionRes, err error) {
|
||||||
type PutObjectPrm struct {
|
type PutObjectPrm struct {
|
||||||
commonObjectPrm
|
commonObjectPrm
|
||||||
|
|
||||||
hdr *object.Object
|
copyNum []uint32
|
||||||
|
|
||||||
|
hdr *objectSDK.Object
|
||||||
|
|
||||||
rdr io.Reader
|
rdr io.Reader
|
||||||
|
|
||||||
headerCallback func(*object.Object)
|
headerCallback func(*objectSDK.Object)
|
||||||
|
|
||||||
|
prepareLocally bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHeader sets object header.
|
// SetHeader sets object header.
|
||||||
func (x *PutObjectPrm) SetHeader(hdr *object.Object) {
|
func (x *PutObjectPrm) SetHeader(hdr *objectSDK.Object) {
|
||||||
x.hdr = hdr
|
x.hdr = hdr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -348,10 +370,44 @@ func (x *PutObjectPrm) SetPayloadReader(rdr io.Reader) {
|
||||||
|
|
||||||
// SetHeaderCallback sets callback which is called on the object after the header is received
|
// SetHeaderCallback sets callback which is called on the object after the header is received
|
||||||
// but before the payload is written.
|
// but before the payload is written.
|
||||||
func (x *PutObjectPrm) SetHeaderCallback(f func(*object.Object)) {
|
func (x *PutObjectPrm) SetHeaderCallback(f func(*objectSDK.Object)) {
|
||||||
x.headerCallback = f
|
x.headerCallback = f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetCopiesNumberByVectors sets ordered list of minimal required object copies numbers
|
||||||
|
// per placement vector.
|
||||||
|
func (x *PutObjectPrm) SetCopiesNumberByVectors(copiesNumbers []uint32) {
|
||||||
|
x.copyNum = copiesNumbers
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrepareLocally generate object header on the client side.
|
||||||
|
// For big object - split locally too.
|
||||||
|
func (x *PutObjectPrm) PrepareLocally() {
|
||||||
|
x.prepareLocally = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *PutObjectPrm) convertToSDKPrm(ctx context.Context) (client.PrmObjectPutInit, error) {
|
||||||
|
putPrm := client.PrmObjectPutInit{
|
||||||
|
XHeaders: x.xHeaders,
|
||||||
|
BearerToken: x.bearerToken,
|
||||||
|
Local: x.local,
|
||||||
|
CopiesNumber: x.copyNum,
|
||||||
|
}
|
||||||
|
|
||||||
|
if x.prepareLocally {
|
||||||
|
res, err := x.cli.NetworkInfo(ctx, client.PrmNetworkInfo{})
|
||||||
|
if err != nil {
|
||||||
|
return client.PrmObjectPutInit{}, err
|
||||||
|
}
|
||||||
|
putPrm.MaxSize = res.Info().MaxObjectSize()
|
||||||
|
putPrm.EpochSource = epochSource(res.Info().CurrentEpoch())
|
||||||
|
putPrm.WithoutHomomorphHash = res.Info().HomomorphicHashingDisabled()
|
||||||
|
} else {
|
||||||
|
putPrm.Session = x.sessionToken
|
||||||
|
}
|
||||||
|
return putPrm, nil
|
||||||
|
}
|
||||||
|
|
||||||
// PutObjectRes groups the resulting values of PutObject operation.
|
// PutObjectRes groups the resulting values of PutObject operation.
|
||||||
type PutObjectRes struct {
|
type PutObjectRes struct {
|
||||||
id oid.ID
|
id oid.ID
|
||||||
|
@ -362,32 +418,26 @@ func (x PutObjectRes) ID() oid.ID {
|
||||||
return x.id
|
return x.id
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutObject saves the object in NeoFS network.
|
type epochSource uint64
|
||||||
|
|
||||||
|
func (s epochSource) CurrentEpoch() uint64 {
|
||||||
|
return uint64(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutObject saves the object in FrostFS network.
|
||||||
//
|
//
|
||||||
// Returns any error which prevented the operation from completing correctly in error return.
|
// Returns any error which prevented the operation from completing correctly in error return.
|
||||||
func PutObject(prm PutObjectPrm) (*PutObjectRes, error) {
|
func PutObject(ctx context.Context, prm PutObjectPrm) (*PutObjectRes, error) {
|
||||||
var putPrm client.PrmObjectPutInit
|
sdkPrm, err := prm.convertToSDKPrm(ctx)
|
||||||
|
if err != nil {
|
||||||
if prm.sessionToken != nil {
|
return nil, fmt.Errorf("unable to create parameters of object put operation: %w", err)
|
||||||
putPrm.WithinSession(*prm.sessionToken)
|
|
||||||
}
|
}
|
||||||
|
wrt, err := prm.cli.ObjectPutInit(ctx, sdkPrm)
|
||||||
if prm.bearerToken != nil {
|
|
||||||
putPrm.WithBearerToken(*prm.bearerToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
if prm.local {
|
|
||||||
putPrm.MarkLocal()
|
|
||||||
}
|
|
||||||
|
|
||||||
putPrm.WithXHeaders(prm.xHeaders...)
|
|
||||||
|
|
||||||
wrt, err := prm.cli.ObjectPutInit(context.Background(), putPrm)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("init object writing: %w", err)
|
return nil, fmt.Errorf("init object writing: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if wrt.WriteHeader(*prm.hdr) {
|
if wrt.WriteHeader(ctx, *prm.hdr) {
|
||||||
if prm.headerCallback != nil {
|
if prm.headerCallback != nil {
|
||||||
prm.headerCallback(prm.hdr)
|
prm.headerCallback(prm.hdr)
|
||||||
}
|
}
|
||||||
|
@ -404,8 +454,7 @@ func PutObject(prm PutObjectPrm) (*PutObjectRes, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if prm.rdr != nil {
|
if prm.rdr != nil {
|
||||||
// TODO: (neofs-node#1198) explore better values or configure it
|
const defaultBufferSizePut = 3 << 20 // Maximum chunk size is 3 MiB in the SDK.
|
||||||
const defaultBufferSizePut = 4096
|
|
||||||
|
|
||||||
if sz == 0 || sz > defaultBufferSizePut {
|
if sz == 0 || sz > defaultBufferSizePut {
|
||||||
sz = defaultBufferSizePut
|
sz = defaultBufferSizePut
|
||||||
|
@ -418,7 +467,7 @@ func PutObject(prm PutObjectPrm) (*PutObjectRes, error) {
|
||||||
for {
|
for {
|
||||||
n, err = prm.rdr.Read(buf)
|
n, err = prm.rdr.Read(buf)
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
if !wrt.WritePayloadChunk(buf[:n]) {
|
if !wrt.WritePayloadChunk(ctx, buf[:n]) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -434,7 +483,7 @@ func PutObject(prm PutObjectPrm) (*PutObjectRes, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cliRes, err := wrt.Close()
|
cliRes, err := wrt.Close(ctx)
|
||||||
if err != nil { // here err already carries both status and client errors
|
if err != nil { // here err already carries both status and client errors
|
||||||
return nil, fmt.Errorf("client failure: %w", err)
|
return nil, fmt.Errorf("client failure: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -460,25 +509,22 @@ func (x DeleteObjectRes) Tombstone() oid.ID {
|
||||||
return x.tomb
|
return x.tomb
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteObject marks an object to be removed from NeoFS through tombstone placement.
|
// DeleteObject marks an object to be removed from FrostFS through tombstone placement.
|
||||||
//
|
//
|
||||||
// Returns any error which prevented the operation from completing correctly in error return.
|
// Returns any error which prevented the operation from completing correctly in error return.
|
||||||
func DeleteObject(prm DeleteObjectPrm) (*DeleteObjectRes, error) {
|
func DeleteObject(ctx context.Context, prm DeleteObjectPrm) (*DeleteObjectRes, error) {
|
||||||
var delPrm client.PrmObjectDelete
|
cnr := prm.objAddr.Container()
|
||||||
delPrm.FromContainer(prm.objAddr.Container())
|
obj := prm.objAddr.Object()
|
||||||
delPrm.ByID(prm.objAddr.Object())
|
|
||||||
|
|
||||||
if prm.sessionToken != nil {
|
delPrm := client.PrmObjectDelete{
|
||||||
delPrm.WithinSession(*prm.sessionToken)
|
XHeaders: prm.xHeaders,
|
||||||
|
ContainerID: &cnr,
|
||||||
|
ObjectID: &obj,
|
||||||
|
Session: prm.sessionToken,
|
||||||
|
BearerToken: prm.bearerToken,
|
||||||
}
|
}
|
||||||
|
|
||||||
if prm.bearerToken != nil {
|
cliRes, err := prm.cli.ObjectDelete(ctx, delPrm)
|
||||||
delPrm.WithBearerToken(*prm.bearerToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
delPrm.WithXHeaders(prm.xHeaders...)
|
|
||||||
|
|
||||||
cliRes, err := prm.cli.ObjectDelete(context.Background(), delPrm)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("remove object via client: %w", err)
|
return nil, fmt.Errorf("remove object via client: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -494,22 +540,22 @@ type GetObjectPrm struct {
|
||||||
objectAddressPrm
|
objectAddressPrm
|
||||||
rawPrm
|
rawPrm
|
||||||
payloadWriterPrm
|
payloadWriterPrm
|
||||||
headerCallback func(*object.Object)
|
headerCallback func(*objectSDK.Object)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHeaderCallback sets callback which is called on the object after the header is received
|
// SetHeaderCallback sets callback which is called on the object after the header is received
|
||||||
// but before the payload is written.
|
// but before the payload is written.
|
||||||
func (p *GetObjectPrm) SetHeaderCallback(f func(*object.Object)) {
|
func (p *GetObjectPrm) SetHeaderCallback(f func(*objectSDK.Object)) {
|
||||||
p.headerCallback = f
|
p.headerCallback = f
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetObjectRes groups the resulting values of GetObject operation.
|
// GetObjectRes groups the resulting values of GetObject operation.
|
||||||
type GetObjectRes struct {
|
type GetObjectRes struct {
|
||||||
hdr *object.Object
|
hdr *objectSDK.Object
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header returns the header of the request object.
|
// Header returns the header of the request object.
|
||||||
func (x GetObjectRes) Header() *object.Object {
|
func (x GetObjectRes) Header() *objectSDK.Object {
|
||||||
return x.hdr
|
return x.hdr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -519,35 +565,26 @@ func (x GetObjectRes) Header() *object.Object {
|
||||||
//
|
//
|
||||||
// Returns any error which prevented the operation from completing correctly in error return.
|
// Returns any error which prevented the operation from completing correctly in error return.
|
||||||
// For raw reading, returns *object.SplitInfoError error if object is virtual.
|
// For raw reading, returns *object.SplitInfoError error if object is virtual.
|
||||||
func GetObject(prm GetObjectPrm) (*GetObjectRes, error) {
|
func GetObject(ctx context.Context, prm GetObjectPrm) (*GetObjectRes, error) {
|
||||||
var getPrm client.PrmObjectGet
|
cnr := prm.objAddr.Container()
|
||||||
getPrm.FromContainer(prm.objAddr.Container())
|
obj := prm.objAddr.Object()
|
||||||
getPrm.ByID(prm.objAddr.Object())
|
|
||||||
|
|
||||||
if prm.sessionToken != nil {
|
getPrm := client.PrmObjectGet{
|
||||||
getPrm.WithinSession(*prm.sessionToken)
|
XHeaders: prm.xHeaders,
|
||||||
|
BearerToken: prm.bearerToken,
|
||||||
|
Session: prm.sessionToken,
|
||||||
|
Raw: prm.raw,
|
||||||
|
Local: prm.local,
|
||||||
|
ContainerID: &cnr,
|
||||||
|
ObjectID: &obj,
|
||||||
}
|
}
|
||||||
|
|
||||||
if prm.bearerToken != nil {
|
rdr, err := prm.cli.ObjectGetInit(ctx, getPrm)
|
||||||
getPrm.WithBearerToken(*prm.bearerToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
if prm.raw {
|
|
||||||
getPrm.MarkRaw()
|
|
||||||
}
|
|
||||||
|
|
||||||
if prm.local {
|
|
||||||
getPrm.MarkLocal()
|
|
||||||
}
|
|
||||||
|
|
||||||
getPrm.WithXHeaders(prm.xHeaders...)
|
|
||||||
|
|
||||||
rdr, err := prm.cli.ObjectGetInit(context.Background(), getPrm)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("init object reading on client: %w", err)
|
return nil, fmt.Errorf("init object reading on client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var hdr object.Object
|
var hdr objectSDK.Object
|
||||||
|
|
||||||
if !rdr.ReadHeader(&hdr) {
|
if !rdr.ReadHeader(&hdr) {
|
||||||
_, err = rdr.Close()
|
_, err = rdr.Close()
|
||||||
|
@ -576,18 +613,18 @@ type HeadObjectPrm struct {
|
||||||
mainOnly bool
|
mainOnly bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetMainOnlyFlag sets flag to get only main fields of an object header in terms of NeoFS API.
|
// SetMainOnlyFlag sets flag to get only main fields of an object header in terms of FrostFS API.
|
||||||
func (x *HeadObjectPrm) SetMainOnlyFlag(v bool) {
|
func (x *HeadObjectPrm) SetMainOnlyFlag(v bool) {
|
||||||
x.mainOnly = v
|
x.mainOnly = v
|
||||||
}
|
}
|
||||||
|
|
||||||
// HeadObjectRes groups the resulting values of HeadObject operation.
|
// HeadObjectRes groups the resulting values of HeadObject operation.
|
||||||
type HeadObjectRes struct {
|
type HeadObjectRes struct {
|
||||||
hdr *object.Object
|
hdr *objectSDK.Object
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header returns the requested object header.
|
// Header returns the requested object header.
|
||||||
func (x HeadObjectRes) Header() *object.Object {
|
func (x HeadObjectRes) Header() *objectSDK.Object {
|
||||||
return x.hdr
|
return x.hdr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -595,35 +632,26 @@ func (x HeadObjectRes) Header() *object.Object {
|
||||||
//
|
//
|
||||||
// Returns any error which prevented the operation from completing correctly in error return.
|
// Returns any error which prevented the operation from completing correctly in error return.
|
||||||
// For raw reading, returns *object.SplitInfoError error if object is virtual.
|
// For raw reading, returns *object.SplitInfoError error if object is virtual.
|
||||||
func HeadObject(prm HeadObjectPrm) (*HeadObjectRes, error) {
|
func HeadObject(ctx context.Context, prm HeadObjectPrm) (*HeadObjectRes, error) {
|
||||||
var cliPrm client.PrmObjectHead
|
cnr := prm.objAddr.Container()
|
||||||
cliPrm.FromContainer(prm.objAddr.Container())
|
obj := prm.objAddr.Object()
|
||||||
cliPrm.ByID(prm.objAddr.Object())
|
|
||||||
|
|
||||||
if prm.sessionToken != nil {
|
headPrm := client.PrmObjectHead{
|
||||||
cliPrm.WithinSession(*prm.sessionToken)
|
XHeaders: prm.xHeaders,
|
||||||
|
BearerToken: prm.bearerToken,
|
||||||
|
Session: prm.sessionToken,
|
||||||
|
Raw: prm.raw,
|
||||||
|
Local: prm.local,
|
||||||
|
ContainerID: &cnr,
|
||||||
|
ObjectID: &obj,
|
||||||
}
|
}
|
||||||
|
|
||||||
if prm.bearerToken != nil {
|
res, err := prm.cli.ObjectHead(ctx, headPrm)
|
||||||
cliPrm.WithBearerToken(*prm.bearerToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
if prm.raw {
|
|
||||||
cliPrm.MarkRaw()
|
|
||||||
}
|
|
||||||
|
|
||||||
if prm.local {
|
|
||||||
cliPrm.MarkLocal()
|
|
||||||
}
|
|
||||||
|
|
||||||
cliPrm.WithXHeaders(prm.xHeaders...)
|
|
||||||
|
|
||||||
res, err := prm.cli.ObjectHead(context.Background(), cliPrm)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("read object header via client: %w", err)
|
return nil, fmt.Errorf("read object header via client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var hdr object.Object
|
var hdr objectSDK.Object
|
||||||
|
|
||||||
if !res.ReadHeader(&hdr) {
|
if !res.ReadHeader(&hdr) {
|
||||||
return nil, fmt.Errorf("missing header in response")
|
return nil, fmt.Errorf("missing header in response")
|
||||||
|
@ -639,11 +667,11 @@ type SearchObjectsPrm struct {
|
||||||
commonObjectPrm
|
commonObjectPrm
|
||||||
containerIDPrm
|
containerIDPrm
|
||||||
|
|
||||||
filters object.SearchFilters
|
filters objectSDK.SearchFilters
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetFilters sets search filters.
|
// SetFilters sets search filters.
|
||||||
func (x *SearchObjectsPrm) SetFilters(filters object.SearchFilters) {
|
func (x *SearchObjectsPrm) SetFilters(filters objectSDK.SearchFilters) {
|
||||||
x.filters = filters
|
x.filters = filters
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -660,26 +688,17 @@ func (x SearchObjectsRes) IDList() []oid.ID {
|
||||||
// SearchObjects selects objects from the container which match the filters.
|
// SearchObjects selects objects from the container which match the filters.
|
||||||
//
|
//
|
||||||
// Returns any error which prevented the operation from completing correctly in error return.
|
// Returns any error which prevented the operation from completing correctly in error return.
|
||||||
func SearchObjects(prm SearchObjectsPrm) (*SearchObjectsRes, error) {
|
func SearchObjects(ctx context.Context, prm SearchObjectsPrm) (*SearchObjectsRes, error) {
|
||||||
var cliPrm client.PrmObjectSearch
|
cliPrm := client.PrmObjectSearch{
|
||||||
cliPrm.InContainer(prm.cnrID)
|
XHeaders: prm.xHeaders,
|
||||||
cliPrm.SetFilters(prm.filters)
|
Local: prm.local,
|
||||||
|
BearerToken: prm.bearerToken,
|
||||||
if prm.sessionToken != nil {
|
Session: prm.sessionToken,
|
||||||
cliPrm.WithinSession(*prm.sessionToken)
|
ContainerID: &prm.cnrID,
|
||||||
|
Filters: prm.filters,
|
||||||
}
|
}
|
||||||
|
|
||||||
if prm.bearerToken != nil {
|
rdr, err := prm.cli.ObjectSearchInit(ctx, cliPrm)
|
||||||
cliPrm.WithBearerToken(*prm.bearerToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
if prm.local {
|
|
||||||
cliPrm.MarkLocal()
|
|
||||||
}
|
|
||||||
|
|
||||||
cliPrm.WithXHeaders(prm.xHeaders...)
|
|
||||||
|
|
||||||
rdr, err := prm.cli.ObjectSearchInit(context.Background(), cliPrm)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("init object search: %w", err)
|
return nil, fmt.Errorf("init object search: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -704,6 +723,11 @@ func SearchObjects(prm SearchObjectsPrm) (*SearchObjectsRes, error) {
|
||||||
return nil, fmt.Errorf("read object list: %w", err)
|
return nil, fmt.Errorf("read object list: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sort.Slice(list, func(i, j int) bool {
|
||||||
|
lhs, rhs := list[i].EncodeToString(), list[j].EncodeToString()
|
||||||
|
return strings.Compare(lhs, rhs) < 0
|
||||||
|
})
|
||||||
|
|
||||||
return &SearchObjectsRes{
|
return &SearchObjectsRes{
|
||||||
ids: list,
|
ids: list,
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -716,7 +740,7 @@ type HashPayloadRangesPrm struct {
|
||||||
|
|
||||||
tz bool
|
tz bool
|
||||||
|
|
||||||
rngs []*object.Range
|
rngs []objectSDK.Range
|
||||||
|
|
||||||
salt []byte
|
salt []byte
|
||||||
}
|
}
|
||||||
|
@ -727,7 +751,7 @@ func (x *HashPayloadRangesPrm) TZ() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetRanges sets a list of payload ranges to hash.
|
// SetRanges sets a list of payload ranges to hash.
|
||||||
func (x *HashPayloadRangesPrm) SetRanges(rngs []*object.Range) {
|
func (x *HashPayloadRangesPrm) SetRanges(rngs []objectSDK.Range) {
|
||||||
x.rngs = rngs
|
x.rngs = rngs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -750,41 +774,27 @@ func (x HashPayloadRangesRes) HashList() [][]byte {
|
||||||
//
|
//
|
||||||
// Returns any error which prevented the operation from completing correctly in error return.
|
// Returns any error which prevented the operation from completing correctly in error return.
|
||||||
// Returns an error if number of received hashes differs with the number of requested ranges.
|
// Returns an error if number of received hashes differs with the number of requested ranges.
|
||||||
func HashPayloadRanges(prm HashPayloadRangesPrm) (*HashPayloadRangesRes, error) {
|
func HashPayloadRanges(ctx context.Context, prm HashPayloadRangesPrm) (*HashPayloadRangesRes, error) {
|
||||||
var cliPrm client.PrmObjectHash
|
cs := checksum.SHA256
|
||||||
cliPrm.FromContainer(prm.objAddr.Container())
|
|
||||||
cliPrm.ByID(prm.objAddr.Object())
|
|
||||||
|
|
||||||
if prm.local {
|
|
||||||
cliPrm.MarkLocal()
|
|
||||||
}
|
|
||||||
|
|
||||||
cliPrm.UseSalt(prm.salt)
|
|
||||||
|
|
||||||
rngs := make([]uint64, 2*len(prm.rngs))
|
|
||||||
|
|
||||||
for i := range prm.rngs {
|
|
||||||
rngs[2*i] = prm.rngs[i].GetOffset()
|
|
||||||
rngs[2*i+1] = prm.rngs[i].GetLength()
|
|
||||||
}
|
|
||||||
|
|
||||||
cliPrm.SetRangeList(rngs...)
|
|
||||||
|
|
||||||
if prm.tz {
|
if prm.tz {
|
||||||
cliPrm.TillichZemorAlgo()
|
cs = checksum.TZ
|
||||||
}
|
}
|
||||||
|
|
||||||
if prm.sessionToken != nil {
|
cnr := prm.objAddr.Container()
|
||||||
cliPrm.WithinSession(*prm.sessionToken)
|
obj := prm.objAddr.Object()
|
||||||
|
cliPrm := client.PrmObjectHash{
|
||||||
|
ContainerID: &cnr,
|
||||||
|
ObjectID: &obj,
|
||||||
|
Local: prm.local,
|
||||||
|
Salt: prm.salt,
|
||||||
|
Ranges: prm.rngs,
|
||||||
|
ChecksumType: cs,
|
||||||
|
Session: prm.sessionToken,
|
||||||
|
BearerToken: prm.bearerToken,
|
||||||
|
XHeaders: prm.xHeaders,
|
||||||
}
|
}
|
||||||
|
|
||||||
if prm.bearerToken != nil {
|
res, err := prm.cli.ObjectHash(ctx, cliPrm)
|
||||||
cliPrm.WithBearerToken(*prm.bearerToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
cliPrm.WithXHeaders(prm.xHeaders...)
|
|
||||||
|
|
||||||
res, err := prm.cli.ObjectHash(context.Background(), cliPrm)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("read payload hashes via client: %w", err)
|
return nil, fmt.Errorf("read payload hashes via client: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -801,50 +811,40 @@ type PayloadRangePrm struct {
|
||||||
rawPrm
|
rawPrm
|
||||||
payloadWriterPrm
|
payloadWriterPrm
|
||||||
|
|
||||||
rng *object.Range
|
rng *objectSDK.Range
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetRange sets payload range to read.
|
// SetRange sets payload range to read.
|
||||||
func (x *PayloadRangePrm) SetRange(rng *object.Range) {
|
func (x *PayloadRangePrm) SetRange(rng *objectSDK.Range) {
|
||||||
x.rng = rng
|
x.rng = rng
|
||||||
}
|
}
|
||||||
|
|
||||||
// PayloadRangeRes groups the resulting values of PayloadRange operation.
|
// PayloadRangeRes groups the resulting values of PayloadRange operation.
|
||||||
type PayloadRangeRes struct{}
|
type PayloadRangeRes struct{}
|
||||||
|
|
||||||
// PayloadRange reads object payload range from NeoFS and writes it to the specified writer.
|
// PayloadRange reads object payload range from FrostFS and writes it to the specified writer.
|
||||||
//
|
//
|
||||||
// Interrupts on any writer error.
|
// Interrupts on any writer error.
|
||||||
//
|
//
|
||||||
// Returns any error which prevented the operation from completing correctly in error return.
|
// Returns any error which prevented the operation from completing correctly in error return.
|
||||||
// For raw reading, returns *object.SplitInfoError error if object is virtual.
|
// For raw reading, returns *object.SplitInfoError error if object is virtual.
|
||||||
func PayloadRange(prm PayloadRangePrm) (*PayloadRangeRes, error) {
|
func PayloadRange(ctx context.Context, prm PayloadRangePrm) (*PayloadRangeRes, error) {
|
||||||
var cliPrm client.PrmObjectRange
|
cnr := prm.objAddr.Container()
|
||||||
cliPrm.FromContainer(prm.objAddr.Container())
|
obj := prm.objAddr.Object()
|
||||||
cliPrm.ByID(prm.objAddr.Object())
|
|
||||||
|
|
||||||
if prm.sessionToken != nil {
|
rangePrm := client.PrmObjectRange{
|
||||||
cliPrm.WithinSession(*prm.sessionToken)
|
XHeaders: prm.xHeaders,
|
||||||
|
BearerToken: prm.bearerToken,
|
||||||
|
Session: prm.sessionToken,
|
||||||
|
Raw: prm.raw,
|
||||||
|
Local: prm.local,
|
||||||
|
ContainerID: &cnr,
|
||||||
|
ObjectID: &obj,
|
||||||
|
Offset: prm.rng.GetOffset(),
|
||||||
|
Length: prm.rng.GetLength(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if prm.bearerToken != nil {
|
rdr, err := prm.cli.ObjectRangeInit(ctx, rangePrm)
|
||||||
cliPrm.WithBearerToken(*prm.bearerToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
if prm.raw {
|
|
||||||
cliPrm.MarkRaw()
|
|
||||||
}
|
|
||||||
|
|
||||||
if prm.local {
|
|
||||||
cliPrm.MarkLocal()
|
|
||||||
}
|
|
||||||
|
|
||||||
cliPrm.SetOffset(prm.rng.GetOffset())
|
|
||||||
cliPrm.SetLength(prm.rng.GetLength())
|
|
||||||
|
|
||||||
cliPrm.WithXHeaders(prm.xHeaders...)
|
|
||||||
|
|
||||||
rdr, err := prm.cli.ObjectRangeInit(context.Background(), cliPrm)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("init payload reading: %w", err)
|
return nil, fmt.Errorf("init payload reading: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -872,18 +872,18 @@ func (s *SyncContainerPrm) SetContainer(c *containerSDK.Container) {
|
||||||
// operation.
|
// operation.
|
||||||
type SyncContainerRes struct{}
|
type SyncContainerRes struct{}
|
||||||
|
|
||||||
// SyncContainerSettings reads global network config from NeoFS and
|
// SyncContainerSettings reads global network config from FrostFS and
|
||||||
// syncs container settings with it.
|
// syncs container settings with it.
|
||||||
//
|
//
|
||||||
// Interrupts on any writer error.
|
// Interrupts on any writer error.
|
||||||
//
|
//
|
||||||
// Panics if a container passed as a parameter is nil.
|
// Panics if a container passed as a parameter is nil.
|
||||||
func SyncContainerSettings(prm SyncContainerPrm) (*SyncContainerRes, error) {
|
func SyncContainerSettings(ctx context.Context, prm SyncContainerPrm) (*SyncContainerRes, error) {
|
||||||
if prm.c == nil {
|
if prm.c == nil {
|
||||||
panic("sync container settings with the network: nil container")
|
panic("sync container settings with the network: nil container")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := client.SyncContainerWithNetwork(context.Background(), prm.c, prm.cli)
|
err := client.SyncContainerWithNetwork(ctx, prm.c, prm.cli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
15
cmd/frostfs-cli/internal/client/doc.go
Normal file
15
cmd/frostfs-cli/internal/client/doc.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// Package internal provides functionality for FrostFS CLI application
|
||||||
|
// communication with FrostFS network.
|
||||||
|
//
|
||||||
|
// The base client for accessing remote nodes via FrostFS API is a FrostFS SDK
|
||||||
|
// Go API client. However, although it encapsulates a useful piece of business
|
||||||
|
// logic (e.g. the signature mechanism), the FrostFS CLI application does not
|
||||||
|
// fully use the client's flexible interface.
|
||||||
|
//
|
||||||
|
// In this regard, this package provides functions over base API client
|
||||||
|
// necessary for the application. This allows you to concentrate the entire
|
||||||
|
// spectrum of the client's use in one place (this will be convenient both when
|
||||||
|
// updating the base client and for evaluating the UX of SDK library). So it is
|
||||||
|
// expected that all application packages will be limited to this package for
|
||||||
|
// the development of functionality requiring FrostFS API communication.
|
||||||
|
package internal
|
|
@ -3,11 +3,11 @@ package internal
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/bearer"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/client"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/session"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
// here are small structures with public setters to share between parameter structures
|
// here are small structures with public setters to share between parameter structures
|
||||||
|
@ -16,7 +16,7 @@ type commonPrm struct {
|
||||||
cli *client.Client
|
cli *client.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetClient sets the base client for NeoFS API communication.
|
// SetClient sets the base client for FrostFS API communication.
|
||||||
func (x *commonPrm) SetClient(cli *client.Client) {
|
func (x *commonPrm) SetClient(cli *client.Client) {
|
||||||
x.cli = cli
|
x.cli = cli
|
||||||
}
|
}
|
102
cmd/frostfs-cli/internal/client/sdk.go
Normal file
102
cmd/frostfs-cli/internal/client/sdk.go
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||||
|
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network"
|
||||||
|
tracing "git.frostfs.info/TrueCloudLab/frostfs-observability/tracing/grpc"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errInvalidEndpoint = errors.New("provided RPC endpoint is incorrect")
|
||||||
|
|
||||||
|
// GetSDKClientByFlag returns default frostfs-sdk-go client using the specified flag for the address.
|
||||||
|
// On error, outputs to stderr of cmd and exits with non-zero code.
|
||||||
|
func GetSDKClientByFlag(cmd *cobra.Command, key *ecdsa.PrivateKey, endpointFlag string) *client.Client {
|
||||||
|
cli, err := getSDKClientByFlag(cmd, key, endpointFlag)
|
||||||
|
if err != nil {
|
||||||
|
commonCmd.ExitOnErr(cmd, "can't create API client: %w", err)
|
||||||
|
}
|
||||||
|
return cli
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSDKClientByFlag(cmd *cobra.Command, key *ecdsa.PrivateKey, endpointFlag string) (*client.Client, error) {
|
||||||
|
var addr network.Address
|
||||||
|
|
||||||
|
err := addr.FromString(viper.GetString(endpointFlag))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%v: %w", errInvalidEndpoint, err)
|
||||||
|
}
|
||||||
|
return GetSDKClient(cmd.Context(), cmd, key, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSDKClient returns default frostfs-sdk-go client.
|
||||||
|
func GetSDKClient(ctx context.Context, cmd *cobra.Command, key *ecdsa.PrivateKey, addr network.Address) (*client.Client, error) {
|
||||||
|
var c client.Client
|
||||||
|
|
||||||
|
prmInit := client.PrmInit{
|
||||||
|
Key: *key,
|
||||||
|
}
|
||||||
|
|
||||||
|
prmDial := client.PrmDial{
|
||||||
|
Endpoint: addr.URIAddr(),
|
||||||
|
GRPCDialOptions: []grpc.DialOption{
|
||||||
|
grpc.WithChainUnaryInterceptor(tracing.NewUnaryClientInteceptor()),
|
||||||
|
grpc.WithChainStreamInterceptor(tracing.NewStreamClientInterceptor()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if timeout := viper.GetDuration(commonflags.Timeout); timeout > 0 {
|
||||||
|
// In CLI we can only set a timeout for the whole operation.
|
||||||
|
// By also setting stream timeout we ensure that no operation hands
|
||||||
|
// for too long.
|
||||||
|
prmDial.DialTimeout = timeout
|
||||||
|
prmDial.StreamTimeout = timeout
|
||||||
|
|
||||||
|
common.PrintVerbose(cmd, "Set request timeout to %s.", timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Init(prmInit)
|
||||||
|
|
||||||
|
if err := c.Dial(ctx, prmDial); err != nil {
|
||||||
|
return nil, fmt.Errorf("can't init SDK client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCurrentEpoch returns current epoch.
|
||||||
|
func GetCurrentEpoch(ctx context.Context, cmd *cobra.Command, endpoint string) (uint64, error) {
|
||||||
|
var addr network.Address
|
||||||
|
|
||||||
|
if err := addr.FromString(endpoint); err != nil {
|
||||||
|
return 0, fmt.Errorf("can't parse RPC endpoint: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("can't generate key to sign query: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := GetSDKClient(ctx, cmd, key, addr)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ni, err := c.NetworkInfo(ctx, client.PrmNetworkInfo{})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ni.Info().CurrentEpoch(), nil
|
||||||
|
}
|
|
@ -4,9 +4,10 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-node/pkg/core/version"
|
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/eacl"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/version"
|
||||||
versionSDK "github.com/nspcc-dev/neofs-sdk-go/version"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||||
|
versionSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/version"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -16,29 +17,29 @@ var errUnsupportedEACLFormat = errors.New("unsupported eACL format")
|
||||||
func ReadEACL(cmd *cobra.Command, eaclPath string) *eacl.Table {
|
func ReadEACL(cmd *cobra.Command, eaclPath string) *eacl.Table {
|
||||||
_, err := os.Stat(eaclPath) // check if `eaclPath` is an existing file
|
_, err := os.Stat(eaclPath) // check if `eaclPath` is an existing file
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ExitOnErr(cmd, "", errors.New("incorrect path to file with EACL"))
|
commonCmd.ExitOnErr(cmd, "", errors.New("incorrect path to file with EACL"))
|
||||||
}
|
}
|
||||||
|
|
||||||
PrintVerbose("Reading EACL from file: %s", eaclPath)
|
PrintVerbose(cmd, "Reading EACL from file: %s", eaclPath)
|
||||||
|
|
||||||
data, err := os.ReadFile(eaclPath)
|
data, err := os.ReadFile(eaclPath)
|
||||||
ExitOnErr(cmd, "can't read file with EACL: %w", err)
|
commonCmd.ExitOnErr(cmd, "can't read file with EACL: %w", err)
|
||||||
|
|
||||||
table := eacl.NewTable()
|
table := eacl.NewTable()
|
||||||
|
|
||||||
if err = table.UnmarshalJSON(data); err == nil {
|
if err = table.UnmarshalJSON(data); err == nil {
|
||||||
validateAndFixEACLVersion(table)
|
validateAndFixEACLVersion(table)
|
||||||
PrintVerbose("Parsed JSON encoded EACL table")
|
PrintVerbose(cmd, "Parsed JSON encoded EACL table")
|
||||||
return table
|
return table
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = table.Unmarshal(data); err == nil {
|
if err = table.Unmarshal(data); err == nil {
|
||||||
validateAndFixEACLVersion(table)
|
validateAndFixEACLVersion(table)
|
||||||
PrintVerbose("Parsed binary encoded EACL table")
|
PrintVerbose(cmd, "Parsed binary encoded EACL table")
|
||||||
return table
|
return table
|
||||||
}
|
}
|
||||||
|
|
||||||
ExitOnErr(cmd, "", errUnsupportedEACLFormat)
|
commonCmd.ExitOnErr(cmd, "", errUnsupportedEACLFormat)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,12 +11,12 @@ import (
|
||||||
func PrettyPrintJSON(cmd *cobra.Command, m json.Marshaler, entity string) {
|
func PrettyPrintJSON(cmd *cobra.Command, m json.Marshaler, entity string) {
|
||||||
data, err := m.MarshalJSON()
|
data, err := m.MarshalJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
PrintVerbose("Can't convert %s to json: %w", entity, err)
|
PrintVerbose(cmd, "Can't convert %s to json: %w", entity, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
if err := json.Indent(buf, data, "", " "); err != nil {
|
if err := json.Indent(buf, data, "", " "); err != nil {
|
||||||
PrintVerbose("Can't pretty print json: %w", err)
|
PrintVerbose(cmd, "Can't pretty print json: %w", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cmd.Println(buf)
|
cmd.Println(buf)
|
|
@ -6,31 +6,32 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/bearer"
|
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReadBearerToken reads bearer token from the path provided in a specified flag.
|
// ReadBearerToken reads bearer token from the path provided in a specified flag.
|
||||||
func ReadBearerToken(cmd *cobra.Command, flagname string) *bearer.Token {
|
func ReadBearerToken(cmd *cobra.Command, flagname string) *bearer.Token {
|
||||||
path, err := cmd.Flags().GetString(flagname)
|
path, err := cmd.Flags().GetString(flagname)
|
||||||
ExitOnErr(cmd, "", err)
|
commonCmd.ExitOnErr(cmd, "", err)
|
||||||
|
|
||||||
if len(path) == 0 {
|
if len(path) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
PrintVerbose("Reading bearer token from file [%s]...", path)
|
PrintVerbose(cmd, "Reading bearer token from file [%s]...", path)
|
||||||
|
|
||||||
var tok bearer.Token
|
var tok bearer.Token
|
||||||
|
|
||||||
err = ReadBinaryOrJSON(&tok, path)
|
err = ReadBinaryOrJSON(cmd, &tok, path)
|
||||||
ExitOnErr(cmd, "invalid bearer token: %v", err)
|
commonCmd.ExitOnErr(cmd, "invalid bearer token: %v", err)
|
||||||
|
|
||||||
return &tok
|
return &tok
|
||||||
}
|
}
|
||||||
|
|
||||||
// BinaryOrJSON is an interface of entities which provide json.Unmarshaler
|
// BinaryOrJSON is an interface of entities which provide json.Unmarshaler
|
||||||
// and NeoFS binary decoder.
|
// and FrostFS binary decoder.
|
||||||
type BinaryOrJSON interface {
|
type BinaryOrJSON interface {
|
||||||
Unmarshal([]byte) error
|
Unmarshal([]byte) error
|
||||||
json.Unmarshaler
|
json.Unmarshaler
|
||||||
|
@ -38,8 +39,8 @@ type BinaryOrJSON interface {
|
||||||
|
|
||||||
// ReadBinaryOrJSON reads file data using provided path and decodes
|
// ReadBinaryOrJSON reads file data using provided path and decodes
|
||||||
// BinaryOrJSON from the data.
|
// BinaryOrJSON from the data.
|
||||||
func ReadBinaryOrJSON(dst BinaryOrJSON, fPath string) error {
|
func ReadBinaryOrJSON(cmd *cobra.Command, dst BinaryOrJSON, fPath string) error {
|
||||||
PrintVerbose("Reading file [%s]...", fPath)
|
PrintVerbose(cmd, "Reading file [%s]...", fPath)
|
||||||
|
|
||||||
// try to read session token from file
|
// try to read session token from file
|
||||||
data, err := os.ReadFile(fPath)
|
data, err := os.ReadFile(fPath)
|
||||||
|
@ -47,17 +48,17 @@ func ReadBinaryOrJSON(dst BinaryOrJSON, fPath string) error {
|
||||||
return fmt.Errorf("read file <%s>: %w", fPath, err)
|
return fmt.Errorf("read file <%s>: %w", fPath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
PrintVerbose("Trying to decode binary...")
|
PrintVerbose(cmd, "Trying to decode binary...")
|
||||||
|
|
||||||
err = dst.Unmarshal(data)
|
err = dst.Unmarshal(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
PrintVerbose("Failed to decode binary: %v", err)
|
PrintVerbose(cmd, "Failed to decode binary: %v", err)
|
||||||
|
|
||||||
PrintVerbose("Trying to decode JSON...")
|
PrintVerbose(cmd, "Trying to decode JSON...")
|
||||||
|
|
||||||
err = dst.UnmarshalJSON(data)
|
err = dst.UnmarshalJSON(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
PrintVerbose("Failed to decode JSON: %v", err)
|
PrintVerbose(cmd, "Failed to decode JSON: %v", err)
|
||||||
return errors.New("invalid format")
|
return errors.New("invalid format")
|
||||||
}
|
}
|
||||||
}
|
}
|
62
cmd/frostfs-cli/internal/common/tracing.go
Normal file
62
cmd/frostfs-cli/internal/common/tracing.go
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||||
|
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/misc"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
type spanKey struct{}
|
||||||
|
|
||||||
|
// StopClientCommandSpan stops tracing span for the command and prints trace ID on the standard output.
|
||||||
|
func StopClientCommandSpan(cmd *cobra.Command, _ []string) {
|
||||||
|
span, ok := cmd.Context().Value(spanKey{}).(trace.Span)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
span.End()
|
||||||
|
|
||||||
|
// Noop provider cannot fail on flush.
|
||||||
|
_ = tracing.Shutdown(cmd.Context())
|
||||||
|
|
||||||
|
cmd.PrintErrf("Trace ID: %s\n", span.SpanContext().TraceID())
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartClientCommandSpan starts tracing span for the command.
|
||||||
|
func StartClientCommandSpan(cmd *cobra.Command) {
|
||||||
|
enableTracing, err := cmd.Flags().GetBool(commonflags.TracingFlag)
|
||||||
|
if err != nil || !enableTracing {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = tracing.Setup(cmd.Context(), tracing.Config{
|
||||||
|
Enabled: true,
|
||||||
|
Exporter: tracing.NoOpExporter,
|
||||||
|
Service: "frostfs-cli",
|
||||||
|
Version: misc.Version,
|
||||||
|
})
|
||||||
|
commonCmd.ExitOnErr(cmd, "init tracing: %w", err)
|
||||||
|
|
||||||
|
var components sort.StringSlice
|
||||||
|
for c := cmd; c != nil; c = c.Parent() {
|
||||||
|
components = append(components, c.Name())
|
||||||
|
}
|
||||||
|
for i, j := 0, len(components)-1; i < j; {
|
||||||
|
components.Swap(i, j)
|
||||||
|
i++
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
|
||||||
|
operation := strings.Join(components, ".")
|
||||||
|
ctx, span := tracing.StartSpanFromContext(cmd.Context(), operation)
|
||||||
|
ctx = context.WithValue(ctx, spanKey{}, span)
|
||||||
|
cmd.SetContext(ctx)
|
||||||
|
}
|
|
@ -2,20 +2,19 @@ package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/checksum"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PrintVerbose prints to the stdout if the commonflags.Verbose flag is on.
|
// PrintVerbose prints to the stdout if the commonflags.Verbose flag is on.
|
||||||
func PrintVerbose(format string, a ...interface{}) {
|
func PrintVerbose(cmd *cobra.Command, format string, a ...any) {
|
||||||
if viper.GetBool(commonflags.Verbose) {
|
if viper.GetBool(commonflags.Verbose) {
|
||||||
fmt.Printf(format+"\n", a...)
|
cmd.Printf(format+"\n", a...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,15 @@ const (
|
||||||
|
|
||||||
ForceFlag = "force"
|
ForceFlag = "force"
|
||||||
ForceFlagShorthand = "f"
|
ForceFlagShorthand = "f"
|
||||||
|
|
||||||
|
CIDFlag = "cid"
|
||||||
|
CIDFlagUsage = "Container ID."
|
||||||
|
|
||||||
|
OIDFlag = "oid"
|
||||||
|
OIDFlagUsage = "Object ID."
|
||||||
|
|
||||||
|
TracingFlag = "trace"
|
||||||
|
TracingFlagUsage = "Generate trace ID and print it."
|
||||||
)
|
)
|
||||||
|
|
||||||
// Init adds common flags to the command:
|
// Init adds common flags to the command:
|
||||||
|
@ -48,12 +57,14 @@ const (
|
||||||
// - WalletPath,
|
// - WalletPath,
|
||||||
// - Account,
|
// - Account,
|
||||||
// - RPC,
|
// - RPC,
|
||||||
|
// - Tracing,
|
||||||
// - Timeout.
|
// - Timeout.
|
||||||
func Init(cmd *cobra.Command) {
|
func Init(cmd *cobra.Command) {
|
||||||
InitWithoutRPC(cmd)
|
InitWithoutRPC(cmd)
|
||||||
|
|
||||||
ff := cmd.Flags()
|
ff := cmd.Flags()
|
||||||
ff.StringP(RPC, RPCShorthand, RPCDefault, RPCUsage)
|
ff.StringP(RPC, RPCShorthand, RPCDefault, RPCUsage)
|
||||||
|
ff.Bool(TracingFlag, false, TracingFlagUsage)
|
||||||
ff.DurationP(Timeout, TimeoutShorthand, TimeoutDefault, TimeoutUsage)
|
ff.DurationP(Timeout, TimeoutShorthand, TimeoutDefault, TimeoutUsage)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,8 @@ import (
|
||||||
|
|
||||||
const SessionToken = "session"
|
const SessionToken = "session"
|
||||||
|
|
||||||
// InitSession registers SessionToken flag representing filepath to the token
|
// InitSession registers SessionToken flag representing file path to the token of
|
||||||
// of the session with the given name. Supports NeoFS-binary and JSON files.
|
// the session with the given name. Supports FrostFS-binary and JSON files.
|
||||||
func InitSession(cmd *cobra.Command, name string) {
|
func InitSession(cmd *cobra.Command, name string) {
|
||||||
cmd.Flags().String(
|
cmd.Flags().String(
|
||||||
SessionToken,
|
SessionToken,
|
|
@ -7,15 +7,22 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||||
"github.com/nspcc-dev/neo-go/cli/input"
|
"github.com/nspcc-dev/neo-go/cli/input"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var testCmd = &cobra.Command{
|
||||||
|
Use: "test",
|
||||||
|
Short: "test",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {},
|
||||||
|
}
|
||||||
|
|
||||||
func Test_getOrGenerate(t *testing.T) {
|
func Test_getOrGenerate(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
|
||||||
|
@ -96,7 +103,7 @@ func Test_getOrGenerate(t *testing.T) {
|
||||||
|
|
||||||
t.Run("generate", func(t *testing.T) {
|
t.Run("generate", func(t *testing.T) {
|
||||||
viper.Set(commonflags.GenerateKey, true)
|
viper.Set(commonflags.GenerateKey, true)
|
||||||
actual, err := getOrGenerate()
|
actual, err := getOrGenerate(testCmd)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, actual)
|
require.NotNil(t, actual)
|
||||||
for _, p := range []*keys.PrivateKey{nep2Key, rawKey, wifKey, acc1.PrivateKey(), acc2.PrivateKey()} {
|
for _, p := range []*keys.PrivateKey{nep2Key, rawKey, wifKey, acc1.PrivateKey(), acc2.PrivateKey()} {
|
||||||
|
@ -107,13 +114,13 @@ func Test_getOrGenerate(t *testing.T) {
|
||||||
|
|
||||||
func checkKeyError(t *testing.T, desc string, err error) {
|
func checkKeyError(t *testing.T, desc string, err error) {
|
||||||
viper.Set(commonflags.WalletPath, desc)
|
viper.Set(commonflags.WalletPath, desc)
|
||||||
_, actualErr := getOrGenerate()
|
_, actualErr := getOrGenerate(testCmd)
|
||||||
require.ErrorIs(t, actualErr, err)
|
require.ErrorIs(t, actualErr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkKey(t *testing.T, desc string, expected *keys.PrivateKey) {
|
func checkKey(t *testing.T, desc string, expected *keys.PrivateKey) {
|
||||||
viper.Set(commonflags.WalletPath, desc)
|
viper.Set(commonflags.WalletPath, desc)
|
||||||
actual, err := getOrGenerate()
|
actual, err := getOrGenerate(testCmd)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, &expected.PrivateKey, actual)
|
require.Equal(t, &expected.PrivateKey, actual)
|
||||||
}
|
}
|
|
@ -6,10 +6,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||||
|
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common"
|
|
||||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
@ -20,12 +20,12 @@ var errCantGenerateKey = errors.New("can't generate new private key")
|
||||||
// Ideally we want to touch file-system on the last step.
|
// Ideally we want to touch file-system on the last step.
|
||||||
// This function assumes that all flags were bind to viper in a `PersistentPreRun`.
|
// This function assumes that all flags were bind to viper in a `PersistentPreRun`.
|
||||||
func Get(cmd *cobra.Command) *ecdsa.PrivateKey {
|
func Get(cmd *cobra.Command) *ecdsa.PrivateKey {
|
||||||
pk, err := get()
|
pk, err := get(cmd)
|
||||||
common.ExitOnErr(cmd, "can't fetch private key: %w", err)
|
commonCmd.ExitOnErr(cmd, "can't fetch private key: %w", err)
|
||||||
return pk
|
return pk
|
||||||
}
|
}
|
||||||
|
|
||||||
func get() (*ecdsa.PrivateKey, error) {
|
func get(cmd *cobra.Command) (*ecdsa.PrivateKey, error) {
|
||||||
keyDesc := viper.GetString(commonflags.WalletPath)
|
keyDesc := viper.GetString(commonflags.WalletPath)
|
||||||
data, err := os.ReadFile(keyDesc)
|
data, err := os.ReadFile(keyDesc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -36,7 +36,7 @@ func get() (*ecdsa.PrivateKey, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w, err := wallet.NewWalletFromFile(keyDesc)
|
w, err := wallet.NewWalletFromFile(keyDesc)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return FromWallet(w, viper.GetString(commonflags.Account))
|
return FromWallet(cmd, w, viper.GetString(commonflags.Account))
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("%w: %v", ErrInvalidKey, err)
|
return nil, fmt.Errorf("%w: %v", ErrInvalidKey, err)
|
||||||
}
|
}
|
||||||
|
@ -45,12 +45,12 @@ func get() (*ecdsa.PrivateKey, error) {
|
||||||
|
|
||||||
// GetOrGenerate is similar to get but generates a new key if commonflags.GenerateKey is set.
|
// GetOrGenerate is similar to get but generates a new key if commonflags.GenerateKey is set.
|
||||||
func GetOrGenerate(cmd *cobra.Command) *ecdsa.PrivateKey {
|
func GetOrGenerate(cmd *cobra.Command) *ecdsa.PrivateKey {
|
||||||
pk, err := getOrGenerate()
|
pk, err := getOrGenerate(cmd)
|
||||||
common.ExitOnErr(cmd, "can't fetch private key: %w", err)
|
commonCmd.ExitOnErr(cmd, "can't fetch private key: %w", err)
|
||||||
return pk
|
return pk
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOrGenerate() (*ecdsa.PrivateKey, error) {
|
func getOrGenerate(cmd *cobra.Command) (*ecdsa.PrivateKey, error) {
|
||||||
if viper.GetBool(commonflags.GenerateKey) {
|
if viper.GetBool(commonflags.GenerateKey) {
|
||||||
priv, err := keys.NewPrivateKey()
|
priv, err := keys.NewPrivateKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -58,5 +58,5 @@ func getOrGenerate() (*ecdsa.PrivateKey, error) {
|
||||||
}
|
}
|
||||||
return &priv.PrivateKey, nil
|
return &priv.PrivateKey, nil
|
||||||
}
|
}
|
||||||
return get()
|
return get(cmd)
|
||||||
}
|
}
|
|
@ -3,13 +3,14 @@ package key
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
|
||||||
"github.com/nspcc-dev/neo-go/cli/flags"
|
"github.com/nspcc-dev/neo-go/cli/flags"
|
||||||
"github.com/nspcc-dev/neo-go/cli/input"
|
"github.com/nspcc-dev/neo-go/cli/input"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -22,37 +23,37 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// FromWallet returns private key of the wallet account.
|
// FromWallet returns private key of the wallet account.
|
||||||
func FromWallet(w *wallet.Wallet, addrStr string) (*ecdsa.PrivateKey, error) {
|
func FromWallet(cmd *cobra.Command, w *wallet.Wallet, addrStr string) (*ecdsa.PrivateKey, error) {
|
||||||
var (
|
var (
|
||||||
addr util.Uint160
|
addr util.Uint160
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
if addrStr == "" {
|
if addrStr == "" {
|
||||||
printVerbose("Using default wallet address")
|
common.PrintVerbose(cmd, "Using default wallet address")
|
||||||
addr = w.GetChangeAddress()
|
addr = w.GetChangeAddress()
|
||||||
} else {
|
} else {
|
||||||
addr, err = flags.ParseAddress(addrStr)
|
addr, err = flags.ParseAddress(addrStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printVerbose("Can't parse address: %s", addrStr)
|
common.PrintVerbose(cmd, "Can't parse address: %s", addrStr)
|
||||||
return nil, ErrInvalidAddress
|
return nil, ErrInvalidAddress
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
acc := w.GetAccount(addr)
|
acc := w.GetAccount(addr)
|
||||||
if acc == nil {
|
if acc == nil {
|
||||||
printVerbose("Can't find wallet account for %s", addrStr)
|
common.PrintVerbose(cmd, "Can't find wallet account for %s", addrStr)
|
||||||
return nil, ErrInvalidAddress
|
return nil, ErrInvalidAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
pass, err := getPassword()
|
pass, err := getPassword()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printVerbose("Can't read password: %v", err)
|
common.PrintVerbose(cmd, "Can't read password: %v", err)
|
||||||
return nil, ErrInvalidPassword
|
return nil, ErrInvalidPassword
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := acc.Decrypt(pass, keys.NEP2ScryptParams()); err != nil {
|
if err := acc.Decrypt(pass, keys.NEP2ScryptParams()); err != nil {
|
||||||
printVerbose("Can't decrypt account: %v", err)
|
common.PrintVerbose(cmd, "Can't decrypt account: %v", err)
|
||||||
return nil, ErrInvalidPassword
|
return nil, ErrInvalidPassword
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,9 +68,3 @@ func getPassword() (string, error) {
|
||||||
|
|
||||||
return input.ReadPassword("Enter password > ")
|
return input.ReadPassword("Enter password > ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func printVerbose(format string, a ...interface{}) {
|
|
||||||
if viper.GetBool("verbose") {
|
|
||||||
fmt.Printf(format+"\n", a...)
|
|
||||||
}
|
|
||||||
}
|
|
7
cmd/frostfs-cli/main.go
Normal file
7
cmd/frostfs-cli/main.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import cmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cmd.Execute()
|
||||||
|
}
|
|
@ -3,14 +3,14 @@ package accounting
|
||||||
import (
|
import (
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
|
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||||
|
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/precision"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/accounting"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||||
internalclient "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/client"
|
|
||||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common"
|
|
||||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags"
|
|
||||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/key"
|
|
||||||
"github.com/nspcc-dev/neofs-node/pkg/util/precision"
|
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/accounting"
|
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/user"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
@ -21,8 +21,8 @@ const (
|
||||||
|
|
||||||
var accountingBalanceCmd = &cobra.Command{
|
var accountingBalanceCmd = &cobra.Command{
|
||||||
Use: "balance",
|
Use: "balance",
|
||||||
Short: "Get internal balance of NeoFS account",
|
Short: "Get internal balance of FrostFS account",
|
||||||
Long: `Get internal balance of NeoFS account`,
|
Long: `Get internal balance of FrostFS account`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
var idUser user.ID
|
var idUser user.ID
|
||||||
|
|
||||||
|
@ -32,17 +32,17 @@ var accountingBalanceCmd = &cobra.Command{
|
||||||
if balanceOwner == "" {
|
if balanceOwner == "" {
|
||||||
user.IDFromKey(&idUser, pk.PublicKey)
|
user.IDFromKey(&idUser, pk.PublicKey)
|
||||||
} else {
|
} else {
|
||||||
common.ExitOnErr(cmd, "can't decode owner ID wallet address: %w", idUser.DecodeString(balanceOwner))
|
commonCmd.ExitOnErr(cmd, "can't decode owner ID wallet address: %w", idUser.DecodeString(balanceOwner))
|
||||||
}
|
}
|
||||||
|
|
||||||
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
|
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
|
||||||
|
|
||||||
var prm internalclient.BalanceOfPrm
|
var prm internalclient.BalanceOfPrm
|
||||||
prm.SetClient(cli)
|
prm.SetClient(cli)
|
||||||
prm.SetAccount(idUser)
|
prm.Account = idUser
|
||||||
|
|
||||||
res, err := internalclient.BalanceOf(prm)
|
res, err := internalclient.BalanceOf(cmd.Context(), prm)
|
||||||
common.ExitOnErr(cmd, "rpc error: %w", err)
|
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||||
|
|
||||||
// print to stdout
|
// print to stdout
|
||||||
prettyPrintDecimal(cmd, res.Balance())
|
prettyPrintDecimal(cmd, res.Balance())
|
|
@ -1,7 +1,7 @@
|
||||||
package accounting
|
package accounting
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
28
cmd/frostfs-cli/modules/acl/basic/print.go
Normal file
28
cmd/frostfs-cli/modules/acl/basic/print.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
|
||||||
|
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var printACLCmd = &cobra.Command{
|
||||||
|
Use: "print",
|
||||||
|
Short: "Pretty print basic ACL from the HEX representation",
|
||||||
|
Example: `frostfs-cli acl basic print 0x1C8C8CCC`,
|
||||||
|
Long: `Pretty print basic ACL from the HEX representation.
|
||||||
|
Few roles have exclusive default access to set of operation, even if particular bit deny it.
|
||||||
|
Container have access to the operations of the data replication mechanism:
|
||||||
|
Get, Head, Put, Search, Hash.
|
||||||
|
InnerRing members are allowed to data audit ops only:
|
||||||
|
Get, Head, Hash, Search.`,
|
||||||
|
Run: printACL,
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
}
|
||||||
|
|
||||||
|
func printACL(cmd *cobra.Command, args []string) {
|
||||||
|
var bacl acl.Basic
|
||||||
|
commonCmd.ExitOnErr(cmd, "unable to parse basic acl: %w", bacl.DecodeString(args[0]))
|
||||||
|
util.PrettyPrintTableBACL(cmd, &bacl)
|
||||||
|
}
|
14
cmd/frostfs-cli/modules/acl/basic/root.go
Normal file
14
cmd/frostfs-cli/modules/acl/basic/root.go
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Cmd = &cobra.Command{
|
||||||
|
Use: "basic",
|
||||||
|
Short: "Operations with Basic Access Control Lists",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Cmd.AddCommand(printACLCmd)
|
||||||
|
}
|
127
cmd/frostfs-cli/modules/acl/extended/create.go
Normal file
127
cmd/frostfs-cli/modules/acl/extended/create.go
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
package extended
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
|
||||||
|
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||||
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var createCmd = &cobra.Command{
|
||||||
|
Use: "create",
|
||||||
|
Short: "Create extended ACL from the text representation",
|
||||||
|
Long: `Create extended ACL from the text representation.
|
||||||
|
|
||||||
|
Rule consist of these blocks: <action> <operation> [<filter1> ...] [<target1> ...]
|
||||||
|
|
||||||
|
Action is 'allow' or 'deny'.
|
||||||
|
|
||||||
|
Operation is an object service verb: 'get', 'head', 'put', 'search', 'delete', 'getrange', or 'getrangehash'.
|
||||||
|
|
||||||
|
Filter consists of <typ>:<key><match><value>
|
||||||
|
Typ is 'obj' for object applied filter or 'req' for request applied filter.
|
||||||
|
Key is a valid unicode string corresponding to object or request header key.
|
||||||
|
Well-known system object headers start with '$Object:' prefix.
|
||||||
|
User defined headers start without prefix.
|
||||||
|
Read more about filter keys at git.frostfs.info.com/TrueCloudLab/frostfs-api/src/branch/master/proto-docs/acl.md#message-eaclrecordfilter
|
||||||
|
Match is '=' for matching and '!=' for non-matching filter.
|
||||||
|
Value is a valid unicode string corresponding to object or request header value.
|
||||||
|
|
||||||
|
Target is
|
||||||
|
'user' for container owner,
|
||||||
|
'system' for Storage nodes in container and Inner Ring nodes,
|
||||||
|
'others' for all other request senders,
|
||||||
|
'pubkey:<key1>,<key2>,...' for exact request sender, where <key> is a hex-encoded 33-byte public key.
|
||||||
|
|
||||||
|
When both '--rule' and '--file' arguments are used, '--rule' records will be placed higher in resulting extended ACL table.
|
||||||
|
`,
|
||||||
|
Example: `frostfs-cli acl extended create --cid EutHBsdT1YCzHxjCfQHnLPL1vFrkSyLSio4vkphfnEk -f rules.txt --out table.json
|
||||||
|
frostfs-cli acl extended create --cid EutHBsdT1YCzHxjCfQHnLPL1vFrkSyLSio4vkphfnEk -r 'allow get obj:Key=Value others' -r 'deny put others'`,
|
||||||
|
Run: createEACL,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
createCmd.Flags().StringArrayP("rule", "r", nil, "Extended ACL table record to apply")
|
||||||
|
createCmd.Flags().StringP("file", "f", "", "Read list of extended ACL table records from text file")
|
||||||
|
createCmd.Flags().StringP("out", "o", "", "Save JSON formatted extended ACL table in file")
|
||||||
|
createCmd.Flags().StringP(commonflags.CIDFlag, "", "", commonflags.CIDFlagUsage)
|
||||||
|
|
||||||
|
_ = cobra.MarkFlagFilename(createCmd.Flags(), "file")
|
||||||
|
_ = cobra.MarkFlagFilename(createCmd.Flags(), "out")
|
||||||
|
}
|
||||||
|
|
||||||
|
func createEACL(cmd *cobra.Command, _ []string) {
|
||||||
|
rules, _ := cmd.Flags().GetStringArray("rule")
|
||||||
|
fileArg, _ := cmd.Flags().GetString("file")
|
||||||
|
outArg, _ := cmd.Flags().GetString("out")
|
||||||
|
cidArg, _ := cmd.Flags().GetString(commonflags.CIDFlag)
|
||||||
|
|
||||||
|
var containerID cid.ID
|
||||||
|
if cidArg != "" {
|
||||||
|
if err := containerID.DecodeString(cidArg); err != nil {
|
||||||
|
cmd.PrintErrf("invalid container ID: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rulesFile, err := getRulesFromFile(fileArg)
|
||||||
|
if err != nil {
|
||||||
|
cmd.PrintErrf("can't read rules from file: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
rules = append(rules, rulesFile...)
|
||||||
|
if len(rules) == 0 {
|
||||||
|
cmd.PrintErrln("no extended ACL rules has been provided")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
tb := eacl.NewTable()
|
||||||
|
commonCmd.ExitOnErr(cmd, "unable to parse provided rules: %w", util.ParseEACLRules(tb, rules))
|
||||||
|
|
||||||
|
tb.SetCID(containerID)
|
||||||
|
|
||||||
|
data, err := tb.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
cmd.PrintErrln(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
err = json.Indent(buf, data, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
cmd.PrintErrln(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(outArg) == 0 {
|
||||||
|
cmd.Println(buf)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile(outArg, buf.Bytes(), 0o644)
|
||||||
|
if err != nil {
|
||||||
|
cmd.PrintErrln(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRulesFromFile(filename string) ([]string, error) {
|
||||||
|
if len(filename) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Split(strings.TrimSpace(string(data)), "\n"), nil
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
package extended
|
package extended
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/eacl"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -63,8 +63,7 @@ func TestParseTable(t *testing.T) {
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
ss := strings.Split(test.rule, " ")
|
err := util.ParseEACLRule(eaclTable, test.rule)
|
||||||
err := parseTable(eaclTable, ss)
|
|
||||||
ok := len(test.jsonRecord) > 0
|
ok := len(test.jsonRecord) > 0
|
||||||
require.Equal(t, ok, err == nil, err)
|
require.Equal(t, ok, err == nil, err)
|
||||||
if ok {
|
if ok {
|
38
cmd/frostfs-cli/modules/acl/extended/print.go
Normal file
38
cmd/frostfs-cli/modules/acl/extended/print.go
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package extended
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
|
||||||
|
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var printEACLCmd = &cobra.Command{
|
||||||
|
Use: "print",
|
||||||
|
Short: "Pretty print extended ACL from the file(in text or json format) or for given container.",
|
||||||
|
Run: printEACL,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flags := printEACLCmd.Flags()
|
||||||
|
flags.StringP("file", "f", "",
|
||||||
|
"Read list of extended ACL table records from text or json file")
|
||||||
|
_ = printEACLCmd.MarkFlagRequired("file")
|
||||||
|
}
|
||||||
|
|
||||||
|
func printEACL(cmd *cobra.Command, _ []string) {
|
||||||
|
file, _ := cmd.Flags().GetString("file")
|
||||||
|
eaclTable := new(eacl.Table)
|
||||||
|
data, err := os.ReadFile(file)
|
||||||
|
commonCmd.ExitOnErr(cmd, "can't read file with EACL: %w", err)
|
||||||
|
if strings.HasSuffix(file, ".json") {
|
||||||
|
commonCmd.ExitOnErr(cmd, "unable to parse json: %w", eaclTable.UnmarshalJSON(data))
|
||||||
|
} else {
|
||||||
|
rules := strings.Split(strings.TrimSpace(string(data)), "\n")
|
||||||
|
commonCmd.ExitOnErr(cmd, "can't parse file with EACL: %w", util.ParseEACLRules(eaclTable, rules))
|
||||||
|
}
|
||||||
|
util.PrettyPrintTableEACL(cmd, eaclTable)
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue