forked from TrueCloudLab/frostfs-node
Compare commits
807 commits
experiment
...
master
Author | SHA1 | Date | |
---|---|---|---|
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 | ||
|
694d888219 | ||
|
d739e06289 | ||
|
cd2faf29a9 | ||
|
34501685b7 | ||
|
56de2f1363 | ||
|
fcdbf5e509 | ||
|
98034005f1 | ||
|
db92e96e40 | ||
|
e8c5f03c30 | ||
|
b1fa084756 | ||
|
98ac525272 | ||
|
7653a1f626 | ||
|
43fe156f7c | ||
c640374d98 | |||
1e0b1919ed | |||
|
bbfc1d9263 | ||
|
e1a085ffd5 | ||
|
ff67e903ca | ||
92e9782c44 | |||
|
846ff515e6 | ||
|
280e56f4bb | ||
|
1e6588e761 | ||
|
7395ab8ef7 | ||
|
e4bc3d0e9d | ||
|
3c6daa2995 | ||
|
eb206d6e59 | ||
|
c4a2a283ae | ||
|
3b939d190c | ||
|
7f54462e60 | ||
|
9a0824766c | ||
|
0631b38da1 | ||
|
d240e2dc87 | ||
|
d09ec2e3fe | ||
|
17f7d0a2ee | ||
|
7c0aa69d11 | ||
|
713fdab177 | ||
|
4fe1aad30f | ||
|
2dcf8d51be | ||
|
41f93dcc1d | ||
|
942cc38146 | ||
|
0371d15b2f | ||
|
f8180447a1 | ||
|
f396ffcc0f | ||
|
6c9423cfb0 | ||
|
7c3ac7150d | ||
|
2404a267b9 | ||
|
6c21e2cc28 | ||
|
c87c3d3721 | ||
|
02bea52f40 | ||
|
8648b102db | ||
|
23622ed283 | ||
|
942155be6e | ||
|
2484c1d35e | ||
|
926830bb9c | ||
|
a4573d5026 | ||
|
7b418c36b4 | ||
|
8bba490c30 | ||
|
04b67f3ba5 | ||
|
8796807040 | ||
|
a189eca5d5 | ||
|
1beafea0b5 | ||
|
87be4f1629 | ||
|
c785e11b20 | ||
|
f769fc83fc | ||
|
60e9de8d63 | ||
|
c50603494b | ||
|
e1be0180f6 | ||
|
217b030d20 | ||
|
810087d8b9 | ||
|
de055f27c7 | ||
|
7d456cb4d0 | ||
|
49c38d642d | ||
|
aa37078570 | ||
|
1766ca2039 | ||
|
1805c6606d | ||
|
6d4beea187 | ||
|
24e9e3f3bf | ||
|
2e199c7ab1 | ||
|
19850ef157 | ||
|
3aa9938b8f | ||
|
9ec01bb9c1 | ||
|
a95fad833e | ||
|
0d14ef69f0 | ||
|
1980ed968a | ||
|
13e818e2ca | ||
|
0d65888005 | ||
|
1cb892c579 | ||
|
9e46799cd4 | ||
|
d772e35aba | ||
|
347912ea0b | ||
|
0a1530afa0 | ||
|
4c63be6629 | ||
|
bc67f77d86 | ||
|
c16a31ef3e | ||
|
f4a3fa2977 | ||
|
f5f560d903 | ||
|
ea9a5690cb | ||
|
e2970bf892 | ||
|
b40dd10b7a | ||
|
f524c812c1 | ||
|
d96139b201 | ||
|
f76516a883 | ||
|
6eb82c4cbf | ||
|
8145e79d7f | ||
|
bcf3df354b | ||
|
1406d096a2 | ||
|
05420173cc | ||
|
2050a623ac | ||
|
ade19077ee | ||
|
beb1998ed1 | ||
|
6e4f7180fe | ||
|
3d8349d7f8 | ||
|
2c02e66939 | ||
|
13c4a9f4b8 | ||
|
80d3c7f9d6 | ||
|
f2793060c5 | ||
|
31c623636d | ||
8714fc42b5 | |||
|
5a2daadd37 | ||
|
b6806ea6b9 | ||
|
8c75cb1dad | ||
|
d7c7022bbd | ||
|
f037022a7a | ||
|
4baf00aa21 | ||
|
dde4d4df2a | ||
|
9a006ac14f | ||
|
49eab6318c | ||
|
c0199dee93 | ||
|
b632260995 | ||
|
19c0a74e94 | ||
74d2f2c8d3 | |||
|
9e2edfedc6 | ||
|
c5fdb7bedf | ||
|
050ad2762c | ||
|
e99e25b52f | ||
|
df5d7bf729 | ||
|
e6f8904040 | ||
feaa9eace7 | |||
|
807c0a1321 | ||
|
2d43892fc9 | ||
|
4b005d3178 | ||
|
6d7ffefec5 | ||
|
328691c94f | ||
|
9b241e4a17 | ||
|
1360273fec | ||
|
b2aa9947c2 | ||
5e493b7f1c | |||
|
6557f5d249 | ||
|
7356ee91ff | ||
|
2ac42b70ce | ||
|
90bfe0bad9 | ||
|
0cb8e7f6f1 | ||
|
ca8dc872b2 | ||
|
e54b52ec03 | ||
|
5834f9807e | ||
|
c20c2e3b39 | ||
|
01ddb3f8e6 | ||
|
713aea06fa | ||
|
082602b668 | ||
|
a7668618c9 | ||
|
eb1fba5182 | ||
|
d6c01199c8 | ||
|
9fcc80ea38 | ||
|
6a2ec21641 | ||
|
bdb8243a5a | ||
|
0903b2af93 | ||
|
6b99f2a0df | ||
|
876e132d22 | ||
|
7a3a827d35 | ||
|
0826310b2a | ||
|
d2d4191868 | ||
|
e0194dbde5 | ||
|
f64ae55806 | ||
|
42fb40e841 | ||
|
4c94faac67 | ||
|
8858840751 | ||
8bf82d738b | |||
|
4eb0ed11f8 | ||
|
8b3b16fe62 | ||
|
236414df49 | ||
|
76893bdc50 | ||
|
2e3ef817f4 | ||
|
b89e71fa78 | ||
|
af56574849 | ||
|
1e35c12cc1 | ||
|
8ebe95747e | ||
|
ab3ef7110e | ||
|
6fc3268ebf | ||
|
887afeaddb | ||
|
fbd5bc8c38 | ||
|
91b56ad3e8 | ||
|
5c69e19016 | ||
|
4aa4694152 | ||
|
2d7166f8d0 | ||
|
9374823950 | ||
76cfcc242c | |||
|
1edc048870 | ||
5284ac53f9 | |||
|
0a411908ee | ||
|
f2045c10d7 | ||
|
1c62f1b2c4 | ||
|
51b8f26a31 | ||
|
3d882e9f47 | ||
|
0b4c867ef1 | ||
|
55148404ee | ||
|
36ab1a2472 | ||
|
93742d37b7 | ||
|
485a5418d2 | ||
|
59de20fbba | ||
|
0fb5c51ac9 | ||
|
6f45cc81fc | ||
|
9113793688 | ||
|
898689ec14 | ||
|
4e043a801c | ||
|
a49137349b | ||
|
93ae3f0b19 | ||
|
7cc68cf4d4 | ||
c7f85994e5 | |||
bb02913c39 | |||
|
3df98ce7ba | ||
|
8fc88487db | ||
|
091d7d30f6 | ||
|
a51b76056e | ||
|
7377979e12 | ||
|
4208f7c0cf | ||
|
5321f8ef9c | ||
|
b064fb24d8 | ||
|
cda8f9df2e | ||
|
ae1dab29bc | ||
|
d6fef68a62 | ||
|
d895a272ad | ||
|
ae655b74f0 | ||
|
4b8b575ca4 | ||
|
532c41ca05 | ||
|
431e331373 | ||
|
ad47e2a985 | ||
|
d872862710 | ||
|
beb7f2a048 | ||
|
edef26a4fd | ||
|
175e2b9fa0 | ||
|
a789159e5b | ||
|
d25b7e177b | ||
|
4083a62679 | ||
|
5adf089c1d | ||
|
2ffcd02ac3 | ||
|
8d0906c6ab | ||
|
876e014b5d | ||
|
4f18893d9b | ||
|
4afb928ab6 | ||
|
f1572a674b | ||
|
d25ec52459 | ||
|
60b2930417 | ||
|
9da5d784cb | ||
|
87bd49563e | ||
|
a2bb3a2a96 | ||
|
699b534416 | ||
|
3a206b5d7a | ||
|
8e8265d5ea | ||
|
8e6e89aca3 | ||
|
60aa53651b | ||
|
1e57565f6c | ||
|
4bd8608b37 | ||
|
4354359aed | ||
|
0140ac354b | ||
|
74c861342e | ||
|
8a47f9ba12 | ||
|
9840936a4f | ||
c4c2840b52 | |||
944665dd5d | |||
|
e4dcc4d6a9 | ||
|
fe0c6db67d | ||
|
0818d8d43a | ||
|
fa18100489 | ||
|
01d7c007aa | ||
|
2132b78aba | ||
|
adff08ad02 | ||
|
387953c1e0 | ||
|
fef4f6d155 | ||
|
d4ac5bdb97 | ||
|
4ccacb89e8 | ||
|
11e6f03aa2 | ||
|
9290a7e1fe | ||
|
eb5e1121c5 | ||
|
e26e70ffcf | ||
|
33143b18a9 | ||
|
46b815c863 | ||
|
491a908af1 | ||
|
17059b34ea | ||
|
e6cf0b3d42 | ||
|
c04126f35e | ||
|
9e41e85295 | ||
|
e9c6ee2623 | ||
|
177e8e01b1 | ||
|
bda084f331 | ||
|
20abdaeed4 | ||
|
82839cb1c9 | ||
|
62b293b7ab | ||
|
a99474c40e | ||
|
6d277a57aa | ||
|
4582b8f0d4 | ||
|
ae6ebccd9c | ||
|
fa3124fc33 | ||
|
5ce8315cd8 | ||
|
d15a7d8d3d | ||
|
a529149e6f | ||
|
7c1babb7d6 | ||
|
d8a00c365a | ||
|
dfac4e1c0b | ||
|
df88afeef3 | ||
|
ae52d53609 | ||
|
180e5e938f | ||
|
78bf17c0b0 | ||
|
b9a2055e1c | ||
|
6c2d3b020f | ||
|
0b95a21701 | ||
|
b2d4cc556e | ||
|
5f95498364 | ||
|
9792e6a4ef | ||
|
482a7e7f2f | ||
|
b8b9d25f9d | ||
|
bc20851cd1 | ||
|
c7c1c257e1 | ||
|
cd6f8e051a | ||
|
9c7b3ce799 | ||
|
745f72fff0 | ||
|
067ab44e0b | ||
|
626766db08 | ||
|
61f0d85834 | ||
|
ddbe129cb2 | ||
|
5827a926c9 | ||
|
19585c63ed | ||
e68c80df0a | |||
31b6af5667 | |||
|
b54f34d7df | ||
|
bbd6b9780f | ||
|
d57c57010f | ||
|
40f1cf36e1 | ||
|
871fae8321 | ||
|
e4370c4129 | ||
|
0720d96c9d | ||
|
d40ed00438 | ||
|
56792d5eaf | ||
|
99903e1aba | ||
|
30341f2192 | ||
|
37ab26bfa9 | ||
|
e8c6dce466 | ||
|
0042c1d4a6 | ||
|
26b4a258e0 | ||
|
13cdbde2e2 | ||
|
0c9e4e6a35 | ||
|
266458fe5c | ||
|
5aa3defc67 | ||
|
ddaed283e9 | ||
|
4176b2a1bc | ||
|
73f8bb3e5f | ||
|
9eb018672c | ||
|
7d6df543d7 | ||
|
ca15083a50 | ||
|
d75d030a90 | ||
|
36c88f0dc8 | ||
|
d9a2f280c9 | ||
|
b621f5983a | ||
|
5139dc9864 | ||
|
4e361e2ab4 | ||
|
89cba2cc7b | ||
|
f8b106ac85 | ||
|
67af4c89a8 | ||
|
7a26b2a57a | ||
|
2740bf7ee4 | ||
e07921fc46 | |||
|
da2975a2f9 | ||
|
81684b6f04 | ||
|
cd71de69a0 | ||
|
5ff1df285b | ||
|
058538768b | ||
|
a5da665e69 | ||
|
156ba85326 | ||
|
9aba0ba512 | ||
|
a97dee008c | ||
|
2467be0117 | ||
|
1c1b5043fc | ||
|
8f44335925 | ||
|
9a5f9d6f0e | ||
|
0a60524a9c | ||
|
713cfa5610 | ||
|
c2918fce3a | ||
|
8c8ae56960 | ||
|
8ffc2fdf5e | ||
|
4558f30575 | ||
|
8d0884e74f | ||
|
7ecf54c6df | ||
|
d3eac7626d | ||
|
5efcd34680 | ||
|
5d02613c33 | ||
|
6146df4998 | ||
|
dd30703a6b | ||
|
fc06b6e89a | ||
|
38558a3238 | ||
|
642d7328ab | ||
|
4abe5a7245 | ||
|
72586f17d4 | ||
|
07465849a4 | ||
|
71fd86f220 | ||
|
8162b27264 | ||
|
a0fae0443f | ||
|
54fe7667fb | ||
|
2a1c5557f2 | ||
|
10c855efef | ||
|
91361c4fe2 | ||
|
25a0f539ea | ||
|
589a54805d | ||
|
6049022f7e | ||
|
a52e7c2c99 | ||
|
175e9da3a7 | ||
|
7e717e80ef | ||
|
2f4e465cf4 | ||
|
ee9312dcfb | ||
|
fb1fac02e9 | ||
|
1691364653 | ||
|
50e28f22f9 | ||
|
fabe717d32 | ||
|
4944490ffb | ||
|
3df62769c0 | ||
|
1e786233bf | ||
|
e38b0aa4ba | ||
|
40a56c6b42 | ||
|
c8911d72d0 | ||
|
339864b720 | ||
|
d8ba954aff | ||
|
7b882b26d8 | ||
|
34d319fed2 | ||
|
f58234aa2f | ||
|
30c7925b3c | ||
|
7410827db8 | ||
|
aed83d1660 | ||
|
a4ed91f9b5 | ||
|
581a9901c9 | ||
|
2455b72844 | ||
|
b549cc314c | ||
|
2334e94fdb | ||
|
a4adb79db7 | ||
|
d62723f038 | ||
|
26041f18bf | ||
|
f80e52fbea | ||
|
3904b0e017 | ||
|
bfdd68dcb3 | ||
|
0dc1a4e336 | ||
|
c7b10598f9 | ||
|
5e843a73f9 | ||
|
8f4ee1aded | ||
|
735931c842 | ||
|
5ffbeb76e6 | ||
|
39f47f61c6 | ||
|
a2edfec0c3 | ||
|
ad3038d16d | ||
|
33d8fb187a | ||
|
8027b7bb6b | ||
|
b04f712773 | ||
|
4437cd7113 | ||
|
3caa982283 | ||
|
01d2e06a9b | ||
|
1f5a650b05 | ||
|
3312924b82 | ||
|
f0a67f948d | ||
|
f5d35571d0 | ||
|
f4bc1c601a | ||
|
835170e452 | ||
|
536857ea5a | ||
|
886baf3136 | ||
|
7703dd5d7f | ||
|
ad48918a97 | ||
|
b30f14978d | ||
|
45321b3f6d | ||
|
16e3421825 | ||
|
94b9e13431 | ||
|
36e24f7f78 | ||
|
8844d9b2db | ||
|
d8ad68d613 | ||
|
910db42748 | ||
|
aea855e8f3 | ||
|
62154da17c | ||
|
46f4ce2773 | ||
|
8cf71b7f1c | ||
|
198beae703 | ||
|
5c8d725447 | ||
|
11c9df6f00 | ||
|
9f7a22e2aa | ||
|
ccba07fe19 | ||
|
59d08b8dae | ||
|
5e1636e50c | ||
|
eeef83a74c | ||
|
e5c0338f8c | ||
|
3a0b06d5da | ||
|
ac46d1a11f | ||
|
12dc5c3395 | ||
|
4a8dfd0ce4 | ||
|
6cc180391e | ||
|
cb0bb7207c | ||
|
558cc1193a | ||
|
b1e54dec9f | ||
|
51afcc1182 | ||
|
18ec5d7c8e | ||
|
b5c56d459a | ||
|
63c00e785d | ||
|
5122be34e7 | ||
|
eaf96bccf7 | ||
|
c6cf8e5c0b | ||
|
9c5ef3bab8 | ||
|
25826bd96d | ||
|
de31bb27b6 | ||
|
595fc60d72 | ||
|
fed9e6679d | ||
|
72c044e2eb | ||
|
6eb5260562 | ||
|
a421344727 | ||
|
89ebc278b8 | ||
|
c4b86cf1f1 | ||
|
b8508585a5 | ||
|
f7d5045876 | ||
|
5569ff82ef | ||
|
b3272e7cf1 | ||
|
4a316ceae9 | ||
|
9bd1951ca4 | ||
|
89118e9da0 | ||
|
ae4740f99f | ||
|
8a61d33c65 | ||
|
fa9c910648 | ||
|
ae86d2907c | ||
|
68e583f143 | ||
|
49ba1ef1b6 | ||
|
1119014e53 | ||
|
0ab168a00a | ||
|
f4a3fc5f11 | ||
|
466b9b4273 | ||
|
1e034c8d48 | ||
|
dac4a838fa | ||
|
96a24b5721 | ||
|
5d5d21e3d0 | ||
|
0504c3e0c6 |
1111 changed files with 37210 additions and 16636 deletions
|
@ -1,19 +1,19 @@
|
|||
FROM golang:1.17 as builder
|
||||
FROM golang:1.18 as builder
|
||||
ARG BUILD=now
|
||||
ARG VERSION=dev
|
||||
ARG REPO=repository
|
||||
WORKDIR /src
|
||||
COPY . /src
|
||||
|
||||
RUN make bin/neofs-adm
|
||||
RUN make bin/frostfs-adm
|
||||
|
||||
# Executable image
|
||||
FROM alpine AS neofs-adm
|
||||
FROM alpine AS frostfs-adm
|
||||
RUN apk add --no-cache bash
|
||||
|
||||
WORKDIR /
|
||||
|
||||
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"]
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
FROM golang:1.17 as builder
|
||||
FROM golang:1.18 as builder
|
||||
ARG BUILD=now
|
||||
ARG VERSION=dev
|
||||
ARG REPO=repository
|
||||
WORKDIR /src
|
||||
COPY . /src
|
||||
|
||||
RUN make bin/neofs-cli
|
||||
RUN make bin/frostfs-cli
|
||||
|
||||
# Executable image
|
||||
FROM alpine AS neofs-cli
|
||||
FROM alpine AS frostfs-cli
|
||||
RUN apk add --no-cache bash
|
||||
|
||||
WORKDIR /
|
||||
|
||||
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 /
|
||||
|
||||
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 /
|
||||
|
||||
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 /
|
||||
|
||||
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 /
|
||||
|
||||
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.18 as builder
|
||||
ARG BUILD=now
|
||||
ARG VERSION=dev
|
||||
ARG REPO=repository
|
||||
WORKDIR /src
|
||||
COPY . /src
|
||||
|
||||
RUN make bin/neofs-ir
|
||||
RUN make bin/frostfs-ir
|
||||
|
||||
# Executable image
|
||||
FROM alpine AS neofs-ir
|
||||
FROM alpine AS frostfs-ir
|
||||
RUN apk add --no-cache bash
|
||||
|
||||
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.18 as builder
|
||||
ARG BUILD=now
|
||||
ARG VERSION=dev
|
||||
ARG REPO=repository
|
||||
WORKDIR /src
|
||||
COPY . /src
|
||||
|
||||
RUN make bin/neofs-node
|
||||
RUN make bin/frostfs-node
|
||||
|
||||
# Executable image
|
||||
FROM alpine AS neofs-node
|
||||
FROM alpine AS frostfs-node
|
||||
RUN apk add --no-cache bash
|
||||
|
||||
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 +1,19 @@
|
|||
FROM golang:1.17 as builder
|
||||
FROM golang:1.18 as builder
|
||||
ARG BUILD=now
|
||||
ARG VERSION=dev
|
||||
ARG REPO=repository
|
||||
WORKDIR /src
|
||||
COPY . /src
|
||||
|
||||
RUN make bin/neofs-node
|
||||
RUN make bin/frostfs-node
|
||||
|
||||
# Executable image
|
||||
FROM alpine AS neofs-node
|
||||
FROM alpine AS frostfs-node
|
||||
RUN apk add --no-cache bash
|
||||
|
||||
WORKDIR /
|
||||
|
||||
COPY --from=builder /src/bin/neofs-node /bin/neofs-node
|
||||
COPY --from=builder /src/bin/frostfs-node /bin/frostfs-node
|
||||
COPY --from=builder /src/config/testnet/config.yml /config.yml
|
||||
|
||||
CMD ["neofs-node", "--config", "/config.yml"]
|
||||
CMD ["frostfs-node", "--config", "/config.yml"]
|
||||
|
|
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
* @TrueCloudLab/storage-core @TrueCloudLab/committers
|
7
.github/ISSUE_TEMPLATE/bug_report.md
vendored
7
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -41,3 +41,10 @@ assignees: ''
|
|||
* Version used:
|
||||
* Server setup and configuration:
|
||||
* Operating System and version (`uname -a`):
|
||||
|
||||
## Don't forget to add labels!
|
||||
- component label (`frostfs-adm`, `frostfs-storage`, ...)
|
||||
- `goodfirstissue`, `helpwanted` if needed
|
||||
- does this issue belong to an epic?
|
||||
- priority (`P0`-`P4`) if already triaged
|
||||
- quarter label (`202XQY`) if possible
|
||||
|
|
8
.github/ISSUE_TEMPLATE/feature_request.md
vendored
8
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -18,3 +18,11 @@ assignees: ''
|
|||
|
||||
## Additional context
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
|
||||
## Don't forget to add labels!
|
||||
- component label (`neofs-adm`, `neofs-storage`, ...)
|
||||
- issue type (`enhancement`, `refactor`, ...)
|
||||
- `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"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
sodipodi:docname="logo_fs.svg"
|
||||
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||
id="svg57"
|
||||
version="1.1"
|
||||
viewBox="0 0 105 25"
|
||||
height="25mm"
|
||||
width="105mm">
|
||||
<defs
|
||||
id="defs51">
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath434">
|
||||
<path
|
||||
d="M 0,0 H 1366 V 768 H 0 Z"
|
||||
id="path432" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-x="130"
|
||||
inkscape:window-height="1040"
|
||||
inkscape:window-width="1274"
|
||||
height="50mm"
|
||||
units="mm"
|
||||
showgrid="false"
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:cy="344.49897"
|
||||
inkscape:cx="468.64708"
|
||||
inkscape:zoom="0.7"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
borderopacity="1.0"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff"
|
||||
id="base" />
|
||||
<metadata
|
||||
id="metadata54">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:groupmode="layer"
|
||||
inkscape:label="Layer 1">
|
||||
<g
|
||||
id="g424"
|
||||
transform="matrix(0.35277777,0,0,-0.35277777,63.946468,10.194047)">
|
||||
<path
|
||||
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"
|
||||
style="fill:#00e396;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
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>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<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"
|
||||
viewBox="0 0 184.2 51.8" style="enable-background:new 0 0 184.2 51.8;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{display:none;}
|
||||
.st1{display:inline;}
|
||||
.st2{fill:#01E397;}
|
||||
.st3{display:inline;fill:#010032;}
|
||||
.st4{display:inline;fill:#00E599;}
|
||||
.st5{display:inline;fill:#00AF92;}
|
||||
.st6{fill:#00C3E5;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
<g id="Layer_1-2" class="st0">
|
||||
<g class="st1">
|
||||
<path class="st2" d="M146.6,18.3v7.2h10.9V29h-10.9v10.7h-4V14.8h18v3.5H146.6z"/>
|
||||
<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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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"/>
|
||||
</g>
|
||||
<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
|
||||
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
|
||||
c1.5-0.8,3.2-1.2,4.9-1.1C68.9,13.8,71.3,14.7,73.3,16.3z"/>
|
||||
<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
|
||||
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
|
||||
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
|
||||
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"/>
|
||||
<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
|
||||
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
|
||||
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
|
||||
C119.9,17.2,117.7,18.2,116.2,19.9z"/>
|
||||
<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 "/>
|
||||
<polygon class="st5" points="24.3,17.9 24.3,36.8 46.8,44.9 46.8,9.6 "/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st6" d="M41.6,17.5H28.2v6.9h10.4v3.3H28.2v10.2h-3.9V14.2h17.2V17.5z"/>
|
||||
<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
|
||||
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
|
||||
c-0.7,0.4-1.3,1-1.8,1.8s-0.7,1.8-0.7,3v9.5H45.8z"/>
|
||||
<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
|
||||
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
|
||||
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
|
||||
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
|
||||
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"/>
|
||||
<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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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"/>
|
||||
<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
|
||||
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
|
||||
s-1.5-0.9-2-1.6c-0.5-0.8-0.7-1.7-0.8-3V15.7L106.6,14.6z"/>
|
||||
<path d="M137.9,17.5h-13.3v6.9h10.4v3.3h-10.4v10.2h-3.9V14.2h17.2V17.5z"/>
|
||||
<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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 5.5 KiB |
29
.github/workflows/changelog.yml
vendored
Normal file
29
.github/workflows/changelog.yml
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
name: CHANGELOG check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- support/**
|
||||
|
||||
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')
|
37
.github/workflows/config-update.yml
vendored
Normal file
37
.github/workflows/config-update.yml
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
name: Configuration check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- support/**
|
||||
|
||||
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')
|
1
.github/workflows/dco.yml
vendored
1
.github/workflows/dco.yml
vendored
|
@ -4,6 +4,7 @@ on:
|
|||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- support/**
|
||||
|
||||
jobs:
|
||||
commits_check_job:
|
||||
|
|
25
.github/workflows/go.yml
vendored
25
.github/workflows/go.yml
vendored
|
@ -1,14 +1,16 @@
|
|||
name: neofs-node tests
|
||||
name: frostfs-node tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- support/**
|
||||
paths-ignore:
|
||||
- '*.md'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- support/**
|
||||
paths-ignore:
|
||||
- '*.md'
|
||||
|
||||
|
@ -17,18 +19,18 @@ jobs:
|
|||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
go: [ '1.17.x', '1.18.x' ]
|
||||
go: [ '1.18.x', '1.19.x' ]
|
||||
steps:
|
||||
- name: Setup go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Cache go mod
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ matrix.go }}-${{ hashFiles('**/go.sum') }}
|
||||
|
@ -46,12 +48,13 @@ jobs:
|
|||
lint:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
version: v1.45.2
|
||||
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
|
||||
|
|
19
.github/workflows/project.yml
vendored
19
.github/workflows/project.yml
vendored
|
@ -1,19 +0,0 @@
|
|||
name: Add issues to NeoFS Core project
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
- transferred
|
||||
- labeled
|
||||
|
||||
jobs:
|
||||
add-to-project:
|
||||
name: Add issue to project
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/add-to-project@main
|
||||
with:
|
||||
project-url: https://github.com/orgs/nspcc-dev/projects/1
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
labeled: triage
|
21
.gitignore
vendored
21
.gitignore
vendored
|
@ -28,3 +28,24 @@ testfile
|
|||
|
||||
# misc
|
||||
.neofs-cli.yml
|
||||
|
||||
# debhelpers
|
||||
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/
|
||||
|
|
|
@ -34,16 +34,23 @@ linters:
|
|||
# some default golangci-lint linters
|
||||
- errcheck
|
||||
- gosimple
|
||||
- godot
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- typecheck
|
||||
- unused
|
||||
|
||||
# extra linters
|
||||
- bidichk
|
||||
- durationcheck
|
||||
- exhaustive
|
||||
- exportloopref
|
||||
- gofmt
|
||||
- whitespace
|
||||
- goimports
|
||||
- unused
|
||||
- misspell
|
||||
- predeclared
|
||||
- reassign
|
||||
- whitespace
|
||||
disable-all: true
|
||||
fast: false
|
||||
|
||||
|
|
447
CHANGELOG.md
447
CHANGELOG.md
|
@ -1,19 +1,451 @@
|
|||
# Changelog
|
||||
Changelog for NeoFS Node
|
||||
Changelog for FrostFS Node
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- Separate batching for replicated operations over the same container in pilorama (#1621)
|
||||
- Doc for extended headers (#2128)
|
||||
- New `frostfs_node_object_container_size` metric for tracking size of reqular objects in a container (#2116)
|
||||
- New `frostfs_node_object_payload_size` metric for tracking size of reqular objects on a single shard (#1794)
|
||||
- Add command `frostfs-adm morph netmap-candidates` (#1889)
|
||||
- `object.delete.tombstone_lifetime` config parameter to set tombstone lifetime in the DELETE service (#2246)
|
||||
- Reload config for pprof and metrics on SIGHUP in `neofs-node` (#1868)
|
||||
- Multiple configs support (#44)
|
||||
- Parameters `nns-name` and `nns-zone` for command `frostfs-cli container create` (#37)
|
||||
|
||||
### Changed
|
||||
- Change `frostfs_node_engine_container_size` to counting sizes of logical objects
|
||||
- `common.PrintVerbose` prints via `cobra.Command.Printf` (#1962)
|
||||
- Env prefix in configuration changed to `FROSTFS_*` (#43)
|
||||
- Link object is broadcast throughout the whole container now (#57)
|
||||
- Pilorama now can merge multiple batches into one (#2231)
|
||||
- Storage engine now can start even when some shard components are unavailable (#2238)
|
||||
- `neofs-cli` buffer for object put increased from 4 KiB to 3 MiB (#2243)
|
||||
- Expired locked object is available for reading (#56)
|
||||
|
||||
### Fixed
|
||||
- Increase payload size metric on shards' `put` operation (#1794)
|
||||
- Big object removal with non-local parts (#1978)
|
||||
- Disable pilorama when moving to degraded mode (#2197)
|
||||
- Fetching blobovnicza objects that not found in write-cache (#2206)
|
||||
- Do not search for the small objects in FSTree (#2206)
|
||||
- Correct status error for expired session token (#2207)
|
||||
- Set flag `mode` required for `frostfs-cli control shards set-mode` (#8)
|
||||
- Fix `dirty` suffix in debian package version (#53)
|
||||
- Prevent node process from killing by systemd when shutting down (#1465)
|
||||
- Restore subscriptions correctly on morph client switch (#2212)
|
||||
- Expired objects could be returned if not marked with GC yet (#2213)
|
||||
- `neofs-adm morph dump-hashes` now properly iterates over custom domain (#2224)
|
||||
- Possible deadlock in write-cache (#2239)
|
||||
- Fix `*_req_count` and `*_req_count_success` metric values (#2241)
|
||||
- Storage ID update by write-cache (#2244)
|
||||
- `neo-go` client deadlock on subscription restoration (#2244)
|
||||
- Possible panic during write-cache initialization (#2234)
|
||||
- Do not fetch an object if `meta` is missing it (#61)
|
||||
- Create contract wallet only by `init` and `update-config` command (#63)
|
||||
- Actually use `object.put.pool_size_local` and independent pool for local puts (#64).
|
||||
|
||||
### Removed
|
||||
### Updated
|
||||
- `neo-go` to `v0.100.1`
|
||||
- `github.com/klauspost/compress` to `v1.15.13`
|
||||
- `github.com/multiformats/go-multiaddr` to `v0.8.0`
|
||||
- `golang.org/x/term` to `v0.3.0`
|
||||
- `google.golang.org/grpc` to `v1.51.0`
|
||||
- `github.com/nats-io/nats.go` to `v1.22.1`
|
||||
- `github.com/TrueCloudLab/hrw` to `v.1.1.1`
|
||||
- Minimum go version to v1.18
|
||||
|
||||
### Updating from v0.35.0
|
||||
|
||||
You need to change configuration environment variables to `FROSTFS_*` if you use any.
|
||||
|
||||
New config field `object.delete.tombstone_lifetime` allows to set tombstone lifetime
|
||||
more appropriate for a specific deployment.
|
||||
|
||||
## [0.35.0] - 2022-12-28 - Sindo (신도, 信島)
|
||||
|
||||
### Added
|
||||
- `morph list-containers` in `neofs-adm` (#1689)
|
||||
- `--binary` flag in `neofs-cli object put/get/delete` commands (#1338)
|
||||
- `session` flag support to `neofs-cli object hash` (#2029)
|
||||
- Shard can now change mode when encountering background disk errors (#2035)
|
||||
- Background workers and object service now use separate client caches (#2048)
|
||||
- `replicator.pool_size` config field to tune replicator pool size (#2049)
|
||||
- Fix NNS hash parsing in morph client (#2063)
|
||||
- `neofs-cli neofs-cli acl basic/extended print` commands (#2012)
|
||||
- `neofs_node_object_*_req_count_success` prometheus metrics for tracking successfully executed requests (#1984)
|
||||
- Metric 'readonly' to get shards mode (#2022)
|
||||
- Tree service replication timeout (#2159)
|
||||
- `apiclient.reconnect_timeout` setting allowing to ignore failed clients for some time (#2164)
|
||||
|
||||
### Changed
|
||||
- `object lock` command reads CID and OID the same way other commands do (#1971)
|
||||
- `LOCK` object are stored on every container node (#1502)
|
||||
- `neofs-cli container get-eacl` print ACL table in json format only with arg `--json' (#2012)
|
||||
- Side chain notary deposits use max uint32 as till parameter (#1486)
|
||||
- Allow object removal without linking object (#2100)
|
||||
- `neofs-cli container delete` command pre-checks container ownership (#2106)
|
||||
- Policer cache size is now 1024 (#2158)
|
||||
- Tree service now synchronizes with container nodes in a random order (#2127)
|
||||
- Pilorama no longer tries to apply already applied operations (#2161)
|
||||
- Use `sync.Pool` in Object.PUT service (#2139)
|
||||
- Shard uses metabase for `HEAD` requests by default, not write-cache (#2167)
|
||||
- Clarify help for `--expire-at` parameter for commands `object lock/put` and `bearer create` (#2097)
|
||||
- Node spawns `GETRANGE` requests signed with the node's key if session key was not found for `RANGEHASH` (#2144)
|
||||
- Full list of container is no longer cached (#2176)
|
||||
|
||||
### Fixed
|
||||
- Open FSTree in sync mode by default (#1992)
|
||||
- `neofs-cli container nodes`'s output (#1991)
|
||||
- Increase error counter for write-cache flush errors (#1818)
|
||||
- Correctly select the shard for applying tree service operations (#1996)
|
||||
- Do not panic and return correct errors for bad inputs in `GET_RANGE` (#2007, #2024)
|
||||
- Physical child object removal by GC (#1699)
|
||||
- Broadcasting helper objects (#1972)
|
||||
- `neofs-cli lock object`'s `lifetime` flag handling (#1972)
|
||||
- Do not move write-cache in read-only mode for flushing (#1906)
|
||||
- Child object collection on CLI side with a bearer token (#2000)
|
||||
- Fix concurrent map writes in `Object.Put` service (#2037)
|
||||
- Malformed request errors' reasons in the responses (#2028)
|
||||
- Session token's IAT and NBF checks in ACL service (#2028)
|
||||
- Losing meta information on request forwarding (#2040)
|
||||
- Assembly process triggered by a request with a bearer token (#2040)
|
||||
- Losing locking context after metabase resync (#1502)
|
||||
- Removing all trees by container ID if tree ID is empty in `pilorama.Forest.TreeDrop` (#1940)
|
||||
- Concurrent mode changes in the metabase and blobstor (#2057)
|
||||
- Panic in IR when performing HEAD requests (#2069)
|
||||
- Write-cache flush duplication (#2074)
|
||||
- Ignore error if a transaction already exists in a morph client (#2075)
|
||||
- ObjectID signature output in the CLI (#2104)
|
||||
- Pack arguments of `setPrice` invocation during contract update (#2078)
|
||||
- `neofs-cli object hash` panic (#2079)
|
||||
- Closing `neo-go` WS clients on shutdown and switch processes (#2080)
|
||||
- Making notary deposits with a zero GAS balance (#2080)
|
||||
- Notary requests on shutdown (#2075)
|
||||
- `neofs-cli container create ` check the sufficiency of the number of nodes in the selector for replicas (#2038)
|
||||
- Data duplication during request forwarding (#2047)
|
||||
- Tree service panic on `TreeMove` operation (#2140)
|
||||
- Panic in `GETRANGE` with zero length (#2095)
|
||||
- Spawning useless `GETRANGE` with zero length for a big object (#2101)
|
||||
- Incomplete object put errors do contain the deepest error's message (#2092)
|
||||
- Prioritize internal addresses for clients (#2156)
|
||||
- Force object removal via control service (#2145)
|
||||
- Synchronizing a tree now longer reports an error for a single-node container (#2154)
|
||||
- Prevent leaking goroutines in the tree service (#2162)
|
||||
- Do not search for LOCK objects when delete container when session provided (#2152)
|
||||
- Race conditions on shard's mode switch (#1956)
|
||||
- Returning expired/removed objects from write-cache (#2016)
|
||||
|
||||
### Removed
|
||||
- `-g` option from `neofs-cli control ...` and `neofs-cli container create` commands (#2089)
|
||||
- `--header` from `neofs-cli object get` (#2090)
|
||||
|
||||
### Updated
|
||||
- `neo-go` to `v0.100.0`
|
||||
- `spf13/cobra` to `v1.6.1`
|
||||
- `spf13/viper` to `v1.8.0`
|
||||
- `google.golang.org/grpc` to `v1.50.1`
|
||||
|
||||
### Updating from v0.34.0
|
||||
Pass CID and OID parameters via the `--cid` and `--oid` flags, not as the command arguments.
|
||||
|
||||
Replicator pool size can now be fine-tuned with `replicator.pool_size` config field.
|
||||
The default value is taken from `object.put.pool_size_remote` as in earlier versions.
|
||||
|
||||
Added `neofs_node_object_*_req_count_success` metrics for tracking successfully executed requests.
|
||||
|
||||
`neofs-cli container delete` command now requires given account or session issuer
|
||||
to match the container owner. Use `--force` (`-f`) flag to bypass this requirement.
|
||||
|
||||
Tree service network replication can now be fine-tuned with `tree.replication_timeout` config field.
|
||||
|
||||
## [0.34.0] - 2022-10-31 - Marado (마라도, 馬羅島)
|
||||
|
||||
### Added
|
||||
- `--timeout` flag in `neofs-cli control` commands (#1917)
|
||||
- Document shard modes of operation (#1909)
|
||||
- `tree list` CLI command (#1332)
|
||||
- `TreeService.GetTrees` RPC (#1902)
|
||||
- All trees synchronization on bootstrap (#1902)
|
||||
- `--force` flag to `neofs-cli control set-status` command (#1916)
|
||||
- Logging `SessionService.Create` RPC on the server for debug (#1930)
|
||||
- Debian packages can now be built with `make debpackage` (#409)
|
||||
|
||||
### Changed
|
||||
- Path to a metabase can now be reloaded with a SIGHUP (#1869)
|
||||
|
||||
### Fixed
|
||||
- `writecache.max_object_size` is now correctly handled (#1925)
|
||||
- Correctly handle setting ONLINE netmap status after maintenance (#1922)
|
||||
- Correctly reset shard errors in `ControlService.SetShardMode` RPC (#1931)
|
||||
- Setting node's network state to `MAINTENANCE` while network settings forbid it (#1916)
|
||||
- Do not panic during API client creation (#1936)
|
||||
- Correctly sign new epoch transaction in neofs-adm for a committee of more than 4 nodes (#1949)
|
||||
- Inability to provide session to NeoFS CLI in a NeoFS-binary format (#1933)
|
||||
- `neofs-adm` now works correctly with a committee of more than 4 nodes (#1949, #1959)
|
||||
- Closing a shard now waits until GC background workers stop (#1964)
|
||||
- Make it possible to use `shard.ContainerSize` in read-only mode (#1975)
|
||||
- Storage node now starts if at least one gRPC endpoint is available (#1893)
|
||||
- Panic in API multy client (#1961)
|
||||
- Blobstor object removal log messages (#1953)
|
||||
- Missing object relatives in object removal session opened by NeoFS CLI (#1978)
|
||||
- Bringing a node back online during maintenance (#1900)
|
||||
|
||||
### Updated
|
||||
- `neo-go` to `v0.99.4`
|
||||
- `protoc` to `v3.21.7`
|
||||
- `neofs-sdk` to `v1.0.0-rc.7`
|
||||
|
||||
### Updating from v0.33.0
|
||||
Now storage node serves Control API `SetNemapStatus` request with `MAINTENANCE`
|
||||
status only if the mode is allowed in the network settings. To force starting the local
|
||||
maintenance on the node, provide `--force` flag to the `neofs-cli control set-status`
|
||||
command.
|
||||
|
||||
## [0.33.0] - 2022-10-17 - Anmado (안마도, 鞍馬島)
|
||||
|
||||
### Added
|
||||
- Serving `NetmapService.NetmapSnapshot` RPC (#1793)
|
||||
- `netmap snapshot` command of NeoFS CLI (#1793)
|
||||
- `apiclient.allow_external` config flag to fallback to node external addresses (#1817)
|
||||
- Support `MAINTENANCE` state of the storage nodes (#1680, #1681)
|
||||
- Changelog updates CI step (#1808)
|
||||
- Validate storage node configuration before node startup (#1805)
|
||||
- `neofs-node -check` command to check the configuration file (#1805)
|
||||
- `flush-cache` control service command to flush write-cache (#1806)
|
||||
- `wallet-address` flag in `neofs-adm morph refill-gas` command (#1820)
|
||||
- Validate policy before container creation (#1704)
|
||||
- `--timeout` flag in `neofs-cli` subcommands (#1837)
|
||||
- `container nodes` command to output list of nodes for container, grouped by replica (#1704)
|
||||
- Configuration flag to ignore shard in `neofs-node` (#1840)
|
||||
- Add new RPC `TreeService.Healthcheck`
|
||||
- Fallback to `GET` if `GET_RANGE` from one storage nodes to another is denied by basic ACL (#1884)
|
||||
- List of shards and logger level runtime reconfiguration (#1770)
|
||||
- `neofs-adm morph set-config` now supports well-known `MaintenanceModeAllowed` key (#1892)
|
||||
- `add`, `get-by-path` and `add-by-path` tree service CLI commands (#1332)
|
||||
- Tree synchronisation on startup (#1329)
|
||||
- Morph client returns to the highest priority endpoint after the switch (#1615)
|
||||
|
||||
### Changed
|
||||
- Allow to evacuate shard data with `EvacuateShard` control RPC (#1800)
|
||||
- Flush write-cache when moving shard to DEGRADED mode (#1825)
|
||||
- Make `morph.cache_ttl` default value equal to morph block time (#1846)
|
||||
- Policer marks nodes under maintenance as OK without requests (#1680)
|
||||
- Unify help messages in CLI (#1854)
|
||||
- `evacuate`, `set-mode` and `flush-cache` control subcommands now accept a list of shard ids (#1867)
|
||||
- Reading `object` commands of NeoFS CLI don't open remote sessions (#1865)
|
||||
- Use hex format to print storage node ID (#1765)
|
||||
|
||||
### Fixed
|
||||
- Description of command `netmap nodeinfo` (#1821)
|
||||
- Proper status for object.Delete if session token is missing (#1697)
|
||||
- Fail startup if metabase has an old version (#1809)
|
||||
- Storage nodes could enter the network with any state (#1796)
|
||||
- Missing check of new state value in `ControlService.SetNetmapStatus` (#1797)
|
||||
- Correlation of object session to request (#1420)
|
||||
- Do not increase error counter in `engine.Inhume` if shard is read-only (#1839)
|
||||
- `control drop-objects` can remove split objects (#1830)
|
||||
- Node's status in `neofs-cli netmap nodeinfo` command (#1833)
|
||||
- Child check in object assembly process of `ObjectService.Get` handler (#1878)
|
||||
- Shard ID in the object counter metrics (#1863)
|
||||
- Metabase migration from the first version (#1860)
|
||||
|
||||
### Removed
|
||||
- Remove WIF and NEP2 support in `neofs-cli`'s --wallet flag (#1128)
|
||||
- Remove --generate-key option in `neofs-cli container delete` (#1692)
|
||||
- Serving `ControlService.NetmapSnapshot` RPC (#1793)
|
||||
- `control netmap-snapshot` command of NeoFS CLI (#1793)
|
||||
|
||||
### Updated
|
||||
|
||||
## [0.29.0] - 2022-07-07
|
||||
- `neofs-contract` to `v0.16.0`
|
||||
- `neofs-api-go` to `v2.14.0`
|
||||
|
||||
### Updating from v0.32.0
|
||||
Replace using the `control netmap-snapshot` command with `netmap snapshot` one in NeoFS CLI.
|
||||
Node can now specify additional addresses in `ExternalAddr` attribute. To allow a node to dial
|
||||
other nodes external address, use `apiclient.allow_external` config setting.
|
||||
Add `--force` option to skip placement validity check for container creation.
|
||||
|
||||
Pass `maintenance` state to `neofs-cli control set-status` to enter maintenance mode.
|
||||
If network allows maintenance state (*), it will be reflected in the network map.
|
||||
Storage nodes under maintenance are not excluded from the network map, but don't
|
||||
serve object operations. (*) can be fetched from network configuration via
|
||||
`neofs-cli netmap netinfo` command.
|
||||
|
||||
To allow maintenance mode during neofs-adm deployments, set
|
||||
`network.maintenance_mode_allowed` parameter in config.
|
||||
|
||||
When issuing an object session token for root (virtual, "big") objects,
|
||||
additionally include all members of the split-chain. If session context
|
||||
includes root object only, it is not spread to physical ("small") objects.
|
||||
|
||||
`neofs-node` configuration now supports `mode: disabled` flag for a shard.
|
||||
This can be used to temporarily ignore shards without completely removing them
|
||||
from the config file.
|
||||
|
||||
## [0.32.0] - 2022-09-14 - Pungdo (풍도, 楓島)
|
||||
|
||||
### Added
|
||||
|
||||
- Objects counter metric (#1712)
|
||||
- `meta` subcommand to `neofs-lens` (#1714)
|
||||
- Storage node metrics with global and per-shard object counters (#1658)
|
||||
- Removal of trees on container removal (#1630)
|
||||
- Logging new epoch events on storage node (#1763)
|
||||
- Timeout for streaming RPC (#1746)
|
||||
- `neofs-adm` is now able to dump hashes from a custom zone (#1748)
|
||||
- Empty filename support in the Tree Service (#1698)
|
||||
- Flag to `neofs-cli container list-objects` command for attribute printing (#1649)
|
||||
|
||||
### Changed
|
||||
|
||||
- `neofs-cli object put`'s object ID output has changed from "ID" to "OID" (#1296)
|
||||
- `neofs-cli container set-eacl` command now pre-checks container ACL's extensibility (#1652)
|
||||
- Access control in Tree service (#1628)
|
||||
- Tree service doesn't restrict depth in `rpc GetSubTree` (#1753)
|
||||
- `neofs-adm` registers contract hashes in both hex and string address formats (#1749)
|
||||
- Container list cache synchronization with the Sidechain (#1632)
|
||||
- Blobstor components are unified (#1584, #1686, #1523)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Panic on write-cache's `Delete` operation (#1664)
|
||||
- Payload duplication in `neofs-cli storagegroup put` (#1706)
|
||||
- Contract calls in notary disabled environments (#1743)
|
||||
- `Blobovnicza.Get` op now iterates over all size buckets (#1707)
|
||||
- Object expiration time (#1670)
|
||||
- Parser of the placement policy (#1775)
|
||||
- Tree service timeout logs (#1759)
|
||||
- Object flushing on writecache side (#1745)
|
||||
- Active blobovniczas caching (#1691)
|
||||
- `neofs-adm` TX waiting (#1738)
|
||||
- `neofs-adm` registers contracts with a minimal GAS payment (#1683)
|
||||
- Permissions of the file created by `neofs-cli` (#1719)
|
||||
- `neofs-adm` creates TX with a high priority attribute (#1702)
|
||||
- Storage node's restart after a hard reboot (#1647)
|
||||
|
||||
### Updated
|
||||
|
||||
- `neo-go` to `v0.99.2`
|
||||
- `nspcc-dev/neofs-contract` to `v0.15.5`
|
||||
- `prometheus/client_golang` to `v1.13.0`
|
||||
- `google.golang.org/protobuf` to `v1.28.1`
|
||||
|
||||
### Updating from v0.31.0
|
||||
|
||||
Storage Node now collects object count prometheus metrics: `neofs_node_object_counter`.
|
||||
|
||||
Provide `--no-precheck` flag to `neofs-cli container set-eacl` for unconditional sending of a request
|
||||
(previous default behavior).
|
||||
|
||||
## [0.31.0] - 2022-08-04 - Baengnyeongdo (백령도, 白翎島)
|
||||
|
||||
### Added
|
||||
|
||||
- `neofs-adm` allows deploying arbitrary contracts (#1629)
|
||||
|
||||
### Changed
|
||||
|
||||
- Priority order in the Morph client (#1648)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Losing request context in eACL response checks (#1595)
|
||||
- Do not return expired objects that have not been handled by the GC yet (#1634)
|
||||
- Setting CID field in `neofs-cli acl extended create` (#1650)
|
||||
- `neofs-ir` no longer hangs if it cannot bind to the control endpoint (#1643)
|
||||
- Do not require `lifetime` flag in `session create` CLI command (#1655)
|
||||
- Using deprecated gRPC options (#1644)
|
||||
- Increasing metabase error counter on disabled pilorama (#1642)
|
||||
- Deadlock in the morph client related to synchronous notification handling (#1653)
|
||||
- Slow metabase `COMMON_PREFIX` search for empty prefix (#1656)
|
||||
|
||||
### Removed
|
||||
|
||||
- Deprecated `profiler` and `metrics` configuration sections (#1654)
|
||||
|
||||
### Updated
|
||||
|
||||
- `chzyer/realine` to `v1.5.1`
|
||||
- `google/uuid` to `v1.3.0`
|
||||
- `nats-io/nats.go` to `v1.16.0`
|
||||
- `prometheus/client_golang` to `v1.12.2`
|
||||
- `spf13/cast` to `v1.5.0`
|
||||
- `spf13/viper` to `v1.12.0`
|
||||
- `go.uber.org/zap` to `v1.21.0`
|
||||
- `google.golang.org/grpc` to `v1.48.0`
|
||||
|
||||
### Updating from v0.30.0
|
||||
1. Change `morph.endpoint.client` priority values using the following rule:
|
||||
the higher the priority the lower the value (non-specified or `0` values are
|
||||
interpreted as the highest priority -- `1`).
|
||||
2. Deprecated `profiler` and `metrics` configuration sections are dropped,
|
||||
use `pprof` and `prometheus` instead.
|
||||
|
||||
## [0.30.2] - 2022-08-01
|
||||
|
||||
### Added
|
||||
- `EACL_NOT_FOUND` status code support (#1645).
|
||||
|
||||
## [0.30.1] - 2022-07-29
|
||||
|
||||
### Fixed
|
||||
|
||||
- `GetRange` operation now works correctly with objects stored in write-cache (#1638)
|
||||
- Losing request context in eACL response checks (#1595)
|
||||
- Wrong balance contract in innerring processor (#1636)
|
||||
- `neofs-adm` now sets groups in manifest for all contracts properly (#1631)
|
||||
|
||||
### Updated
|
||||
|
||||
- `neo-go` to `v0.99.1`
|
||||
- `neofs-sdk-go` to `v1.0.0-rc.6`
|
||||
|
||||
## [0.30.0] - 2022-07-22 - Saengildo (생일도, 生日島)
|
||||
|
||||
### Added
|
||||
|
||||
- Profiler and metrics services now should be enabled with a separate flag
|
||||
- Experimental support for the tree-service, disabled by default (#1607)
|
||||
- Homomorphic hashes calculation can be disabled across the whole network (#1365)
|
||||
- Improve `neofs-adm` auto-completion (#1594)
|
||||
|
||||
### Changed
|
||||
|
||||
- Require SG members to be unique (#1490)
|
||||
- `neofs-cli` now doesn't remove container with LOCK objects without `--force` flag (#1500)
|
||||
- LOCK objects are now required to have an expiration epoch (#1461)
|
||||
- `morph` sections in IR and storage node configuration now accept an address and a priority of an endpoint (#1609)
|
||||
- Morph client now retries connecting to the failed endpoint too (#1609)
|
||||
- Redirecting `GET` and `GETRANGE` requests now does not store full object copy in memory (#1605)
|
||||
- `neofs-adm` now registers candidates during initialization in a single transaction (#1608)
|
||||
|
||||
### Fixed
|
||||
- Invalid smart contract address in balance contract listener (#1636)
|
||||
|
||||
- Shard now can start in degraded mode if the metabase is unavailable (#1559)
|
||||
- Shard can now be disabled completely on init failure (#1559)
|
||||
- Storage group members are now required to be unique (#1490)
|
||||
- Print shard ID in component logs (#1611)
|
||||
|
||||
### Updated
|
||||
- `neofs-contract` to `v0.15.3`
|
||||
- `neo-go` to the pre-release version
|
||||
- `github.com/spf13/cobra` to v1.5.0
|
||||
|
||||
### Updating from v0.29.0
|
||||
1. Change morph endpoints from simple string to a pair of `address` and `priority`. The second can be omitted.
|
||||
For inner ring node this resides in `morph.endpoint.client` section,
|
||||
for storage node -- in `morph.rpc_endpoint` section. See `config/example` for an example.
|
||||
|
||||
2. Move `storage.default` section to `storage.shard.default`.
|
||||
3. Rename `metrics` and `profiler` sections to `prometheus` and `pprof` respectively, though old versions are supported.
|
||||
In addition, these sections must now be explicitly enabled with `enabled: true` flag.
|
||||
|
||||
## [0.29.0] - 2022-07-07 - Yeonpyeongdo (연평도, 延坪島)
|
||||
|
||||
Support WalletConnect signature scheme.
|
||||
|
||||
|
@ -1138,8 +1570,15 @@ NeoFS-API v2.0 support and updated brand-new storage node application.
|
|||
## [0.10.0] - 2020-07-10
|
||||
|
||||
First public review release.
|
||||
|
||||
[Unreleased]: https://github.com/nspcc-dev/neofs-node/compare/v0.29.0...master
|
||||
[Unreleased]: https://github.com/nspcc-dev/neofs-node/compare/v0.35.0...master
|
||||
[0.35.0]: https://github.com/nspcc-dev/neofs-node/compare/v0.34.0...v0.35.0
|
||||
[0.34.0]: https://github.com/nspcc-dev/neofs-node/compare/v0.33.0...v0.34.0
|
||||
[0.33.0]: https://github.com/nspcc-dev/neofs-node/compare/v0.32.0...v0.33.0
|
||||
[0.32.0]: https://github.com/nspcc-dev/neofs-node/compare/v0.31.0...v0.32.0
|
||||
[0.31.0]: https://github.com/nspcc-dev/neofs-node/compare/v0.30.2...v0.31.0
|
||||
[0.30.2]: https://github.com/nspcc-dev/neofs-node/compare/v0.30.1...v0.30.2
|
||||
[0.30.1]: https://github.com/nspcc-dev/neofs-node/compare/v0.30.0...v0.30.1
|
||||
[0.30.0]: https://github.com/nspcc-dev/neofs-node/compare/v0.29.0...v0.30.0
|
||||
[0.29.0]: https://github.com/nspcc-dev/neofs-node/compare/v0.28.3...v0.29.0
|
||||
[0.28.3]: https://github.com/nspcc-dev/neofs-node/compare/v0.28.2...v0.28.3
|
||||
[0.28.2]: https://github.com/nspcc-dev/neofs-node/compare/v0.28.1...v0.28.2
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
First, thank you for contributing! We love and encourage pull requests from
|
||||
everyone. Please follow the guidelines:
|
||||
|
||||
- Check the open [issues](https://github.com/nspcc-dev/neofs-node/issues) and
|
||||
[pull requests](https://github.com/nspcc-dev/neofs-node/pulls) for existing
|
||||
- Check the open [issues](https://github.com/TrueCloudLab/frostfs-node/issues) and
|
||||
[pull requests](https://github.com/TrueCloudLab/frostfs-node/pulls) for existing
|
||||
discussions.
|
||||
|
||||
- Open an issue first, to discuss a new feature or enhancement.
|
||||
|
@ -23,23 +23,23 @@ everyone. Please follow the guidelines:
|
|||
|
||||
## 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
|
||||
are the steps in details:
|
||||
|
||||
### Set up your GitHub Repository
|
||||
Fork [NeoFS node upstream](https://github.com/nspcc-dev/neofs-node/fork) source
|
||||
Fork [FrostFS node upstream](https://github.com/TrueCloudLab/frostfs-node/fork) source
|
||||
repository to your own personal repository. Copy the URL of your fork (you will
|
||||
need it for the `git clone` command below).
|
||||
|
||||
```sh
|
||||
$ git clone https://github.com/nspcc-dev/neofs-node
|
||||
$ git clone https://github.com/TrueCloudLab/frostfs-node
|
||||
```
|
||||
|
||||
### Set up git remote as ``upstream``
|
||||
```sh
|
||||
$ cd neofs-node
|
||||
$ git remote add upstream https://github.com/nspcc-dev/neofs-node
|
||||
$ cd frostfs-node
|
||||
$ git remote add upstream https://github.com/TrueCloudLab/frostfs-node
|
||||
$ git fetch upstream
|
||||
$ git merge upstream/master
|
||||
...
|
||||
|
@ -79,7 +79,7 @@ Description
|
|||
```
|
||||
|
||||
```
|
||||
$ git commit -am '[#123] Add some feature'
|
||||
$ git commit -sam '[#123] Add some feature'
|
||||
```
|
||||
|
||||
### Push to the branch
|
||||
|
@ -106,7 +106,8 @@ contributors".
|
|||
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`.
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# Credits
|
||||
|
||||
FrostFS continues the development of NeoFS.
|
||||
|
||||
Initial NeoFS research and development (2018-2020) was done by
|
||||
[NeoSPCC](https://nspcc.ru) team.
|
||||
|
||||
|
|
53
Makefile
53
Makefile
|
@ -2,15 +2,13 @@
|
|||
SHELL = bash
|
||||
|
||||
REPO ?= $(shell go list -m)
|
||||
VERSION ?= $(shell git describe --tags --dirty --always 2>/dev/null || cat VERSION 2>/dev/null || echo "develop")
|
||||
BUILD ?= $(shell date -u --iso=seconds)
|
||||
DEBUG ?= false
|
||||
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//')"
|
||||
|
||||
GO_VERSION ?= 1.17
|
||||
LINT_VERSION ?= 1.46.2
|
||||
GO_VERSION ?= 1.19
|
||||
LINT_VERSION ?= 1.50.0
|
||||
ARCH = amd64
|
||||
|
||||
BIN = bin
|
||||
|
@ -18,13 +16,20 @@ RELEASE = release
|
|||
DIRS = $(BIN) $(RELEASE)
|
||||
|
||||
# List of binaries to build.
|
||||
CMDS = $(notdir $(basename $(wildcard cmd/*)))
|
||||
CMDS = $(notdir $(basename $(wildcard cmd/frostfs-*)))
|
||||
BINS = $(addprefix $(BIN)/, $(CMDS))
|
||||
|
||||
.PHONY: help all images dep clean fmts fmt imports test lint docker/lint prepare-release
|
||||
# .deb package versioning
|
||||
OS_RELEASE = $(shell lsb_release -cs)
|
||||
PKG_VERSION ?= $(shell echo $(VERSION) | sed "s/^v//" | \
|
||||
sed -E "s/(.*)-(g[a-fA-F0-9]{6,8})(.*)/\1\3~\2/" | \
|
||||
sed "s/-/~/")-${OS_RELEASE}
|
||||
|
||||
.PHONY: help all images dep clean fmts fmt imports test lint docker/lint
|
||||
prepare-release debpackage
|
||||
|
||||
# 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
|
||||
all: $(DIRS) $(BINS)
|
||||
|
||||
|
@ -35,9 +40,7 @@ $(BINS): $(DIRS) dep
|
|||
@echo "⇒ Build $@"
|
||||
CGO_ENABLED=0 \
|
||||
go build -v -trimpath \
|
||||
-ldflags "-X $(REPO)/misc.Version=$(VERSION) \
|
||||
-X $(REPO)/misc.Build=$(BUILD) \
|
||||
-X $(REPO)/misc.Debug=$(DEBUG)" \
|
||||
-ldflags "-X $(REPO)/misc.Version=$(VERSION)" \
|
||||
-o $@ ./cmd/$(notdir $@)
|
||||
|
||||
$(DIRS):
|
||||
|
@ -47,7 +50,7 @@ $(DIRS):
|
|||
# Prepare binaries and archives for release
|
||||
.ONESHELL:
|
||||
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)
|
||||
strip $(RELEASE)/`basename $$file`-$(ARCH)
|
||||
tar -czf $(RELEASE)/`basename $$file`-$(ARCH).tar.gz $(RELEASE)/`basename $$file`-$(ARCH)
|
||||
|
@ -64,26 +67,26 @@ dep:
|
|||
|
||||
# Regenerate proto files:
|
||||
protoc:
|
||||
@GOPRIVATE=github.com/nspcc-dev go mod vendor
|
||||
@GOPRIVATE=github.com/TrueCloudLab go mod vendor
|
||||
# Install specific version for protobuf lib
|
||||
@go list -f '{{.Path}}/...@{{.Version}}' -m github.com/golang/protobuf | xargs go install -v
|
||||
@GOBIN=$(abspath $(BIN)) go install -mod=mod -v github.com/nspcc-dev/neofs-api-go/v2/util/protogen
|
||||
@GOBIN=$(abspath $(BIN)) go install -mod=mod -v github.com/TrueCloudLab/frostfs-api-go/v2/util/protogen
|
||||
# Protoc generate
|
||||
@for f in `find . -type f -name '*.proto' -not -path './vendor/*'`; do \
|
||||
echo "⇒ Processing $$f "; \
|
||||
protoc \
|
||||
--proto_path=.:./vendor:/usr/local/include \
|
||||
--plugin=protoc-gen-go-neofs=$(BIN)/protogen \
|
||||
--go-neofs_out=. --go-neofs_opt=paths=source_relative \
|
||||
--plugin=protoc-gen-go-frostfs=$(BIN)/protogen \
|
||||
--go-frostfs_out=. --go-frostfs_opt=paths=source_relative \
|
||||
--go_out=. --go_opt=paths=source_relative \
|
||||
--go-grpc_opt=require_unimplemented_servers=false \
|
||||
--go-grpc_out=. --go-grpc_opt=paths=source_relative $$f; \
|
||||
done
|
||||
rm -rf vendor
|
||||
|
||||
# Build NeoFS component's docker image
|
||||
# Build FrostFS component's docker image
|
||||
image-%:
|
||||
@echo "⇒ Build NeoFS $* docker image "
|
||||
@echo "⇒ Build FrostFS $* docker image "
|
||||
@docker build \
|
||||
--build-arg REPO=$(REPO) \
|
||||
--build-arg VERSION=$(VERSION) \
|
||||
|
@ -146,3 +149,15 @@ clean:
|
|||
rm -rf .cache
|
||||
rm -rf $(BIN)
|
||||
rm -rf $(RELEASE)
|
||||
|
||||
# Package for Debian
|
||||
debpackage:
|
||||
dch -b --package frostfs-node \
|
||||
--controlmaint \
|
||||
--newversion $(PKG_VERSION) \
|
||||
--distribution $(OS_RELEASE) \
|
||||
"Please see CHANGELOG.md for code changes for $(VERSION)"
|
||||
dpkg-buildpackage --no-sign -b
|
||||
|
||||
debclean:
|
||||
dh clean
|
||||
|
|
41
README.md
41
README.md
|
@ -1,39 +1,40 @@
|
|||
<p align="center">
|
||||
<img src="./.github/logo.svg" width="500px" alt="NeoFS">
|
||||
<img src="./.github/logo.svg" width="500px" alt="FrostFS">
|
||||
</p>
|
||||
|
||||
<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>
|
||||
|
||||
---
|
||||
[![Report](https://goreportcard.com/badge/github.com/nspcc-dev/neofs-node)](https://goreportcard.com/report/github.com/nspcc-dev/neofs-node)
|
||||
![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/nspcc-dev/neofs-node?sort=semver)
|
||||
![License](https://img.shields.io/github/license/nspcc-dev/neofs-node.svg?style=popout)
|
||||
[![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/TrueCloudLab/frostfs-node?sort=semver)
|
||||
![License](https://img.shields.io/github/license/TrueCloudLab/frostfs-node.svg?style=popout)
|
||||
|
||||
# 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
|
||||
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
|
||||
policies. Each node is responsible for executing the storage policies that the
|
||||
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
|
||||
[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)
|
||||
code level. This way dApps are not limited to on-chain storage and can
|
||||
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://github.com/TrueCloudLab/frostfs-api) and has
|
||||
protocol gateways for popular protocols such as [AWS
|
||||
S3](https://github.com/nspcc-dev/neofs-s3-gw),
|
||||
[HTTP](https://github.com/nspcc-dev/neofs-http-gw),
|
||||
S3](https://github.com/TrueCloudLab/frostfs-s3-gw),
|
||||
[HTTP](https://github.com/TrueCloudLab/frostfs-http-gw),
|
||||
[FUSE](https://wikipedia.org/wiki/Filesystem_in_Userspace) and
|
||||
[sFTP](https://en.wikipedia.org/wiki/SSH_File_Transfer_Protocol) allowing
|
||||
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
|
||||
platforms will be officially supported after release `1.0`.
|
||||
|
||||
The latest version of neofs-node works with neofs-contract
|
||||
[v0.13.0](https://github.com/nspcc-dev/neofs-contract/releases/tag/v0.13.0).
|
||||
The latest version of frostfs-node works with frostfs-contract
|
||||
[v0.16.0](https://github.com/TrueCloudLab/frostfs-contract/releases/tag/v0.16.0).
|
||||
|
||||
# Building
|
||||
|
||||
To make all binaries you need Go 1.17+ and `make`:
|
||||
To make all binaries you need Go 1.18+ and `make`:
|
||||
```
|
||||
make all
|
||||
```
|
||||
|
@ -56,7 +57,7 @@ The resulting binaries will appear in `bin/` folder.
|
|||
|
||||
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.
|
||||
|
||||
|
@ -65,12 +66,12 @@ See the list of all available commands in the `cmd` folder.
|
|||
Building can also be performed in a container:
|
||||
```
|
||||
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
|
||||
|
||||
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
|
||||
```
|
||||
|
@ -85,7 +86,7 @@ the feature/topic you are going to implement.
|
|||
|
||||
# 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.
|
||||
|
||||
Please see [CREDITS](CREDITS.md) for details.
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
v0.28.3
|
||||
v0.35.0
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
# NeoFS Admin Tool
|
||||
# FrostFS Admin Tool
|
||||
|
||||
## Overview
|
||||
|
||||
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
|
||||
the sidechain, and provides functions to update the network and register new
|
||||
Storage nodes.
|
||||
|
||||
## 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`.
|
||||
|
||||
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
|
||||
[neofs-contract repository](https://github.com/nspcc-dev/neofs-contract/releases).
|
||||
[frostfs-contract repository](https://github.com/TrueCloudLab/frostfs-contract/releases).
|
||||
|
||||
|
||||
## Commands
|
||||
|
@ -35,8 +35,8 @@ Config example:
|
|||
rpc-endpoint: https://address:port # sidechain RPC node endpoint
|
||||
alphabet-wallets: /path # path to consensus node / alphabet wallets storage
|
||||
network:
|
||||
max_object_size: 67108864 # max size of a single NeoFS object, bytes
|
||||
epoch_duration: 240 # duration of a NeoFS epoch in blocks, consider block generation frequency in the sidechain
|
||||
max_object_size: 67108864 # max size of a single FrostFS object, bytes
|
||||
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:
|
||||
audit: 0 # network audit fee, for private installation consider 0
|
||||
|
@ -62,16 +62,18 @@ credentials: # passwords for consensus node / alphabet wallets
|
|||
Alphabet nodes.
|
||||
|
||||
- `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
|
||||
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
|
||||
|
||||
- `force-new-epoch` increments NeoFS epoch number and executes new epoch
|
||||
handlers in NeoFS nodes.
|
||||
- `set-config` add/update configuration values in the Netmap contract.
|
||||
|
||||
- `force-new-epoch` increments FrostFS epoch number and executes new epoch
|
||||
handlers in FrostFS nodes.
|
||||
|
||||
- `refill-gas` transfers sidechain GAS to the specified wallet.
|
||||
|
||||
|
@ -88,11 +90,13 @@ info. These commands **do not migrate actual objects**.
|
|||
- `restore-containers` restores previously saved containers by their repeated registration in
|
||||
the container contract.
|
||||
|
||||
- `list-containers` output all containers ids.
|
||||
|
||||
#### 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
|
|
@ -1,16 +1,16 @@
|
|||
# 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
|
||||
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.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
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 [neofs-adm](https://github.com/nspcc-dev/neofs-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 [frostfs-adm](https://github.com/TrueCloudLab/frostfs-node/releases) utility (v0.25.1 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
|
||||
|
||||
|
@ -19,12 +19,12 @@ 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't be changed so easily right now. Consider this before going any further.
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
$ cat foo.network.yml
|
||||
|
@ -57,7 +57,7 @@ wallets will be used for Alphabet nodes. Make sure, that dir for alphabet
|
|||
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
|
||||
alphabet-wallets: /home/user/deploy/alphabet-wallets
|
||||
wallet[0]: hunter2
|
||||
|
@ -118,19 +118,19 @@ and possible overload issues.
|
|||
|
||||
## 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.
|
||||
Waiting for transactions to persist...
|
||||
Stage 2: set notary and alphabet nodes in designate contract.
|
||||
Waiting for transactions to persist...
|
||||
Stage 3: deploy NNS contract.
|
||||
Waiting for transactions to persist...
|
||||
Stage 4: deploy NeoFS contracts.
|
||||
Stage 4: deploy FrostFS contracts.
|
||||
Waiting for transactions to persist...
|
||||
Stage 4.1: Transfer GAS to proxy contract.
|
||||
Waiting for transactions to persist...
|
||||
|
@ -140,14 +140,14 @@ Stage 6: transfer NEO to alphabet contracts.
|
|||
Waiting for transactions to persist...
|
||||
Stage 7: set addresses in NNS.
|
||||
Waiting for transactions to persist...
|
||||
NNS: Set alphabet0.neofs -> f692dfb4d43a15b464eb51a7041160fb29c44b6a
|
||||
NNS: Set audit.neofs -> 7df847b993affb3852074345a7c2bd622171ee0d
|
||||
NNS: Set balance.neofs -> 103519b3067a66307080a66570c0491ee8f68879
|
||||
NNS: Set container.neofs -> cae60bdd689d185901e495352d0247752ce50846
|
||||
NNS: Set neofsid.neofs -> c421fb60a3895865a8f24d197d6a80ef686041d2
|
||||
NNS: Set netmap.neofs -> 894eb854632f50fb124412ce7951ebc00763525e
|
||||
NNS: Set proxy.neofs -> ac6e6fe4b373d0ca0ca4969d1e58fa0988724e7d
|
||||
NNS: Set reputation.neofs -> 6eda57c9d93d990573646762d1fea327ce41191f
|
||||
NNS: Set alphabet0.frostfs -> f692dfb4d43a15b464eb51a7041160fb29c44b6a
|
||||
NNS: Set audit.frostfs -> 7df847b993affb3852074345a7c2bd622171ee0d
|
||||
NNS: Set balance.frostfs -> 103519b3067a66307080a66570c0491ee8f68879
|
||||
NNS: Set container.frostfs -> cae60bdd689d185901e495352d0247752ce50846
|
||||
NNS: Set frostfsid.frostfs -> c421fb60a3895865a8f24d197d6a80ef686041d2
|
||||
NNS: Set netmap.frostfs -> 894eb854632f50fb124412ce7951ebc00763525e
|
||||
NNS: Set proxy.frostfs -> ac6e6fe4b373d0ca0ca4969d1e58fa0988724e7d
|
||||
NNS: Set reputation.frostfs -> 6eda57c9d93d990573646762d1fea327ce41191f
|
||||
Waiting for transactions to persist...
|
||||
```
|
||||
|
||||
|
@ -177,7 +177,7 @@ contracts:
|
|||
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 >
|
||||
Waiting for transactions to persist...
|
||||
|
||||
|
@ -196,16 +196,16 @@ node:
|
|||
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.
|
||||
|
||||
```
|
||||
$ 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.
|
||||
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.
|
|
@ -1,6 +1,6 @@
|
|||
# NeoFS subnetwork creation
|
||||
# FrostFS subnetwork creation
|
||||
|
||||
This is a short guide on how to create NeoFS subnetworks. This guide
|
||||
This is a short guide on how to create FrostFS subnetworks. This guide
|
||||
considers that the sidechain and the inner ring (alphabet nodes) have already been
|
||||
deployed and the sidechain contains a deployed `subnet` contract.
|
||||
|
||||
|
@ -8,13 +8,13 @@ deployed and the sidechain contains a deployed `subnet` contract.
|
|||
|
||||
To follow this guide, you need:
|
||||
- neo-go sidechain RPC endpoint;
|
||||
- latest released version of [neofs-adm](https://github.com/nspcc-dev/neofs-node/releases);
|
||||
- wallet with NeoFS account.
|
||||
- latest released version of [frostfs-adm](https://github.com/TrueCloudLab/frostfs-node/releases);
|
||||
- wallet with FrostFS account.
|
||||
|
||||
## Creation
|
||||
|
||||
```shell
|
||||
$ neofs-adm morph subnet create \
|
||||
$ frostfs-adm morph subnet create \
|
||||
-r <side_chain_RPC_endpoint> \
|
||||
-w </path/to/owner/wallet> \
|
||||
--notary
|
||||
|
@ -31,7 +31,7 @@ of the just created subnetwork.
|
|||
You can check if your subnetwork was created successfully:
|
||||
|
||||
```shell
|
||||
$ neofs-adm morph subnet get \
|
||||
$ frostfs-adm morph subnet get \
|
||||
-r <side_chain_RPC_endpoint> \
|
||||
--subnet <subnet_ID>
|
||||
Owner: NUc734PMJXiqa2J9jRtvskU3kCdyyuSN8Q
|
|
@ -1,18 +1,18 @@
|
|||
# Managing Subnetworks
|
||||
|
||||
This is a short guide on how to manage NeoFS subnetworks. This guide
|
||||
This is a short guide on how to manage FrostFS subnetworks. This guide
|
||||
considers that the sidechain and the inner ring (alphabet nodes) have already been
|
||||
deployed, and the sidechain contains a deployed `subnet` contract.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- neo-go sidechain RPC endpoint;
|
||||
- latest released version of [neofs-adm](https://github.com/nspcc-dev/neofs-node/releases);
|
||||
- latest released version of [frostfs-adm](https://github.com/TrueCloudLab/frostfs-node/releases);
|
||||
- [created](subnetwork-creation.md) subnetwork;
|
||||
- wallet with the account that owns the subnetwork;
|
||||
- public key of the Storage Node;
|
||||
- public keys of the node and client administrators;
|
||||
- owner IDs of the NeoFS users.
|
||||
- owner IDs of the FrostFS users.
|
||||
|
||||
## Add node administrator
|
||||
|
||||
|
@ -21,7 +21,7 @@ the whitelist of the nodes which can be included to a subnetwork. Only the subne
|
|||
owner is allowed to add and remove node administrators from the subnetwork.
|
||||
|
||||
```shell
|
||||
$ neofs-adm morph subnet admin add \
|
||||
$ frostfs-adm morph subnet admin add \
|
||||
-r <side_chain_RPC_endpoint> \
|
||||
-w </path/to/owner/wallet> \
|
||||
--admin <HEX_admin_public_key> \
|
||||
|
@ -37,7 +37,7 @@ the list of the allowed nodes. Node is not required to be bootstrapped at the
|
|||
moment of its inclusion.
|
||||
|
||||
```shell
|
||||
$ neofs-adm morph subnet node add \
|
||||
$ frostfs-adm morph subnet node add \
|
||||
-r <side_chain_RPC_endpoint> \
|
||||
-w </path/to/node_admin/wallet> \
|
||||
--node <HEX_node_public_key> \
|
||||
|
@ -55,7 +55,7 @@ subnetwork. Only the subnet owner is allowed to add and remove client
|
|||
administrators from the subnetwork.
|
||||
|
||||
```shell
|
||||
$ neofs-adm morph subnet admin add \
|
||||
$ frostfs-adm morph subnet admin add \
|
||||
-r <side_chain_RPC_endpoint> \
|
||||
-w </path/to/owner/wallet> \
|
||||
--admin <HEX_admin_public_key> \
|
||||
|
@ -72,7 +72,7 @@ positive integer number.
|
|||
## Add client
|
||||
|
||||
```shell
|
||||
$ neofs-adm morph subnet client add \
|
||||
$ frostfs-adm morph subnet client add \
|
||||
-r <side_chain_RPC_endpoint> \
|
||||
-w </path/to/client_admin/wallet> \
|
||||
--client <client_ownerID> \
|
||||
|
@ -99,7 +99,7 @@ configuration:
|
|||
node:
|
||||
...
|
||||
subnet:
|
||||
entries: # list of IDs of subnets to enter in a text format of NeoFS API protocol (overrides corresponding attributes)
|
||||
entries: # list of IDs of subnets to enter in a text format of FrostFS API protocol (overrides corresponding attributes)
|
||||
- <subnetwork_ID>
|
||||
...
|
||||
...
|
||||
|
@ -129,9 +129,9 @@ To create a container in a private network, your wallet must be added to
|
|||
the client whitelist by the client admins or the subnet owners:
|
||||
|
||||
```shell
|
||||
$ neofs-cli container create \
|
||||
$ frostfs-cli container create \
|
||||
--policy 'REP 1' \
|
||||
-w </path/to/wallet> \
|
||||
-r s01.neofs.devenv:8080 \
|
||||
-r s01.frostfs.devenv:8080 \
|
||||
--subnet <subnet_ID>
|
||||
```
|
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,24 +7,25 @@ import (
|
|||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
"github.com/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||
"github.com/nspcc-dev/neo-go/cli/input"
|
||||
"github.com/nspcc-dev/neofs-node/pkg/innerring"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type configTemplate struct {
|
||||
Endpoint string
|
||||
AlphabetDir string
|
||||
MaxObjectSize int
|
||||
EpochDuration int
|
||||
BasicIncomeRate int
|
||||
AuditFee int
|
||||
CandidateFee int
|
||||
ContainerFee int
|
||||
ContainerAliasFee int
|
||||
WithdrawFee int
|
||||
Glagolitics []string
|
||||
Endpoint string
|
||||
AlphabetDir string
|
||||
MaxObjectSize int
|
||||
EpochDuration int
|
||||
BasicIncomeRate int
|
||||
AuditFee int
|
||||
CandidateFee int
|
||||
ContainerFee int
|
||||
ContainerAliasFee int
|
||||
WithdrawFee int
|
||||
Glagolitics []string
|
||||
HomomorphicHashDisabled bool
|
||||
}
|
||||
|
||||
const configTxtTemplate = `rpc-endpoint: {{ .Endpoint}}
|
||||
|
@ -33,13 +34,14 @@ network:
|
|||
max_object_size: {{ .MaxObjectSize}}
|
||||
epoch_duration: {{ .EpochDuration}}
|
||||
basic_income_rate: {{ .BasicIncomeRate}}
|
||||
homomorphic_hash_disabled: {{ .HomomorphicHashDisabled}}
|
||||
fee:
|
||||
audit: {{ .AuditFee}}
|
||||
candidate: {{ .CandidateFee}}
|
||||
container: {{ .ContainerFee}}
|
||||
container_alias: {{ .ContainerAliasFee }}
|
||||
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:
|
||||
contract: password # wallet for contract group signature{{ range.Glagolitics}}
|
||||
{{.}}: password{{end}}
|
||||
|
@ -97,7 +99,7 @@ func defaultConfigPath() (string, error) {
|
|||
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
|
||||
|
@ -106,16 +108,17 @@ func defaultConfigPath() (string, error) {
|
|||
// some comments as well.
|
||||
func generateConfigExample(appDir string, credSize int) (string, error) {
|
||||
tmpl := configTemplate{
|
||||
Endpoint: "https://neo.rpc.node:30333",
|
||||
MaxObjectSize: 67108864, // 64 MiB
|
||||
EpochDuration: 240, // 1 hour with 15s per block
|
||||
BasicIncomeRate: 1_0000_0000, // 0.0001 GAS per GiB (Fixed12)
|
||||
AuditFee: 1_0000, // 0.00000001 GAS per audit (Fixed12)
|
||||
CandidateFee: 100_0000_0000, // 100.0 GAS (Fixed8)
|
||||
ContainerFee: 1000, // 0.000000001 * 7 GAS per container (Fixed12)
|
||||
ContainerAliasFee: 500, // ContainerFee / 2
|
||||
WithdrawFee: 1_0000_0000, // 1.0 GAS (Fixed8)
|
||||
Glagolitics: make([]string, 0, credSize),
|
||||
Endpoint: "https://neo.rpc.node:30333",
|
||||
MaxObjectSize: 67108864, // 64 MiB
|
||||
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
|
||||
AuditFee: 1_0000, // 0.00000001 GAS per audit (Fixed12)
|
||||
CandidateFee: 100_0000_0000, // 100.0 GAS (Fixed8)
|
||||
ContainerFee: 1000, // 0.000000001 * 7 GAS per container (Fixed12)
|
||||
ContainerAliasFee: 500, // ContainerFee / 2
|
||||
WithdrawFee: 1_0000_0000, // 1.0 GAS (Fixed8)
|
||||
Glagolitics: make([]string, 0, credSize),
|
||||
}
|
||||
|
||||
appDir, err := filepath.Abs(appDir)
|
|
@ -5,7 +5,7 @@ import (
|
|||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neofs-node/pkg/innerring"
|
||||
"github.com/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -13,7 +13,7 @@ import (
|
|||
func TestGenerateConfigExample(t *testing.T) {
|
||||
const (
|
||||
n = 10
|
||||
appDir = "/home/example/.neofs"
|
||||
appDir = "/home/example/.frostfs"
|
||||
)
|
||||
|
||||
configText, err := generateConfigExample(appDir, n)
|
29
cmd/frostfs-adm/internal/modules/config/root.go
Normal file
29
cmd/frostfs-adm/internal/modules/config/root.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const configPathFlag = "path"
|
||||
|
||||
var (
|
||||
// RootCmd is a root command of config section.
|
||||
RootCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Section for frostfs-adm config related commands",
|
||||
}
|
||||
|
||||
initCmd = &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Initialize basic frostfs-adm configuration file",
|
||||
Example: `frostfs-adm config init
|
||||
frostfs-adm config init --path .config/frostfs-adm.yml`,
|
||||
RunE: initConfig,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(initCmd)
|
||||
|
||||
initCmd.Flags().String(configPathFlag, "", "Path to config (default ~/.frostfs/adm/config.yml)")
|
||||
}
|
|
@ -6,20 +6,23 @@ import (
|
|||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"github.com/TrueCloudLab/frostfs-contract/nns"
|
||||
"github.com/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/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"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/nspcc-dev/neofs-contract/nns"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
@ -55,20 +58,7 @@ func dumpBalances(cmd *cobra.Command, _ []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
ns, err := getNativeHashes(c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch the list of native contracts: %w", err)
|
||||
}
|
||||
|
||||
gasHash, ok := ns[nativenames.Gas]
|
||||
if !ok {
|
||||
return fmt.Errorf("can't find the %s native contract hash", nativenames.Gas)
|
||||
}
|
||||
|
||||
desigHash, ok := ns[nativenames.Designation]
|
||||
if !ok {
|
||||
return fmt.Errorf("can't find the %s native contract hash", nativenames.Designation)
|
||||
}
|
||||
inv := invoker.New(c, nil)
|
||||
|
||||
if !notaryEnabled || dumpStorage || dumpAlphabet || dumpProxy {
|
||||
nnsCs, err = c.GetContractStateByID(1)
|
||||
|
@ -76,29 +66,25 @@ func dumpBalances(cmd *cobra.Command, _ []string) error {
|
|||
return fmt.Errorf("can't get NNS contract info: %w", err)
|
||||
}
|
||||
|
||||
nmHash, err = nnsResolveHash(c, nnsCs.Hash, netmapContract+".neofs")
|
||||
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, nmHash, desigHash)
|
||||
irList, err := fetchIRNodes(c, nmHash, rolemgmt.Hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fetchBalances(c, gasHash, irList); err != nil {
|
||||
if err := fetchBalances(inv, gas.Hash, irList); err != nil {
|
||||
return err
|
||||
}
|
||||
printBalances(cmd, "Inner ring nodes balances:", irList)
|
||||
|
||||
if dumpStorage {
|
||||
res, err := invokeFunction(c, nmHash, "netmap", []interface{}{}, nil)
|
||||
if err != nil || res.State != vm.HaltState.String() || len(res.Stack) == 0 {
|
||||
return errors.New("can't fetch the list of storage nodes")
|
||||
}
|
||||
arr, ok := res.Stack[0].Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
arr, err := unwrap.Array(inv.Call(nmHash, "netmap"))
|
||||
if err != nil {
|
||||
return errors.New("can't fetch the list of storage nodes")
|
||||
}
|
||||
|
||||
|
@ -123,20 +109,20 @@ func dumpBalances(cmd *cobra.Command, _ []string) error {
|
|||
snList[i].scriptHash = pub.GetScriptHash()
|
||||
}
|
||||
|
||||
if err := fetchBalances(c, gasHash, snList); err != nil {
|
||||
if err := fetchBalances(inv, gas.Hash, snList); err != nil {
|
||||
return err
|
||||
}
|
||||
printBalances(cmd, "\nStorage node balances:", snList)
|
||||
}
|
||||
|
||||
if dumpProxy {
|
||||
h, err := nnsResolveHash(c, nnsCs.Hash, proxyContract+".neofs")
|
||||
h, err := nnsResolveHash(inv, nnsCs.Hash, 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(c, gasHash, proxyList); err != nil {
|
||||
if err := fetchBalances(inv, gas.Hash, proxyList); err != nil {
|
||||
return err
|
||||
}
|
||||
printBalances(cmd, "\nProxy contract balance:", proxyList)
|
||||
|
@ -168,7 +154,7 @@ func dumpBalances(cmd *cobra.Command, _ []string) error {
|
|||
alphaList[i].scriptHash = h
|
||||
}
|
||||
|
||||
if err := fetchBalances(c, gasHash, alphaList); err != nil {
|
||||
if err := fetchBalances(inv, gas.Hash, alphaList); err != nil {
|
||||
return err
|
||||
}
|
||||
printBalances(cmd, "\nAlphabet contracts balances:", alphaList)
|
||||
|
@ -180,13 +166,15 @@ func dumpBalances(cmd *cobra.Command, _ []string) error {
|
|||
func fetchIRNodes(c Client, nmHash, desigHash util.Uint160) ([]accBalancePair, error) {
|
||||
var irList []accBalancePair
|
||||
|
||||
inv := invoker.New(c, nil)
|
||||
|
||||
if notaryEnabled {
|
||||
height, err := c.GetBlockCount()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't get block height: %w", err)
|
||||
}
|
||||
|
||||
arr, err := getDesignatedByRole(c, desigHash, noderoles.NeoFSAlphabet, height)
|
||||
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")
|
||||
}
|
||||
|
@ -196,27 +184,14 @@ func fetchIRNodes(c Client, nmHash, desigHash util.Uint160) ([]accBalancePair, e
|
|||
irList[i].scriptHash = arr[i].GetScriptHash()
|
||||
}
|
||||
} else {
|
||||
res, err := invokeFunction(c, nmHash, "innerRingList", []interface{}{}, nil)
|
||||
if err != nil || res.State != vm.HaltState.String() || len(res.Stack) == 0 {
|
||||
arr, err := unwrap.ArrayOfBytes(inv.Call(nmHash, "innerRingList"))
|
||||
if err != nil {
|
||||
return nil, errors.New("can't fetch list of IR nodes from the netmap contract")
|
||||
}
|
||||
|
||||
arr, ok := res.Stack[0].Value().([]stackitem.Item)
|
||||
if !ok || len(arr) == 0 {
|
||||
return nil, errors.New("can't fetch list of IR nodes: invalid response")
|
||||
}
|
||||
|
||||
irList = make([]accBalancePair, len(arr))
|
||||
for i := range arr {
|
||||
node, ok := arr[i].Value().([]stackitem.Item)
|
||||
if !ok || len(arr) == 0 {
|
||||
return nil, errors.New("can't fetch list of IR nodes: invalid response")
|
||||
}
|
||||
bs, err := node[0].TryBytes()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't fetch list of IR nodes: %w", err)
|
||||
}
|
||||
pub, err := keys.NewPublicKeyFromBytes(bs, elliptic.P256())
|
||||
pub, err := keys.NewPublicKeyFromBytes(arr[i], elliptic.P256())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't parse IR node public key: %w", err)
|
||||
}
|
||||
|
@ -241,7 +216,7 @@ func printBalances(cmd *cobra.Command, prefix string, accounts []accBalancePair)
|
|||
}
|
||||
}
|
||||
|
||||
func fetchBalances(c Client, gasHash util.Uint160, accounts []accBalancePair) error {
|
||||
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)
|
||||
|
@ -250,8 +225,8 @@ func fetchBalances(c Client, gasHash util.Uint160, accounts []accBalancePair) er
|
|||
panic(w.Err)
|
||||
}
|
||||
|
||||
res, err := c.InvokeScript(w.Bytes(), nil)
|
||||
if err != nil || res.State != vm.HaltState.String() || len(res.Stack) != len(accounts) {
|
||||
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")
|
||||
}
|
||||
|
192
cmd/frostfs-adm/internal/modules/morph/config.go
Normal file
192
cmd/frostfs-adm/internal/modules/morph/config.go
Normal file
|
@ -0,0 +1,192 @@
|
|||
package morph
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"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/unwrap"
|
||||
"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/stackitem"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const forceConfigSet = "force"
|
||||
|
||||
func dumpNetworkConfig(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)
|
||||
|
||||
cs, err := c.GetContractStateByID(1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get NNS contract info: %w", err)
|
||||
}
|
||||
|
||||
nmHash, err := nnsResolveHash(inv, cs.Hash, netmapContract+".frostfs")
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
||||
}
|
||||
|
||||
arr, err := unwrap.Array(inv.Call(nmHash, "listConfig"))
|
||||
if err != nil {
|
||||
return errors.New("can't fetch list of network config keys from the netmap contract")
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
|
||||
|
||||
for _, param := range arr {
|
||||
tuple, ok := param.Value().([]stackitem.Item)
|
||||
if !ok || len(tuple) != 2 {
|
||||
return errors.New("invalid ListConfig response from netmap contract")
|
||||
}
|
||||
|
||||
k, err := tuple[0].TryBytes()
|
||||
if err != nil {
|
||||
return errors.New("invalid config key from netmap contract")
|
||||
}
|
||||
|
||||
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)
|
||||
copy(nbuf[:], v)
|
||||
n := binary.LittleEndian.Uint64(nbuf)
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%d (int)\n", k, n)))
|
||||
case netmapEigenTrustAlphaKey:
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%s (str)\n", k, v)))
|
||||
case netmapHomomorphicHashDisabledKey, netmapMaintenanceAllowedKey:
|
||||
vBool, err := tuple[1].TryBool()
|
||||
if err != nil {
|
||||
return invalidConfigValueErr(k)
|
||||
}
|
||||
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%t (bool)\n", k, vBool)))
|
||||
default:
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%s (hex)\n", k, hex.EncodeToString(v))))
|
||||
}
|
||||
}
|
||||
|
||||
_ = tw.Flush()
|
||||
cmd.Print(buf.String())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setConfigCmd(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return errors.New("empty config pairs")
|
||||
}
|
||||
|
||||
wCtx, err := newInitializeContext(cmd, viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't initialize context: %w", err)
|
||||
}
|
||||
|
||||
cs, err := wCtx.Client.GetContractStateByID(1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get NNS contract info: %w", err)
|
||||
}
|
||||
|
||||
nmHash, err := nnsResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, netmapContract+".frostfs")
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
||||
}
|
||||
|
||||
forceFlag, _ := cmd.Flags().GetBool(forceConfigSet)
|
||||
|
||||
bw := io.NewBufBinWriter()
|
||||
for _, arg := range args {
|
||||
k, v, err := parseConfigPair(arg, forceFlag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// In NeoFS this is done via Notary contract. Here, however, we can form the
|
||||
// transaction locally. The first `nil` argument is required only for notary
|
||||
// disabled environment which is not supported by that command.
|
||||
emit.AppCall(bw.BinWriter, nmHash, "setConfig", callflag.All, nil, k, v)
|
||||
if bw.Err != nil {
|
||||
return fmt.Errorf("can't form raw transaction: %w", bw.Err)
|
||||
}
|
||||
}
|
||||
|
||||
err = wCtx.sendConsensusTx(bw.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return wCtx.awaitTx()
|
||||
}
|
||||
|
||||
func parseConfigPair(kvStr string, force bool) (key string, val any, err error) {
|
||||
k, v, found := strings.Cut(kvStr, "=")
|
||||
if !found {
|
||||
return "", nil, fmt.Errorf("invalid parameter format: must be 'key=val', got: %s", kvStr)
|
||||
}
|
||||
|
||||
key = k
|
||||
valRaw := v
|
||||
|
||||
switch key {
|
||||
case netmapAuditFeeKey, netmapBasicIncomeRateKey,
|
||||
netmapContainerFeeKey, netmapContainerAliasFeeKey,
|
||||
netmapEigenTrustIterationsKey,
|
||||
netmapEpochKey, netmapInnerRingCandidateFeeKey,
|
||||
netmapMaxObjectSizeKey, netmapWithdrawFeeKey:
|
||||
val, err = strconv.ParseInt(valRaw, 10, 64)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not parse %s's value '%s' as int: %w", key, valRaw, err)
|
||||
}
|
||||
case netmapEigenTrustAlphaKey:
|
||||
// just check that it could
|
||||
// be parsed correctly
|
||||
_, err = strconv.ParseFloat(v, 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)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not parse %s's value '%s' as bool: %w", key, valRaw, err)
|
||||
}
|
||||
|
||||
default:
|
||||
if !force {
|
||||
return "", nil, fmt.Errorf(
|
||||
"'%s' key is not well-known, use '--%s' flag if want to set it anyway",
|
||||
key, forceConfigSet)
|
||||
}
|
||||
|
||||
val = valRaw
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func invalidConfigValueErr(key []byte) error {
|
||||
return fmt.Errorf("invalid %s config value from netmap contract", key)
|
||||
}
|
|
@ -7,19 +7,52 @@ import (
|
|||
"os"
|
||||
"sort"
|
||||
|
||||
cid "github.com/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"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/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"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var errInvalidContainerResponse = errors.New("invalid response from container contract")
|
||||
|
||||
func getContainerContractHash(cmd *cobra.Command, inv *invoker.Invoker, c Client) (util.Uint160, error) {
|
||||
s, err := cmd.Flags().GetString(containerContractFlag)
|
||||
var ch util.Uint160
|
||||
if err == nil {
|
||||
ch, err = util.Uint160DecodeStringLE(s)
|
||||
}
|
||||
if err != nil {
|
||||
nnsCs, err := c.GetContractStateByID(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 getContainersList(inv *invoker.Invoker, ch util.Uint160) ([][]byte, error) {
|
||||
res, err := inv.Call(ch, "list", "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
|
||||
}
|
||||
itm, err := unwrap.Item(res, err)
|
||||
if _, ok := itm.(stackitem.Null); !ok {
|
||||
return unwrap.ArrayOfBytes(res, err)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func dumpContainers(cmd *cobra.Command, _ []string) error {
|
||||
filename, err := cmd.Flags().GetString(containerDumpFlag)
|
||||
if err != nil {
|
||||
|
@ -31,41 +64,18 @@ func dumpContainers(cmd *cobra.Command, _ []string) error {
|
|||
return fmt.Errorf("can't create N3 client: %w", err)
|
||||
}
|
||||
|
||||
nnsCs, err := c.GetContractStateByID(1)
|
||||
inv := invoker.New(c, nil)
|
||||
|
||||
ch, err := getContainerContractHash(cmd, inv, c)
|
||||
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(c, nnsCs.Hash, containerContract+".neofs")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
res, err := invokeFunction(c, ch, "list", []interface{}{""}, nil)
|
||||
cids, err := getContainersList(inv, ch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
|
||||
}
|
||||
|
||||
var cids [][]byte
|
||||
arr, ok := res.Stack[0].Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
return fmt.Errorf("%w: not a struct", errInvalidContainerResponse)
|
||||
}
|
||||
for _, item := range arr {
|
||||
id, err := item.TryBytes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
|
||||
}
|
||||
cids = append(cids, id)
|
||||
}
|
||||
|
||||
isOK, err := getCIDFilterFunc(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -80,7 +90,7 @@ func dumpContainers(cmd *cobra.Command, _ []string) error {
|
|||
bw.Reset()
|
||||
emit.AppCall(bw.BinWriter, ch, "get", callflag.All, id)
|
||||
emit.AppCall(bw.BinWriter, ch, "eACL", callflag.All, id)
|
||||
res, err := c.InvokeScript(bw.Bytes(), nil)
|
||||
res, err := inv.Run(bw.Bytes())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get container info: %w", err)
|
||||
}
|
||||
|
@ -113,6 +123,35 @@ func dumpContainers(cmd *cobra.Command, _ []string) error {
|
|||
return os.WriteFile(filename, out, 0o660)
|
||||
}
|
||||
|
||||
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, c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get contaract hash: %w", err)
|
||||
}
|
||||
|
||||
cids, err := getContainersList(inv, ch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
|
||||
}
|
||||
|
||||
for _, id := range cids {
|
||||
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 {
|
||||
filename, err := cmd.Flags().GetString(containerDumpFlag)
|
||||
if err != nil {
|
||||
|
@ -123,13 +162,14 @@ func restoreContainers(cmd *cobra.Command, _ []string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer wCtx.close()
|
||||
|
||||
nnsCs, err := wCtx.Client.GetContractStateByID(1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get NNS contract state: %w", err)
|
||||
}
|
||||
|
||||
ch, err := nnsResolveHash(wCtx.Client, nnsCs.Hash, containerContract+".neofs")
|
||||
ch, err := nnsResolveHash(wCtx.ReadOnlyInvoker, nnsCs.Hash, containerContract+".frostfs")
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch container contract hash: %w", err)
|
||||
}
|
||||
|
@ -188,7 +228,7 @@ func restoreContainers(cmd *cobra.Command, _ []string) error {
|
|||
panic(bw.Err)
|
||||
}
|
||||
|
||||
if err := wCtx.sendCommitteeTx(bw.Bytes(), -1, true); err != nil {
|
||||
if err := wCtx.sendConsensusTx(bw.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
224
cmd/frostfs-adm/internal/modules/morph/deploy.go
Normal file
224
cmd/frostfs-adm/internal/modules/morph/deploy.go
Normal file
|
@ -0,0 +1,224 @@
|
|||
package morph
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/TrueCloudLab/frostfs-contract/nns"
|
||||
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
||||
"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/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/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
contractPathFlag = "contract"
|
||||
updateFlag = "update"
|
||||
customZoneFlag = "domain"
|
||||
)
|
||||
|
||||
var deployCmd = &cobra.Command{
|
||||
Use: "deploy",
|
||||
Short: "Deploy additional smart-contracts",
|
||||
Long: `Deploy additional smart-contract which are not related to core.
|
||||
All contracts are deployed by the committee, so access to the alphabet wallets is required.
|
||||
Optionally, arguments can be provided to be passed to a contract's _deploy function.
|
||||
The syntax is the same as for 'neo-go contract testinvokefunction' command.
|
||||
Compiled contract file name must contain '_contract.nef' suffix.
|
||||
Contract's manifest file name must be 'config.json'.
|
||||
NNS name is taken by stripping '_contract.nef' from the NEF file (similar to frostfs contracts).`,
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
},
|
||||
RunE: deployContractCmd,
|
||||
}
|
||||
|
||||
func init() {
|
||||
ff := deployCmd.Flags()
|
||||
|
||||
ff.String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
|
||||
_ = deployCmd.MarkFlagFilename(alphabetWalletsFlag)
|
||||
|
||||
ff.StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
||||
ff.String(contractPathFlag, "", "Path to the contract directory")
|
||||
_ = deployCmd.MarkFlagFilename(contractPathFlag)
|
||||
|
||||
ff.Bool(updateFlag, false, "Update an existing contract")
|
||||
ff.String(customZoneFlag, "frostfs", "Custom zone for NNS")
|
||||
}
|
||||
|
||||
func deployContractCmd(cmd *cobra.Command, args []string) error {
|
||||
v := viper.GetViper()
|
||||
c, err := newInitializeContext(cmd, v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("initialization error: %w", err)
|
||||
}
|
||||
defer c.close()
|
||||
|
||||
ctrPath, _ := cmd.Flags().GetString(contractPathFlag)
|
||||
ctrName, err := probeContractName(ctrPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cs, err := readContract(ctrPath, ctrName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nnsCs, err := c.Client.GetContractStateByID(1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch NNS contract state: %w", err)
|
||||
}
|
||||
|
||||
callHash := management.Hash
|
||||
method := deployMethodName
|
||||
zone, _ := cmd.Flags().GetString(customZoneFlag)
|
||||
domain := ctrName + "." + zone
|
||||
isUpdate, _ := cmd.Flags().GetBool(updateFlag)
|
||||
if isUpdate {
|
||||
cs.Hash, err = nnsResolveHash(c.ReadOnlyInvoker, nnsCs.Hash, domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch contract hash from NNS: %w", err)
|
||||
}
|
||||
callHash = cs.Hash
|
||||
method = updateMethodName
|
||||
} else {
|
||||
cs.Hash = state.CreateContractHash(
|
||||
c.CommitteeAcc.Contract.ScriptHash(),
|
||||
cs.NEF.Checksum,
|
||||
cs.Manifest.Name)
|
||||
}
|
||||
|
||||
w := io.NewBufBinWriter()
|
||||
if err := emitDeploymentArguments(w.BinWriter, args); err != nil {
|
||||
return err
|
||||
}
|
||||
emit.Bytes(w.BinWriter, cs.RawManifest)
|
||||
emit.Bytes(w.BinWriter, cs.RawNEF)
|
||||
emit.Int(w.BinWriter, 3)
|
||||
emit.Opcodes(w.BinWriter, opcode.PACK)
|
||||
emit.AppCallNoArgs(w.BinWriter, callHash, method, callflag.All)
|
||||
emit.Opcodes(w.BinWriter, opcode.DROP) // contract state on stack
|
||||
if !isUpdate {
|
||||
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(),
|
||||
"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 {
|
||||
panic(fmt.Errorf("BUG: can't create deployment script: %w", w.Err))
|
||||
}
|
||||
|
||||
if err := c.sendCommitteeTx(w.Bytes(), false); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.awaitTx()
|
||||
}
|
||||
|
||||
func emitDeploymentArguments(w *io.BinWriter, args []string) error {
|
||||
_, ps, err := cmdargs.ParseParams(args, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(ps) == 0 {
|
||||
emit.Opcodes(w, opcode.NEWARRAY0)
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(ps) != 1 {
|
||||
return fmt.Errorf("at most one argument is expected for deploy, got %d", len(ps))
|
||||
}
|
||||
|
||||
// We could emit this directly, but round-trip through JSON is more robust.
|
||||
// This a CLI, so optimizing the conversion is not worth the effort.
|
||||
data, err := json.Marshal(ps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var pp params.Params
|
||||
if err := json.Unmarshal(data, &pp); err != nil {
|
||||
return err
|
||||
}
|
||||
return params.ExpandArrayIntoScript(w, pp)
|
||||
}
|
||||
|
||||
func probeContractName(ctrPath string) (string, error) {
|
||||
ds, err := os.ReadDir(ctrPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("can't read directory: %w", err)
|
||||
}
|
||||
|
||||
var ctrName string
|
||||
for i := range ds {
|
||||
if strings.HasSuffix(ds[i].Name(), "_contract.nef") {
|
||||
ctrName = strings.TrimSuffix(ds[i].Name(), "_contract.nef")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ctrName == "" {
|
||||
return "", fmt.Errorf("can't find any NEF files in %s", ctrPath)
|
||||
}
|
||||
return ctrName, nil
|
||||
}
|
|
@ -14,7 +14,7 @@ import (
|
|||
|
||||
func downloadContractsFromGithub(cmd *cobra.Command) (io.ReadCloser, error) {
|
||||
gcl := github.NewClient(nil)
|
||||
release, _, err := gcl.Repositories.GetLatestRelease(context.Background(), "nspcc-dev", "neofs-contract")
|
||||
release, _, err := gcl.Repositories.GetLatestRelease(context.Background(), "nspcc-dev", "frostfs-contract")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't fetch release info: %w", err)
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ func downloadContractsFromGithub(cmd *cobra.Command) (io.ReadCloser, error) {
|
|||
|
||||
var url string
|
||||
for _, a := range release.Assets {
|
||||
if strings.HasPrefix(a.GetName(), "neofs-contract") {
|
||||
if strings.HasPrefix(a.GetName(), "frostfs-contract") {
|
||||
url = a.GetBrowserDownloadURL()
|
||||
break
|
||||
}
|
250
cmd/frostfs-adm/internal/modules/morph/dump_hashes.go
Normal file
250
cmd/frostfs-adm/internal/modules/morph/dump_hashes.go
Normal file
|
@ -0,0 +1,250 @@
|
|||
package morph
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/TrueCloudLab/frostfs-contract/nns"
|
||||
"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/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/opcode"
|
||||
"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"
|
||||
)
|
||||
|
||||
const lastGlagoliticLetter = 41
|
||||
|
||||
type contractDumpInfo struct {
|
||||
hash util.Uint160
|
||||
name string
|
||||
version string
|
||||
}
|
||||
|
||||
func dumpContractHashes(cmd *cobra.Command, _ []string) error {
|
||||
c, err := getN3Client(viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create N3 client: %w", err)
|
||||
}
|
||||
|
||||
cs, err := c.GetContractStateByID(1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
zone, _ := cmd.Flags().GetString(customZoneFlag)
|
||||
if zone != "" {
|
||||
return dumpCustomZoneHashes(cmd, cs.Hash, zone, c)
|
||||
}
|
||||
|
||||
infos := []contractDumpInfo{{name: nnsContract, hash: cs.Hash}}
|
||||
|
||||
irSize := 0
|
||||
for ; irSize < lastGlagoliticLetter; irSize++ {
|
||||
ok, err := nnsIsAvailable(c, cs.Hash, getAlphabetNNSDomain(irSize))
|
||||
if err != nil {
|
||||
return err
|
||||
} else if ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
bw := io.NewBufBinWriter()
|
||||
|
||||
if irSize != 0 {
|
||||
bw.Reset()
|
||||
for i := 0; i < irSize; i++ {
|
||||
emit.AppCall(bw.BinWriter, cs.Hash, "resolve", callflag.ReadOnly,
|
||||
getAlphabetNNSDomain(i),
|
||||
int64(nns.TXT))
|
||||
}
|
||||
|
||||
alphaRes, err := c.InvokeScript(bw.Bytes(), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch info from NNS: %w", err)
|
||||
}
|
||||
|
||||
for i := 0; i < irSize; i++ {
|
||||
info := contractDumpInfo{name: fmt.Sprintf("alphabet %d", i)}
|
||||
if h, err := parseNNSResolveResult(alphaRes.Stack[i]); err == nil {
|
||||
info.hash = h
|
||||
}
|
||||
infos = append(infos, info)
|
||||
}
|
||||
}
|
||||
|
||||
for _, ctrName := range contractList {
|
||||
bw.Reset()
|
||||
emit.AppCall(bw.BinWriter, cs.Hash, "resolve", callflag.ReadOnly,
|
||||
ctrName+".frostfs", int64(nns.TXT))
|
||||
|
||||
res, err := c.InvokeScript(bw.Bytes(), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch info from NNS: %w", err)
|
||||
}
|
||||
|
||||
info := contractDumpInfo{name: ctrName}
|
||||
if len(res.Stack) != 0 {
|
||||
if h, err := parseNNSResolveResult(res.Stack[0]); err == nil {
|
||||
info.hash = h
|
||||
}
|
||||
}
|
||||
infos = append(infos, info)
|
||||
}
|
||||
|
||||
fillContractVersion(cmd, c, infos)
|
||||
printContractInfo(cmd, infos)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func dumpCustomZoneHashes(cmd *cobra.Command, nnsHash util.Uint160, zone string, c Client) error {
|
||||
const nnsMaxTokens = 100
|
||||
|
||||
inv := invoker.New(c, nil)
|
||||
|
||||
if !strings.HasPrefix(zone, ".") {
|
||||
zone = "." + zone
|
||||
}
|
||||
|
||||
var infos []contractDumpInfo
|
||||
processItem := func(item stackitem.Item) {
|
||||
bs, err := item.TryBytes()
|
||||
if err != nil {
|
||||
cmd.PrintErrf("Invalid NNS record: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !bytes.HasSuffix(bs, []byte(zone)) {
|
||||
// Related https://github.com/nspcc-dev/neofs-contract/issues/316.
|
||||
return
|
||||
}
|
||||
|
||||
h, err := nnsResolveHash(inv, nnsHash, string(bs))
|
||||
if err != nil {
|
||||
cmd.PrintErrf("Could not resolve name %s: %v\n", string(bs), err)
|
||||
return
|
||||
}
|
||||
|
||||
infos = append(infos, contractDumpInfo{
|
||||
hash: h,
|
||||
name: strings.TrimSuffix(string(bs), zone),
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
printContractInfo(cmd, infos)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseContractVersion(item stackitem.Item) string {
|
||||
bi, err := item.TryInteger()
|
||||
if err != nil || bi.Sign() == 0 || !bi.IsInt64() {
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
v := bi.Int64()
|
||||
major := v / 1_000_000
|
||||
minor := (v % 1_000_000) / 1000
|
||||
patch := v % 1_000
|
||||
return fmt.Sprintf("v%d.%d.%d", major, minor, patch)
|
||||
}
|
||||
|
||||
func printContractInfo(cmd *cobra.Command, infos []contractDumpInfo) {
|
||||
if len(infos) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
|
||||
for _, info := range infos {
|
||||
if info.version == "" {
|
||||
info.version = "unknown"
|
||||
}
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("%s\t(%s):\t%s\n",
|
||||
info.name, info.version, info.hash.StringLE())))
|
||||
}
|
||||
_ = tw.Flush()
|
||||
|
||||
cmd.Print(buf.String())
|
||||
}
|
||||
|
||||
func fillContractVersion(cmd *cobra.Command, c Client, infos []contractDumpInfo) {
|
||||
bw := io.NewBufBinWriter()
|
||||
sub := io.NewBufBinWriter()
|
||||
for i := range infos {
|
||||
if infos[i].hash.Equals(util.Uint160{}) {
|
||||
emit.Int(bw.BinWriter, 0)
|
||||
} else {
|
||||
sub.Reset()
|
||||
emit.AppCall(sub.BinWriter, infos[i].hash, "version", callflag.NoneFlag)
|
||||
if sub.Err != nil {
|
||||
panic(fmt.Errorf("BUG: can't create version script: %w", bw.Err))
|
||||
}
|
||||
|
||||
script := sub.Bytes()
|
||||
emit.Instruction(bw.BinWriter, opcode.TRY, []byte{byte(3 + len(script) + 2), 0})
|
||||
bw.BinWriter.WriteBytes(script)
|
||||
emit.Instruction(bw.BinWriter, opcode.ENDTRY, []byte{2 + 1})
|
||||
emit.Opcodes(bw.BinWriter, opcode.PUSH0)
|
||||
}
|
||||
}
|
||||
emit.Opcodes(bw.BinWriter, opcode.NOP) // for the last ENDTRY target
|
||||
if bw.Err != nil {
|
||||
panic(fmt.Errorf("BUG: can't create version script: %w", bw.Err))
|
||||
}
|
||||
|
||||
res, err := c.InvokeScript(bw.Bytes(), nil)
|
||||
if err != nil {
|
||||
cmd.Printf("Can't fetch version from NNS: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if res.State == vmstate.Halt.String() {
|
||||
for i := range res.Stack {
|
||||
infos[i].version = parseContractVersion(res.Stack[i])
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,9 +5,9 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"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"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
@ -24,7 +24,7 @@ func forceNewEpochCmd(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("can't get NNS contract info: %w", err)
|
||||
}
|
||||
|
||||
nmHash, err := nnsResolveHash(wCtx.Client, cs.Hash, netmapContract+".neofs")
|
||||
nmHash, err := nnsResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, netmapContract+".frostfs")
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ func forceNewEpochCmd(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := wCtx.sendCommitteeTx(bw.Bytes(), -1, true); err != nil {
|
||||
if err := wCtx.sendConsensusTx(bw.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -42,18 +42,13 @@ func forceNewEpochCmd(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
|
||||
func emitNewEpochCall(bw *io.BufBinWriter, wCtx *initializeContext, nmHash util.Uint160) error {
|
||||
res, err := invokeFunction(wCtx.Client, nmHash, "epoch", nil, nil)
|
||||
if err != nil || res.State != vm.HaltState.String() || len(res.Stack) == 0 {
|
||||
curr, err := unwrap.Int64(wCtx.ReadOnlyInvoker.Call(nmHash, "epoch"))
|
||||
if err != nil {
|
||||
return errors.New("can't fetch current epoch from the netmap contract")
|
||||
}
|
||||
|
||||
bi, err := res.Stack[0].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't parse current epoch: %w", err)
|
||||
}
|
||||
|
||||
newEpoch := bi.Int64() + 1
|
||||
wCtx.Command.Printf("Current epoch: %s, increase to %d.\n", bi, newEpoch)
|
||||
newEpoch := curr + 1
|
||||
wCtx.Command.Printf("Current epoch: %d, increase to %d.\n", curr, newEpoch)
|
||||
|
||||
// In NeoFS this is done via Notary contract. Here, however, we can form the
|
||||
// transaction locally.
|
|
@ -6,17 +6,19 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||
"github.com/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||
"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/smartcontract"
|
||||
"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/opcode"
|
||||
"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/viper"
|
||||
)
|
||||
|
@ -44,11 +46,10 @@ func generateAlphabetCreds(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
w, err := initializeContractWallet(v, walletDir)
|
||||
_, err = initializeContractWallet(v, walletDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.Close()
|
||||
|
||||
cmd.Println("size:", size)
|
||||
cmd.Println("alphabet-wallets:", walletDir)
|
||||
|
@ -111,7 +112,6 @@ func initializeWallets(v *viper.Viper, walletDir string, size int) ([]string, er
|
|||
if err := w.SavePretty(); err != nil {
|
||||
return nil, fmt.Errorf("can't save wallet: %w", err)
|
||||
}
|
||||
w.Close()
|
||||
}
|
||||
|
||||
return passwords, nil
|
||||
|
@ -135,44 +135,55 @@ func generateStorageCreds(cmd *cobra.Command, _ []string) error {
|
|||
return refillGas(cmd, storageGasConfigFlag, true)
|
||||
}
|
||||
|
||||
func refillGas(cmd *cobra.Command, gasFlag string, createWallet bool) error {
|
||||
func refillGas(cmd *cobra.Command, gasFlag string, createWallet bool) (err error) {
|
||||
// storage wallet path is not part of the config
|
||||
storageWalletPath, err := cmd.Flags().GetString(storageWalletFlag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if storageWalletPath == "" {
|
||||
return fmt.Errorf("missing wallet path (use '--%s <out.json>')", storageWalletFlag)
|
||||
}
|
||||
storageWalletPath, _ := cmd.Flags().GetString(storageWalletFlag)
|
||||
// wallet address is not part of the config
|
||||
walletAddress, _ := cmd.Flags().GetString(walletAddressFlag)
|
||||
|
||||
var w *wallet.Wallet
|
||||
var gasReceiver util.Uint160
|
||||
|
||||
if createWallet {
|
||||
w, err = wallet.NewWallet(storageWalletPath)
|
||||
} else {
|
||||
w, err = wallet.NewWalletFromFile(storageWalletPath)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create wallet: %w", err)
|
||||
}
|
||||
|
||||
if createWallet {
|
||||
var password string
|
||||
|
||||
label, _ := cmd.Flags().GetString(storageWalletLabelFlag)
|
||||
password, err := config.GetStoragePassword(viper.GetViper(), label)
|
||||
if len(walletAddress) != 0 {
|
||||
gasReceiver, err = address.StringToUint160(walletAddress)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch password: %w", err)
|
||||
return fmt.Errorf("invalid wallet address %s: %w", walletAddress, err)
|
||||
}
|
||||
} else {
|
||||
if storageWalletPath == "" {
|
||||
return fmt.Errorf("missing wallet path (use '--%s <out.json>')", storageWalletFlag)
|
||||
}
|
||||
|
||||
if label == "" {
|
||||
label = singleAccountName
|
||||
var w *wallet.Wallet
|
||||
|
||||
if createWallet {
|
||||
w, err = wallet.NewWallet(storageWalletPath)
|
||||
} else {
|
||||
w, err = wallet.NewWalletFromFile(storageWalletPath)
|
||||
}
|
||||
|
||||
if err := w.CreateAccount(label, password); err != nil {
|
||||
return fmt.Errorf("can't create account: %w", err)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create wallet: %w", err)
|
||||
}
|
||||
|
||||
if createWallet {
|
||||
var password string
|
||||
|
||||
label, _ := cmd.Flags().GetString(storageWalletLabelFlag)
|
||||
password, err := config.GetStoragePassword(viper.GetViper(), label)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch password: %w", err)
|
||||
}
|
||||
|
||||
if label == "" {
|
||||
label = singleAccountName
|
||||
}
|
||||
|
||||
if err := w.CreateAccount(label, password); err != nil {
|
||||
return fmt.Errorf("can't create account: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
gasReceiver = w.Accounts[0].Contract.ScriptHash()
|
||||
}
|
||||
|
||||
gasStr := viper.GetString(gasFlag)
|
||||
|
@ -187,17 +198,15 @@ func refillGas(cmd *cobra.Command, gasFlag string, createWallet bool) error {
|
|||
return err
|
||||
}
|
||||
|
||||
gasHash := wCtx.nativeHash(nativenames.Gas)
|
||||
|
||||
bw := io.NewBufBinWriter()
|
||||
emit.AppCall(bw.BinWriter, gasHash, "transfer", callflag.All,
|
||||
wCtx.CommitteeAcc.Contract.ScriptHash(), w.Accounts[0].Contract.ScriptHash(), int64(gasAmount), nil)
|
||||
emit.AppCall(bw.BinWriter, gas.Hash, "transfer", callflag.All,
|
||||
wCtx.CommitteeAcc.Contract.ScriptHash(), gasReceiver, int64(gasAmount), nil)
|
||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||
if bw.Err != nil {
|
||||
return fmt.Errorf("BUG: invalid transfer arguments: %w", bw.Err)
|
||||
}
|
||||
|
||||
if err := wCtx.sendCommitteeTx(bw.Bytes(), -1, false); err != nil {
|
||||
if err := wCtx.sendCommitteeTx(bw.Bytes(), false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -9,19 +9,22 @@ import (
|
|||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||
"github.com/nspcc-dev/neo-go/cli/input"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/nspcc-dev/neofs-node/pkg/innerring"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
const testContractPassword = "grouppass"
|
||||
|
||||
func TestGenerateAlphabet(t *testing.T) {
|
||||
const size = 4
|
||||
|
||||
walletDir := newTempDir(t)
|
||||
walletDir := t.TempDir()
|
||||
buf := setupTestTerminal(t)
|
||||
|
||||
cmd := generateAlphabetCmd
|
||||
|
@ -58,7 +61,15 @@ func TestGenerateAlphabet(t *testing.T) {
|
|||
require.Error(t, generateAlphabetCreds(cmd, nil))
|
||||
})
|
||||
|
||||
testGenerateAlphabet(t, buf, v, size, walletDir)
|
||||
buf.Reset()
|
||||
v.Set(alphabetWalletsFlag, walletDir)
|
||||
require.NoError(t, generateAlphabetCmd.Flags().Set(alphabetSizeFlag, strconv.FormatUint(size, 10)))
|
||||
for i := uint64(0); i < size; i++ {
|
||||
buf.WriteString(strconv.FormatUint(i, 10) + "\r")
|
||||
}
|
||||
|
||||
buf.WriteString(testContractPassword + "\r")
|
||||
require.NoError(t, generateAlphabetCreds(generateAlphabetCmd, nil))
|
||||
|
||||
for i := uint64(0); i < size; i++ {
|
||||
p := filepath.Join(walletDir, innerring.GlagoliticLetter(i).String()+".json")
|
||||
|
@ -70,9 +81,9 @@ func TestGenerateAlphabet(t *testing.T) {
|
|||
require.NoError(t, err, "can't decrypt account")
|
||||
switch a.Label {
|
||||
case consensusAccountName:
|
||||
require.Equal(t, size/2+1, len(a.Contract.Parameters))
|
||||
require.Equal(t, smartcontract.GetDefaultHonestNodeCount(size), len(a.Contract.Parameters))
|
||||
case committeeAccountName:
|
||||
require.Equal(t, size*2/3+1, len(a.Contract.Parameters))
|
||||
require.Equal(t, smartcontract.GetMajorityHonestNodeCount(size), len(a.Contract.Parameters))
|
||||
default:
|
||||
require.Equal(t, singleAccountName, a.Label)
|
||||
}
|
||||
|
@ -99,26 +110,3 @@ func setupTestTerminal(t *testing.T) *bytes.Buffer {
|
|||
|
||||
return in
|
||||
}
|
||||
|
||||
func newTempDir(t *testing.T) string {
|
||||
dir := filepath.Join(os.TempDir(), "neofs-adm.test."+strconv.FormatUint(rand.Uint64(), 10))
|
||||
require.NoError(t, os.Mkdir(dir, os.ModePerm))
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, os.RemoveAll(dir))
|
||||
})
|
||||
return dir
|
||||
}
|
||||
|
||||
const testContractPassword = "grouppass"
|
||||
|
||||
func testGenerateAlphabet(t *testing.T, buf *bytes.Buffer, v *viper.Viper, size uint64, walletDir string) {
|
||||
buf.Reset()
|
||||
v.Set(alphabetWalletsFlag, walletDir)
|
||||
require.NoError(t, generateAlphabetCmd.Flags().Set(alphabetSizeFlag, strconv.FormatUint(size, 10)))
|
||||
for i := uint64(0); i < size; i++ {
|
||||
buf.WriteString(strconv.FormatUint(i, 10) + "\r")
|
||||
}
|
||||
|
||||
buf.WriteString(testContractPassword + "\r")
|
||||
require.NoError(t, generateAlphabetCreds(generateAlphabetCmd, nil))
|
||||
}
|
|
@ -6,11 +6,11 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/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/smartcontract/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"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/viper"
|
||||
)
|
|
@ -7,18 +7,18 @@ import (
|
|||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||
"github.com/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||
morphClient "github.com/TrueCloudLab/frostfs-node/pkg/morph/client"
|
||||
"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/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||
"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/viper"
|
||||
)
|
||||
|
@ -43,7 +43,6 @@ type initializeContext struct {
|
|||
Contracts map[string]*contractState
|
||||
Command *cobra.Command
|
||||
ContractPath string
|
||||
Natives map[string]util.Uint160
|
||||
}
|
||||
|
||||
func initializeSideChainCmd(cmd *cobra.Command, args []string) error {
|
||||
|
@ -116,20 +115,22 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex
|
|||
return nil, err
|
||||
}
|
||||
|
||||
w, err := openContractWallet(v, cmd, walletDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
needContracts := cmd.Name() == "update-contracts" || cmd.Name() == "init"
|
||||
|
||||
var w *wallet.Wallet
|
||||
if needContracts {
|
||||
w, err = openContractWallet(v, cmd, walletDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var c Client
|
||||
if v.GetString(localDumpFlag) != "" {
|
||||
if cmd.Name() != "init" {
|
||||
return nil, errors.New("dump creation is only supported for `init` command")
|
||||
}
|
||||
if v.GetString(endpointFlag) != "" {
|
||||
return nil, fmt.Errorf("`%s` and `%s` flags are mutually exclusive", endpointFlag, localDumpFlag)
|
||||
}
|
||||
c, err = newLocalClient(v, wallets)
|
||||
c, err = newLocalClient(cmd, v, wallets)
|
||||
} else {
|
||||
c, err = getN3Client(v)
|
||||
}
|
||||
|
@ -158,7 +159,6 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex
|
|||
}
|
||||
}
|
||||
|
||||
needContracts := cmd.Name() == "update-contracts" || cmd.Name() == "init"
|
||||
if needContracts {
|
||||
ctrPath, err = cmd.Flags().GetString(contractsInitFlag)
|
||||
if err != nil {
|
||||
|
@ -166,8 +166,7 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex
|
|||
}
|
||||
}
|
||||
|
||||
nativeHashes, err := getNativeHashes(c)
|
||||
if err != nil {
|
||||
if err := checkNotaryEnabled(c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -180,8 +179,13 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex
|
|||
accounts[i] = acc
|
||||
}
|
||||
|
||||
cliCtx, err := defaultClientContext(c, committeeAcc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("client context: %w", err)
|
||||
}
|
||||
|
||||
initCtx := &initializeContext{
|
||||
clientContext: *defaultClientContext(c),
|
||||
clientContext: *cliCtx,
|
||||
ConsensusAcc: consensusAcc,
|
||||
CommitteeAcc: committeeAcc,
|
||||
ContractWallet: w,
|
||||
|
@ -190,7 +194,6 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex
|
|||
Command: cmd,
|
||||
Contracts: make(map[string]*contractState),
|
||||
ContractPath: ctrPath,
|
||||
Natives: nativeHashes,
|
||||
}
|
||||
|
||||
if needContracts {
|
||||
|
@ -203,10 +206,6 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex
|
|||
return initCtx, nil
|
||||
}
|
||||
|
||||
func (c *initializeContext) nativeHash(name string) util.Uint160 {
|
||||
return c.Natives[name]
|
||||
}
|
||||
|
||||
func openAlphabetWallets(v *viper.Viper, walletDir string) ([]*wallet.Wallet, error) {
|
||||
walletFiles, err := os.ReadDir(walletDir)
|
||||
if err != nil {
|
||||
|
@ -273,17 +272,17 @@ func (c *initializeContext) nnsContractState() (*state.Contract, error) {
|
|||
return cs, nil
|
||||
}
|
||||
|
||||
func (c *initializeContext) getSigner(tryGroup bool) transaction.Signer {
|
||||
func (c *initializeContext) getSigner(tryGroup bool, acc *wallet.Account) transaction.Signer {
|
||||
if tryGroup && c.groupKey != nil {
|
||||
return transaction.Signer{
|
||||
Account: c.CommitteeAcc.Contract.ScriptHash(),
|
||||
Account: acc.Contract.ScriptHash(),
|
||||
Scopes: transaction.CustomGroups,
|
||||
AllowedGroups: keys.PublicKeys{c.groupKey},
|
||||
}
|
||||
}
|
||||
|
||||
signer := transaction.Signer{
|
||||
Account: c.CommitteeAcc.Contract.ScriptHash(),
|
||||
Account: acc.Contract.ScriptHash(),
|
||||
Scopes: transaction.Global, // Scope is important, as we have nested call to container contract.
|
||||
}
|
||||
|
||||
|
@ -296,7 +295,7 @@ func (c *initializeContext) getSigner(tryGroup bool) transaction.Signer {
|
|||
return signer
|
||||
}
|
||||
|
||||
groupKey, err := nnsResolveKey(c.Client, nnsCs.Hash, morphClient.NNSGroupKeyName)
|
||||
groupKey, err := nnsResolveKey(c.ReadOnlyInvoker, nnsCs.Hash, morphClient.NNSGroupKeyName)
|
||||
if err == nil {
|
||||
c.groupKey = groupKey
|
||||
|
||||
|
@ -307,7 +306,7 @@ func (c *initializeContext) getSigner(tryGroup bool) transaction.Signer {
|
|||
}
|
||||
|
||||
func (c *clientContext) awaitTx(cmd *cobra.Command) error {
|
||||
if len(c.Hashes) == 0 {
|
||||
if len(c.SentTxs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -317,41 +316,58 @@ func (c *clientContext) awaitTx(cmd *cobra.Command) error {
|
|||
}
|
||||
}
|
||||
|
||||
err := awaitTx(cmd, c.Client, c.SentTxs)
|
||||
c.SentTxs = c.SentTxs[:0]
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func awaitTx(cmd *cobra.Command, c Client, txs []hashVUBPair) error {
|
||||
cmd.Println("Waiting for transactions to persist...")
|
||||
|
||||
tick := time.NewTicker(c.PollInterval)
|
||||
defer tick.Stop()
|
||||
const pollInterval = time.Second
|
||||
|
||||
timer := time.NewTimer(c.WaitDuration)
|
||||
defer timer.Stop()
|
||||
tick := time.NewTicker(pollInterval)
|
||||
defer tick.Stop()
|
||||
|
||||
at := trigger.Application
|
||||
|
||||
var retErr error
|
||||
|
||||
currBlock, err := c.GetBlockCount()
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch current block height: %w", err)
|
||||
}
|
||||
|
||||
loop:
|
||||
for i := range c.Hashes {
|
||||
res, err := c.Client.GetApplicationLog(c.Hashes[i], &at)
|
||||
for i := range txs {
|
||||
res, err := c.GetApplicationLog(txs[i].hash, &at)
|
||||
if err == nil {
|
||||
if retErr == nil && len(res.Executions) > 0 && res.Executions[0].VMState != vm.HaltState {
|
||||
if retErr == nil && len(res.Executions) > 0 && res.Executions[0].VMState != vmstate.Halt {
|
||||
retErr = fmt.Errorf("tx %d persisted in %s state: %s",
|
||||
i, res.Executions[0].VMState, res.Executions[0].FaultException)
|
||||
}
|
||||
continue loop
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-tick.C:
|
||||
res, err := c.Client.GetApplicationLog(c.Hashes[i], &at)
|
||||
if err == nil {
|
||||
if retErr == nil && len(res.Executions) > 0 && res.Executions[0].VMState != vm.HaltState {
|
||||
retErr = fmt.Errorf("tx %d persisted in %s state: %s",
|
||||
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.
|
||||
currBlock, err = c.GetBlockCount()
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch current block height: %w", err)
|
||||
}
|
||||
res, err := c.GetApplicationLog(txs[i].hash, &at)
|
||||
if err == nil {
|
||||
if retErr == nil && len(res.Executions) > 0 && res.Executions[0].VMState != vmstate.Halt {
|
||||
retErr = fmt.Errorf("tx %d persisted in %s state: %s",
|
||||
i, res.Executions[0].VMState, res.Executions[0].FaultException)
|
||||
}
|
||||
case <-timer.C:
|
||||
return errors.New("timeout while waiting for transaction to persist")
|
||||
continue loop
|
||||
}
|
||||
if txs[i].vub < currBlock {
|
||||
return fmt.Errorf("tx was not persisted: vub=%d, height=%d", txs[i].vub, currBlock)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -359,41 +375,61 @@ loop:
|
|||
return retErr
|
||||
}
|
||||
|
||||
// sendCommitteeTx creates transaction from script and sends it to RPC.
|
||||
// If sysFee is -1, it is calculated automatically. If tryGroup is false,
|
||||
// global scope is used for the signer (useful when working with native contracts).
|
||||
func (c *initializeContext) sendCommitteeTx(script []byte, sysFee int64, tryGroup bool) error {
|
||||
tx, err := c.Client.CreateTxFromScript(script, c.CommitteeAcc, sysFee, 0, []client.SignerAccount{{
|
||||
Signer: c.getSigner(tryGroup),
|
||||
Account: c.CommitteeAcc,
|
||||
}})
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create tx: %w", err)
|
||||
}
|
||||
|
||||
return c.multiSignAndSend(tx, committeeAccountName)
|
||||
// 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
|
||||
// working with native contracts).
|
||||
func (c *initializeContext) sendCommitteeTx(script []byte, tryGroup bool) error {
|
||||
return c.sendMultiTx(script, tryGroup, false)
|
||||
}
|
||||
|
||||
// sendSingleTx creates transaction signed by a simple account and pushes in onto the chain.
|
||||
// It neither waits until tx persists nor checks the execution result.
|
||||
func (c *initializeContext) sendSingleTx(script []byte, sysFee int64, acc *wallet.Account) error {
|
||||
tx, err := c.Client.CreateTxFromScript(script, acc, sysFee, 0, []client.SignerAccount{{
|
||||
Signer: transaction.Signer{
|
||||
Account: acc.Contract.ScriptHash(),
|
||||
Scopes: transaction.CalledByEntry,
|
||||
},
|
||||
Account: acc,
|
||||
}})
|
||||
// sendConsensusTx creates transaction from script, signs it by alphabet nodes and sends it to RPC.
|
||||
// Not that because this is used only after the contracts were initialized and deployed,
|
||||
// we always try to have a group scope.
|
||||
func (c *initializeContext) sendConsensusTx(script []byte) error {
|
||||
return c.sendMultiTx(script, true, true)
|
||||
}
|
||||
|
||||
func (c *initializeContext) sendMultiTx(script []byte, tryGroup bool, withConsensus bool) error {
|
||||
var act *actor.Actor
|
||||
var err error
|
||||
|
||||
withConsensus = withConsensus && !c.ConsensusAcc.Contract.ScriptHash().Equals(c.CommitteeAcc.ScriptHash())
|
||||
if tryGroup {
|
||||
// Even for consensus signatures we need the committee to pay.
|
||||
signers := make([]actor.SignerAccount, 1, 2)
|
||||
signers[0] = actor.SignerAccount{
|
||||
Signer: c.getSigner(tryGroup, c.CommitteeAcc),
|
||||
Account: c.CommitteeAcc,
|
||||
}
|
||||
if withConsensus {
|
||||
signers = append(signers, actor.SignerAccount{
|
||||
Signer: c.getSigner(tryGroup, c.ConsensusAcc),
|
||||
Account: c.ConsensusAcc,
|
||||
})
|
||||
}
|
||||
act, err = actor.New(c.Client, signers)
|
||||
} else {
|
||||
if withConsensus {
|
||||
panic("BUG: should never happen")
|
||||
}
|
||||
act, err = c.CommitteeAct, nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("could not create actor: %w", err)
|
||||
}
|
||||
|
||||
magic, err := c.Client.GetNetwork()
|
||||
tx, err := act.MakeUnsignedRun(script, []transaction.Attribute{{Type: transaction.HighPriority}})
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch network magic: %w", err)
|
||||
return fmt.Errorf("could not perform test invocation: %w", err)
|
||||
}
|
||||
if err := acc.SignTx(magic, tx); err != nil {
|
||||
return fmt.Errorf("can't sign tx: %w", err)
|
||||
|
||||
if err := c.multiSign(tx, committeeAccountName); err != nil {
|
||||
return err
|
||||
}
|
||||
if withConsensus {
|
||||
if err := c.multiSign(tx, consensusAccountName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return c.sendTx(tx, c.Command, false)
|
||||
|
@ -408,10 +444,10 @@ func getWalletAccount(w *wallet.Wallet, typ string) (*wallet.Account, error) {
|
|||
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()
|
||||
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
|
||||
|
@ -423,7 +459,7 @@ func getNativeHashes(c Client) (map[string]util.Uint160, error) {
|
|||
nativeHashes[ns[i].Manifest.Name] = ns[i].Hash
|
||||
}
|
||||
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,34 +12,37 @@ import (
|
|||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"github.com/TrueCloudLab/frostfs-contract/common"
|
||||
"github.com/TrueCloudLab/frostfs-contract/nns"
|
||||
"github.com/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||
morphClient "github.com/TrueCloudLab/frostfs-node/pkg/morph/client"
|
||||
"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"
|
||||
io2 "github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
|
||||
"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/management"
|
||||
"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/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
||||
"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/opcode"
|
||||
"github.com/nspcc-dev/neofs-node/pkg/innerring"
|
||||
morphClient "github.com/nspcc-dev/neofs-node/pkg/morph/client"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
nnsContract = "nns"
|
||||
neofsContract = "neofs" // not deployed in side-chain.
|
||||
frostfsContract = "frostfs" // not deployed in side-chain.
|
||||
processingContract = "processing" // not deployed in side-chain.
|
||||
alphabetContract = "alphabet"
|
||||
auditContract = "audit"
|
||||
balanceContract = "balance"
|
||||
containerContract = "container"
|
||||
neofsIDContract = "neofsid"
|
||||
frostfsIDContract = "frostfsid"
|
||||
netmapContract = "netmap"
|
||||
proxyContract = "proxy"
|
||||
reputationContract = "reputation"
|
||||
|
@ -47,16 +50,18 @@ const (
|
|||
)
|
||||
|
||||
const (
|
||||
netmapEpochKey = "EpochDuration"
|
||||
netmapMaxObjectSizeKey = "MaxObjectSize"
|
||||
netmapAuditFeeKey = "AuditFee"
|
||||
netmapContainerFeeKey = "ContainerFee"
|
||||
netmapContainerAliasFeeKey = "ContainerAliasFee"
|
||||
netmapEigenTrustIterationsKey = "EigenTrustIterations"
|
||||
netmapEigenTrustAlphaKey = "EigenTrustAlpha"
|
||||
netmapBasicIncomeRateKey = "BasicIncomeRate"
|
||||
netmapInnerRingCandidateFeeKey = "InnerRingCandidateFee"
|
||||
netmapWithdrawFeeKey = "WithdrawFee"
|
||||
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"
|
||||
|
@ -67,7 +72,7 @@ var (
|
|||
auditContract,
|
||||
balanceContract,
|
||||
containerContract,
|
||||
neofsIDContract,
|
||||
frostfsIDContract,
|
||||
netmapContract,
|
||||
proxyContract,
|
||||
reputationContract,
|
||||
|
@ -75,7 +80,7 @@ var (
|
|||
}
|
||||
|
||||
fullContractList = append([]string{
|
||||
neofsContract,
|
||||
frostfsContract,
|
||||
processingContract,
|
||||
nnsContract,
|
||||
alphabetContract,
|
||||
|
@ -102,7 +107,11 @@ func (c *initializeContext) deployNNS(method string) error {
|
|||
nnsCs, err := c.nnsContractState()
|
||||
if err == nil {
|
||||
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
|
||||
}
|
||||
h = nnsCs.Hash
|
||||
|
@ -119,7 +128,7 @@ func (c *initializeContext) deployNNS(method string) error {
|
|||
Scopes: transaction.CalledByEntry,
|
||||
}
|
||||
|
||||
invokeHash := c.nativeHash(nativenames.Management)
|
||||
invokeHash := management.Hash
|
||||
if method == updateMethodName {
|
||||
invokeHash = nnsCs.Hash
|
||||
}
|
||||
|
@ -128,11 +137,11 @@ func (c *initializeContext) deployNNS(method string) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("can't deploy NNS contract: %w", err)
|
||||
}
|
||||
if res.State != vm.HaltState.String() {
|
||||
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, []client.SignerAccount{{
|
||||
tx, err := c.Client.CreateTxFromScript(res.Script, c.CommitteeAcc, res.GasConsumed, 0, []rpcclient.SignerAccount{{
|
||||
Signer: signer,
|
||||
Account: c.CommitteeAcc,
|
||||
}})
|
||||
|
@ -148,7 +157,6 @@ func (c *initializeContext) deployNNS(method string) error {
|
|||
}
|
||||
|
||||
func (c *initializeContext) updateContracts() error {
|
||||
mgmtHash := c.nativeHash(nativenames.Management)
|
||||
alphaCs := c.getContract(alphabetContract)
|
||||
|
||||
nnsCs, err := c.nnsContractState()
|
||||
|
@ -157,28 +165,28 @@ func (c *initializeContext) updateContracts() error {
|
|||
}
|
||||
nnsHash := nnsCs.Hash
|
||||
|
||||
totalGasCost := int64(0)
|
||||
w := io2.NewBufBinWriter()
|
||||
|
||||
var keysParam []interface{}
|
||||
var keysParam []any
|
||||
|
||||
// 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.
|
||||
// The generated script is as following.
|
||||
// 1. Initialize static slots for alphabet NEF and manifest.
|
||||
// 2. Store NEF and manifest into static slots.
|
||||
// 1. Initialize static slot for alphabet NEF.
|
||||
// 2. Store NEF into the static slot.
|
||||
// 3. Push parameters for each alphabet contract on stack.
|
||||
// 4. For each alphabet contract, invoke `update` using parameters on stack and
|
||||
// NEF and manifest from step 2.
|
||||
// 5. Update other contracts as usual.
|
||||
emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{2})
|
||||
emit.Bytes(w.BinWriter, alphaCs.RawManifest)
|
||||
// 4. Add contract group to the manifest.
|
||||
// 5. For each alphabet contract, invoke `update` using parameters on stack and
|
||||
// NEF from step 2 and manifest from step 4.
|
||||
emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1})
|
||||
emit.Bytes(w.BinWriter, alphaCs.RawNEF)
|
||||
emit.Opcodes(w.BinWriter, opcode.STSFLD0, opcode.STSFLD1)
|
||||
emit.Opcodes(w.BinWriter, opcode.STSFLD0)
|
||||
|
||||
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.Client, nnsHash, getAlphabetNNSDomain(i))
|
||||
ctrHash, err := nnsResolveHash(c.ReadOnlyInvoker, nnsHash, getAlphabetNNSDomain(i))
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't resolve hash for contract update: %w", err)
|
||||
}
|
||||
|
@ -187,32 +195,39 @@ func (c *initializeContext) updateContracts() error {
|
|||
|
||||
params := c.getAlphabetDeployItems(i, len(c.Wallets))
|
||||
emit.Array(w.BinWriter, params...)
|
||||
emit.Opcodes(w.BinWriter, opcode.LDSFLD1, opcode.LDSFLD0)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
res, err := c.Client.InvokeScript(w.Bytes(), []transaction.Signer{{
|
||||
Account: c.CommitteeAcc.Contract.ScriptHash(),
|
||||
Scopes: transaction.Global,
|
||||
}})
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't update alphabet contracts: %w", err)
|
||||
}
|
||||
if res.State != vm.HaltState.String() {
|
||||
return fmt.Errorf("can't update alphabet contracts: %s", res.FaultException)
|
||||
if err := c.sendCommitteeTx(w.Bytes(), false); err != nil {
|
||||
if !strings.Contains(err.Error(), common.ErrAlreadyUpdated) {
|
||||
return err
|
||||
}
|
||||
c.Command.Println("Alphabet contracts are already updated.")
|
||||
}
|
||||
|
||||
w.Reset()
|
||||
totalGasCost += res.GasConsumed
|
||||
w.WriteBytes(res.Script)
|
||||
|
||||
emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1})
|
||||
emit.AppCall(w.BinWriter, nnsHash, "getPrice", callflag.All)
|
||||
emit.Opcodes(w.BinWriter, opcode.STSFLD0)
|
||||
emit.AppCall(w.BinWriter, nnsHash, "setPrice", callflag.All, 1)
|
||||
|
||||
for _, ctrName := range contractList {
|
||||
cs := c.getContract(ctrName)
|
||||
|
||||
method := updateMethodName
|
||||
ctrHash, err := nnsResolveHash(c.Client, nnsHash, ctrName+".neofs")
|
||||
ctrHash, err := nnsResolveHash(c.ReadOnlyInvoker, nnsHash, ctrName+".frostfs")
|
||||
if err != nil {
|
||||
if errors.Is(err, errMissingNNSRecord) {
|
||||
// if contract not found we deploy it instead of update
|
||||
|
@ -222,67 +237,69 @@ func (c *initializeContext) updateContracts() error {
|
|||
}
|
||||
}
|
||||
|
||||
err = c.addManifestGroup(ctrHash, alphaCs)
|
||||
err = c.addManifestGroup(ctrHash, cs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't sign manifest group: %v", err)
|
||||
}
|
||||
|
||||
invokeHash := mgmtHash
|
||||
invokeHash := management.Hash
|
||||
if method == updateMethodName {
|
||||
invokeHash = ctrHash
|
||||
}
|
||||
|
||||
params := getContractDeployParameters(cs, c.getContractDeployData(ctrName, keysParam))
|
||||
signer := transaction.Signer{
|
||||
Account: c.CommitteeAcc.Contract.ScriptHash(),
|
||||
Scopes: transaction.Global,
|
||||
}
|
||||
|
||||
res, err := invokeFunction(c.Client, invokeHash, method, params, []transaction.Signer{signer})
|
||||
res, err := c.CommitteeAct.MakeCall(invokeHash, method, params...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't deploy %s contract: %w", ctrName, err)
|
||||
}
|
||||
if res.State != vm.HaltState.String() {
|
||||
return fmt.Errorf("can't deploy %s contract: %s", ctrName, res.FaultException)
|
||||
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
|
||||
}
|
||||
|
||||
totalGasCost += res.GasConsumed
|
||||
w.WriteBytes(res.Script)
|
||||
|
||||
if method == deployMethodName {
|
||||
// same actions are done in initializeContext.setNNS, can be unified
|
||||
domain := ctrName + ".neofs"
|
||||
script, err := c.nnsRegisterDomainScript(nnsHash, cs.Hash, domain)
|
||||
domain := ctrName + ".frostfs"
|
||||
script, ok, err := c.nnsRegisterDomainScript(nnsHash, cs.Hash, domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if script != nil {
|
||||
totalGasCost += defaultRegisterSysfee + native.GASFactor
|
||||
if !ok {
|
||||
w.WriteBytes(script)
|
||||
emit.AppCall(w.BinWriter, nnsHash, "deleteRecords", callflag.All, domain, int64(nns.TXT))
|
||||
emit.AppCall(w.BinWriter, nnsHash, "addRecord", callflag.All,
|
||||
domain, int64(nns.TXT), cs.Hash.StringLE())
|
||||
emit.AppCall(w.BinWriter, nnsHash, "addRecord", callflag.All,
|
||||
domain, int64(nns.TXT), address.Uint160ToString(cs.Hash))
|
||||
}
|
||||
c.Command.Printf("NNS: Set %s -> %s\n", domain, cs.Hash.StringLE())
|
||||
}
|
||||
}
|
||||
|
||||
groupKey := c.ContractWallet.Accounts[0].PrivateKey().PublicKey()
|
||||
sysFee, err := c.emitUpdateNNSGroupScript(w, nnsHash, groupKey)
|
||||
_, _, 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()))
|
||||
|
||||
totalGasCost += sysFee
|
||||
if err := c.sendCommitteeTx(w.Bytes(), totalGasCost, false); err != nil {
|
||||
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) deployContracts() error {
|
||||
mgmtHash := c.nativeHash(nativenames.Management)
|
||||
alphaCs := c.getContract(alphabetContract)
|
||||
|
||||
var keysParam []interface{}
|
||||
var keysParam []any
|
||||
|
||||
baseGroups := alphaCs.Manifest.Groups
|
||||
|
||||
|
@ -303,20 +320,17 @@ func (c *initializeContext) deployContracts() error {
|
|||
keysParam = append(keysParam, acc.PrivateKey().PublicKey().Bytes())
|
||||
params := getContractDeployParameters(alphaCs, c.getAlphabetDeployItems(i, len(c.Wallets)))
|
||||
|
||||
res, err := invokeFunction(c.Client, mgmtHash, deployMethodName, params, []transaction.Signer{{
|
||||
Account: acc.Contract.ScriptHash(),
|
||||
Scopes: transaction.CalledByEntry,
|
||||
}})
|
||||
act, err := actor.NewSimple(c.Client, acc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create actor: %w", err)
|
||||
}
|
||||
|
||||
txHash, vub, err := act.SendCall(management.Hash, deployMethodName, params...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't deploy alphabet #%d contract: %w", i, err)
|
||||
}
|
||||
if res.State != vm.HaltState.String() {
|
||||
return fmt.Errorf("can't deploy alpabet #%d contract: %s", i, res.FaultException)
|
||||
}
|
||||
|
||||
if err := c.sendSingleTx(res.Script, res.GasConsumed, acc); err != nil {
|
||||
return err
|
||||
}
|
||||
c.SentTxs = append(c.SentTxs, hashVUBPair{hash: txHash, vub: vub})
|
||||
}
|
||||
|
||||
for _, ctrName := range contractList {
|
||||
|
@ -334,20 +348,12 @@ func (c *initializeContext) deployContracts() error {
|
|||
}
|
||||
|
||||
params := getContractDeployParameters(cs, c.getContractDeployData(ctrName, keysParam))
|
||||
signer := transaction.Signer{
|
||||
Account: c.CommitteeAcc.Contract.ScriptHash(),
|
||||
Scopes: transaction.Global,
|
||||
}
|
||||
|
||||
res, err := invokeFunction(c.Client, mgmtHash, deployMethodName, params, []transaction.Signer{signer})
|
||||
res, err := c.CommitteeAct.MakeCall(management.Hash, deployMethodName, params...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't deploy %s contract: %w", ctrName, err)
|
||||
}
|
||||
if res.State != vm.HaltState.String() {
|
||||
return fmt.Errorf("can't deploy %s contract: %s", ctrName, res.FaultException)
|
||||
}
|
||||
|
||||
if err := c.sendCommitteeTx(res.Script, res.GasConsumed, false); err != nil {
|
||||
if err := c.sendCommitteeTx(res.Script, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -378,14 +384,9 @@ func (c *initializeContext) readContracts(names []string) error {
|
|||
|
||||
if c.ContractPath != "" && fi.IsDir() {
|
||||
for _, ctrName := range names {
|
||||
cs := new(contractState)
|
||||
cs.RawNEF, err = os.ReadFile(filepath.Join(c.ContractPath, ctrName, ctrName+"_contract.nef"))
|
||||
cs, err := readContract(filepath.Join(c.ContractPath, ctrName), ctrName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't read NEF file for %s contract: %w", ctrName, err)
|
||||
}
|
||||
cs.RawManifest, err = os.ReadFile(filepath.Join(c.ContractPath, ctrName, "config.json"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't read manifest file for %s contract: %w", ctrName, err)
|
||||
return err
|
||||
}
|
||||
c.Contracts[ctrName] = cs
|
||||
}
|
||||
|
@ -407,17 +408,16 @@ func (c *initializeContext) readContracts(names []string) error {
|
|||
return err
|
||||
}
|
||||
for _, name := range names {
|
||||
if err := m[name].parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Contracts[name] = m[name]
|
||||
}
|
||||
}
|
||||
|
||||
for _, ctrName := range names {
|
||||
cs := c.Contracts[ctrName]
|
||||
if err := cs.parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ctrName != alphabetContract {
|
||||
cs := c.Contracts[ctrName]
|
||||
cs.Hash = state.CreateContractHash(c.CommitteeAcc.Contract.ScriptHash(),
|
||||
cs.NEF.Checksum, cs.Manifest.Name)
|
||||
}
|
||||
|
@ -425,6 +425,24 @@ func (c *initializeContext) readContracts(names []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func readContract(ctrPath, ctrName string) (*contractState, error) {
|
||||
rawNef, err := os.ReadFile(filepath.Join(ctrPath, ctrName+"_contract.nef"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read NEF file for %s contract: %w", ctrName, err)
|
||||
}
|
||||
rawManif, err := os.ReadFile(filepath.Join(ctrPath, "config.json"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read manifest file for %s contract: %w", ctrName, err)
|
||||
}
|
||||
|
||||
cs := &contractState{
|
||||
RawNEF: rawNef,
|
||||
RawManifest: rawManif,
|
||||
}
|
||||
|
||||
return cs, cs.parse()
|
||||
}
|
||||
|
||||
func (cs *contractState) parse() error {
|
||||
nf, err := nef.FileFromBytes(cs.RawNEF)
|
||||
if err != nil {
|
||||
|
@ -492,22 +510,22 @@ func readContractsFromArchive(file io.Reader, names []string) (map[string]*contr
|
|||
return m, nil
|
||||
}
|
||||
|
||||
func getContractDeployParameters(cs *contractState, deployData []interface{}) []interface{} {
|
||||
return []interface{}{cs.RawNEF, cs.RawManifest, deployData}
|
||||
func getContractDeployParameters(cs *contractState, deployData []any) []any {
|
||||
return []any{cs.RawNEF, cs.RawManifest, deployData}
|
||||
}
|
||||
|
||||
func (c *initializeContext) getContractDeployData(ctrName string, keysParam []interface{}) []interface{} {
|
||||
items := make([]interface{}, 1, 6)
|
||||
func (c *initializeContext) getContractDeployData(ctrName string, keysParam []any) []any {
|
||||
items := make([]any, 1, 6)
|
||||
items[0] = false // notaryDisabled is false
|
||||
|
||||
switch ctrName {
|
||||
case neofsContract:
|
||||
case frostfsContract:
|
||||
items = append(items,
|
||||
c.Contracts[processingContract].Hash,
|
||||
keysParam,
|
||||
smartcontract.Parameter{})
|
||||
case processingContract:
|
||||
items = append(items, c.Contracts[neofsContract].Hash)
|
||||
items = append(items, c.Contracts[frostfsContract].Hash)
|
||||
return items[1:] // no notary info
|
||||
case auditContract:
|
||||
items = append(items, c.Contracts[netmapContract].Hash)
|
||||
|
@ -525,15 +543,15 @@ func (c *initializeContext) getContractDeployData(ctrName string, keysParam []in
|
|||
items = append(items,
|
||||
c.Contracts[netmapContract].Hash,
|
||||
c.Contracts[balanceContract].Hash,
|
||||
c.Contracts[neofsIDContract].Hash,
|
||||
c.Contracts[frostfsIDContract].Hash,
|
||||
nnsCs.Hash,
|
||||
"container")
|
||||
case neofsIDContract:
|
||||
case frostfsIDContract:
|
||||
items = append(items,
|
||||
c.Contracts[netmapContract].Hash,
|
||||
c.Contracts[containerContract].Hash)
|
||||
case netmapContract:
|
||||
configParam := []interface{}{
|
||||
configParam := []any{
|
||||
netmapEpochKey, viper.GetInt64(epochDurationInitFlag),
|
||||
netmapMaxObjectSizeKey, viper.GetInt64(maxObjectSizeInitFlag),
|
||||
netmapAuditFeeKey, viper.GetInt64(auditFeeInitFlag),
|
||||
|
@ -544,6 +562,8 @@ func (c *initializeContext) getContractDeployData(ctrName string, keysParam []in
|
|||
netmapBasicIncomeRateKey, viper.GetInt64(incomeRateInitFlag),
|
||||
netmapInnerRingCandidateFeeKey, viper.GetInt64(candidateFeeInitFlag),
|
||||
netmapWithdrawFeeKey, viper.GetInt64(withdrawFeeInitFlag),
|
||||
netmapHomomorphicHashDisabledKey, viper.GetBool(homomorphicHashDisabledInitFlag),
|
||||
netmapMaintenanceAllowedKey, viper.GetBool(maintenanceModeAllowedInitFlag),
|
||||
}
|
||||
items = append(items,
|
||||
c.Contracts[balanceContract].Hash,
|
||||
|
@ -560,8 +580,8 @@ func (c *initializeContext) getContractDeployData(ctrName string, keysParam []in
|
|||
return items
|
||||
}
|
||||
|
||||
func (c *initializeContext) getAlphabetDeployItems(i, n int) []interface{} {
|
||||
items := make([]interface{}, 6)
|
||||
func (c *initializeContext) getAlphabetDeployItems(i, n int) []any {
|
||||
items := make([]any, 6)
|
||||
items[0] = false
|
||||
items[1] = c.Contracts[netmapContract].Hash
|
||||
items[2] = c.Contracts[proxyContract].Hash
|
291
cmd/frostfs-adm/internal/modules/morph/initialize_nns.go
Normal file
291
cmd/frostfs-adm/internal/modules/morph/initialize_nns.go
Normal file
|
@ -0,0 +1,291 @@
|
|||
package morph
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/TrueCloudLab/frostfs-contract/nns"
|
||||
morphClient "github.com/TrueCloudLab/frostfs-node/pkg/morph/client"
|
||||
"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/io"
|
||||
"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/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/opcode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||
)
|
||||
|
||||
const defaultExpirationTime = 10 * 365 * 24 * time.Hour / time.Second
|
||||
|
||||
func (c *initializeContext) setNNS() error {
|
||||
nnsCs, err := c.Client.GetContractStateByID(1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ok, err := c.nnsRootRegistered(nnsCs.Hash, "frostfs")
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !ok {
|
||||
bw := io.NewBufBinWriter()
|
||||
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
|
||||
"frostfs", c.CommitteeAcc.Contract.ScriptHash(),
|
||||
"ops@nspcc.ru", int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
|
||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||
if err := c.sendCommitteeTx(bw.Bytes(), true); err != nil {
|
||||
return fmt.Errorf("can't add domain root to NNS: %w", err)
|
||||
}
|
||||
if err := c.awaitTx(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
alphaCs := c.getContract(alphabetContract)
|
||||
for i, acc := range c.Accounts {
|
||||
alphaCs.Hash = state.CreateContractHash(acc.Contract.ScriptHash(), alphaCs.NEF.Checksum, alphaCs.Manifest.Name)
|
||||
|
||||
domain := getAlphabetNNSDomain(i)
|
||||
if err := c.nnsRegisterDomain(nnsCs.Hash, alphaCs.Hash, domain); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Command.Printf("NNS: Set %s -> %s\n", domain, alphaCs.Hash.StringLE())
|
||||
}
|
||||
|
||||
for _, ctrName := range contractList {
|
||||
cs := c.getContract(ctrName)
|
||||
|
||||
domain := ctrName + ".frostfs"
|
||||
if err := c.nnsRegisterDomain(nnsCs.Hash, cs.Hash, domain); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Command.Printf("NNS: Set %s -> %s\n", domain, cs.Hash.StringLE())
|
||||
}
|
||||
|
||||
groupKey := c.ContractWallet.Accounts[0].PrivateKey().PublicKey()
|
||||
err = c.updateNNSGroup(nnsCs.Hash, groupKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Command.Printf("NNS: Set %s -> %s\n", morphClient.NNSGroupKeyName, hex.EncodeToString(groupKey.Bytes()))
|
||||
|
||||
return c.awaitTx()
|
||||
}
|
||||
|
||||
func (c *initializeContext) updateNNSGroup(nnsHash util.Uint160, pub *keys.PublicKey) error {
|
||||
bw := io.NewBufBinWriter()
|
||||
needUpdate, needRegister, err := c.emitUpdateNNSGroupScript(bw, nnsHash, pub)
|
||||
if !needUpdate || err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
script := bw.Bytes()
|
||||
if needRegister {
|
||||
w := io.NewBufBinWriter()
|
||||
emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1})
|
||||
wrapRegisterScriptWithPrice(w, nnsHash, script)
|
||||
script = w.Bytes()
|
||||
}
|
||||
|
||||
return c.sendCommitteeTx(script, true)
|
||||
}
|
||||
|
||||
// emitUpdateNNSGroupScript emits script for updating group key stored in NNS.
|
||||
// First return value is true iff the key is already there and nothing should be done.
|
||||
// Second return value is true iff a domain registration code was emitted.
|
||||
func (c *initializeContext) emitUpdateNNSGroupScript(bw *io.BufBinWriter, nnsHash util.Uint160, pub *keys.PublicKey) (bool, bool, error) {
|
||||
isAvail, err := nnsIsAvailable(c.Client, nnsHash, morphClient.NNSGroupKeyName)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
if !isAvail {
|
||||
currentPub, err := nnsResolveKey(c.ReadOnlyInvoker, nnsHash, morphClient.NNSGroupKeyName)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
if pub.Equal(currentPub) {
|
||||
return true, false, nil
|
||||
}
|
||||
}
|
||||
|
||||
if isAvail {
|
||||
emit.AppCall(bw.BinWriter, nnsHash, "register", callflag.All,
|
||||
morphClient.NNSGroupKeyName, 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, nnsHash, "deleteRecords", callflag.All, "group.frostfs", int64(nns.TXT))
|
||||
emit.AppCall(bw.BinWriter, nnsHash, "addRecord", callflag.All,
|
||||
"group.frostfs", int64(nns.TXT), hex.EncodeToString(pub.Bytes()))
|
||||
|
||||
return false, isAvail, nil
|
||||
}
|
||||
|
||||
func getAlphabetNNSDomain(i int) string {
|
||||
return alphabetContract + strconv.FormatUint(uint64(i), 10) + ".frostfs"
|
||||
}
|
||||
|
||||
// wrapRegisterScriptWithPrice wraps a given script with `getPrice`/`setPrice` calls for NNS.
|
||||
// It is intended to be used for a single transaction, and not as a part of other scripts.
|
||||
// It is assumed that script already contains static slot initialization code, the first one
|
||||
// (with index 0) is used to store the price.
|
||||
func wrapRegisterScriptWithPrice(w *io.BufBinWriter, nnsHash util.Uint160, s []byte) {
|
||||
if len(s) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
emit.AppCall(w.BinWriter, nnsHash, "getPrice", callflag.All)
|
||||
emit.Opcodes(w.BinWriter, opcode.STSFLD0)
|
||||
emit.AppCall(w.BinWriter, nnsHash, "setPrice", callflag.All, 1)
|
||||
|
||||
w.WriteBytes(s)
|
||||
|
||||
emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.PUSH1, opcode.PACK)
|
||||
emit.AppCallNoArgs(w.BinWriter, nnsHash, "setPrice", callflag.All)
|
||||
|
||||
if w.Err != nil {
|
||||
panic(fmt.Errorf("BUG: can't wrap register script: %w", w.Err))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *initializeContext) nnsRegisterDomainScript(nnsHash, expectedHash util.Uint160, domain string) ([]byte, bool, error) {
|
||||
ok, err := nnsIsAvailable(c.Client, nnsHash, domain)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if ok {
|
||||
bw := io.NewBufBinWriter()
|
||||
emit.AppCall(bw.BinWriter, nnsHash, "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)
|
||||
|
||||
if bw.Err != nil {
|
||||
panic(bw.Err)
|
||||
}
|
||||
return bw.Bytes(), false, nil
|
||||
}
|
||||
|
||||
s, err := nnsResolveHash(c.ReadOnlyInvoker, nnsHash, domain)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return nil, s == expectedHash, nil
|
||||
}
|
||||
|
||||
func (c *initializeContext) nnsRegisterDomain(nnsHash, expectedHash util.Uint160, domain string) error {
|
||||
script, ok, err := c.nnsRegisterDomainScript(nnsHash, expectedHash, domain)
|
||||
if ok || err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w := io.NewBufBinWriter()
|
||||
emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1})
|
||||
wrapRegisterScriptWithPrice(w, nnsHash, script)
|
||||
|
||||
emit.AppCall(w.BinWriter, nnsHash, "deleteRecords", callflag.All, domain, int64(nns.TXT))
|
||||
emit.AppCall(w.BinWriter, nnsHash, "addRecord", callflag.All,
|
||||
domain, int64(nns.TXT), expectedHash.StringLE())
|
||||
emit.AppCall(w.BinWriter, nnsHash, "addRecord", callflag.All,
|
||||
domain, int64(nns.TXT), address.Uint160ToString(expectedHash))
|
||||
return c.sendCommitteeTx(w.Bytes(), true)
|
||||
}
|
||||
|
||||
func (c *initializeContext) nnsRootRegistered(nnsHash util.Uint160, zone string) (bool, error) {
|
||||
res, err := c.CommitteeAct.Call(nnsHash, "isAvailable", "name."+zone)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return res.State == vmstate.Halt.String(), nil
|
||||
}
|
||||
|
||||
var errMissingNNSRecord = errors.New("missing NNS record")
|
||||
|
||||
// Returns errMissingNNSRecord if invocation fault exception contains "token not found".
|
||||
func nnsResolveHash(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (util.Uint160, error) {
|
||||
item, err := nnsResolve(inv, nnsHash, domain)
|
||||
if err != nil {
|
||||
return util.Uint160{}, err
|
||||
}
|
||||
return parseNNSResolveResult(item)
|
||||
}
|
||||
|
||||
func nnsResolve(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (stackitem.Item, error) {
|
||||
return unwrap.Item(inv.Call(nnsHash, "resolve", domain, int64(nns.TXT)))
|
||||
}
|
||||
|
||||
func nnsResolveKey(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (*keys.PublicKey, error) {
|
||||
item, err := nnsResolve(inv, nnsHash, domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v, ok := item.Value().(stackitem.Null)
|
||||
if ok {
|
||||
return nil, errors.New("NNS record is missing")
|
||||
}
|
||||
bs, err := v.TryBytes()
|
||||
if err != nil {
|
||||
return nil, errors.New("malformed response")
|
||||
}
|
||||
|
||||
return keys.NewPublicKeyFromString(string(bs))
|
||||
}
|
||||
|
||||
// parseNNSResolveResult parses the result of resolving NNS record.
|
||||
// It works with multiple formats (corresponding to multiple NNS versions).
|
||||
// If array of hashes is provided, it returns only the first one.
|
||||
func parseNNSResolveResult(res stackitem.Item) (util.Uint160, error) {
|
||||
arr, ok := res.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
arr = []stackitem.Item{res}
|
||||
}
|
||||
if _, ok := res.Value().(stackitem.Null); ok || len(arr) == 0 {
|
||||
return util.Uint160{}, errors.New("NNS record is missing")
|
||||
}
|
||||
for i := range arr {
|
||||
bs, err := arr[i].TryBytes()
|
||||
if err != nil {
|
||||
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))
|
||||
if err == nil {
|
||||
return h, nil
|
||||
}
|
||||
|
||||
h, err = address.StringToUint160(string(bs))
|
||||
if err == nil {
|
||||
return h, nil
|
||||
}
|
||||
}
|
||||
return util.Uint160{}, errors.New("no valid hashes are found")
|
||||
}
|
||||
|
||||
func nnsIsAvailable(c Client, nnsHash util.Uint160, name string) (bool, error) {
|
||||
switch ct := c.(type) {
|
||||
case *rpcclient.Client:
|
||||
return ct.NNSIsAvailable(nnsHash, name)
|
||||
default:
|
||||
b, err := unwrap.Bool(invokeFunction(c, nnsHash, "isAvailable", []any{name}, nil))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("`isAvailable`: invalid response: %w", err)
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
}
|
|
@ -5,33 +5,32 @@ import (
|
|||
"fmt"
|
||||
|
||||
"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/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo"
|
||||
"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"
|
||||
"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/stackitem"
|
||||
)
|
||||
|
||||
// initialAlphabetNEOAmount represents the total amount of GAS distributed between alphabet nodes.
|
||||
const initialAlphabetNEOAmount = native.NEOTotalSupply
|
||||
|
||||
func (c *initializeContext) registerCandidates() error {
|
||||
neoHash := c.nativeHash(nativenames.Neo)
|
||||
neoHash := neo.Hash
|
||||
|
||||
res, err := invokeFunction(c.Client, neoHash, "getCandidates", nil, nil)
|
||||
cc, err := unwrap.Array(c.ReadOnlyInvoker.Call(neoHash, "getCandidates"))
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("`getCandidates`: %w", err)
|
||||
}
|
||||
if res.State == vm.HaltState.String() && len(res.Stack) > 0 {
|
||||
arr, ok := res.Stack[0].Value().([]stackitem.Item)
|
||||
if ok && len(arr) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(cc) > 0 {
|
||||
c.Command.Println("Candidates are already registered.")
|
||||
return nil
|
||||
}
|
||||
|
||||
regPrice, err := c.getCandidateRegisterPrice()
|
||||
|
@ -39,22 +38,52 @@ func (c *initializeContext) registerCandidates() error {
|
|||
return fmt.Errorf("can't fetch registration price: %w", err)
|
||||
}
|
||||
|
||||
sysGas := regPrice + native.GASFactor // + 1 GAS
|
||||
w := io.NewBufBinWriter()
|
||||
emit.AppCall(w.BinWriter, neoHash, "setRegisterPrice", callflag.States, 1)
|
||||
for _, acc := range c.Accounts {
|
||||
w := io.NewBufBinWriter()
|
||||
emit.AppCall(w.BinWriter, neoHash, "registerCandidate", callflag.States, acc.PrivateKey().PublicKey().Bytes())
|
||||
emit.Opcodes(w.BinWriter, opcode.ASSERT)
|
||||
}
|
||||
emit.AppCall(w.BinWriter, neoHash, "setRegisterPrice", callflag.States, regPrice)
|
||||
if w.Err != nil {
|
||||
panic(fmt.Sprintf("BUG: %v", w.Err))
|
||||
}
|
||||
|
||||
if err := c.sendSingleTx(w.Bytes(), sysGas, acc); err != nil {
|
||||
return err
|
||||
signers := []rpcclient.SignerAccount{{
|
||||
Signer: c.getSigner(false, c.CommitteeAcc),
|
||||
Account: c.CommitteeAcc,
|
||||
}}
|
||||
for i := range c.Accounts {
|
||||
signers = append(signers, rpcclient.SignerAccount{
|
||||
Signer: transaction.Signer{
|
||||
Account: c.Accounts[i].Contract.ScriptHash(),
|
||||
Scopes: transaction.CustomContracts,
|
||||
AllowedContracts: []util.Uint160{neoHash},
|
||||
},
|
||||
Account: c.Accounts[i],
|
||||
})
|
||||
}
|
||||
|
||||
tx, err := c.Client.CreateTxFromScript(w.Bytes(), c.CommitteeAcc, -1, 0, signers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create tx: %w", err)
|
||||
}
|
||||
if err := c.multiSign(tx, committeeAccountName); err != nil {
|
||||
return fmt.Errorf("can't sign a transaction: %w", err)
|
||||
}
|
||||
|
||||
network := c.CommitteeAct.GetNetwork()
|
||||
for i := range c.Accounts {
|
||||
if err := c.Accounts[i].SignTx(network, tx); err != nil {
|
||||
return fmt.Errorf("can't sign a transaction: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return c.awaitTx()
|
||||
return c.sendTx(tx, c.Command, true)
|
||||
}
|
||||
|
||||
func (c *initializeContext) transferNEOToAlphabetContracts() error {
|
||||
neoHash := c.nativeHash(nativenames.Neo)
|
||||
neoHash := neo.Hash
|
||||
|
||||
ok, err := c.transferNEOFinished(neoHash)
|
||||
if ok || err != nil {
|
||||
|
@ -72,7 +101,7 @@ func (c *initializeContext) transferNEOToAlphabetContracts() error {
|
|||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||
}
|
||||
|
||||
if err := c.sendCommitteeTx(bw.Bytes(), -1, false); err != nil {
|
||||
if err := c.sendCommitteeTx(bw.Bytes(), false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -88,10 +117,10 @@ var errGetPriceInvalid = errors.New("`getRegisterPrice`: invalid response")
|
|||
|
||||
func (c *initializeContext) getCandidateRegisterPrice() (int64, error) {
|
||||
switch ct := c.Client.(type) {
|
||||
case *client.Client:
|
||||
case *rpcclient.Client:
|
||||
return ct.GetCandidateRegisterPrice()
|
||||
default:
|
||||
neoHash := c.nativeHash(nativenames.Neo)
|
||||
neoHash := neo.Hash
|
||||
res, err := invokeFunction(c.Client, neoHash, "getRegisterPrice", nil, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
|
@ -1,9 +1,9 @@
|
|||
package morph
|
||||
|
||||
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/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/vm/emit"
|
||||
)
|
||||
|
@ -16,20 +16,18 @@ func (c *initializeContext) setNotaryAndAlphabetNodes() error {
|
|||
return err
|
||||
}
|
||||
|
||||
designateHash := c.nativeHash(nativenames.Designation)
|
||||
|
||||
var pubs []interface{}
|
||||
var pubs []any
|
||||
for _, acc := range c.Accounts {
|
||||
pubs = append(pubs, acc.PrivateKey().PublicKey().Bytes())
|
||||
}
|
||||
|
||||
w := io.NewBufBinWriter()
|
||||
emit.AppCall(w.BinWriter, designateHash, "designateAsRole",
|
||||
emit.AppCall(w.BinWriter, rolemgmt.Hash, "designateAsRole",
|
||||
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)
|
||||
|
||||
if err := c.sendCommitteeTx(w.Bytes(), -1, false); err != nil {
|
||||
if err := c.sendCommitteeTx(w.Bytes(), false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -42,7 +40,6 @@ func (c *initializeContext) setRolesFinished() (bool, error) {
|
|||
return false, err
|
||||
}
|
||||
|
||||
h := c.nativeHash(nativenames.Designation)
|
||||
pubs, err := getDesignatedByRole(c.Client, h, noderoles.NeoFSAlphabet, height)
|
||||
pubs, err := getDesignatedByRole(c.ReadOnlyInvoker, rolemgmt.Hash, noderoles.NeoFSAlphabet, height)
|
||||
return len(pubs) == len(c.Wallets), err
|
||||
}
|
121
cmd/frostfs-adm/internal/modules/morph/initialize_test.go
Normal file
121
cmd/frostfs-adm/internal/modules/morph/initialize_test.go
Normal file
|
@ -0,0 +1,121 @@
|
|||
package morph
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/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 = "../../../../../../frostfs-contract/frostfs-contract-v0.16.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)
|
||||
})
|
||||
}
|
||||
|
||||
func testInitialize(t *testing.T, committeeSize int) {
|
||||
testdataDir := t.TempDir()
|
||||
v := viper.GetViper()
|
||||
|
||||
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))
|
||||
v.Set(localDumpFlag, filepath.Join(testdataDir, "out"))
|
||||
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, forceNewEpochCmd(forceNewEpoch, nil))
|
||||
})
|
||||
t.Run("set-config", func(t *testing.T) {
|
||||
require.NoError(t, setConfigCmd(setConfig, []string{"MaintenanceModeAllowed=true"}))
|
||||
})
|
||||
t.Run("set-policy", func(t *testing.T) {
|
||||
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, removeNodesCmd(removeNodes, []string{pub}))
|
||||
})
|
||||
}
|
||||
|
||||
func generateTestData(t *testing.T, dir string, size int) {
|
||||
v := viper.GetViper()
|
||||
v.Set(alphabetWalletsFlag, dir)
|
||||
|
||||
sizeStr := strconv.FormatUint(uint64(size), 10)
|
||||
require.NoError(t, generateAlphabetCmd.Flags().Set(alphabetSizeFlag, sizeStr))
|
||||
|
||||
setTestCredentials(v, size)
|
||||
require.NoError(t, generateAlphabetCreds(generateAlphabetCmd, nil))
|
||||
|
||||
var pubs []string
|
||||
for i := 0; i < size; i++ {
|
||||
p := filepath.Join(dir, innerring.GlagoliticLetter(i).String()+".json")
|
||||
w, err := wallet.NewWalletFromFile(p)
|
||||
require.NoError(t, err, "wallet doesn't exist")
|
||||
for _, acc := range w.Accounts {
|
||||
if acc.Label == singleAccountName {
|
||||
pub, ok := vm.ParseSignatureContract(acc.Contract.Script)
|
||||
require.True(t, ok)
|
||||
pubs = append(pubs, hex.EncodeToString(pub))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg := config.Config{}
|
||||
cfg.ProtocolConfiguration.Magic = 12345
|
||||
cfg.ProtocolConfiguration.ValidatorsCount = size
|
||||
cfg.ProtocolConfiguration.SecondsPerBlock = 1
|
||||
cfg.ProtocolConfiguration.StandbyCommittee = pubs // sorted by glagolic letters
|
||||
cfg.ProtocolConfiguration.P2PSigExtensions = true
|
||||
cfg.ProtocolConfiguration.VerifyTransactions = true
|
||||
cfg.ProtocolConfiguration.VerifyBlocks = true
|
||||
data, err := yaml.Marshal(cfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
protoPath := filepath.Join(dir, protoFileName)
|
||||
require.NoError(t, 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)
|
||||
}
|
|
@ -4,10 +4,11 @@ import (
|
|||
"fmt"
|
||||
|
||||
"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/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||
"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/smartcontract/callflag"
|
||||
scContext "github.com/nspcc-dev/neo-go/pkg/smartcontract/context"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
|
@ -32,15 +33,12 @@ func (c *initializeContext) transferFunds() error {
|
|||
return err
|
||||
}
|
||||
|
||||
gasHash := c.nativeHash(nativenames.Gas)
|
||||
neoHash := c.nativeHash(nativenames.Neo)
|
||||
|
||||
var transfers []client.TransferTarget
|
||||
var transfers []rpcclient.TransferTarget
|
||||
for _, acc := range c.Accounts {
|
||||
to := acc.Contract.ScriptHash()
|
||||
transfers = append(transfers,
|
||||
client.TransferTarget{
|
||||
Token: gasHash,
|
||||
rpcclient.TransferTarget{
|
||||
Token: gas.Hash,
|
||||
Address: to,
|
||||
Amount: initialAlphabetGASAmount,
|
||||
},
|
||||
|
@ -49,19 +47,19 @@ func (c *initializeContext) transferFunds() error {
|
|||
|
||||
// It is convenient to have all funds at the committee account.
|
||||
transfers = append(transfers,
|
||||
client.TransferTarget{
|
||||
Token: gasHash,
|
||||
rpcclient.TransferTarget{
|
||||
Token: gas.Hash,
|
||||
Address: c.CommitteeAcc.Contract.ScriptHash(),
|
||||
Amount: (gasInitialTotalSupply - initialAlphabetGASAmount*int64(len(c.Wallets))) / 2,
|
||||
},
|
||||
client.TransferTarget{
|
||||
Token: neoHash,
|
||||
rpcclient.TransferTarget{
|
||||
Token: neo.Hash,
|
||||
Address: c.CommitteeAcc.Contract.ScriptHash(),
|
||||
Amount: native.NEOTotalSupply,
|
||||
},
|
||||
)
|
||||
|
||||
tx, err := createNEP17MultiTransferTx(c.Client, c.ConsensusAcc, 0, transfers, []client.SignerAccount{{
|
||||
tx, err := createNEP17MultiTransferTx(c.Client, c.ConsensusAcc, 0, transfers, []rpcclient.SignerAccount{{
|
||||
Signer: transaction.Signer{
|
||||
Account: c.ConsensusAcc.Contract.ScriptHash(),
|
||||
Scopes: transaction.CalledByEntry,
|
||||
|
@ -80,10 +78,9 @@ func (c *initializeContext) transferFunds() error {
|
|||
}
|
||||
|
||||
func (c *initializeContext) transferFundsFinished() (bool, error) {
|
||||
gasHash := c.nativeHash(nativenames.Gas)
|
||||
acc := c.Accounts[0]
|
||||
|
||||
res, err := c.Client.NEP17BalanceOf(gasHash, acc.Contract.ScriptHash())
|
||||
res, err := c.Client.NEP17BalanceOf(gas.Hash, acc.Contract.ScriptHash())
|
||||
return res > initialAlphabetGASAmount/2, err
|
||||
}
|
||||
|
||||
|
@ -125,26 +122,37 @@ func (c *initializeContext) multiSign(tx *transaction.Transaction, accType strin
|
|||
}
|
||||
}
|
||||
|
||||
w, err := pc.GetWitness(tx.Signers[0].Account)
|
||||
w, err := pc.GetWitness(h)
|
||||
if err != nil {
|
||||
return fmt.Errorf("incomplete signature: %w", err)
|
||||
}
|
||||
tx.Scripts = append(tx.Scripts, *w)
|
||||
|
||||
return nil
|
||||
for i := range tx.Signers {
|
||||
if tx.Signers[i].Account == h {
|
||||
if i < len(tx.Scripts) {
|
||||
tx.Scripts[i] = *w
|
||||
} else if i == len(tx.Scripts) {
|
||||
tx.Scripts = append(tx.Scripts, *w)
|
||||
} else {
|
||||
panic("BUG: invalid signing order")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("%s account was not found among transaction signers", accType)
|
||||
}
|
||||
|
||||
func (c *initializeContext) transferGASToProxy() error {
|
||||
gasHash := c.nativeHash(nativenames.Gas)
|
||||
proxyCs := c.getContract(proxyContract)
|
||||
|
||||
bal, err := c.Client.NEP17BalanceOf(gasHash, proxyCs.Hash)
|
||||
bal, err := c.Client.NEP17BalanceOf(gas.Hash, proxyCs.Hash)
|
||||
if err != nil || bal > 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
tx, err := createNEP17MultiTransferTx(c.Client, c.CommitteeAcc, 0, []client.TransferTarget{{
|
||||
Token: gasHash,
|
||||
tx, err := createNEP17MultiTransferTx(c.Client, c.CommitteeAcc, 0, []rpcclient.TransferTarget{{
|
||||
Token: gas.Hash,
|
||||
Address: proxyCs.Hash,
|
||||
Amount: initialProxyGASAmount,
|
||||
}}, nil)
|
||||
|
@ -160,7 +168,7 @@ func (c *initializeContext) transferGASToProxy() error {
|
|||
}
|
||||
|
||||
func createNEP17MultiTransferTx(c Client, acc *wallet.Account, netFee int64,
|
||||
recipients []client.TransferTarget, cosigners []client.SignerAccount) (*transaction.Transaction, error) {
|
||||
recipients []rpcclient.TransferTarget, cosigners []rpcclient.SignerAccount) (*transaction.Transaction, error) {
|
||||
from := acc.Contract.ScriptHash()
|
||||
|
||||
w := io.NewBufBinWriter()
|
||||
|
@ -172,7 +180,7 @@ func createNEP17MultiTransferTx(c Client, acc *wallet.Account, netFee int64,
|
|||
if w.Err != nil {
|
||||
return nil, fmt.Errorf("failed to create transfer script: %w", w.Err)
|
||||
}
|
||||
return c.CreateTxFromScript(w.Bytes(), acc, -1, netFee, append([]client.SignerAccount{{
|
||||
return c.CreateTxFromScript(w.Bytes(), acc, -1, netFee, append([]rpcclient.SignerAccount{{
|
||||
Signer: transaction.Signer{
|
||||
Account: from,
|
||||
Scopes: transaction.CalledByEntry,
|
|
@ -18,7 +18,7 @@ func StringifySubnetClientGroupID(id *SubnetClientGroupID) string {
|
|||
return string(text)
|
||||
}
|
||||
|
||||
// MarshalText encodes SubnetClientGroupID into text format according to NeoFS API V2 protocol:
|
||||
// MarshalText encodes SubnetClientGroupID into text format according to FrostFS API V2 protocol:
|
||||
// value in base-10 integer string format.
|
||||
//
|
||||
// It implements encoding.TextMarshaler.
|
||||
|
@ -28,7 +28,7 @@ func (x *SubnetClientGroupID) MarshalText() ([]byte, error) {
|
|||
return []byte(strconv.FormatUint(uint64(num), 10)), nil
|
||||
}
|
||||
|
||||
// UnmarshalText decodes the SubnetID from the text according to NeoFS API V2 protocol:
|
||||
// UnmarshalText decodes the SubnetID from the text according to FrostFS API V2 protocol:
|
||||
// should be base-10 integer string format with bitsize = 32.
|
||||
//
|
||||
// Returns strconv.ErrRange if integer overflows uint32.
|
||||
|
@ -47,13 +47,13 @@ func (x *SubnetClientGroupID) UnmarshalText(txt []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Marshal encodes the SubnetClientGroupID into a binary format of NeoFS API V2 protocol
|
||||
// Marshal encodes the SubnetClientGroupID into a binary format of FrostFS API V2 protocol
|
||||
// (Protocol Buffers with direct field order).
|
||||
func (x *SubnetClientGroupID) Marshal() ([]byte, error) {
|
||||
return proto.Marshal(x)
|
||||
}
|
||||
|
||||
// Unmarshal decodes the SubnetClientGroupID from NeoFS API V2 binary format (see Marshal). Must not be called on nil.
|
||||
// Unmarshal decodes the SubnetClientGroupID from FrostFS API V2 binary format (see Marshal). Must not be called on nil.
|
||||
func (x *SubnetClientGroupID) Unmarshal(data []byte) error {
|
||||
return proto.Unmarshal(data, x)
|
||||
}
|
BIN
cmd/frostfs-adm/internal/modules/morph/internal/types.pb.go
generated
Normal file
BIN
cmd/frostfs-adm/internal/modules/morph/internal/types.pb.go
generated
Normal file
Binary file not shown.
|
@ -2,9 +2,9 @@ syntax = "proto3";
|
|||
|
||||
package neo.fs.v2.refs;
|
||||
|
||||
option go_package = "github.com/nspcc-dev/neofs-node/cmd/neofs-adm/internal/modules/morph/internal";
|
||||
option go_package = "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/internal";
|
||||
|
||||
// Client group identifier in the NeoFS subnet.
|
||||
// Client group identifier in the FrostFS subnet.
|
||||
//
|
||||
// String representation of a value is base-10 integer.
|
||||
//
|
|
@ -1,13 +1,14 @@
|
|||
package morph
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/elliptic"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"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"
|
||||
|
@ -18,21 +19,26 @@ import (
|
|||
"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/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/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/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
|
||||
"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/unwrap"
|
||||
"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/trigger"
|
||||
"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/opcode"
|
||||
"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/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
@ -42,19 +48,19 @@ type localClient struct {
|
|||
transactions []*transaction.Transaction
|
||||
dumpPath string
|
||||
accounts []*wallet.Account
|
||||
maxGasInvoke int64
|
||||
}
|
||||
|
||||
func newLocalClient(v *viper.Viper, wallets []*wallet.Wallet) (*localClient, error) {
|
||||
func newLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet) (*localClient, error) {
|
||||
cfg, err := config.LoadFile(v.GetString(protoConfigPath))
|
||||
if err != nil {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { go bc.Run() }()
|
||||
|
||||
m := smartcontract.GetDefaultHonestNodeCount(cfg.ProtocolConfiguration.ValidatorsCount)
|
||||
accounts := make([]*wallet.Account, len(wallets))
|
||||
|
@ -76,15 +82,37 @@ func newLocalClient(v *viper.Viper, wallets []*wallet.Wallet) (*localClient, err
|
|||
return indexMap[string(pi)] < indexMap[string(pj)]
|
||||
})
|
||||
sort.Slice(accounts[:cfg.ProtocolConfiguration.ValidatorsCount], func(i, j int) bool {
|
||||
pi := accounts[i].PrivateKey().PublicKey().Bytes()
|
||||
pj := accounts[j].PrivateKey().PublicKey().Bytes()
|
||||
return bytes.Compare(pi, pj) == -1
|
||||
return accounts[i].PublicKey().Cmp(accounts[j].PublicKey()) == -1
|
||||
})
|
||||
|
||||
go bc.Run()
|
||||
|
||||
dumpPath := v.GetString(localDumpFlag)
|
||||
if cmd.Name() != "init" {
|
||||
f, err := os.OpenFile(dumpPath, os.O_RDONLY, 0600)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't open local dump: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
r := io.NewBinReaderFromIO(f)
|
||||
|
||||
var skip uint32
|
||||
if bc.BlockHeight() != 0 {
|
||||
skip = bc.BlockHeight() + 1
|
||||
}
|
||||
|
||||
count := r.ReadU32LE() - skip
|
||||
if err := chaindump.Restore(bc, r, skip, count, nil); err != nil {
|
||||
return nil, fmt.Errorf("can't restore local dump: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &localClient{
|
||||
bc: bc,
|
||||
dumpPath: v.GetString(localDumpFlag),
|
||||
accounts: accounts[:m],
|
||||
bc: bc,
|
||||
dumpPath: dumpPath,
|
||||
accounts: accounts[:m],
|
||||
maxGasInvoke: 15_0000_0000,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -125,7 +153,7 @@ func (l *localClient) GetApplicationLog(h util.Uint256, t *trigger.Type) (*resul
|
|||
return &a, nil
|
||||
}
|
||||
|
||||
func (l *localClient) CreateTxFromScript(script []byte, acc *wallet.Account, sysFee int64, netFee int64, cosigners []client.SignerAccount) (*transaction.Transaction, error) {
|
||||
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)
|
||||
|
@ -158,9 +186,19 @@ func (l *localClient) GetCommittee() (keys.PublicKeys, error) {
|
|||
panic("unexpected call")
|
||||
}
|
||||
|
||||
func (l *localClient) InvokeFunction(_ util.Uint160, _ string, _ []smartcontract.Parameter, _ []transaction.Signer) (*result.Invoke, error) {
|
||||
// not used by `morph init` command
|
||||
panic("unexpected call")
|
||||
// InvokeFunction is implemented via `InvokeScript`.
|
||||
func (l *localClient) InvokeFunction(h util.Uint160, method string, sPrm []smartcontract.Parameter, ss []transaction.Signer) (*result.Invoke, error) {
|
||||
var err error
|
||||
|
||||
pp := make([]any, len(sPrm))
|
||||
for i, p := range sPrm {
|
||||
pp[i], err = smartcontract.ExpandParameterToEmitable(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("incorrect parameter type %s: %w", p.Type, err)
|
||||
}
|
||||
}
|
||||
|
||||
return invokeFunction(l, h, method, pp, ss)
|
||||
}
|
||||
|
||||
func (l *localClient) CalculateNotaryFee(_ uint8) (int64, error) {
|
||||
|
@ -173,11 +211,90 @@ func (l *localClient) SignAndPushP2PNotaryRequest(_ *transaction.Transaction, _
|
|||
panic("unexpected call")
|
||||
}
|
||||
|
||||
func (l *localClient) SignAndPushInvocationTx(_ []byte, _ *wallet.Account, _ int64, _ fixedn.Fixed8, _ []client.SignerAccount) (util.Uint256, error) {
|
||||
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) {
|
||||
// not used by `morph init` command
|
||||
panic("unexpected call")
|
||||
}
|
||||
|
||||
func (l *localClient) TraverseIterator(_, _ uuid.UUID, _ int) ([]stackitem.Item, error) {
|
||||
// not used by `morph init` command
|
||||
panic("unexpected call")
|
||||
}
|
||||
|
||||
// GetVersion return default version.
|
||||
func (l *localClient) GetVersion() (*result.Version, error) {
|
||||
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) {
|
||||
// not used by `morph init` command
|
||||
panic("unexpected call")
|
||||
}
|
||||
|
||||
// CalculateNetworkFee calculates network fee for the given transaction.
|
||||
// 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
|
||||
func (l *localClient) CalculateNetworkFee(tx *transaction.Transaction) (int64, error) {
|
||||
hashablePart, err := tx.EncodeHashableFields()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to compute tx size: %w", err)
|
||||
}
|
||||
|
||||
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):
|
||||
|
@ -204,7 +321,7 @@ func (l *localClient) AddNetworkFee(tx *transaction.Transaction, extraFee int64,
|
|||
// 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 []client.SignerAccount) ([]transaction.Signer, []*wallet.Account, error) {
|
||||
func getSigners(sender *wallet.Account, cosigners []rpcclient.SignerAccount) ([]transaction.Signer, []*wallet.Account, error) {
|
||||
var (
|
||||
signers []transaction.Signer
|
||||
accounts []*wallet.Account
|
||||
|
@ -229,11 +346,11 @@ func getSigners(sender *wallet.Account, cosigners []client.SignerAccount) ([]tra
|
|||
}
|
||||
|
||||
func (l *localClient) NEP17BalanceOf(h util.Uint160, acc util.Uint160) (int64, error) {
|
||||
res, err := invokeFunction(l, h, "balanceOf", []interface{}{acc}, nil)
|
||||
res, err := invokeFunction(l, h, "balanceOf", []any{acc}, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if res.State != vm.HaltState.String() || len(res.Stack) == 0 {
|
||||
if res.State != vmstate.Halt.String() || len(res.Stack) == 0 {
|
||||
return 0, fmt.Errorf("`balance`: invalid response (empty: %t): %s",
|
||||
len(res.Stack) == 0, res.FaultException)
|
||||
}
|
||||
|
@ -254,12 +371,15 @@ func (l *localClient) InvokeScript(script []byte, signers []transaction.Signer)
|
|||
tx.Signers = signers
|
||||
tx.ValidUntilBlock = l.bc.BlockHeight() + 2
|
||||
|
||||
ic := l.bc.GetTestVM(trigger.Application, tx, &block.Block{
|
||||
ic, err := l.bc.GetTestVM(trigger.Application, tx, &block.Block{
|
||||
Header: block.Header{
|
||||
Index: lastBlock.Index + 1,
|
||||
Timestamp: lastBlock.Timestamp + 1,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get test VM: %w", err)
|
||||
}
|
||||
|
||||
ic.VM.GasLimit = 100_0000_0000
|
||||
ic.VM.LoadScriptWithFlags(script, callflag.All)
|
||||
|
@ -278,6 +398,13 @@ func (l *localClient) InvokeScript(script []byte, signers []transaction.Signer)
|
|||
}
|
||||
|
||||
func (l *localClient) SendRawTransaction(tx *transaction.Transaction) (util.Uint256, error) {
|
||||
// We need to test that transaction was formed correctly to catch as many errors as we can.
|
||||
bs := tx.Bytes()
|
||||
_, err := transaction.NewTransactionFromBytes(bs)
|
||||
if err != nil {
|
||||
return tx.Hash(), fmt.Errorf("invalid transaction: %w", err)
|
||||
}
|
||||
|
||||
l.transactions = append(l.transactions, tx)
|
||||
return tx.Hash(), nil
|
||||
}
|
||||
|
@ -324,7 +451,7 @@ func (l *localClient) putTransactions() error {
|
|||
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()
|
||||
emit.Array(w.BinWriter, parameters...)
|
||||
emit.AppCallNoArgs(w.BinWriter, h, method, callflag.All)
|
||||
|
@ -336,16 +463,9 @@ func invokeFunction(c Client, h util.Uint160, method string, parameters []interf
|
|||
|
||||
var errGetDesignatedByRoleResponse = errors.New("`getDesignatedByRole`: invalid response")
|
||||
|
||||
func getDesignatedByRole(c Client, h util.Uint160, role noderoles.Role, u uint32) (keys.PublicKeys, error) {
|
||||
res, err := invokeFunction(c, h, "getDesignatedByRole", []interface{}{int64(role), int64(u)}, nil)
|
||||
func getDesignatedByRole(inv *invoker.Invoker, h util.Uint160, role noderoles.Role, u uint32) (keys.PublicKeys, error) {
|
||||
arr, err := unwrap.Array(inv.Call(h, "getDesignatedByRole", int64(role), int64(u)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.State != vm.HaltState.String() || len(res.Stack) == 0 {
|
||||
return nil, errGetDesignatedByRoleResponse
|
||||
}
|
||||
arr, ok := res.Stack[0].Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
return nil, errGetDesignatedByRoleResponse
|
||||
}
|
||||
|
||||
|
@ -379,6 +499,7 @@ func (l *localClient) dump() (err error) {
|
|||
}()
|
||||
|
||||
w := io.NewBinWriterFromIO(f)
|
||||
err = chaindump.Dump(l.bc, w, 0, l.bc.BlockHeight())
|
||||
w.WriteU32LE(l.bc.BlockHeight() + 1)
|
||||
err = chaindump.Dump(l.bc, w, 0, l.bc.BlockHeight()+1)
|
||||
return
|
||||
}
|
|
@ -11,9 +11,11 @@ import (
|
|||
"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/encoding/fixedn"
|
||||
"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/rpc/client"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
|
||||
"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/smartcontract/trigger"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
|
@ -24,28 +26,36 @@ import (
|
|||
// Client represents N3 client interface capable of test-invoking scripts
|
||||
// and sending signed transactions to chain.
|
||||
type Client interface {
|
||||
invoker.RPCInvoke
|
||||
|
||||
GetBlockCount() (uint32, error)
|
||||
GetContractStateByID(int32) (*state.Contract, error)
|
||||
GetContractStateByHash(util.Uint160) (*state.Contract, error)
|
||||
GetNativeContracts() ([]state.NativeContract, error)
|
||||
GetNetwork() (netmode.Magic, error)
|
||||
GetApplicationLog(util.Uint256, *trigger.Type) (*result.ApplicationLog, error)
|
||||
CreateTxFromScript([]byte, *wallet.Account, int64, int64, []client.SignerAccount) (*transaction.Transaction, error)
|
||||
GetVersion() (*result.Version, error)
|
||||
CreateTxFromScript([]byte, *wallet.Account, int64, int64, []rpcclient.SignerAccount) (*transaction.Transaction, error)
|
||||
NEP17BalanceOf(util.Uint160, util.Uint160) (int64, error)
|
||||
InvokeScript([]byte, []transaction.Signer) (*result.Invoke, error)
|
||||
SendRawTransaction(*transaction.Transaction) (util.Uint256, error)
|
||||
GetCommittee() (keys.PublicKeys, error)
|
||||
CalculateNotaryFee(uint8) (int64, error)
|
||||
CalculateNetworkFee(tx *transaction.Transaction) (int64, error)
|
||||
AddNetworkFee(*transaction.Transaction, int64, ...*wallet.Account) error
|
||||
SignAndPushInvocationTx([]byte, *wallet.Account, int64, fixedn.Fixed8, []client.SignerAccount) (util.Uint256, 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 {
|
||||
hash util.Uint256
|
||||
vub uint32
|
||||
}
|
||||
|
||||
type clientContext struct {
|
||||
Client Client
|
||||
Hashes []util.Uint256
|
||||
WaitDuration time.Duration
|
||||
PollInterval time.Duration
|
||||
Client Client // a raw neo-go client OR a local chain implementation
|
||||
CommitteeAct *actor.Actor // committee actor with the Global witness scope
|
||||
ReadOnlyInvoker *invoker.Invoker // R/O contract invoker, does not contain any signer
|
||||
SentTxs []hashVUBPair
|
||||
}
|
||||
|
||||
func getN3Client(v *viper.Viper) (Client, error) {
|
||||
|
@ -61,7 +71,7 @@ func getN3Client(v *viper.Viper) (Client, error) {
|
|||
if endpoint == "" {
|
||||
return nil, errors.New("missing endpoint")
|
||||
}
|
||||
c, err := client.New(ctx, endpoint, client.Options{
|
||||
c, err := rpcclient.New(ctx, endpoint, rpcclient.Options{
|
||||
MaxConnsPerHost: maxConnsPerHost,
|
||||
RequestTimeout: requestTimeout,
|
||||
})
|
||||
|
@ -74,12 +84,23 @@ func getN3Client(v *viper.Viper) (Client, error) {
|
|||
return c, nil
|
||||
}
|
||||
|
||||
func defaultClientContext(c Client) *clientContext {
|
||||
return &clientContext{
|
||||
Client: c,
|
||||
WaitDuration: time.Second * 30,
|
||||
PollInterval: time.Second,
|
||||
func defaultClientContext(c Client, committeeAcc *wallet.Account) (*clientContext, error) {
|
||||
commAct, err := actor.New(c, []actor.SignerAccount{{
|
||||
Signer: transaction.Signer{
|
||||
Account: committeeAcc.Contract.ScriptHash(),
|
||||
Scopes: transaction.Global,
|
||||
},
|
||||
Account: committeeAcc,
|
||||
}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &clientContext{
|
||||
Client: c,
|
||||
CommitteeAct: commAct,
|
||||
ReadOnlyInvoker: invoker.New(c, nil),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *clientContext) sendTx(tx *transaction.Transaction, cmd *cobra.Command, await bool) error {
|
||||
|
@ -92,7 +113,7 @@ func (c *clientContext) sendTx(tx *transaction.Transaction, cmd *cobra.Command,
|
|||
return fmt.Errorf("sent and actual tx hashes mismatch:\n\tsent: %v\n\tactual: %v", tx.Hash().StringLE(), h.StringLE())
|
||||
}
|
||||
|
||||
c.Hashes = append(c.Hashes, h)
|
||||
c.SentTxs = append(c.SentTxs, hashVUBPair{hash: h, vub: tx.ValidUntilBlock})
|
||||
|
||||
if await {
|
||||
return c.awaitTx(cmd)
|
29
cmd/frostfs-adm/internal/modules/morph/netmap_candidates.go
Normal file
29
cmd/frostfs-adm/internal/modules/morph/netmap_candidates.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package morph
|
||||
|
||||
import (
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"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)
|
||||
|
||||
cs, err := c.GetContractStateByID(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))
|
||||
}
|
|
@ -2,18 +2,17 @@ package morph
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
|
||||
"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/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
|
||||
"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/opcode"
|
||||
"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/notary"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
@ -86,18 +85,8 @@ func depositNotary(cmd *cobra.Command, _ []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
nhs, err := getNativeHashes(c)
|
||||
if err != nil {
|
||||
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)
|
||||
if err := checkNotaryEnabled(c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
height, err := c.GetBlockCount()
|
||||
|
@ -105,15 +94,7 @@ func depositNotary(cmd *cobra.Command, _ []string) error {
|
|||
return fmt.Errorf("can't get current height: %v", err)
|
||||
}
|
||||
|
||||
bw := io.NewBufBinWriter()
|
||||
emit.AppCall(bw.BinWriter, gasHash, "transfer", callflag.All,
|
||||
accHash, notaryHash.BytesBE(), int64(gasAmount), []interface{}{nil, int64(height) + till})
|
||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||
if bw.Err != nil {
|
||||
return fmt.Errorf("BUG: invalid transfer arguments: %w", bw.Err)
|
||||
}
|
||||
|
||||
tx, err := c.CreateTxFromScript(bw.Bytes(), acc, -1, 0, []client.SignerAccount{{
|
||||
act, err := actor.New(c, []actor.SignerAccount{{
|
||||
Signer: transaction.Signer{
|
||||
Account: acc.Contract.ScriptHash(),
|
||||
Scopes: transaction.Global,
|
||||
|
@ -121,21 +102,20 @@ func depositNotary(cmd *cobra.Command, _ []string) error {
|
|||
Account: acc,
|
||||
}})
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create tx: %w", err)
|
||||
return fmt.Errorf("could not create actor: %w", err)
|
||||
}
|
||||
|
||||
mn, err := c.GetNetwork()
|
||||
gasActor := nep17.New(act, gas.Hash)
|
||||
|
||||
txHash, vub, err := gasActor.Transfer(
|
||||
accHash,
|
||||
notary.Hash,
|
||||
big.NewInt(int64(gasAmount)),
|
||||
[]any{nil, int64(height) + till},
|
||||
)
|
||||
if err != nil {
|
||||
// error appears only if client
|
||||
// has not been initialized
|
||||
panic(err)
|
||||
return fmt.Errorf("could not send tx: %w", err)
|
||||
}
|
||||
|
||||
err = acc.SignTx(mn, tx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't sign tx: %w", err)
|
||||
}
|
||||
|
||||
cc := defaultClientContext(c)
|
||||
return cc.sendTx(tx, cmd, true)
|
||||
return awaitTx(cmd, c, []hashVUBPair{{hash: txHash, vub: vub}})
|
||||
}
|
|
@ -5,8 +5,8 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"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"
|
||||
|
@ -25,30 +25,28 @@ func setPolicyCmd(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("can't to initialize context: %w", err)
|
||||
}
|
||||
|
||||
policyHash := wCtx.nativeHash(nativenames.Policy)
|
||||
|
||||
bw := io.NewBufBinWriter()
|
||||
for i := range args {
|
||||
kv := strings.SplitN(args[i], "=", 2)
|
||||
if len(kv) != 2 {
|
||||
k, v, found := strings.Cut(args[i], "=")
|
||||
if !found {
|
||||
return fmt.Errorf("invalid parameter format, must be Parameter=Value")
|
||||
}
|
||||
|
||||
switch kv[0] {
|
||||
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(kv[1], 10, 32)
|
||||
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, policyHash, "set"+kv[0], callflag.All, int64(value))
|
||||
emit.AppCall(bw.BinWriter, policy.Hash, "set"+k, callflag.All, int64(value))
|
||||
}
|
||||
|
||||
if err := wCtx.sendCommitteeTx(bw.Bytes(), -1, false); err != nil {
|
||||
if err := wCtx.sendCommitteeTx(bw.Bytes(), false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -4,11 +4,11 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
|
||||
netmapcontract "github.com/TrueCloudLab/frostfs-contract/netmap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
netmapcontract "github.com/nspcc-dev/neofs-contract/netmap"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
@ -31,13 +31,14 @@ func removeNodesCmd(cmd *cobra.Command, args []string) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("can't initialize context: %w", err)
|
||||
}
|
||||
defer wCtx.close()
|
||||
|
||||
cs, err := wCtx.Client.GetContractStateByID(1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get NNS contract info: %w", err)
|
||||
}
|
||||
|
||||
nmHash, err := nnsResolveHash(wCtx.Client, cs.Hash, netmapContract+".neofs")
|
||||
nmHash, err := nnsResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, netmapContract+".frostfs")
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
||||
}
|
||||
|
@ -45,14 +46,14 @@ func removeNodesCmd(cmd *cobra.Command, args []string) error {
|
|||
bw := io.NewBufBinWriter()
|
||||
for i := range nodeKeys {
|
||||
emit.AppCall(bw.BinWriter, nmHash, "updateStateIR", callflag.All,
|
||||
int64(netmapcontract.OfflineState), nodeKeys[i].Bytes())
|
||||
int64(netmapcontract.NodeStateOffline), nodeKeys[i].Bytes())
|
||||
}
|
||||
|
||||
if err := emitNewEpochCall(bw, wCtx, nmHash); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := wCtx.sendCommitteeTx(bw.Bytes(), -1, true); err != nil {
|
||||
if err := wCtx.sendConsensusTx(bw.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -6,38 +6,42 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
alphabetWalletsFlag = "alphabet-wallets"
|
||||
alphabetSizeFlag = "size"
|
||||
endpointFlag = "rpc-endpoint"
|
||||
storageWalletFlag = "storage-wallet"
|
||||
storageWalletLabelFlag = "label"
|
||||
storageGasCLIFlag = "initial-gas"
|
||||
storageGasConfigFlag = "storage.initial_gas"
|
||||
contractsInitFlag = "contracts"
|
||||
maxObjectSizeInitFlag = "network.max_object_size"
|
||||
maxObjectSizeCLIFlag = "max-object-size"
|
||||
epochDurationInitFlag = "network.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"
|
||||
containerAliasFeeInitFlag = "network.fee.container_alias"
|
||||
containerFeeCLIFlag = "container-fee"
|
||||
containerAliasFeeCLIFlag = "container-alias-fee"
|
||||
candidateFeeInitFlag = "network.fee.candidate"
|
||||
candidateFeeCLIFlag = "candidate-fee"
|
||||
withdrawFeeInitFlag = "network.fee.withdraw"
|
||||
withdrawFeeCLIFlag = "withdraw-fee"
|
||||
containerDumpFlag = "dump"
|
||||
containerContractFlag = "container-contract"
|
||||
containerIDsFlag = "cid"
|
||||
refillGasAmountFlag = "gas"
|
||||
walletAccountFlag = "account"
|
||||
notaryDepositTillFlag = "till"
|
||||
localDumpFlag = "local-dump"
|
||||
protoConfigPath = "protocol"
|
||||
alphabetWalletsFlag = "alphabet-wallets"
|
||||
alphabetSizeFlag = "size"
|
||||
endpointFlag = "rpc-endpoint"
|
||||
storageWalletFlag = "storage-wallet"
|
||||
storageWalletLabelFlag = "label"
|
||||
storageGasCLIFlag = "initial-gas"
|
||||
storageGasConfigFlag = "storage.initial_gas"
|
||||
contractsInitFlag = "contracts"
|
||||
maxObjectSizeInitFlag = "network.max_object_size"
|
||||
maxObjectSizeCLIFlag = "max-object-size"
|
||||
epochDurationInitFlag = "network.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"
|
||||
containerAliasFeeInitFlag = "network.fee.container_alias"
|
||||
containerFeeCLIFlag = "container-fee"
|
||||
containerAliasFeeCLIFlag = "container-alias-fee"
|
||||
candidateFeeInitFlag = "network.fee.candidate"
|
||||
candidateFeeCLIFlag = "candidate-fee"
|
||||
homomorphicHashDisabledInitFlag = "network.homomorphic_hash_disabled"
|
||||
maintenanceModeAllowedInitFlag = "network.maintenance_mode_allowed"
|
||||
homomorphicHashDisabledCLIFlag = "homomorphic-disabled"
|
||||
withdrawFeeInitFlag = "network.fee.withdraw"
|
||||
withdrawFeeCLIFlag = "withdraw-fee"
|
||||
containerDumpFlag = "dump"
|
||||
containerContractFlag = "container-contract"
|
||||
containerIDsFlag = "cid"
|
||||
refillGasAmountFlag = "gas"
|
||||
walletAccountFlag = "account"
|
||||
notaryDepositTillFlag = "till"
|
||||
localDumpFlag = "local-dump"
|
||||
protoConfigPath = "protocol"
|
||||
walletAddressFlag = "wallet-address"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -66,6 +70,7 @@ var (
|
|||
_ = viper.BindPFlag(epochDurationInitFlag, cmd.Flags().Lookup(epochDurationCLIFlag))
|
||||
_ = viper.BindPFlag(maxObjectSizeInitFlag, cmd.Flags().Lookup(maxObjectSizeCLIFlag))
|
||||
_ = viper.BindPFlag(incomeRateInitFlag, cmd.Flags().Lookup(incomeRateCLIFlag))
|
||||
_ = viper.BindPFlag(homomorphicHashDisabledInitFlag, cmd.Flags().Lookup(homomorphicHashDisabledCLIFlag))
|
||||
_ = viper.BindPFlag(auditFeeInitFlag, cmd.Flags().Lookup(auditFeeCLIFlag))
|
||||
_ = viper.BindPFlag(candidateFeeInitFlag, cmd.Flags().Lookup(candidateFeeCLIFlag))
|
||||
_ = viper.BindPFlag(containerFeeInitFlag, cmd.Flags().Lookup(containerFeeCLIFlag))
|
||||
|
@ -103,7 +108,7 @@ var (
|
|||
|
||||
forceNewEpoch = &cobra.Command{
|
||||
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) {
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
|
@ -122,6 +127,18 @@ var (
|
|||
RunE: removeNodesCmd,
|
||||
}
|
||||
|
||||
setConfig = &cobra.Command{
|
||||
Use: "set-config key1=val1 [key2=val2 ...]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: "Add/update global config value in the FrostFS network",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
},
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: setConfigCmd,
|
||||
}
|
||||
|
||||
setPolicy = &cobra.Command{
|
||||
Use: "set-policy [ExecFeeFactor=<n1>] [StoragePrice=<n2>] [FeePerByte=<n3>]",
|
||||
DisableFlagsInUseLine: true,
|
||||
|
@ -131,6 +148,9 @@ var (
|
|||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
},
|
||||
RunE: setPolicyCmd,
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return []string{"ExecFeeFactor=", "StoragePrice=", "FeePerByte="}, cobra.ShellCompDirectiveNoSpace
|
||||
},
|
||||
}
|
||||
|
||||
dumpContractHashesCmd = &cobra.Command{
|
||||
|
@ -144,7 +164,7 @@ var (
|
|||
|
||||
dumpNetworkConfigCmd = &cobra.Command{
|
||||
Use: "dump-config",
|
||||
Short: "Dump NeoFS network config",
|
||||
Short: "Dump FrostFS network config",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
},
|
||||
|
@ -162,7 +182,7 @@ var (
|
|||
|
||||
updateContractsCmd = &cobra.Command{
|
||||
Use: "update-contracts",
|
||||
Short: "Update NeoFS contracts",
|
||||
Short: "Update FrostFS contracts",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
|
@ -172,7 +192,7 @@ var (
|
|||
|
||||
dumpContainersCmd = &cobra.Command{
|
||||
Use: "dump-containers",
|
||||
Short: "Dump NeoFS containers to file",
|
||||
Short: "Dump FrostFS containers to file",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
},
|
||||
|
@ -181,7 +201,7 @@ var (
|
|||
|
||||
restoreContainersCmd = &cobra.Command{
|
||||
Use: "restore-containers",
|
||||
Short: "Restore NeoFS containers from file",
|
||||
Short: "Restore FrostFS containers from file",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
|
@ -189,6 +209,15 @@ var (
|
|||
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{
|
||||
Use: "deposit-notary",
|
||||
Short: "Deposit GAS for notary service",
|
||||
|
@ -197,86 +226,114 @@ var (
|
|||
},
|
||||
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() {
|
||||
RootCmd.AddCommand(generateAlphabetCmd)
|
||||
generateAlphabetCmd.Flags().String(alphabetWalletsFlag, "", "path to alphabet wallets dir")
|
||||
generateAlphabetCmd.Flags().Uint(alphabetSizeFlag, 7, "amount of alphabet wallets to generate")
|
||||
generateAlphabetCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
|
||||
generateAlphabetCmd.Flags().Uint(alphabetSizeFlag, 7, "Amount of alphabet wallets to generate")
|
||||
|
||||
RootCmd.AddCommand(initCmd)
|
||||
initCmd.Flags().String(alphabetWalletsFlag, "", "path to alphabet wallets dir")
|
||||
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 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().String(contractsInitFlag, "", "Path to archive with compiled FrostFS contracts (default fetched from latest github release)")
|
||||
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")
|
||||
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().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")
|
||||
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().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().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().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")
|
||||
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().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)")
|
||||
updateContractsCmd.Flags().String(contractsInitFlag, "", "Path to archive with compiled FrostFS 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")
|
||||
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().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")
|
||||
restoreContainersCmd.Flags().String(containerDumpFlag, "", "File to restore containers from")
|
||||
restoreContainersCmd.Flags().StringSlice(containerIDsFlag, nil, "Containers to restore")
|
||||
|
||||
RootCmd.AddCommand(listContainersCmd)
|
||||
listContainersCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
||||
listContainersCmd.Flags().String(containerContractFlag, "", "Container contract hash (for networks without NNS)")
|
||||
|
||||
RootCmd.AddCommand(refillGasCmd)
|
||||
refillGasCmd.Flags().String(alphabetWalletsFlag, "", "path to alphabet wallets dir")
|
||||
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(refillGasAmountFlag, "", "additional amount of GAS to transfer")
|
||||
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)
|
||||
|
||||
RootCmd.AddCommand(depositNotaryCmd)
|
||||
depositNotaryCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
||||
depositNotaryCmd.Flags().String(storageWalletFlag, "", "path to storage node wallet")
|
||||
depositNotaryCmd.Flags().String(walletAccountFlag, "", "wallet account address")
|
||||
depositNotaryCmd.Flags().String(refillGasAmountFlag, "", "amount of GAS to deposit")
|
||||
depositNotaryCmd.Flags().String(notaryDepositTillFlag, "", "notary deposit duration in blocks")
|
||||
depositNotaryCmd.Flags().String(storageWalletFlag, "", "Path to storage node wallet")
|
||||
depositNotaryCmd.Flags().String(walletAccountFlag, "", "Wallet account address")
|
||||
depositNotaryCmd.Flags().String(refillGasAmountFlag, "", "Amount of GAS to deposit")
|
||||
depositNotaryCmd.Flags().String(notaryDepositTillFlag, "", "Notary deposit duration in blocks")
|
||||
|
||||
RootCmd.AddCommand(netmapCandidatesCmd)
|
||||
netmapCandidatesCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
||||
}
|
|
@ -5,6 +5,12 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/internal"
|
||||
"github.com/TrueCloudLab/frostfs-node/pkg/morph/client"
|
||||
"github.com/TrueCloudLab/frostfs-node/pkg/util/rand"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/subnet"
|
||||
subnetid "github.com/TrueCloudLab/frostfs-sdk-go/subnet/id"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"github.com/nspcc-dev/neo-go/cli/flags"
|
||||
"github.com/nspcc-dev/neo-go/cli/input"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
|
@ -12,19 +18,14 @@ import (
|
|||
"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"
|
||||
neogocli "github.com/nspcc-dev/neo-go/pkg/rpc/client"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"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/wallet"
|
||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-adm/internal/modules/morph/internal"
|
||||
"github.com/nspcc-dev/neofs-node/pkg/morph/client"
|
||||
"github.com/nspcc-dev/neofs-node/pkg/util/rand"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/subnet"
|
||||
subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/user"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
@ -38,7 +39,7 @@ func viperBindFlags(cmd *cobra.Command, flags ...string) {
|
|||
// subnet command section.
|
||||
var cmdSubnet = &cobra.Command{
|
||||
Use: "subnet",
|
||||
Short: "NeoFS subnet management",
|
||||
Short: "FrostFS subnet management",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
viperBindFlags(cmd,
|
||||
endpointFlag,
|
||||
|
@ -48,14 +49,10 @@ var cmdSubnet = &cobra.Command{
|
|||
|
||||
// shared flags of cmdSubnet sub-commands.
|
||||
const (
|
||||
// subnet identifier
|
||||
flagSubnet = "subnet"
|
||||
// subnet client group ID
|
||||
flagSubnetGroup = "group"
|
||||
// filepath to wallet
|
||||
flagSubnetWallet = "wallet"
|
||||
// address in the wallet, optional
|
||||
flagSubnetAddress = "address"
|
||||
flagSubnet = "subnet" // subnet identifier
|
||||
flagSubnetGroup = "group" // subnet client group ID
|
||||
flagSubnetWallet = "wallet" // filepath to wallet
|
||||
flagSubnetAddress = "address" // address in the wallet, optional
|
||||
)
|
||||
|
||||
// reads wallet from the filepath configured in flagSubnetWallet flag,
|
||||
|
@ -115,7 +112,7 @@ func readSubnetKey(key *keys.PrivateKey) error {
|
|||
// create subnet command.
|
||||
var cmdSubnetCreate = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create NeoFS subnet",
|
||||
Short: "Create FrostFS subnet",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
viperBindFlags(cmd,
|
||||
flagSubnetWallet,
|
||||
|
@ -170,7 +167,7 @@ var cmdSubnetCreate = &cobra.Command{
|
|||
|
||||
// cmdSubnetRemove flags.
|
||||
const (
|
||||
// subnet ID to be removed
|
||||
// subnet ID to be removed.
|
||||
flagSubnetRemoveID = flagSubnet
|
||||
)
|
||||
|
||||
|
@ -180,7 +177,7 @@ var errZeroSubnet = errors.New("zero subnet")
|
|||
// remove subnet command.
|
||||
var cmdSubnetRemove = &cobra.Command{
|
||||
Use: "remove",
|
||||
Short: "Remove NeoFS subnet",
|
||||
Short: "Remove FrostFS subnet",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
viperBindFlags(cmd,
|
||||
flagSubnetWallet,
|
||||
|
@ -222,14 +219,14 @@ var cmdSubnetRemove = &cobra.Command{
|
|||
|
||||
// cmdSubnetGet flags.
|
||||
const (
|
||||
// subnet ID to be read
|
||||
// subnet ID to be read.
|
||||
flagSubnetGetID = flagSubnet
|
||||
)
|
||||
|
||||
// get subnet command.
|
||||
var cmdSubnetGet = &cobra.Command{
|
||||
Use: "get",
|
||||
Short: "Read information about the NeoFS subnet",
|
||||
Short: "Read information about the FrostFS subnet",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
viperBindFlags(cmd,
|
||||
flagSubnetGetID,
|
||||
|
@ -285,18 +282,15 @@ var cmdSubnetGet = &cobra.Command{
|
|||
|
||||
// cmdSubnetAdmin subnet flags.
|
||||
const (
|
||||
// subnet ID to be managed
|
||||
flagSubnetAdminSubnet = flagSubnet
|
||||
// admin public key
|
||||
flagSubnetAdminID = "admin"
|
||||
// manage client admins instead of node ones
|
||||
flagSubnetAdminClient = "client"
|
||||
flagSubnetAdminSubnet = flagSubnet // subnet ID to be managed
|
||||
flagSubnetAdminID = "admin" // admin public key
|
||||
flagSubnetAdminClient = "client" // manage client admins instead of node ones
|
||||
)
|
||||
|
||||
// command to manage subnet admins.
|
||||
var cmdSubnetAdmin = &cobra.Command{
|
||||
Use: "admin",
|
||||
Short: "Manage administrators of the NeoFS subnet",
|
||||
Short: "Manage administrators of the FrostFS subnet",
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
viperBindFlags(cmd,
|
||||
flagSubnetWallet,
|
||||
|
@ -309,8 +303,7 @@ var cmdSubnetAdmin = &cobra.Command{
|
|||
|
||||
// cmdSubnetAdminAdd flags.
|
||||
const (
|
||||
// client group ID
|
||||
flagSubnetAdminAddGroup = flagSubnetGroup
|
||||
flagSubnetAdminAddGroup = flagSubnetGroup // client group ID
|
||||
)
|
||||
|
||||
// common executor cmdSubnetAdminAdd and cmdSubnetAdminRemove commands.
|
||||
|
@ -347,7 +340,7 @@ func manageSubnetAdmins(cmd *cobra.Command, rm bool) error {
|
|||
}
|
||||
|
||||
// prepare call parameters
|
||||
prm := make([]interface{}, 0, 3)
|
||||
prm := make([]any, 0, 3)
|
||||
prm = append(prm, id.Marshal())
|
||||
|
||||
var method string
|
||||
|
@ -404,7 +397,7 @@ func manageSubnetAdmins(cmd *cobra.Command, rm bool) error {
|
|||
// command to add subnet admin.
|
||||
var cmdSubnetAdminAdd = &cobra.Command{
|
||||
Use: "add",
|
||||
Short: "Add admin to the NeoFS subnet",
|
||||
Short: "Add admin to the FrostFS subnet",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
viperBindFlags(cmd,
|
||||
flagSubnetAdminAddGroup,
|
||||
|
@ -419,7 +412,7 @@ var cmdSubnetAdminAdd = &cobra.Command{
|
|||
// command to remove subnet admin.
|
||||
var cmdSubnetAdminRemove = &cobra.Command{
|
||||
Use: "remove",
|
||||
Short: "Remove admin of the NeoFS subnet",
|
||||
Short: "Remove admin of the FrostFS subnet",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
viperBindFlags(cmd,
|
||||
flagSubnetAdminClient,
|
||||
|
@ -432,18 +425,15 @@ var cmdSubnetAdminRemove = &cobra.Command{
|
|||
|
||||
// cmdSubnetClient flags.
|
||||
const (
|
||||
// ID of the subnet to be managed
|
||||
flagSubnetClientSubnet = flagSubnet
|
||||
// client's NeoFS ID
|
||||
flagSubnetClientID = flagSubnetAdminClient
|
||||
// ID of the subnet client group
|
||||
flagSubnetClientGroup = flagSubnetGroup
|
||||
flagSubnetClientSubnet = flagSubnet // ID of the subnet to be managed
|
||||
flagSubnetClientID = flagSubnetAdminClient // client's NeoFS ID
|
||||
flagSubnetClientGroup = flagSubnetGroup // ID of the subnet client group
|
||||
)
|
||||
|
||||
// command to manage subnet clients.
|
||||
var cmdSubnetClient = &cobra.Command{
|
||||
Use: "client",
|
||||
Short: "Manage clients of the NeoFS subnet",
|
||||
Short: "Manage clients of the FrostFS subnet",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
viperBindFlags(cmd,
|
||||
flagSubnetWallet,
|
||||
|
@ -526,7 +516,7 @@ func manageSubnetClients(cmd *cobra.Command, rm bool) error {
|
|||
// command to add subnet client.
|
||||
var cmdSubnetClientAdd = &cobra.Command{
|
||||
Use: "add",
|
||||
Short: "Add client to the NeoFS subnet",
|
||||
Short: "Add client to the FrostFS subnet",
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
return manageSubnetClients(cmd, false)
|
||||
},
|
||||
|
@ -535,7 +525,7 @@ var cmdSubnetClientAdd = &cobra.Command{
|
|||
// command to remove subnet client.
|
||||
var cmdSubnetClientRemove = &cobra.Command{
|
||||
Use: "remove",
|
||||
Short: "Remove client of the NeoFS subnet",
|
||||
Short: "Remove client of the FrostFS subnet",
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
return manageSubnetClients(cmd, true)
|
||||
},
|
||||
|
@ -543,10 +533,8 @@ var cmdSubnetClientRemove = &cobra.Command{
|
|||
|
||||
// cmdSubnetNode flags.
|
||||
const (
|
||||
// node ID
|
||||
flagSubnetNode = "node"
|
||||
// ID of the subnet to be managed
|
||||
flagSubnetNodeSubnet = flagSubnet
|
||||
flagSubnetNode = "node" // node ID
|
||||
flagSubnetNodeSubnet = flagSubnet // ID of the subnet to be managed
|
||||
)
|
||||
|
||||
// common executor cmdSubnetNodeAdd and cmdSubnetNodeRemove commands.
|
||||
|
@ -610,7 +598,7 @@ func manageSubnetNodes(cmd *cobra.Command, rm bool) error {
|
|||
// command to manage subnet nodes.
|
||||
var cmdSubnetNode = &cobra.Command{
|
||||
Use: "node",
|
||||
Short: "Manage nodes of the NeoFS subnet",
|
||||
Short: "Manage nodes of the FrostFS subnet",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
viperBindFlags(cmd,
|
||||
flagSubnetWallet,
|
||||
|
@ -623,7 +611,7 @@ var cmdSubnetNode = &cobra.Command{
|
|||
// command to add subnet node.
|
||||
var cmdSubnetNodeAdd = &cobra.Command{
|
||||
Use: "add",
|
||||
Short: "Add node to the NeoFS subnet",
|
||||
Short: "Add node to the FrostFS subnet",
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
return manageSubnetNodes(cmd, false)
|
||||
},
|
||||
|
@ -632,7 +620,7 @@ var cmdSubnetNodeAdd = &cobra.Command{
|
|||
// command to remove subnet node.
|
||||
var cmdSubnetNodeRemove = &cobra.Command{
|
||||
Use: "remove",
|
||||
Short: "Remove node from the NeoFS subnet",
|
||||
Short: "Remove node from the FrostFS subnet",
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
return manageSubnetNodes(cmd, true)
|
||||
},
|
||||
|
@ -705,7 +693,7 @@ func init() {
|
|||
_ = cmdSubnetClient.MarkFlagRequired(flagSubnetClientSubnet)
|
||||
clientFlags.String(flagSubnetClientGroup, "", "ID of the client group to work with")
|
||||
_ = cmdSubnetClient.MarkFlagRequired(flagSubnetClientGroup)
|
||||
clientFlags.String(flagSubnetClientID, "", "Client's user ID in NeoFS system in text format")
|
||||
clientFlags.String(flagSubnetClientID, "", "Client's user ID in FrostFS system in text format")
|
||||
_ = cmdSubnetClient.MarkFlagRequired(flagSubnetClientID)
|
||||
clientFlags.StringP(flagSubnetWallet, "w", "", "Path to file with wallet")
|
||||
_ = cmdSubnetClient.MarkFlagRequired(flagSubnetWallet)
|
||||
|
@ -754,7 +742,7 @@ func init() {
|
|||
)
|
||||
}
|
||||
|
||||
func testInvokeMethod(key keys.PrivateKey, method string, args ...interface{}) ([]stackitem.Item, error) {
|
||||
func testInvokeMethod(key keys.PrivateKey, method string, args ...any) ([]stackitem.Item, error) {
|
||||
c, err := getN3Client(viper.GetViper())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("morph client creation: %w", err)
|
||||
|
@ -765,11 +753,6 @@ func testInvokeMethod(key keys.PrivateKey, method string, args ...interface{}) (
|
|||
return nil, fmt.Errorf("NNS contract resolving: %w", err)
|
||||
}
|
||||
|
||||
subnetHash, err := nnsResolveHash(c, nnsCs.Hash, subnetContract+".neofs")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("subnet hash resolving: %w", err)
|
||||
}
|
||||
|
||||
cosigner := []transaction.Signer{
|
||||
{
|
||||
Account: key.PublicKey().GetScriptHash(),
|
||||
|
@ -777,7 +760,14 @@ func testInvokeMethod(key keys.PrivateKey, method string, args ...interface{}) (
|
|||
},
|
||||
}
|
||||
|
||||
res, err := invokeFunction(c, subnetHash, method, args, cosigner)
|
||||
inv := invoker.New(c, cosigner)
|
||||
|
||||
subnetHash, err := nnsResolveHash(inv, nnsCs.Hash, subnetContract+".frostfs")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("subnet hash resolving: %w", err)
|
||||
}
|
||||
|
||||
res, err := inv.Call(subnetHash, method, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invocation parameters prepararion: %w", err)
|
||||
}
|
||||
|
@ -790,7 +780,7 @@ func testInvokeMethod(key keys.PrivateKey, method string, args ...interface{}) (
|
|||
return res.Stack, nil
|
||||
}
|
||||
|
||||
func invokeMethod(key keys.PrivateKey, tryNotary bool, method string, args ...interface{}) error {
|
||||
func invokeMethod(key keys.PrivateKey, tryNotary bool, method string, args ...any) error {
|
||||
c, err := getN3Client(viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("morph client creation: %w", err)
|
||||
|
@ -831,17 +821,12 @@ func invokeMethod(key keys.PrivateKey, tryNotary bool, method string, args ...in
|
|||
return nil
|
||||
}
|
||||
|
||||
func invokeNonNotary(c Client, key keys.PrivateKey, method string, args ...interface{}) error {
|
||||
func invokeNonNotary(c Client, key keys.PrivateKey, method string, args ...any) error {
|
||||
nnsCs, err := c.GetContractStateByID(1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("NNS contract resolving: %w", err)
|
||||
}
|
||||
|
||||
subnetHash, err := nnsResolveHash(c, nnsCs.Hash, subnetContract+".neofs")
|
||||
if err != nil {
|
||||
return fmt.Errorf("subnet hash resolving: %w", err)
|
||||
}
|
||||
|
||||
acc := wallet.NewAccountFromPrivateKey(&key)
|
||||
|
||||
cosigner := []transaction.Signer{
|
||||
|
@ -851,14 +836,21 @@ func invokeNonNotary(c Client, key keys.PrivateKey, method string, args ...inter
|
|||
},
|
||||
}
|
||||
|
||||
cosignerAcc := []neogocli.SignerAccount{
|
||||
cosignerAcc := []rpcclient.SignerAccount{
|
||||
{
|
||||
Signer: cosigner[0],
|
||||
Account: acc,
|
||||
},
|
||||
}
|
||||
|
||||
test, err := invokeFunction(c, subnetHash, method, args, cosigner)
|
||||
inv := invoker.New(c, cosigner)
|
||||
|
||||
subnetHash, err := nnsResolveHash(inv, nnsCs.Hash, subnetContract+".frostfs")
|
||||
if err != nil {
|
||||
return fmt.Errorf("subnet hash resolving: %w", err)
|
||||
}
|
||||
|
||||
test, err := inv.Call(subnetHash, method, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("test invocation: %w", err)
|
||||
}
|
||||
|
@ -876,17 +868,12 @@ func invokeNonNotary(c Client, key keys.PrivateKey, method string, args ...inter
|
|||
return nil
|
||||
}
|
||||
|
||||
func invokeNotary(c Client, key keys.PrivateKey, method string, notaryHash util.Uint160, args ...interface{}) error {
|
||||
func invokeNotary(c Client, key keys.PrivateKey, method string, notaryHash util.Uint160, args ...any) error {
|
||||
nnsCs, err := c.GetContractStateByID(1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("NNS contract resolving: %w", err)
|
||||
}
|
||||
|
||||
subnetHash, err := nnsResolveHash(c, nnsCs.Hash, subnetContract+".neofs")
|
||||
if err != nil {
|
||||
return fmt.Errorf("subnet hash resolving: %w", err)
|
||||
}
|
||||
|
||||
alphabet, err := c.GetCommittee()
|
||||
if err != nil {
|
||||
return fmt.Errorf("alphabet list: %w", err)
|
||||
|
@ -902,8 +889,15 @@ func invokeNotary(c Client, key keys.PrivateKey, method string, notaryHash util.
|
|||
return fmt.Errorf("cosigners collecting: %w", err)
|
||||
}
|
||||
|
||||
inv := invoker.New(c, cosigners)
|
||||
|
||||
subnetHash, err := nnsResolveHash(inv, nnsCs.Hash, subnetContract+".frostfs")
|
||||
if err != nil {
|
||||
return fmt.Errorf("subnet hash resolving: %w", err)
|
||||
}
|
||||
|
||||
// make test invocation of the method
|
||||
test, err := invokeFunction(c, subnetHash, method, args, cosigners)
|
||||
test, err := inv.Call(subnetHash, method, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("test invocation: %w", err)
|
||||
}
|
||||
|
@ -974,7 +968,7 @@ func invokeNotary(c Client, key keys.PrivateKey, method string, notaryHash util.
|
|||
|
||||
func notaryCosigners(c Client, notaryHash util.Uint160, nnsCs *state.Contract,
|
||||
key keys.PrivateKey, alphabetAccount util.Uint160) ([]transaction.Signer, error) {
|
||||
proxyHash, err := nnsResolveHash(c, nnsCs.Hash, proxyContract+".neofs")
|
||||
proxyHash, err := nnsResolveHash(invoker.New(c, nil), nnsCs.Hash, proxyContract+".frostfs")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proxy hash resolving: %w", err)
|
||||
}
|
85
cmd/frostfs-adm/internal/modules/root.go
Normal file
85
cmd/frostfs-adm/internal/modules/root.go
Normal file
|
@ -0,0 +1,85 @@
|
|||
package modules
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph"
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/storagecfg"
|
||||
"github.com/TrueCloudLab/frostfs-node/misc"
|
||||
"github.com/TrueCloudLab/frostfs-node/pkg/util/autocomplete"
|
||||
utilConfig "github.com/TrueCloudLab/frostfs-node/pkg/util/config"
|
||||
"github.com/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))
|
||||
}
|
||||
|
||||
func Execute() error {
|
||||
return rootCmd.Execute()
|
||||
}
|
||||
|
||||
func entryPoint(cmd *cobra.Command, args []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
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ node:
|
|||
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)
|
||||
entries: [] # list of IDs of subnets to enter in a text format of FrostFS API protocol (overrides corresponding attributes)
|
||||
|
||||
grpc:
|
||||
num: 1 # total number of listener endpoints
|
||||
|
@ -37,39 +37,37 @@ control:
|
|||
|
||||
morph:
|
||||
dial_timeout: 20s # timeout for side chain NEO RPC client connection
|
||||
disable_cache: false # use TTL cache for side chain GET operations
|
||||
cache_ttl: 15s # use TTL cache for side chain GET operations
|
||||
rpc_endpoint: # side chain N3 RPC endpoints
|
||||
{{- range .MorphRPC }}
|
||||
- wss://{{.}}/ws{{end}}
|
||||
- address: wss://{{.}}/ws{{end}}
|
||||
{{if not .Relay }}
|
||||
storage:
|
||||
shard_pool_size: 15 # size of per-shard worker pools used for PUT operations
|
||||
shard_num: 1 # total number of shards
|
||||
|
||||
default: # section with the default shard parameters
|
||||
metabase:
|
||||
perm: 0644 # permissions for metabase files(directories: +x for current user and group)
|
||||
|
||||
blobstor:
|
||||
perm: 0644 # permissions for blobstor files(directories: +x for current user and group)
|
||||
depth: 2 # max depth of object tree storage in FS
|
||||
small_object_size: 102400 # 100KiB, size threshold for "small" objects which are stored in key-value DB, not in FS, bytes
|
||||
compress: true # turn on/off Zstandard compression (level 3) of stored objects
|
||||
compression_exclude_content_types:
|
||||
- audio/*
|
||||
- video/*
|
||||
|
||||
blobovnicza:
|
||||
size: 1073741824 # approximate size limit of single blobovnicza instance, total size will be: size*width^(depth+1), bytes
|
||||
depth: 1 # max depth of object tree storage in key-value DB
|
||||
width: 4 # max width of object tree storage in key-value DB
|
||||
opened_cache_capacity: 50 # maximum number of opened database files
|
||||
|
||||
gc:
|
||||
remover_batch_size: 200 # number of objects to be removed by the garbage collector
|
||||
remover_sleep_interval: 5m # frequency of the garbage collector invocation
|
||||
|
||||
shard:
|
||||
default: # section with the default shard parameters
|
||||
metabase:
|
||||
perm: 0644 # permissions for metabase files(directories: +x for current user and group)
|
||||
|
||||
blobstor:
|
||||
perm: 0644 # permissions for blobstor files(directories: +x for current user and group)
|
||||
depth: 2 # max depth of object tree storage in FS
|
||||
small_object_size: 102400 # 100KiB, size threshold for "small" objects which are stored in key-value DB, not in FS, bytes
|
||||
compress: true # turn on/off Zstandard compression (level 3) of stored objects
|
||||
compression_exclude_content_types:
|
||||
- audio/*
|
||||
- video/*
|
||||
|
||||
blobovnicza:
|
||||
size: 1073741824 # approximate size limit of single blobovnicza instance, total size will be: size*width^(depth+1), bytes
|
||||
depth: 1 # max depth of object tree storage in key-value DB
|
||||
width: 4 # max width of object tree storage in key-value DB
|
||||
opened_cache_capacity: 50 # maximum number of opened database files
|
||||
|
||||
gc:
|
||||
remover_batch_size: 200 # number of objects to be removed by the garbage collector
|
||||
remover_sleep_interval: 5m # frequency of the garbage collector invocation
|
||||
0:
|
||||
mode: "read-write" # mode of the shard, must be one of the: "read-write" (default), "read-only"
|
||||
|
|
@ -6,7 +6,6 @@ import (
|
|||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/url"
|
||||
|
@ -17,20 +16,20 @@ import (
|
|||
"text/template"
|
||||
"time"
|
||||
|
||||
netutil "github.com/TrueCloudLab/frostfs-node/pkg/network"
|
||||
"github.com/chzyer/readline"
|
||||
"github.com/nspcc-dev/neo-go/cli/flags"
|
||||
"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/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"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/nep17"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
netutil "github.com/nspcc-dev/neofs-node/pkg/network"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
@ -41,8 +40,8 @@ const (
|
|||
)
|
||||
|
||||
const (
|
||||
defaultControlEndpoint = "127.0.0.1:8090"
|
||||
defaultDataEndpoint = "127.0.0.1"
|
||||
defaultControlEndpoint = "localhost:8090"
|
||||
defaultDataEndpoint = "localhost"
|
||||
)
|
||||
|
||||
// RootCmd is a root command of config section.
|
||||
|
@ -55,8 +54,8 @@ var RootCmd = &cobra.Command{
|
|||
func init() {
|
||||
fs := RootCmd.Flags()
|
||||
|
||||
fs.StringP(walletFlag, "w", "", "path to wallet")
|
||||
fs.StringP(accountFlag, "a", "", "wallet account")
|
||||
fs.StringP(walletFlag, "w", "", "Path to wallet")
|
||||
fs.StringP(accountFlag, "a", "", "Wallet account")
|
||||
}
|
||||
|
||||
type config struct {
|
||||
|
@ -91,7 +90,7 @@ func storageConfig(cmd *cobra.Command, args []string) {
|
|||
}
|
||||
}
|
||||
|
||||
historyPath := filepath.Join(os.TempDir(), "neofs-adm.history")
|
||||
historyPath := filepath.Join(os.TempDir(), "frostfs-adm.history")
|
||||
readline.SetHistoryPath(historyPath)
|
||||
|
||||
var c config
|
||||
|
@ -154,7 +153,7 @@ func storageConfig(cmd *cobra.Command, args []string) {
|
|||
validator := netutil.Address{}
|
||||
err := validator.FromString(c.AnnouncedAddress)
|
||||
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://github.com/TrueCloudLab/frostfs-node/blob/master/pkg/network/address.go for details.")
|
||||
continue
|
||||
}
|
||||
uriAddr, err := url.Parse(validator.URIAddr())
|
||||
|
@ -210,7 +209,7 @@ func storageConfig(cmd *cobra.Command, args []string) {
|
|||
out := applyTemplate(c)
|
||||
fatalOnErr(os.WriteFile(outPath, out, 0644))
|
||||
|
||||
cmd.Println("Node is ready for work! Run `neofs-node -config " + outPath + "`")
|
||||
cmd.Println("Node is ready for work! Run `frostfs-node -config " + outPath + "`")
|
||||
}
|
||||
|
||||
func getWalletAccount(w *wallet.Wallet, prompt string) string {
|
||||
|
@ -322,23 +321,17 @@ func depositGas(cmd *cobra.Command, acc *wallet.Account, network string) {
|
|||
sideClient := initClient(n3config[network].MorphRPC)
|
||||
balanceHash, _ := util.Uint160DecodeStringLE(n3config[network].BalanceContract)
|
||||
|
||||
res, err := sideClient.InvokeFunction(balanceHash, "balanceOf", []smartcontract.Parameter{{
|
||||
Type: smartcontract.Hash160Type,
|
||||
Value: acc.Contract.ScriptHash(),
|
||||
}}, nil)
|
||||
fatalOnErr(err)
|
||||
|
||||
if res.State != vm.HaltState.String() {
|
||||
fatalOnErr(fmt.Errorf("invalid response from balance contract: %s", res.FaultException))
|
||||
sideActor, err := actor.NewSimple(sideClient, acc)
|
||||
if err != nil {
|
||||
fatalOnErr(fmt.Errorf("creating actor over side chain client: %w", err))
|
||||
}
|
||||
|
||||
var balance *big.Int
|
||||
if len(res.Stack) != 0 {
|
||||
balance, _ = res.Stack[0].TryInteger()
|
||||
}
|
||||
sideGas := nep17.NewReader(sideActor, balanceHash)
|
||||
accSH := acc.Contract.ScriptHash()
|
||||
|
||||
if balance == nil {
|
||||
fatalOnErr(errors.New("invalid response from balance contract"))
|
||||
balance, err := sideGas.BalanceOf(accSH)
|
||||
if err != nil {
|
||||
fatalOnErr(fmt.Errorf("side chain balance: %w", err))
|
||||
}
|
||||
|
||||
ok := getConfirmation(false, fmt.Sprintf("Current NeoFS balance is %s, make a deposit? y/[n]: ",
|
||||
|
@ -356,14 +349,17 @@ func depositGas(cmd *cobra.Command, acc *wallet.Account, network string) {
|
|||
mainClient := initClient(n3config[network].RPC)
|
||||
neofsHash, _ := util.Uint160DecodeStringLE(n3config[network].NeoFSContract)
|
||||
|
||||
gasHash, err := mainClient.GetNativeContractHash(nativenames.Gas)
|
||||
fatalOnErr(err)
|
||||
mainActor, err := actor.NewSimple(mainClient, acc)
|
||||
if err != nil {
|
||||
fatalOnErr(fmt.Errorf("creating actor over main chain client: %w", err))
|
||||
}
|
||||
|
||||
tx, err := mainClient.CreateNEP17TransferTx(acc, neofsHash, gasHash, amount.Int64(), 0, nil, nil)
|
||||
fatalOnErr(err)
|
||||
mainGas := nep17.New(mainActor, gas.Hash)
|
||||
|
||||
txHash, err := mainClient.SignAndPushTx(tx, acc, nil)
|
||||
fatalOnErr(err)
|
||||
txHash, _, err := mainGas.Transfer(accSH, neofsHash, amount, nil)
|
||||
if err != nil {
|
||||
fatalOnErr(fmt.Errorf("sending TX to the NeoFS contract: %w", err))
|
||||
}
|
||||
|
||||
cmd.Print("Waiting for transactions to persist.")
|
||||
tick := time.NewTicker(time.Second / 2)
|
||||
|
@ -394,8 +390,8 @@ loop:
|
|||
}
|
||||
}
|
||||
|
||||
func initClient(rpc []string) *client.Client {
|
||||
var c *client.Client
|
||||
func initClient(rpc []string) *rpcclient.Client {
|
||||
var c *rpcclient.Client
|
||||
var err error
|
||||
|
||||
shuffled := make([]string, len(rpc))
|
||||
|
@ -403,7 +399,7 @@ func initClient(rpc []string) *client.Client {
|
|||
rand.Shuffle(len(shuffled), func(i, j int) { shuffled[i], shuffled[j] = shuffled[j], shuffled[i] })
|
||||
|
||||
for _, endpoint := range shuffled {
|
||||
c, err = client.New(context.Background(), "https://"+endpoint, client.Options{
|
||||
c, err = rpcclient.New(context.Background(), "https://"+endpoint, rpcclient.Options{
|
||||
DialTimeout: time.Second * 2,
|
||||
RequestTimeout: time.Second * 5,
|
||||
})
|
|
@ -3,7 +3,7 @@ package main
|
|||
import (
|
||||
"os"
|
||||
|
||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-adm/internal/modules"
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules"
|
||||
)
|
||||
|
||||
func main() {
|
75
cmd/frostfs-cli/docs/sessions.md
Normal file
75
cmd/frostfs-cli/docs/sessions.md
Normal file
|
@ -0,0 +1,75 @@
|
|||
# How FrostFS CLI uses session mechanism of the FrostFS
|
||||
|
||||
## Overview
|
||||
|
||||
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
|
||||
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
|
||||
the session. The token contains information which limits power of attorney like
|
||||
action context or lifetime.
|
||||
|
||||
The client confirms trust in a third party by signing its public (session) key
|
||||
with his private key. Any operation signed using private session key with
|
||||
attached session token is treated as performed by the original client.
|
||||
|
||||
## Types
|
||||
|
||||
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
|
||||
one (2).
|
||||
|
||||
### Dynamic
|
||||
|
||||
For case (1) CLI user can only open dynamic sessions. Protocol call
|
||||
`SessionService.Create` is used for this purpose. As a result of the call, a
|
||||
private session key will be generated on the server, thus making the remote
|
||||
server trusted. This type of session is useful when the client needs to
|
||||
transfer part of the responsibility for the formation of strict system elements
|
||||
to the trusted server. At the moment, the approach is applicable only to
|
||||
creating objects.
|
||||
|
||||
```shell
|
||||
$ frostfs-cli session create --rpc-endpoint <server_ip> --out ./blank_token
|
||||
```
|
||||
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.
|
||||
Later this token can be attached to the operations which support dynamic
|
||||
sessions. Then the token will be finally formed and signed by CLI itself.
|
||||
|
||||
### Static
|
||||
|
||||
For case (2) CLI user can act on behalf of the person who issued the session
|
||||
token to him. Unlike (1) the token must be fully prepared on the side of the
|
||||
original client, and the CLI uses it only for reading. Ready token MUST have:
|
||||
- correct context (object, container, etc.)
|
||||
- valid lifetime
|
||||
- public session key corresponding to the CLI key
|
||||
- valid client signature
|
||||
|
||||
To sign the session token, exec:
|
||||
```shell
|
||||
$ frostfs-cli --wallet <client_wallet> util sign session-token --from ./blank_token --to ./token
|
||||
```
|
||||
Once the token is signed, it MUST NOT be modified.
|
||||
|
||||
## Commands
|
||||
|
||||
### Object
|
||||
|
||||
Here are sub-commands of `object` command which support only dynamic sessions (1):
|
||||
- `put`
|
||||
- `delete`
|
||||
- `lock`
|
||||
|
||||
These commands accept blank token of the dynamically opened session or open
|
||||
session internally if it has not been opened yet.
|
||||
|
||||
All other `object` sub-commands support only static sessions (2).
|
||||
|
||||
### Container
|
||||
|
||||
List of commands supporting sessions (static only):
|
||||
- `create`
|
||||
- `delete`
|
||||
- `set-eacl`
|
34
cmd/frostfs-cli/docs/storage-node-xheaders.md
Normal file
34
cmd/frostfs-cli/docs/storage-node-xheaders.md
Normal file
|
@ -0,0 +1,34 @@
|
|||
# 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 `__FROSTFS__` prefix that
|
||||
affect system behaviour. For backward compatibility, the same set of
|
||||
"well-known" headers may also use `__NEOFS__` prefix:
|
||||
|
||||
* `__FROSTFS__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.
|
||||
* `__FROSTFS__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 `__FROSTFS__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`
|
||||
* `storagegroup delete/get/list/put`
|
||||
|
||||
Example:
|
||||
```shell
|
||||
$ frostfs-cli object put -r s01.frostfs.devenv:8080 -w wallet.json --cid CID --file FILE --xhdr "__FROSTFS__NETMAP_EPOCH=777"
|
||||
```
|
|
@ -7,15 +7,15 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/nspcc-dev/neofs-sdk-go/accounting"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/client"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/container"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/eacl"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/object"
|
||||
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/version"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/accounting"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/client"
|
||||
containerSDK "github.com/TrueCloudLab/frostfs-sdk-go/container"
|
||||
cid "github.com/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "github.com/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/version"
|
||||
)
|
||||
|
||||
// BalanceOfPrm groups parameters of BalanceOf operation.
|
||||
|
@ -30,11 +30,11 @@ type BalanceOfRes struct {
|
|||
}
|
||||
|
||||
// Balance returns the current balance.
|
||||
func (x BalanceOfRes) Balance() *accounting.Decimal {
|
||||
func (x BalanceOfRes) Balance() accounting.Decimal {
|
||||
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.
|
||||
func BalanceOf(prm BalanceOfPrm) (res BalanceOfRes, err error) {
|
||||
|
@ -59,7 +59,7 @@ func (x ListContainersRes) IDList() []cid.ID {
|
|||
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.
|
||||
func ListContainers(prm ListContainersPrm) (res ListContainersRes, err error) {
|
||||
|
@ -84,7 +84,7 @@ func (x PutContainerRes) ID() cid.ID {
|
|||
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.
|
||||
// The required time is also not predictable.
|
||||
|
@ -95,12 +95,7 @@ func (x PutContainerRes) ID() cid.ID {
|
|||
func PutContainer(prm PutContainerPrm) (res PutContainerRes, err error) {
|
||||
cliRes, err := prm.cli.ContainerPut(context.Background(), prm.PrmContainerPut)
|
||||
if err == nil {
|
||||
cnr := cliRes.ID()
|
||||
if cnr == nil {
|
||||
err = errors.New("missing container ID in response")
|
||||
} else {
|
||||
res.cnr = *cnr
|
||||
}
|
||||
res.cnr = cliRes.ID()
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -123,11 +118,11 @@ type GetContainerRes struct {
|
|||
}
|
||||
|
||||
// Container returns structured of the requested container.
|
||||
func (x GetContainerRes) Container() container.Container {
|
||||
func (x GetContainerRes) Container() containerSDK.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.
|
||||
func GetContainer(prm GetContainerPrm) (res GetContainerRes, err error) {
|
||||
|
@ -136,6 +131,21 @@ func GetContainer(prm GetContainerPrm) (res GetContainerRes, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// IsACLExtendable checks if ACL of the container referenced by the given identifier
|
||||
// can be extended. Client connection MUST BE correctly established in advance.
|
||||
func IsACLExtendable(c *client.Client, cnr cid.ID) (bool, error) {
|
||||
var prm GetContainerPrm
|
||||
prm.SetClient(c)
|
||||
prm.SetContainer(cnr)
|
||||
|
||||
res, err := GetContainer(prm)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("get container from the FrostFS: %w", err)
|
||||
}
|
||||
|
||||
return res.Container().BasicACL().Extendable(), nil
|
||||
}
|
||||
|
||||
// DeleteContainerPrm groups parameters of DeleteContainerPrm operation.
|
||||
type DeleteContainerPrm struct {
|
||||
commonPrm
|
||||
|
@ -145,7 +155,7 @@ type DeleteContainerPrm struct {
|
|||
// DeleteContainerRes groups the resulting values of DeleteContainer operation.
|
||||
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.
|
||||
// The required time is also not predictable.
|
||||
|
@ -171,11 +181,11 @@ type EACLRes struct {
|
|||
}
|
||||
|
||||
// EACL returns requested eACL table.
|
||||
func (x EACLRes) EACL() *eacl.Table {
|
||||
func (x EACLRes) EACL() eacl.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.
|
||||
func EACL(prm EACLPrm) (res EACLRes, err error) {
|
||||
|
@ -193,7 +203,7 @@ type SetEACLPrm struct {
|
|||
// SetEACLRes groups the resulting values of SetEACL operation.
|
||||
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.
|
||||
// The required time is also not predictable.
|
||||
|
@ -218,12 +228,12 @@ type NetworkInfoRes struct {
|
|||
cliRes *client.ResNetworkInfo
|
||||
}
|
||||
|
||||
// NetworkInfo returns structured information about the NeoFS network.
|
||||
func (x NetworkInfoRes) NetworkInfo() *netmap.NetworkInfo {
|
||||
// NetworkInfo returns structured information about the FrostFS network.
|
||||
func (x NetworkInfoRes) NetworkInfo() netmap.NetworkInfo {
|
||||
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.
|
||||
func NetworkInfo(prm NetworkInfoPrm) (res NetworkInfoRes, err error) {
|
||||
|
@ -245,15 +255,15 @@ type NodeInfoRes struct {
|
|||
|
||||
// NodeInfo returns information about the node from netmap.
|
||||
func (x NodeInfoRes) NodeInfo() netmap.NodeInfo {
|
||||
return *x.cliRes.NodeInfo()
|
||||
return x.cliRes.NodeInfo()
|
||||
}
|
||||
|
||||
// LatestVersion returns the latest NeoFS API version in use.
|
||||
func (x NodeInfoRes) LatestVersion() *version.Version {
|
||||
// LatestVersion returns the latest FrostFS API version in use.
|
||||
func (x NodeInfoRes) LatestVersion() version.Version {
|
||||
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.
|
||||
func NodeInfo(prm NodeInfoPrm) (res NodeInfoRes, err error) {
|
||||
|
@ -262,6 +272,29 @@ func NodeInfo(prm NodeInfoPrm) (res NodeInfoRes, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// NetMapSnapshotPrm groups parameters of NetMapSnapshot operation.
|
||||
type NetMapSnapshotPrm struct {
|
||||
commonPrm
|
||||
}
|
||||
|
||||
// NetMapSnapshotRes groups the resulting values of NetMapSnapshot operation.
|
||||
type NetMapSnapshotRes struct {
|
||||
cliRes *client.ResNetMapSnapshot
|
||||
}
|
||||
|
||||
// NetMap returns current local snapshot of the FrostFS network map.
|
||||
func (x NetMapSnapshotRes) NetMap() netmap.NetMap {
|
||||
return x.cliRes.NetMap()
|
||||
}
|
||||
|
||||
// NetMapSnapshot requests current network view of the remote server.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func NetMapSnapshot(prm NetMapSnapshotPrm) (res NetMapSnapshotRes, err error) {
|
||||
res.cliRes, err = prm.cli.NetMapSnapshot(context.Background(), client.PrmNetMapSnapshot{})
|
||||
return
|
||||
}
|
||||
|
||||
// CreateSessionPrm groups parameters of CreateSession operation.
|
||||
type CreateSessionPrm struct {
|
||||
commonPrm
|
||||
|
@ -329,31 +362,31 @@ func (x PutObjectRes) ID() oid.ID {
|
|||
return x.id
|
||||
}
|
||||
|
||||
// PutObject saves the object in NeoFS network.
|
||||
// PutObject saves the object in FrostFS network.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func PutObject(prm PutObjectPrm) (*PutObjectRes, error) {
|
||||
var putPrm client.PrmObjectPutInit
|
||||
|
||||
if prm.sessionToken != nil {
|
||||
putPrm.WithinSession(*prm.sessionToken)
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, fmt.Errorf("init object writing: %w", err)
|
||||
}
|
||||
|
||||
if prm.sessionToken != nil {
|
||||
wrt.WithinSession(*prm.sessionToken)
|
||||
}
|
||||
|
||||
if prm.bearerToken != nil {
|
||||
wrt.WithBearerToken(*prm.bearerToken)
|
||||
}
|
||||
|
||||
if prm.local {
|
||||
wrt.MarkLocal()
|
||||
}
|
||||
|
||||
wrt.WithXHeaders(prm.xHeaders...)
|
||||
|
||||
if wrt.WriteHeader(*prm.hdr) {
|
||||
if prm.headerCallback != nil {
|
||||
prm.headerCallback(prm.hdr)
|
||||
|
@ -371,8 +404,7 @@ func PutObject(prm PutObjectPrm) (*PutObjectRes, error) {
|
|||
}
|
||||
|
||||
if prm.rdr != nil {
|
||||
// TODO: (neofs-node#1198) explore better values or configure it
|
||||
const defaultBufferSizePut = 4096
|
||||
const defaultBufferSizePut = 3 << 20 // Maximum chunk size is 3 MiB in the SDK.
|
||||
|
||||
if sz == 0 || sz > defaultBufferSizePut {
|
||||
sz = defaultBufferSizePut
|
||||
|
@ -406,13 +438,9 @@ func PutObject(prm PutObjectPrm) (*PutObjectRes, error) {
|
|||
return nil, fmt.Errorf("client failure: %w", err)
|
||||
}
|
||||
|
||||
var res PutObjectRes
|
||||
|
||||
if !cliRes.ReadStoredObjectID(&res.id) {
|
||||
return nil, errors.New("missing ID of the stored object")
|
||||
}
|
||||
|
||||
return &res, nil
|
||||
return &PutObjectRes{
|
||||
id: cliRes.StoredObjectID(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteObjectPrm groups parameters of DeleteObject operation.
|
||||
|
@ -431,7 +459,7 @@ func (x DeleteObjectRes) Tombstone() oid.ID {
|
|||
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.
|
||||
func DeleteObject(prm DeleteObjectPrm) (*DeleteObjectRes, error) {
|
||||
|
@ -454,14 +482,8 @@ func DeleteObject(prm DeleteObjectPrm) (*DeleteObjectRes, error) {
|
|||
return nil, fmt.Errorf("remove object via client: %w", err)
|
||||
}
|
||||
|
||||
var id oid.ID
|
||||
|
||||
if !cliRes.ReadTombstoneID(&id) {
|
||||
return nil, errors.New("object removed but tombstone ID is missing")
|
||||
}
|
||||
|
||||
return &DeleteObjectRes{
|
||||
tomb: id,
|
||||
tomb: cliRes.Tombstone(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -553,7 +575,7 @@ type HeadObjectPrm struct {
|
|||
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) {
|
||||
x.mainOnly = v
|
||||
}
|
||||
|
@ -789,7 +811,7 @@ func (x *PayloadRangePrm) SetRange(rng *object.Range) {
|
|||
// PayloadRangeRes groups the resulting values of PayloadRange operation.
|
||||
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.
|
||||
//
|
||||
|
@ -833,3 +855,37 @@ func PayloadRange(prm PayloadRangePrm) (*PayloadRangeRes, error) {
|
|||
|
||||
return new(PayloadRangeRes), nil
|
||||
}
|
||||
|
||||
// SyncContainerPrm groups parameters of SyncContainerSettings operation.
|
||||
type SyncContainerPrm struct {
|
||||
commonPrm
|
||||
c *containerSDK.Container
|
||||
}
|
||||
|
||||
// SetContainer sets a container that is required to be synced.
|
||||
func (s *SyncContainerPrm) SetContainer(c *containerSDK.Container) {
|
||||
s.c = c
|
||||
}
|
||||
|
||||
// SyncContainerRes groups resulting values of SyncContainerSettings
|
||||
// operation.
|
||||
type SyncContainerRes struct{}
|
||||
|
||||
// SyncContainerSettings reads global network config from FrostFS and
|
||||
// syncs container settings with it.
|
||||
//
|
||||
// Interrupts on any writer error.
|
||||
//
|
||||
// Panics if a container passed as a parameter is nil.
|
||||
func SyncContainerSettings(prm SyncContainerPrm) (*SyncContainerRes, error) {
|
||||
if prm.c == nil {
|
||||
panic("sync container settings with the network: nil container")
|
||||
}
|
||||
|
||||
err := client.SyncContainerWithNetwork(context.Background(), prm.c, prm.cli)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return new(SyncContainerRes), nil
|
||||
}
|
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 (
|
||||
"io"
|
||||
|
||||
"github.com/nspcc-dev/neofs-sdk-go/bearer"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/client"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/session"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/client"
|
||||
cid "github.com/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
oid "github.com/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/session"
|
||||
)
|
||||
|
||||
// here are small structures with public setters to share between parameter structures
|
||||
|
@ -16,7 +16,7 @@ type commonPrm struct {
|
|||
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) {
|
||||
x.cli = cli
|
||||
}
|
96
cmd/frostfs-cli/internal/client/sdk.go
Normal file
96
cmd/frostfs-cli/internal/client/sdk.go
Normal file
|
@ -0,0 +1,96 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/TrueCloudLab/frostfs-node/pkg/network"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/client"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
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, key, addr)
|
||||
}
|
||||
|
||||
// GetSDKClient returns default frostfs-sdk-go client.
|
||||
func GetSDKClient(cmd *cobra.Command, key *ecdsa.PrivateKey, addr network.Address) (*client.Client, error) {
|
||||
var (
|
||||
c client.Client
|
||||
prmInit client.PrmInit
|
||||
prmDial client.PrmDial
|
||||
)
|
||||
|
||||
prmInit.SetDefaultPrivateKey(*key)
|
||||
prmInit.ResolveNeoFSFailures()
|
||||
prmDial.SetServerURI(addr.URIAddr())
|
||||
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.SetTimeout(timeout)
|
||||
prmDial.SetStreamTimeout(timeout)
|
||||
|
||||
common.PrintVerbose(cmd, "Set request timeout to %s.", timeout)
|
||||
}
|
||||
|
||||
c.Init(prmInit)
|
||||
|
||||
if err := c.Dial(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(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"
|
||||
"os"
|
||||
|
||||
"github.com/nspcc-dev/neofs-node/pkg/core/version"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/eacl"
|
||||
versionSDK "github.com/nspcc-dev/neofs-sdk-go/version"
|
||||
commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/TrueCloudLab/frostfs-node/pkg/core/version"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||
versionSDK "github.com/TrueCloudLab/frostfs-sdk-go/version"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
@ -16,29 +17,29 @@ var errUnsupportedEACLFormat = errors.New("unsupported eACL format")
|
|||
func ReadEACL(cmd *cobra.Command, eaclPath string) *eacl.Table {
|
||||
_, err := os.Stat(eaclPath) // check if `eaclPath` is an existing file
|
||||
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)
|
||||
ExitOnErr(cmd, "can't read file with EACL: %w", err)
|
||||
commonCmd.ExitOnErr(cmd, "can't read file with EACL: %w", err)
|
||||
|
||||
table := eacl.NewTable()
|
||||
|
||||
if err = table.UnmarshalJSON(data); err == nil {
|
||||
validateAndFixEACLVersion(table)
|
||||
PrintVerbose("Parsed JSON encoded EACL table")
|
||||
PrintVerbose(cmd, "Parsed JSON encoded EACL table")
|
||||
return table
|
||||
}
|
||||
|
||||
if err = table.Unmarshal(data); err == nil {
|
||||
validateAndFixEACLVersion(table)
|
||||
PrintVerbose("Parsed binary encoded EACL table")
|
||||
PrintVerbose(cmd, "Parsed binary encoded EACL table")
|
||||
return table
|
||||
}
|
||||
|
||||
ExitOnErr(cmd, "", errUnsupportedEACLFormat)
|
||||
commonCmd.ExitOnErr(cmd, "", errUnsupportedEACLFormat)
|
||||
return nil
|
||||
}
|
||||
|
28
cmd/frostfs-cli/internal/common/epoch.go
Normal file
28
cmd/frostfs-cli/internal/common/epoch.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ParseEpoch parses epoch argument. Second return value is true if
|
||||
// the specified epoch is relative, and false otherwise.
|
||||
func ParseEpoch(cmd *cobra.Command, flag string) (uint64, bool, error) {
|
||||
s, _ := cmd.Flags().GetString(flag)
|
||||
if len(s) == 0 {
|
||||
return 0, false, nil
|
||||
}
|
||||
|
||||
relative := s[0] == '+'
|
||||
if relative {
|
||||
s = s[1:]
|
||||
}
|
||||
|
||||
epoch, err := strconv.ParseUint(s, 10, 64)
|
||||
if err != nil {
|
||||
return 0, relative, fmt.Errorf("can't parse epoch for %s argument: %w", flag, err)
|
||||
}
|
||||
return epoch, relative, nil
|
||||
}
|
|
@ -11,12 +11,12 @@ import (
|
|||
func PrettyPrintJSON(cmd *cobra.Command, m json.Marshaler, entity string) {
|
||||
data, err := m.MarshalJSON()
|
||||
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
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
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
|
||||
}
|
||||
cmd.Println(buf)
|
67
cmd/frostfs-cli/internal/common/token.go
Normal file
67
cmd/frostfs-cli/internal/common/token.go
Normal file
|
@ -0,0 +1,67 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ReadBearerToken reads bearer token from the path provided in a specified flag.
|
||||
func ReadBearerToken(cmd *cobra.Command, flagname string) *bearer.Token {
|
||||
path, err := cmd.Flags().GetString(flagname)
|
||||
commonCmd.ExitOnErr(cmd, "", err)
|
||||
|
||||
if len(path) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
PrintVerbose(cmd, "Reading bearer token from file [%s]...", path)
|
||||
|
||||
var tok bearer.Token
|
||||
|
||||
err = ReadBinaryOrJSON(cmd, &tok, path)
|
||||
commonCmd.ExitOnErr(cmd, "invalid bearer token: %v", err)
|
||||
|
||||
return &tok
|
||||
}
|
||||
|
||||
// BinaryOrJSON is an interface of entities which provide json.Unmarshaler
|
||||
// and FrostFS binary decoder.
|
||||
type BinaryOrJSON interface {
|
||||
Unmarshal([]byte) error
|
||||
json.Unmarshaler
|
||||
}
|
||||
|
||||
// ReadBinaryOrJSON reads file data using provided path and decodes
|
||||
// BinaryOrJSON from the data.
|
||||
func ReadBinaryOrJSON(cmd *cobra.Command, dst BinaryOrJSON, fPath string) error {
|
||||
PrintVerbose(cmd, "Reading file [%s]...", fPath)
|
||||
|
||||
// try to read session token from file
|
||||
data, err := os.ReadFile(fPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read file <%s>: %w", fPath, err)
|
||||
}
|
||||
|
||||
PrintVerbose(cmd, "Trying to decode binary...")
|
||||
|
||||
err = dst.Unmarshal(data)
|
||||
if err != nil {
|
||||
PrintVerbose(cmd, "Failed to decode binary: %v", err)
|
||||
|
||||
PrintVerbose(cmd, "Trying to decode JSON...")
|
||||
|
||||
err = dst.UnmarshalJSON(data)
|
||||
if err != nil {
|
||||
PrintVerbose(cmd, "Failed to decode JSON: %v", err)
|
||||
return errors.New("invalid format")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -2,20 +2,19 @@ package common
|
|||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/checksum"
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/checksum"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// 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) {
|
||||
fmt.Printf(format+"\n", a...)
|
||||
cmd.Printf(format+"\n", a...)
|
||||
}
|
||||
}
|
||||
|
9
cmd/frostfs-cli/internal/commonflags/expiration.go
Normal file
9
cmd/frostfs-cli/internal/commonflags/expiration.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package commonflags
|
||||
|
||||
const (
|
||||
// ExpireAt is a flag for setting last epoch of an object or a token.
|
||||
ExpireAt = "expire-at"
|
||||
// Lifetime is a flag for setting the lifetime of an object or a token,
|
||||
// starting from the current epoch.
|
||||
Lifetime = "lifetime"
|
||||
)
|
|
@ -1,6 +1,8 @@
|
|||
package commonflags
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
@ -11,38 +13,54 @@ const (
|
|||
GenerateKey = "generate-key"
|
||||
generateKeyShorthand = "g"
|
||||
generateKeyDefault = false
|
||||
generateKeyUsage = "generate new private key"
|
||||
generateKeyUsage = "Generate new private key"
|
||||
|
||||
WalletPath = "wallet"
|
||||
WalletPathShorthand = "w"
|
||||
WalletPathDefault = ""
|
||||
WalletPathUsage = "WIF (NEP-2) string or path to the wallet or binary key"
|
||||
WalletPathUsage = "Path to the wallet or binary key"
|
||||
|
||||
Account = "address"
|
||||
AccountShorthand = ""
|
||||
AccountDefault = ""
|
||||
AccountUsage = "address of wallet account"
|
||||
AccountUsage = "Address of wallet account"
|
||||
|
||||
RPC = "rpc-endpoint"
|
||||
RPCShorthand = "r"
|
||||
RPCDefault = ""
|
||||
RPCUsage = "remote node address (as 'multiaddr' or '<host>:<port>')"
|
||||
RPCUsage = "Remote node address (as 'multiaddr' or '<host>:<port>')"
|
||||
|
||||
Timeout = "timeout"
|
||||
TimeoutShorthand = "t"
|
||||
TimeoutDefault = 15 * time.Second
|
||||
TimeoutUsage = "Timeout for an operation"
|
||||
|
||||
Verbose = "verbose"
|
||||
VerboseShorthand = "v"
|
||||
VerboseUsage = "verbose output"
|
||||
VerboseUsage = "Verbose output"
|
||||
|
||||
ForceFlag = "force"
|
||||
ForceFlagShorthand = "f"
|
||||
|
||||
CIDFlag = "cid"
|
||||
CIDFlagUsage = "Container ID."
|
||||
|
||||
OIDFlag = "oid"
|
||||
OIDFlagUsage = "Object ID."
|
||||
)
|
||||
|
||||
// Init adds common flags to the command:
|
||||
// - GenerateKey
|
||||
// - WalletPath
|
||||
// - Account
|
||||
// - RPC
|
||||
// - GenerateKey,
|
||||
// - WalletPath,
|
||||
// - Account,
|
||||
// - RPC,
|
||||
// - Timeout.
|
||||
func Init(cmd *cobra.Command) {
|
||||
InitWithoutRPC(cmd)
|
||||
|
||||
ff := cmd.Flags()
|
||||
ff.StringP(RPC, RPCShorthand, RPCDefault, RPCUsage)
|
||||
ff.DurationP(Timeout, TimeoutShorthand, TimeoutDefault, TimeoutUsage)
|
||||
}
|
||||
|
||||
// InitWithoutRPC is similar to Init but doesn't create the RPC flag.
|
||||
|
@ -62,4 +80,5 @@ func Bind(cmd *cobra.Command) {
|
|||
_ = viper.BindPFlag(WalletPath, ff.Lookup(WalletPath))
|
||||
_ = viper.BindPFlag(Account, ff.Lookup(Account))
|
||||
_ = viper.BindPFlag(RPC, ff.Lookup(RPC))
|
||||
_ = viper.BindPFlag(Timeout, ff.Lookup(Timeout))
|
||||
}
|
19
cmd/frostfs-cli/internal/commonflags/session.go
Normal file
19
cmd/frostfs-cli/internal/commonflags/session.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package commonflags
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const SessionToken = "session"
|
||||
|
||||
// InitSession registers SessionToken flag representing file path to the token of
|
||||
// the session with the given name. Supports FrostFS-binary and JSON files.
|
||||
func InitSession(cmd *cobra.Command, name string) {
|
||||
cmd.Flags().String(
|
||||
SessionToken,
|
||||
"",
|
||||
fmt.Sprintf("Filepath to a JSON- or binary-encoded token of the %s session", name),
|
||||
)
|
||||
}
|
|
@ -7,15 +7,22 @@ import (
|
|||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"github.com/nspcc-dev/neo-go/cli/input"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"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/stretchr/testify/require"
|
||||
"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) {
|
||||
dir := t.TempDir()
|
||||
|
||||
|
@ -23,6 +30,9 @@ func Test_getOrGenerate(t *testing.T) {
|
|||
w, err := wallet.NewWallet(wallPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
badWallPath := filepath.Join(dir, "bad_wallet.json")
|
||||
require.NoError(t, os.WriteFile(badWallPath, []byte("bad content"), os.ModePerm))
|
||||
|
||||
acc1, err := wallet.NewAccount()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, acc1.Encrypt("pass", keys.NEP2ScryptParams()))
|
||||
|
@ -34,7 +44,6 @@ func Test_getOrGenerate(t *testing.T) {
|
|||
acc2.Default = true
|
||||
w.AddAccount(acc2)
|
||||
require.NoError(t, w.Save())
|
||||
w.Close()
|
||||
|
||||
keyPath := filepath.Join(dir, "binary.key")
|
||||
rawKey, err := keys.NewPrivateKey()
|
||||
|
@ -55,7 +64,8 @@ func Test_getOrGenerate(t *testing.T) {
|
|||
Writer: io.Discard,
|
||||
}, "")
|
||||
|
||||
checkKeyError(t, filepath.Join(dir, "badfile"), ErrInvalidKey)
|
||||
checkKeyError(t, filepath.Join(dir, "badfile"), ErrFs)
|
||||
checkKeyError(t, badWallPath, ErrInvalidKey)
|
||||
|
||||
t.Run("wallet", func(t *testing.T) {
|
||||
checkKeyError(t, wallPath, ErrInvalidPassword)
|
||||
|
@ -80,27 +90,11 @@ func Test_getOrGenerate(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("WIF", func(t *testing.T) {
|
||||
checkKey(t, wifKey.WIF(), wifKey)
|
||||
checkKeyError(t, wifKey.WIF(), ErrFs)
|
||||
})
|
||||
|
||||
t.Run("NEP-2", func(t *testing.T) {
|
||||
checkKeyError(t, nep2, ErrInvalidPassword)
|
||||
|
||||
in.WriteString("invalid\r")
|
||||
checkKeyError(t, nep2, ErrInvalidPassword)
|
||||
|
||||
in.WriteString("pass\r")
|
||||
checkKey(t, nep2, nep2Key)
|
||||
|
||||
t.Run("password from config", func(t *testing.T) {
|
||||
viper.Set("password", "invalid")
|
||||
in.WriteString("pass\r")
|
||||
checkKeyError(t, nep2, ErrInvalidPassword)
|
||||
|
||||
viper.Set("password", "pass")
|
||||
in.WriteString("invalid\r")
|
||||
checkKey(t, nep2, nep2Key)
|
||||
})
|
||||
checkKeyError(t, nep2, ErrFs)
|
||||
})
|
||||
|
||||
t.Run("raw key", func(t *testing.T) {
|
||||
|
@ -109,7 +103,7 @@ func Test_getOrGenerate(t *testing.T) {
|
|||
|
||||
t.Run("generate", func(t *testing.T) {
|
||||
viper.Set(commonflags.GenerateKey, true)
|
||||
actual, err := getOrGenerate()
|
||||
actual, err := getOrGenerate(testCmd)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, actual)
|
||||
for _, p := range []*keys.PrivateKey{nep2Key, rawKey, wifKey, acc1.PrivateKey(), acc2.PrivateKey()} {
|
||||
|
@ -120,13 +114,13 @@ func Test_getOrGenerate(t *testing.T) {
|
|||
|
||||
func checkKeyError(t *testing.T, desc string, err error) {
|
||||
viper.Set(commonflags.WalletPath, desc)
|
||||
_, actualErr := getOrGenerate()
|
||||
_, actualErr := getOrGenerate(testCmd)
|
||||
require.ErrorIs(t, actualErr, err)
|
||||
}
|
||||
|
||||
func checkKey(t *testing.T, desc string, expected *keys.PrivateKey) {
|
||||
viper.Set(commonflags.WalletPath, desc)
|
||||
actual, err := getOrGenerate()
|
||||
actual, err := getOrGenerate(testCmd)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &expected.PrivateKey, actual)
|
||||
}
|
62
cmd/frostfs-cli/internal/key/raw.go
Normal file
62
cmd/frostfs-cli/internal/key/raw.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
package key
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var errCantGenerateKey = errors.New("can't generate new private key")
|
||||
|
||||
// Get returns private key from wallet or binary file.
|
||||
// Ideally we want to touch file-system on the last step.
|
||||
// This function assumes that all flags were bind to viper in a `PersistentPreRun`.
|
||||
func Get(cmd *cobra.Command) *ecdsa.PrivateKey {
|
||||
pk, err := get(cmd)
|
||||
commonCmd.ExitOnErr(cmd, "can't fetch private key: %w", err)
|
||||
return pk
|
||||
}
|
||||
|
||||
func get(cmd *cobra.Command) (*ecdsa.PrivateKey, error) {
|
||||
keyDesc := viper.GetString(commonflags.WalletPath)
|
||||
data, err := os.ReadFile(keyDesc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrFs, err)
|
||||
}
|
||||
|
||||
priv, err := keys.NewPrivateKeyFromBytes(data)
|
||||
if err != nil {
|
||||
w, err := wallet.NewWalletFromFile(keyDesc)
|
||||
if err == nil {
|
||||
return FromWallet(cmd, w, viper.GetString(commonflags.Account))
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %v", ErrInvalidKey, err)
|
||||
}
|
||||
return &priv.PrivateKey, nil
|
||||
}
|
||||
|
||||
// GetOrGenerate is similar to get but generates a new key if commonflags.GenerateKey is set.
|
||||
func GetOrGenerate(cmd *cobra.Command) *ecdsa.PrivateKey {
|
||||
pk, err := getOrGenerate(cmd)
|
||||
commonCmd.ExitOnErr(cmd, "can't fetch private key: %w", err)
|
||||
return pk
|
||||
}
|
||||
|
||||
func getOrGenerate(cmd *cobra.Command) (*ecdsa.PrivateKey, error) {
|
||||
if viper.GetBool(commonflags.GenerateKey) {
|
||||
priv, err := keys.NewPrivateKey()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", errCantGenerateKey, err)
|
||||
}
|
||||
return &priv.PrivateKey, nil
|
||||
}
|
||||
return get(cmd)
|
||||
}
|
|
@ -3,55 +3,57 @@ package key
|
|||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
|
||||
"github.com/nspcc-dev/neo-go/cli/flags"
|
||||
"github.com/nspcc-dev/neo-go/cli/input"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Key-related errors.
|
||||
var (
|
||||
ErrInvalidKey = errors.New("provided key is incorrect")
|
||||
ErrFs = errors.New("unable to read file from given path")
|
||||
ErrInvalidKey = errors.New("provided key is incorrect, only wallet or binary key supported")
|
||||
ErrInvalidAddress = errors.New("--address option must be specified and valid")
|
||||
ErrInvalidPassword = errors.New("invalid password for the encrypted key")
|
||||
)
|
||||
|
||||
// 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 (
|
||||
addr util.Uint160
|
||||
err error
|
||||
)
|
||||
|
||||
if addrStr == "" {
|
||||
printVerbose("Using default wallet address")
|
||||
common.PrintVerbose(cmd, "Using default wallet address")
|
||||
addr = w.GetChangeAddress()
|
||||
} else {
|
||||
addr, err = flags.ParseAddress(addrStr)
|
||||
if err != nil {
|
||||
printVerbose("Can't parse address: %s", addrStr)
|
||||
common.PrintVerbose(cmd, "Can't parse address: %s", addrStr)
|
||||
return nil, ErrInvalidAddress
|
||||
}
|
||||
}
|
||||
|
||||
acc := w.GetAccount(addr)
|
||||
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
|
||||
}
|
||||
|
||||
pass, err := getPassword()
|
||||
if err != nil {
|
||||
printVerbose("Can't read password: %v", err)
|
||||
common.PrintVerbose(cmd, "Can't read password: %v", err)
|
||||
return nil, ErrInvalidPassword
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -66,9 +68,3 @@ func getPassword() (string, error) {
|
|||
|
||||
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 "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules"
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
|
@ -3,14 +3,14 @@ package accounting
|
|||
import (
|
||||
"math/big"
|
||||
|
||||
internalclient "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/TrueCloudLab/frostfs-node/pkg/util/precision"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/accounting"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"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/viper"
|
||||
)
|
||||
|
@ -21,8 +21,8 @@ const (
|
|||
|
||||
var accountingBalanceCmd = &cobra.Command{
|
||||
Use: "balance",
|
||||
Short: "Get internal balance of NeoFS account",
|
||||
Long: `Get internal balance of NeoFS account`,
|
||||
Short: "Get internal balance of FrostFS account",
|
||||
Long: `Get internal balance of FrostFS account`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var idUser user.ID
|
||||
|
||||
|
@ -32,7 +32,7 @@ var accountingBalanceCmd = &cobra.Command{
|
|||
if balanceOwner == "" {
|
||||
user.IDFromKey(&idUser, pk.PublicKey)
|
||||
} 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)
|
||||
|
@ -42,7 +42,7 @@ var accountingBalanceCmd = &cobra.Command{
|
|||
prm.SetAccount(idUser)
|
||||
|
||||
res, err := internalclient.BalanceOf(prm)
|
||||
common.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
// print to stdout
|
||||
prettyPrintDecimal(cmd, res.Balance())
|
||||
|
@ -58,11 +58,7 @@ func initAccountingBalanceCmd() {
|
|||
ff.String(ownerFlag, "", "owner of balance account (omit to use owner from private key)")
|
||||
}
|
||||
|
||||
func prettyPrintDecimal(cmd *cobra.Command, decimal *accounting.Decimal) {
|
||||
if decimal == nil {
|
||||
return
|
||||
}
|
||||
|
||||
func prettyPrintDecimal(cmd *cobra.Command, decimal accounting.Decimal) {
|
||||
if viper.GetBool(commonflags.Verbose) {
|
||||
cmd.Println("value:", decimal.Value())
|
||||
cmd.Println("precision:", decimal.Precision())
|
|
@ -1,12 +1,12 @@
|
|||
package accounting
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags"
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Cmd represents the accounting command
|
||||
// Cmd represents the accounting command.
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "accounting",
|
||||
Short: "Operations with accounts and balances",
|
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 (
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
|
||||
commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/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"
|
||||
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
|
||||
commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
cid "github.com/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"github.com/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 github.com/TrueCloudLab/frostfs-api/blob/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(), 0644)
|
||||
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
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neofs-sdk-go/eacl"
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -63,8 +63,7 @@ func TestParseTable(t *testing.T) {
|
|||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ss := strings.Split(test.rule, " ")
|
||||
err := parseTable(eaclTable, ss)
|
||||
err := util.ParseEACLRule(eaclTable, test.rule)
|
||||
ok := len(test.jsonRecord) > 0
|
||||
require.Equal(t, ok, err == nil, err)
|
||||
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"
|
||||
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
|
||||
commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/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)
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package extended
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "extended",
|
||||
|
@ -9,4 +11,5 @@ var Cmd = &cobra.Command{
|
|||
|
||||
func init() {
|
||||
Cmd.AddCommand(createCmd)
|
||||
Cmd.AddCommand(printEACLCmd)
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
package acl
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/modules/acl/extended"
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/acl/basic"
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/acl/extended"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
@ -12,4 +13,5 @@ var Cmd = &cobra.Command{
|
|||
|
||||
func init() {
|
||||
Cmd.AddCommand(extended.Cmd)
|
||||
Cmd.AddCommand(basic.Cmd)
|
||||
}
|
126
cmd/frostfs-cli/modules/bearer/create.go
Normal file
126
cmd/frostfs-cli/modules/bearer/create.go
Normal file
|
@ -0,0 +1,126 @@
|
|||
package bearer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
internalclient "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||
eaclSDK "github.com/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
eaclFlag = "eacl"
|
||||
issuedAtFlag = "issued-at"
|
||||
notValidBeforeFlag = "not-valid-before"
|
||||
ownerFlag = "owner"
|
||||
outFlag = "out"
|
||||
jsonFlag = commonflags.JSON
|
||||
)
|
||||
|
||||
var createCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create bearer token",
|
||||
Long: `Create bearer token.
|
||||
|
||||
All epoch flags can be specified relative to the current epoch with the +n syntax.
|
||||
In this case --` + commonflags.RPC + ` flag should be specified and the epoch in bearer token
|
||||
is set to current epoch + n.
|
||||
`,
|
||||
Run: createToken,
|
||||
}
|
||||
|
||||
func init() {
|
||||
createCmd.Flags().StringP(eaclFlag, "e", "", "Path to the extended ACL table")
|
||||
createCmd.Flags().StringP(issuedAtFlag, "i", "", "Epoch to issue token at")
|
||||
createCmd.Flags().StringP(notValidBeforeFlag, "n", "", "Not valid before epoch")
|
||||
createCmd.Flags().StringP(commonflags.ExpireAt, "x", "", "The last active epoch for the token")
|
||||
createCmd.Flags().StringP(ownerFlag, "o", "", "Token owner")
|
||||
createCmd.Flags().String(outFlag, "", "File to write token to")
|
||||
createCmd.Flags().Bool(jsonFlag, false, "Output token in JSON")
|
||||
createCmd.Flags().StringP(commonflags.RPC, commonflags.RPCShorthand, commonflags.RPCDefault, commonflags.RPCUsage)
|
||||
|
||||
_ = cobra.MarkFlagFilename(createCmd.Flags(), eaclFlag)
|
||||
|
||||
_ = cobra.MarkFlagRequired(createCmd.Flags(), issuedAtFlag)
|
||||
_ = cobra.MarkFlagRequired(createCmd.Flags(), notValidBeforeFlag)
|
||||
_ = cobra.MarkFlagRequired(createCmd.Flags(), commonflags.ExpireAt)
|
||||
_ = cobra.MarkFlagRequired(createCmd.Flags(), ownerFlag)
|
||||
_ = cobra.MarkFlagRequired(createCmd.Flags(), outFlag)
|
||||
}
|
||||
|
||||
func createToken(cmd *cobra.Command, _ []string) {
|
||||
iat, iatRelative, err := common.ParseEpoch(cmd, issuedAtFlag)
|
||||
commonCmd.ExitOnErr(cmd, "can't parse --"+issuedAtFlag+" flag: %w", err)
|
||||
|
||||
exp, expRelative, err := common.ParseEpoch(cmd, commonflags.ExpireAt)
|
||||
commonCmd.ExitOnErr(cmd, "can't parse --"+commonflags.ExpireAt+" flag: %w", err)
|
||||
|
||||
nvb, nvbRelative, err := common.ParseEpoch(cmd, notValidBeforeFlag)
|
||||
commonCmd.ExitOnErr(cmd, "can't parse --"+notValidBeforeFlag+" flag: %w", err)
|
||||
|
||||
if iatRelative || expRelative || nvbRelative {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
|
||||
endpoint, _ := cmd.Flags().GetString(commonflags.RPC)
|
||||
currEpoch, err := internalclient.GetCurrentEpoch(ctx, cmd, endpoint)
|
||||
commonCmd.ExitOnErr(cmd, "can't fetch current epoch: %w", err)
|
||||
|
||||
if iatRelative {
|
||||
iat += currEpoch
|
||||
}
|
||||
if expRelative {
|
||||
exp += currEpoch
|
||||
}
|
||||
if nvbRelative {
|
||||
nvb += currEpoch
|
||||
}
|
||||
}
|
||||
if exp < nvb {
|
||||
commonCmd.ExitOnErr(cmd, "",
|
||||
fmt.Errorf("expiration epoch is less than not-valid-before epoch: %d < %d", exp, nvb))
|
||||
}
|
||||
|
||||
ownerStr, _ := cmd.Flags().GetString(ownerFlag)
|
||||
|
||||
var ownerID user.ID
|
||||
commonCmd.ExitOnErr(cmd, "can't parse recipient: %w", ownerID.DecodeString(ownerStr))
|
||||
|
||||
var b bearer.Token
|
||||
b.SetExp(exp)
|
||||
b.SetNbf(nvb)
|
||||
b.SetIat(iat)
|
||||
b.ForUser(ownerID)
|
||||
|
||||
eaclPath, _ := cmd.Flags().GetString(eaclFlag)
|
||||
if eaclPath != "" {
|
||||
table := eaclSDK.NewTable()
|
||||
raw, err := os.ReadFile(eaclPath)
|
||||
commonCmd.ExitOnErr(cmd, "can't read extended ACL file: %w", err)
|
||||
commonCmd.ExitOnErr(cmd, "can't parse extended ACL: %w", json.Unmarshal(raw, table))
|
||||
b.SetEACLTable(*table)
|
||||
}
|
||||
|
||||
var data []byte
|
||||
|
||||
toJSON, _ := cmd.Flags().GetBool(jsonFlag)
|
||||
if toJSON {
|
||||
data, err = json.Marshal(b)
|
||||
commonCmd.ExitOnErr(cmd, "can't mashal token to JSON: %w", err)
|
||||
} else {
|
||||
data = b.Marshal()
|
||||
}
|
||||
|
||||
out, _ := cmd.Flags().GetString(outFlag)
|
||||
err = os.WriteFile(out, data, 0644)
|
||||
commonCmd.ExitOnErr(cmd, "can't write token to file: %w", err)
|
||||
}
|
9
cmd/frostfs-cli/modules/completion.go
Normal file
9
cmd/frostfs-cli/modules/completion.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/TrueCloudLab/frostfs-node/pkg/util/autocomplete"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(autocomplete.Command("frostfs-cli"))
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue