Compare commits

...

32 commits

Author SHA1 Message Date
b89c3fd584 [#23] Remove unused notifications from container contract
Signed-off-by: Anton Nikiforov <an.nikiforov@yadro.com>
2023-05-18 08:25:32 +00:00
6a19989aa6 [#1] tests: Update domain email after rebranding
Signed-off-by: Alex Vanin <a.vanin@yadro.com>
2023-05-17 10:52:10 +03:00
f786040322 [#1] container: Update domain email after rebranding
Signed-off-by: Alex Vanin <a.vanin@yadro.com>
2023-05-17 10:52:09 +03:00
a8029c71cd [#21] Document contract storage schemes
Signed-off-by: Airat Arifullin a.arifullin@yadro.com
2023-05-12 14:51:21 +03:00
8537293e11 [#20] subnet: Drop contract
Signed-off-by: Dmitrii Stepanov <d.stepanov@yadro.com>
2023-04-17 14:29:30 +03:00
Evgenii Stratonikov
e19fe15ed8 [#19] common: Update current version
Signed-off-by: Evgenii Stratonikov <evgeniy@morphbits.ru>
2023-04-13 11:24:52 +03:00
Evgenii Stratonikov
8d17306c3c Release v0.17.0
Signed-off-by: Evgenii Stratonikov <evgeniy@morphbits.ru>
2023-04-13 11:22:42 +03:00
cbe0f01e08 [#18] Add Issue Template
Add bug report and feature request templates

Signed-off-by: Liza <e.chichindaeva@yadro.com>
2023-03-23 12:20:08 +03:00
b9be2ac036 [#7] Refactor common.CheckAlphabetWitness()
Signed-off-by: Anton Nikiforov <an.nikiforov@yadro.com>
2023-03-14 12:09:24 +03:00
d8530284fd [#7] Remove notary disabled code
Signed-off-by: Anton Nikiforov <an.nikiforov@yadro.com>
2023-03-14 12:09:22 +03:00
6b13a83736 [#14] Bump contracts version
Signed-off-by: Anton Nikiforov <an.nikiforov@yadro.com>
2023-03-14 10:59:55 +03:00
d626660c1a [#12] Use non-specific contract names
Allows easier project name updates in the future.

Signed-off-by: Alex Vanin <a.vanin@yadro.com>
2023-03-09 19:56:33 +00:00
19a8ef2d02 Rename package name
Due to source code relocation from GitHub.

Signed-off-by: Alex Vanin <a.vanin@yadro.com>
2023-03-07 14:06:21 +03:00
Evgenii Stratonikov
4f3c08f552 [#300] container: Allow to iterate over container list
Signed-off-by: Evgenii Stratonikov <evgeniy@morphbits.ru>
2023-01-16 14:40:21 +03:00
03bff785d2 [#293] container: Add IterateContainerSizes method
Add method that allows to iterate over estimation records.

Update tests to assert that list of estimations built with existing methods
is identical to estimations from iterator.

Signed-off-by: Vladimir Domnich <v.domnich@yadro.com>
2023-01-16 14:40:21 +03:00
1a4fa7e421 [#296] container: Increase default expiration time
Signed-off-by: Alex Vanin <a.vanin@yadro.com>
2023-01-16 14:40:21 +03:00
0ba830f48f [#291] Debian packaging
Debian package contains compiled contracts and manifests with
corresponding directories, which will be placed into
/var/lib/neofs/contract for further usage.

Depends on neo-go package to build.

Signed-off-by: Dmitriy Zabolotskiy <d.zabolotskiy@yadro.com>
2023-01-16 14:40:21 +03:00
4077921794 [TrueCloudLab#3] *: Use frostfs in docs
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-01-11 17:19:35 +03:00
0c5723964f [TrueCloudLab#3] makefile: Rename archive to frostfs
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-01-11 17:19:35 +03:00
e1cd043248 [TrueCloudLab#3] netmap: Rename neofs to frostfs
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-01-11 17:19:35 +03:00
9c004cab2e [TrueCloudLab#3] container: Rename neofs to frostfs
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-01-11 17:19:35 +03:00
4052e4873b [TrueCloudLab#3] balance: Rename neofs to frostfs
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-01-11 17:19:35 +03:00
a21630c1d7 [TrueCloudLab#3] neofsid: Rename contract to frostfsid
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-01-11 17:19:35 +03:00
402c13a607 [TrueCloudLab#3] processing: Rename neofs to frostfs
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-01-11 17:19:35 +03:00
0093e89ad9 [TrueCloudLab#3] neofs: Rename contract to frostfs
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-01-11 17:19:35 +03:00
e95c598dfc Change logo
Signed-off-by: Stanislav Bogatyrev <s.bogatyrev@yadro.com>
2023-01-09 11:43:08 +03:00
Evgenii Stratonikov
6c805c1b4e Move from nspcc-dev to TrueCloudLab
Signed-off-by: Evgenii Stratonikov <evgeniy@morphbits.ru>
2022-12-13 11:12:48 +03:00
Evgenii Stratonikov
8ca71d22b2 *: goimports -w
Signed-off-by: Evgenii Stratonikov <evgeniy@morphbits.ru>
2022-12-13 11:12:48 +03:00
Evgenii Stratonikov
9052ec62d6 [#285] Makefile: Install neo-go in the current directory
`go install` doesn't have `-o` flag but it respects `GOBIN` environment
variable.

Signed-off-by: Evgenii Stratonikov <evgeniy@morphbits.ru>
2022-11-23 20:29:17 +03:00
Evgenii Stratonikov
beaef7b10d [#284] *: Update version and remove the old migration code
Less chances to forget anything in the next release.

Signed-off-by: Evgenii Stratonikov <evgeniy@morphbits.ru>
2022-10-18 09:54:53 +03:00
Evgenii Stratonikov
38246cd54f [#284] tests: Add a test for checking VERSION file
Signed-off-by: Evgenii Stratonikov <evgeniy@morphbits.ru>
2022-10-18 09:54:53 +03:00
Evgenii Stratonikov
363d2a2a3a [#284] go.mod: Update neo-go to v0.99.4
Signed-off-by: Evgenii Stratonikov <evgeniy@morphbits.ru>
2022-10-18 09:54:53 +03:00
74 changed files with 1218 additions and 2760 deletions

45
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,45 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: community, triage, bug
assignees: ''
---
<!--- Provide a general summary of the issue in the Title above -->
## Expected Behavior
<!--- If you're describing a bug, tell us what should happen -->
<!--- If you're suggesting a change/improvement, tell us how it should work -->
## Current Behavior
<!--- If describing a bug, tell us what happens instead of the expected behavior -->
<!--- If suggesting a change/improvement, explain the difference from current behavior -->
## Possible Solution
<!--- Not obligatory -->
<!--- If no reason/fix/additions for the bug can be suggested, -->
<!--- uncomment the following phrase: -->
<!--- No fix can be suggested by a QA engineer. Further solutions shall be up to developers. -->
## Steps to Reproduce (for bugs)
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
<!--- reproduce this bug. -->
1.
## Context
<!--- How has this issue affected you? What are you trying to accomplish? -->
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
## Regression
<!-- Is this issue a regression? (Yes / No) -->
<!-- If Yes, optionally please include version or commit id or PR# that caused this regression, if you have these details. -->
## Your Environment
<!--- Include as many relevant details about the environment you experienced the bug in -->
* Version used:
* Server setup and configuration:
* Operating System and version (`uname -a`):

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1 @@
blank_issues_enabled: false

View file

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: community, triage
assignees: ''
---
## Is your feature request related to a problem? Please describe.
<!--- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
## Describe the solution you'd like
<!--- A clear and concise description of what you want to happen. -->
## Describe alternatives you've considered
<!--- A clear and concise description of any alternative solutions or features you've considered. -->
## Additional context
<!--- Add any other context or screenshots about the feature request here. -->

197
.github/logo.svg vendored
View file

@ -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

4
.gitignore vendored
View file

@ -4,3 +4,7 @@
config.json
/vendor/
.idea
/bin/
# debhelpers
**/.debhelper

View file

@ -1,11 +1,27 @@
# Changelog
Changelog for NeoFS Contract
Changelog for FrostFS Contract
## [Unreleased]
### Added
### Changed
### Removed
- `subnet` contract (#20)
### Updated
### Fixed
### Updating from v0.17.0
## [0.17.0] - 2023-04-13 - Furtwängler
### Added
### Changed
### Removed
- Notary disabled code from all contracts (#7)
### Updated
- `neo-go` to `v0.99.4`
### Fixed
### Updating from v0.16.0

View file

@ -1,19 +1,29 @@
#!/usr/bin/make -f
SHELL=bash
GOBIN ?= $(shell go env GOPATH)/bin
# GOBIN is used only to install neo-go and allows to override
# the location of written binary.
export GOBIN ?= $(shell pwd)/bin
NEOGO ?= $(GOBIN)/cli
VERSION ?= $(shell git describe --tags --dirty --match "v*" --always --abbrev=8 2>/dev/null || cat VERSION 2>/dev/null || echo "develop")
# .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: all build clean test neo-go
.PHONY: alphabet mainnet morph nns sidechain
.PHONY: debpackage debclean
build: neo-go all
all: sidechain mainnet
sidechain: alphabet morph nns
alphabet_sc = alphabet
morph_sc = audit balance container neofsid netmap proxy reputation subnet
mainnet_sc = neofs processing
morph_sc = audit balance container frostfsid netmap proxy reputation
mainnet_sc = frostfs processing
nns_sc = nns
define sc_template
@ -45,6 +55,7 @@ test:
clean:
find . -name '*.nef' -exec rm -rf {} \;
find . -name 'config.json' -exec rm -rf {} \;
rm -rf ./bin/
mr_proper: clean
for sc in $(alphabet_sc); do\
@ -52,6 +63,18 @@ mr_proper: clean
done
archive: build
@tar --transform "s|^./|neofs-contract-$(VERSION)/|" \
-czf neofs-contract-$(VERSION).tar.gz \
@tar --transform "s|^./|frostfs-contract-$(VERSION)/|" \
-czf frostfs-contract-$(VERSION).tar.gz \
$(shell find . -name '*.nef' -o -name 'config.json')
# Package for Debian
debpackage:
dch --package frostfs-contract \
--controlmaint \
--newversion $(PKG_VERSION) \
--distribution $(OS_RELEASE) \
"Please see CHANGELOG.md for code changes for $(VERSION)"
dpkg-buildpackage --no-sign -b
debclean:
dh clean

View file

@ -1,21 +1,21 @@
<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> related smart contracts.
<a href="https://frostfs.info">FrostFS</a> related smart contracts.
</p>
---
# Overview
NeoFS-Contract contains all NeoFS related contracts written for
FrostFS-Contract contains all FrostFS related contracts written for
[neo-go](https://github.com/nspcc-dev/neo-go) compiler. These contracts
are deployed both in the mainchain and the sidechain.
Mainchain contracts:
- neofs
- frostfs
- processing
Sidechain contracts:
@ -24,12 +24,11 @@ Sidechain contracts:
- audit
- balance
- container
- neofsid
- frostfsid
- netmap
- nns
- proxy
- reputation
- subnet
# Getting started
@ -51,13 +50,12 @@ $ make all
/home/user/go/bin/cli contract compile -i audit -c audit/config.yml -m audit/config.json -o audit/audit_contract.nef
/home/user/go/bin/cli contract compile -i balance -c balance/config.yml -m balance/config.json -o balance/balance_contract.nef
/home/user/go/bin/cli contract compile -i container -c container/config.yml -m container/config.json -o container/container_contract.nef
/home/user/go/bin/cli contract compile -i neofsid -c neofsid/config.yml -m neofsid/config.json -o neofsid/neofsid_contract.nef
/home/user/go/bin/cli contract compile -i frostfsid -c frostfsid/config.yml -m frostfsid/config.json -o frostfsid/frostfsid_contract.nef
/home/user/go/bin/cli contract compile -i netmap -c netmap/config.yml -m netmap/config.json -o netmap/netmap_contract.nef
/home/user/go/bin/cli contract compile -i proxy -c proxy/config.yml -m proxy/config.json -o proxy/proxy_contract.nef
/home/user/go/bin/cli contract compile -i reputation -c reputation/config.yml -m reputation/config.json -o reputation/reputation_contract.nef
/home/user/go/bin/cli contract compile -i subnet -c subnet/config.yml -m subnet/config.json -o subnet/subnet_contract.nef
/home/user/go/bin/cli contract compile -i nns -c nns/config.yml -m nns/config.json -o nns/nns_contract.nef
/home/user/go/bin/cli contract compile -i neofs -c neofs/config.yml -m neofs/config.json -o neofs/neofs_contract.nef
/home/user/go/bin/cli contract compile -i frostfs -c frostfs/config.yml -m frostfs/config.json -o frostfs/frostfs_contract.nef
/home/user/go/bin/cli contract compile -i processing -c processing/config.yml -m processing/config.json -o processing/processing_contract.nef
```
@ -69,29 +67,24 @@ $ NEOGO=/home/user/neo-go/bin/neo-go make all
Remove compiled files with `make clean` or `make mr_proper` command.
## Building Debian package
To build Debian package containing compiled contracts, run `make debpackage`
command. Package will install compiled contracts `*_contract.nef` and manifest
`config.json` with corresponding directories to `/var/lib/neofs/contract` for
further usage.
It will download and build neo-go, if needed.
To clean package-related files, use `make debclean`.
# Testing
Smartcontract tests reside in `tests/` directory. To execute test suite
after applying changes, simply run `make test`.
```
$ make test
ok github.com/nspcc-dev/neofs-contract/tests 0.462s
ok git.frostfs.info/TrueCloudLab/frostfs-contract/tests 0.462s
```
# NeoFS API compatibility
| neofs-contract version | supported NeoFS API versions |
|:----------------------:|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|
| v0.9.x | [v2.7.0](https://github.com/nspcc-dev/neofs-api/releases/tag/v2.7.0), [v2.8.0](https://github.com/nspcc-dev/neofs-api/releases/tag/v2.8.0) |
| v0.10.x | [v2.7.0](https://github.com/nspcc-dev/neofs-api/releases/tag/v2.7.0), [v2.8.0](https://github.com/nspcc-dev/neofs-api/releases/tag/v2.8.0) |
| v0.11.x | [v2.7.0](https://github.com/nspcc-dev/neofs-api/releases/tag/v2.7.0), [v2.8.0](https://github.com/nspcc-dev/neofs-api/releases/tag/v2.8.0), [v2.9.0](https://github.com/nspcc-dev/neofs-api/releases/tag/v2.9.0) |
| v0.12.x | [v2.10.0](https://github.com/nspcc-dev/neofs-api/releases/tag/v2.10.0) |
| v0.13.x | [v2.11.0](https://github.com/nspcc-dev/neofs-api/releases/tag/v2.11.0) |
| v0.14.x | [v2.11.0](https://github.com/nspcc-dev/neofs-api/releases/tag/v2.11.0) |
| v0.15.x | [v2.11.0](https://github.com/nspcc-dev/neofs-api/releases/tag/v2.11.0), [v2.12.0](https://github.com/nspcc-dev/neofs-api/releases/tag/v2.12.0) |
| v0.15.x | [v2.11.0](https://github.com/nspcc-dev/neofs-api/releases/tag/v2.11.0), [v2.12.0](https://github.com/nspcc-dev/neofs-api/releases/tag/v2.12.0) |
| v0.16.x | [v2.14.0](https://github.com/nspcc-dev/neofs-api/releases/tag/v2.14.0) |
# License
This project is licensed under the GPLv3 License - see the

View file

@ -1 +1 @@
v0.16.0
v0.18.0

View file

@ -1,15 +1,14 @@
package alphabet
import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/native/crypto"
"github.com/nspcc-dev/neo-go/pkg/interop/native/gas"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
"github.com/nspcc-dev/neo-go/pkg/interop/native/neo"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
"github.com/nspcc-dev/neofs-contract/common"
)
const (
@ -34,6 +33,9 @@ func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) {
func _deploy(data interface{}, isUpdate bool) {
ctx := storage.GetContext()
common.RmAndCheckNotaryDisabledKey(data, notaryDisabledKey)
if isUpdate {
args := data.([]interface{})
common.CheckVersion(args[len(args)-1].(int))
@ -41,6 +43,7 @@ func _deploy(data interface{}, isUpdate bool) {
}
args := data.(struct {
//TODO(@acid-ant): #9 remove notaryDisabled in future version
notaryDisabled bool
addrNetmap interop.Hash160
addrProxy interop.Hash160
@ -49,7 +52,7 @@ func _deploy(data interface{}, isUpdate bool) {
total int
})
if len(args.addrNetmap) != interop.Hash160Len || !args.notaryDisabled && len(args.addrProxy) != interop.Hash160Len {
if len(args.addrNetmap) != interop.Hash160Len || len(args.addrProxy) != interop.Hash160Len {
panic("incorrect length of contract script hash")
}
@ -59,13 +62,6 @@ func _deploy(data interface{}, isUpdate bool) {
storage.Put(ctx, indexKey, args.index)
storage.Put(ctx, totalKey, args.total)
// initialize the way to collect signatures
storage.Put(ctx, notaryDisabledKey, args.notaryDisabled)
if args.notaryDisabled {
common.InitVote(ctx)
runtime.Log(args.name + " notary disabled")
}
runtime.Log(args.name + " contract initialized")
}
@ -120,15 +116,11 @@ func checkPermission(ir []interop.PublicKey) bool {
// and proxy contract. It can be invoked only by an Alphabet node of the Inner Ring.
//
// To produce GAS, an alphabet contract transfers all available NEO from the contract
// account to itself. If notary is enabled, 50% of the GAS in the contract account
// account to itself. 50% of the GAS in the contract account
// are transferred to proxy contract. 43.75% of the GAS are equally distributed
// among all Inner Ring nodes. Remaining 6.25% of the GAS stay in the contract.
//
// If notary is disabled, 87.5% of the GAS are equally distributed among all
// Inner Ring nodes. Remaining 12.5% of the GAS stay in the contract.
func Emit() {
ctx := storage.GetReadOnlyContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
alphabet := common.AlphabetNodes()
if !checkPermission(alphabet) {
@ -143,32 +135,23 @@ func Emit() {
gasBalance := gas.BalanceOf(contractHash)
if !notaryDisabled {
proxyAddr := storage.Get(ctx, proxyKey).(interop.Hash160)
proxyAddr := storage.Get(ctx, proxyKey).(interop.Hash160)
proxyGas := gasBalance / 2
if proxyGas == 0 {
panic("no gas to emit")
}
if !gas.Transfer(contractHash, proxyAddr, proxyGas, nil) {
runtime.Log("could not transfer GAS to proxy contract")
}
gasBalance -= proxyGas
runtime.Log("utility token has been emitted to proxy contract")
proxyGas := gasBalance / 2
if proxyGas == 0 {
panic("no gas to emit")
}
var innerRing []interop.PublicKey
if notaryDisabled {
netmapContract := storage.Get(ctx, netmapKey).(interop.Hash160)
innerRing = common.InnerRingNodesFromNetmap(netmapContract)
} else {
innerRing = common.InnerRingNodes()
if !gas.Transfer(contractHash, proxyAddr, proxyGas, nil) {
runtime.Log("could not transfer GAS to proxy contract")
}
gasBalance -= proxyGas
runtime.Log("utility token has been emitted to proxy contract")
innerRing := common.InnerRingNodes()
gasPerNode := gasBalance * 7 / 8 / len(innerRing)
if gasPerNode != 0 {
@ -192,25 +175,10 @@ func Emit() {
// alphabet contracts) should vote for a new committee.
func Vote(epoch int, candidates []interop.PublicKey) {
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
index := index(ctx)
name := name(ctx)
var ( // for invocation collection without notary
alphabet []interop.PublicKey
nodeKey []byte
)
if notaryDisabled {
alphabet = common.AlphabetNodes()
nodeKey = common.InnerRingInvoker(alphabet)
if len(nodeKey) == 0 {
panic("invalid invoker")
}
} else {
multiaddr := common.AlphabetAddress()
common.CheckAlphabetWitness(multiaddr)
}
common.CheckAlphabetWitness()
curEpoch := currentEpoch(ctx)
if epoch != curEpoch {
@ -220,18 +188,6 @@ func Vote(epoch int, candidates []interop.PublicKey) {
candidate := candidates[index%len(candidates)]
address := runtime.GetExecutingScriptHash()
if notaryDisabled {
threshold := len(alphabet)*2/3 + 1
id := voteID(epoch, candidates)
n := common.Vote(ctx, id, nodeKey)
if n < threshold {
return
}
common.RemoveVotes(ctx, id)
}
ok := neo.Vote(address, candidate)
if ok {
runtime.Log(name + ": successfully voted for validator")
@ -240,21 +196,6 @@ func Vote(epoch int, candidates []interop.PublicKey) {
}
}
func voteID(epoch interface{}, args []interop.PublicKey) []byte {
var (
result []byte
epochBytes = epoch.([]byte)
)
result = append(result, epochBytes...)
for i := range args {
result = append(result, args[i]...)
}
return crypto.Sha256(result)
}
// Name returns the Glagolitic name of the contract.
func Name() string {
ctx := storage.GetReadOnlyContext()

View file

@ -1,4 +1,4 @@
name: "NeoFS Alphabet"
name: "Alphabet"
safemethods: ["gas", "neo", "name", "version"]
permissions:
- methods: ["update", "transfer", "vote"]

View file

@ -1,5 +1,5 @@
/*
Alphabet contract is a contract deployed in NeoFS sidechain.
Alphabet contract is a contract deployed in FrostFS sidechain.
Alphabet contract is designed to support GAS production and vote for new
validators in the sidechain. NEO token is required to produce GAS and vote for
@ -14,8 +14,19 @@ one of the alphabetical contracts to emit GAS. To vote for a new list of side
chain committee, alphabet nodes of the Inner Ring create multisignature transactions
for each alphabet contract.
Contract notifications
# Contract notifications
Alphabet contract does not produce notifications to process.
# Contract storage scheme
| Key | Value | Description |
|--------------------|------------|-------------------------------------------------|
| `netmapScriptHash` | Hash160 | netmap contract hash |
| `proxyScriptHash` | Hash160 | proxy contract hash |
| `name` | string | assigned glagolitic letter |
| `index` | int | the index of deployed alphabet contract |
| `threshold` | int | the total number of deployed alphabet contracts |
*/
package alphabet

View file

@ -1,6 +1,7 @@
package audit
import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
@ -8,7 +9,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
"github.com/nspcc-dev/neofs-contract/common"
)
type (
@ -44,6 +44,9 @@ const (
func _deploy(data interface{}, isUpdate bool) {
ctx := storage.GetContext()
common.RmAndCheckNotaryDisabledKey(data, notaryDisabledKey)
if isUpdate {
args := data.([]interface{})
common.CheckVersion(args[len(args)-1].(int))
@ -51,6 +54,7 @@ func _deploy(data interface{}, isUpdate bool) {
}
args := data.(struct {
//TODO(@acid-ant): #9 remove notaryDisabled in future version
notaryDisabled bool
addrNetmap interop.Hash160
})
@ -61,12 +65,6 @@ func _deploy(data interface{}, isUpdate bool) {
storage.Put(ctx, netmapContractKey, args.addrNetmap)
// initialize the way to collect signatures
storage.Put(ctx, notaryDisabledKey, args.notaryDisabled)
if args.notaryDisabled {
runtime.Log("audit contract notary disabled")
}
runtime.Log("audit contract initialized")
}
@ -90,16 +88,8 @@ func Update(script []byte, manifest []byte, data interface{}) {
// in later epochs.
func Put(rawAuditResult []byte) {
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
var innerRing []interop.PublicKey
if notaryDisabled {
netmapContract := storage.Get(ctx, netmapContractKey).(interop.Hash160)
innerRing = common.InnerRingNodesFromNetmap(netmapContract)
} else {
innerRing = common.InnerRingNodes()
}
innerRing := common.InnerRingNodes()
hdr := newAuditHeader(rawAuditResult)
presented := false
@ -182,7 +172,6 @@ func list(it iterator.Iterator) [][]byte {
ignore := [][]byte{
[]byte(netmapContractKey),
[]byte(notaryDisabledKey),
}
loop:

View file

@ -1,4 +1,4 @@
name: "NeoFS Audit"
name: "Audit"
safemethods: ["get", "list", "listByEpoch", "listByCID", "listByNode", "version"]
permissions:
- methods: ["update"]

View file

@ -1,5 +1,5 @@
/*
Audit contract is a contract deployed in NeoFS sidechain.
Audit contract is a contract deployed in FrostFS sidechain.
Inner Ring nodes perform audit of the registered containers during every epoch.
If a container contains StorageGroup objects, an Inner Ring node initializes
@ -15,8 +15,16 @@ nodes send a stable marshaled version of the DataAuditResult structure to the
contract. When Alphabet nodes of the Inner Ring perform settlement operations,
they make a list and get these AuditResultStructures from the audit contract.
Contract notifications
# Contract notifications
Audit contract does not produce notifications to process.
# Contract storage scheme
| Key | Value | Description |
|--------------------|------------|-----------------------------------------------------------|
| `netmapScriptHash` | Hash160 | netmap contract hash |
| auditID | ByteArray | serialized DataAuditResult structure |
*/
package audit

View file

@ -1,6 +1,7 @@
package balance
import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
@ -8,7 +9,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/interop/native/std"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
"github.com/nspcc-dev/neofs-contract/common"
)
type (
@ -22,7 +22,7 @@ type (
CirculationKey string
}
// Account structure stores metadata of each NeoFS balance account.
// Account structure stores metadata of each FrostFS balance account.
Account struct {
// Active balance
Balance int
@ -35,7 +35,7 @@ type (
)
const (
symbol = "NEOFS"
symbol = "FROSTFS"
decimals = 12
circulation = "MainnetGAS"
@ -60,6 +60,9 @@ func init() {
func _deploy(data interface{}, isUpdate bool) {
ctx := storage.GetContext()
common.RmAndCheckNotaryDisabledKey(data, notaryDisabledKey)
if isUpdate {
args := data.([]interface{})
common.CheckVersion(args[len(args)-1].(int))
@ -67,6 +70,7 @@ func _deploy(data interface{}, isUpdate bool) {
}
args := data.(struct {
//TODO(@acid-ant): #9 remove notaryDisabled in future version
notaryDisabled bool
addrNetmap interop.Hash160
addrContainer interop.Hash160
@ -79,13 +83,6 @@ func _deploy(data interface{}, isUpdate bool) {
storage.Put(ctx, netmapContractKey, args.addrNetmap)
storage.Put(ctx, containerContractKey, args.addrContainer)
// initialize the way to collect signatures
storage.Put(ctx, notaryDisabledKey, args.notaryDisabled)
if args.notaryDisabled {
common.InitVote(ctx)
runtime.Log("balance contract notary disabled")
}
runtime.Log("balance contract initialized")
}
@ -101,32 +98,32 @@ func Update(script []byte, manifest []byte, data interface{}) {
runtime.Log("balance contract updated")
}
// Symbol is a NEP-17 standard method that returns NEOFS token symbol.
// Symbol is a NEP-17 standard method that returns FROSTFS token symbol.
func Symbol() string {
return token.Symbol
}
// Decimals is a NEP-17 standard method that returns precision of NeoFS
// Decimals is a NEP-17 standard method that returns precision of FrostFS
// balances.
func Decimals() int {
return token.Decimals
}
// TotalSupply is a NEP-17 standard method that returns total amount of main
// chain GAS in NeoFS network.
// chain GAS in FrostFS network.
func TotalSupply() int {
ctx := storage.GetReadOnlyContext()
return token.getSupply(ctx)
}
// BalanceOf is a NEP-17 standard method that returns NeoFS balance of the specified
// BalanceOf is a NEP-17 standard method that returns FrostFS balance of the specified
// account.
func BalanceOf(account interop.Hash160) int {
ctx := storage.GetReadOnlyContext()
return token.balanceOf(ctx, account)
}
// Transfer is a NEP-17 standard method that transfers NeoFS balance from one
// Transfer is a NEP-17 standard method that transfers FrostFS balance from one
// account to another. It can be invoked only by the account owner.
//
// It produces Transfer and TransferX notifications. TransferX notification
@ -136,7 +133,7 @@ func Transfer(from, to interop.Hash160, amount int, data interface{}) bool {
return token.transfer(ctx, from, to, amount, false, nil)
}
// TransferX is a method for NeoFS balance to be transferred from one account to
// TransferX is a method for FrostFS balance to be transferred from one account to
// another. It can be invoked by the account owner or by Alphabet nodes.
//
// It produces Transfer and TransferX notifications.
@ -146,42 +143,8 @@ func Transfer(from, to interop.Hash160, amount int, data interface{}) bool {
// Inner Ring with multisignature.
func TransferX(from, to interop.Hash160, amount int, details []byte) {
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
var ( // for invocation collection without notary
alphabet []interop.PublicKey
nodeKey []byte
indirectCall bool
)
if notaryDisabled {
alphabet = common.AlphabetNodes()
nodeKey = common.InnerRingInvoker(alphabet)
if len(nodeKey) == 0 {
panic("this method must be invoked from inner ring")
}
indirectCall = common.FromKnownContract(
ctx,
runtime.GetCallingScriptHash(),
containerContractKey,
)
} else {
multiaddr := common.AlphabetAddress()
common.CheckAlphabetWitness(multiaddr)
}
if notaryDisabled && !indirectCall {
threshold := len(alphabet)*2/3 + 1
id := common.InvokeID([]interface{}{from, to, amount}, []byte("transfer"))
n := common.Vote(ctx, id, nodeKey)
if n < threshold {
return
}
common.RemoveVotes(ctx, id)
}
common.CheckAlphabetWitness()
result := token.transfer(ctx, from, to, amount, true, details)
if !result {
@ -197,27 +160,12 @@ func TransferX(from, to interop.Hash160, amount int, details []byte) {
// It produces Lock, Transfer and TransferX notifications.
//
// Lock method is invoked by Alphabet nodes of the Inner Ring when they process
// Withdraw notification from NeoFS contract. This should transfer assets
// Withdraw notification from FrostFS contract. This should transfer assets
// to a new lock account that won't be used for anything beside Unlock and Burn.
func Lock(txDetails []byte, from, to interop.Hash160, amount, until int) {
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
var ( // for invocation collection without notary
alphabet []interop.PublicKey
nodeKey []byte
)
if notaryDisabled {
alphabet = common.AlphabetNodes()
nodeKey = common.InnerRingInvoker(alphabet)
if len(nodeKey) == 0 {
panic("this method must be invoked from inner ring")
}
} else {
multiaddr := common.AlphabetAddress()
common.CheckAlphabetWitness(multiaddr)
}
common.CheckAlphabetWitness()
details := common.LockTransferDetails(txDetails)
@ -227,18 +175,6 @@ func Lock(txDetails []byte, from, to interop.Hash160, amount, until int) {
Parent: from,
}
if notaryDisabled {
threshold := len(alphabet)*2/3 + 1
id := common.InvokeID([]interface{}{txDetails}, []byte("lock"))
n := common.Vote(ctx, id, nodeKey)
if n < threshold {
return
}
common.RemoveVotes(ctx, id)
}
common.SetSerialized(ctx, to, lockAccount)
result := token.transfer(ctx, from, to, amount, true, details)
@ -258,21 +194,8 @@ func Lock(txDetails []byte, from, to interop.Hash160, amount, until int) {
// It produces Transfer and TransferX notifications.
func NewEpoch(epochNum int) {
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
if notaryDisabled {
indirectCall := common.FromKnownContract(
ctx,
runtime.GetCallingScriptHash(),
netmapContractKey,
)
if !indirectCall {
panic("this method must be invoked from inner ring")
}
} else {
multiaddr := common.AlphabetAddress()
common.CheckAlphabetWitness(multiaddr)
}
common.CheckAlphabetWitness()
it := storage.Find(ctx, []byte{}, storage.KeysOnly)
for iterator.Next(it) {
@ -300,43 +223,16 @@ func NewEpoch(epochNum int) {
// It produces Mint, Transfer and TransferX notifications.
//
// Mint method is invoked by Alphabet nodes of the Inner Ring when they process
// Deposit notification from NeoFS contract. Before that, Alphabet nodes should
// Deposit notification from FrostFS contract. Before that, Alphabet nodes should
// synchronize precision of mainchain GAS contract and Balance contract.
// Mint increases total supply of NEP-17 compatible NeoFS token.
// Mint increases total supply of NEP-17 compatible FrostFS token.
func Mint(to interop.Hash160, amount int, txDetails []byte) {
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
var ( // for invocation collection without notary
alphabet []interop.PublicKey
nodeKey []byte
)
if notaryDisabled {
alphabet = common.AlphabetNodes()
nodeKey = common.InnerRingInvoker(alphabet)
if len(nodeKey) == 0 {
panic("this method must be invoked from inner ring")
}
} else {
multiaddr := common.AlphabetAddress()
common.CheckAlphabetWitness(multiaddr)
}
common.CheckAlphabetWitness()
details := common.MintTransferDetails(txDetails)
if notaryDisabled {
threshold := len(alphabet)*2/3 + 1
id := common.InvokeID([]interface{}{txDetails}, []byte("mint"))
n := common.Vote(ctx, id, nodeKey)
if n < threshold {
return
}
common.RemoveVotes(ctx, id)
}
ok := token.transfer(ctx, nil, to, amount, true, details)
if !ok {
panic("can't transfer assets")
@ -355,45 +251,18 @@ func Mint(to interop.Hash160, amount int, txDetails []byte) {
// It produces Burn, Transfer and TransferX notifications.
//
// Burn method is invoked by Alphabet nodes of the Inner Ring when they process
// Cheque notification from NeoFS contract. It means that locked assets have been
// Cheque notification from FrostFS contract. It means that locked assets have been
// transferred to the user in the mainchain, therefore the lock account should be destroyed.
// Before that, Alphabet nodes should synchronize precision of mainchain GAS
// contract and Balance contract. Burn decreases total supply of NEP-17
// compatible NeoFS token.
// compatible FrostFS token.
func Burn(from interop.Hash160, amount int, txDetails []byte) {
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
var ( // for invocation collection without notary
alphabet []interop.PublicKey
nodeKey []byte
)
if notaryDisabled {
alphabet = common.AlphabetNodes()
nodeKey = common.InnerRingInvoker(alphabet)
if len(nodeKey) == 0 {
panic("this method must be invoked from inner ring")
}
} else {
multiaddr := common.AlphabetAddress()
common.CheckAlphabetWitness(multiaddr)
}
common.CheckAlphabetWitness()
details := common.BurnTransferDetails(txDetails)
if notaryDisabled {
threshold := len(alphabet)*2/3 + 1
id := common.InvokeID([]interface{}{txDetails}, []byte("burn"))
n := common.Vote(ctx, id, nodeKey)
if n < threshold {
return
}
common.RemoveVotes(ctx, id)
}
ok := token.transfer(ctx, from, nil, amount, true, details)
if !ok {
panic("can't transfer assets")

View file

@ -1,4 +1,4 @@
name: "NeoFS Balance"
name: "Balance"
supportedstandards: ["NEP-17"]
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "version"]
permissions:

View file

@ -1,7 +1,7 @@
/*
Balance contract is a contract deployed in NeoFS sidechain.
Balance contract is a contract deployed in FrostFS sidechain.
Balance contract stores all NeoFS account balances. It is a NEP-17 compatible
Balance contract stores all FrostFS account balances. It is a NEP-17 compatible
contract, so it can be tracked and controlled by N3 compatible network
monitors and wallet software.
@ -10,70 +10,78 @@ data audit settlements or container fee payments. It is inefficient to make such
small payment transactions in the mainchain. To process small transfers, balance
contract has higher (12) decimal precision than native GAS contract.
NeoFS balances are synchronized with mainchain operations. Deposit produces
minting of NEOFS tokens in Balance contract. Withdraw locks some NEOFS tokens
in a special lock account. When NeoFS contract transfers GAS assets back to the
FrostFS balances are synchronized with mainchain operations. Deposit produces
minting of FROSTFS tokens in Balance contract. Withdraw locks some FROSTFS tokens
in a special lock account. When FrostFS contract transfers GAS assets back to the
user, the lock account is destroyed with burn operation.
Contract notifications
# Contract notifications
Transfer notification. This is a NEP-17 standard notification.
Transfer:
- name: from
type: Hash160
- name: to
type: Hash160
- name: amount
type: Integer
Transfer:
- name: from
type: Hash160
- name: to
type: Hash160
- name: amount
type: Integer
TransferX notification. This is an enhanced transfer notification with details.
TransferX:
- name: from
type: Hash160
- name: to
type: Hash160
- name: amount
type: Integer
- name: details
type: ByteArray
TransferX:
- name: from
type: Hash160
- name: to
type: Hash160
- name: amount
type: Integer
- name: details
type: ByteArray
Lock notification. This notification is produced when a lock account is
created. It contains information about the mainchain transaction that has produced
the asset lock, the address of the lock account and the NeoFS epoch number until which the
the asset lock, the address of the lock account and the FrostFS epoch number until which the
lock account is valid. Alphabet nodes of the Inner Ring catch notification and initialize
Cheque method invocation of NeoFS contract.
Cheque method invocation of FrostFS contract.
Lock:
- name: txID
type: ByteArray
- name: from
type: Hash160
- name: to
type: Hash160
- name: amount
type: Integer
- name: until
type: Integer
Lock:
- name: txID
type: ByteArray
- name: from
type: Hash160
- name: to
type: Hash160
- name: amount
type: Integer
- name: until
type: Integer
Mint notification. This notification is produced when user balance is
replenished from deposit in the mainchain.
Mint:
- name: to
type: Hash160
- name: amount
type: Integer
Mint:
- name: to
type: Hash160
- name: amount
type: Integer
Burn notification. This notification is produced after user balance is reduced
when NeoFS contract has transferred GAS assets back to the user.
when FrostFS contract has transferred GAS assets back to the user.
Burn:
- name: from
type: Hash160
- name: amount
type: Integer
# Contract storage scheme
| Key | Value | Description |
|-----------------------|------------|----------------------------------|
| `netmapScriptHash` | Hash160 | netmap contract hash |
| `containerScriptHash` | Hash160 | container contract hash |
| circulationKey | int | the token circulation key value |
Burn:
- name: from
type: Hash160
- name: amount
type: Integer
*/
package balance

26
common/common.go Normal file
View file

@ -0,0 +1,26 @@
package common
import (
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
"github.com/nspcc-dev/neo-go/pkg/interop/util"
)
const (
panicMsgForNotaryDisabledEnv = "contract not applicable for notary-disabled environment"
)
// BytesEqual compares two slices of bytes by wrapping them into strings,
// which is necessary with new util.Equals interop behaviour, see neo-go#1176.
func BytesEqual(a []byte, b []byte) bool {
return util.Equals(string(a), string(b))
}
// RmAndCheckNotaryDisabledKey remove notary disabled key from storage and
// panic in notary disabled environment
func RmAndCheckNotaryDisabledKey(data interface{}, key interface{}) {
//TODO(@acid-ant): #9 remove notaryDisabled from args in future version
storage.Delete(storage.GetContext(), key)
if data.([]interface{})[0].(bool) {
panic(panicMsgForNotaryDisabledEnv)
}
}

View file

@ -6,28 +6,12 @@ import (
"github.com/nspcc-dev/neo-go/pkg/interop/native/ledger"
"github.com/nspcc-dev/neo-go/pkg/interop/native/neo"
"github.com/nspcc-dev/neo-go/pkg/interop/native/roles"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
)
type IRNode struct {
PublicKey interop.PublicKey
}
const irListMethod = "innerRingList"
// InnerRingInvoker returns the public key of the inner ring node that has invoked the contract.
// Work around for environments without notary support.
func InnerRingInvoker(ir []interop.PublicKey) interop.PublicKey {
for i := 0; i < len(ir); i++ {
node := ir[i]
if runtime.CheckWitness(node) {
return node
}
}
return nil
}
// InnerRingNodes return a list of inner ring nodes from state validator role
// in the sidechain.
func InnerRingNodes() []interop.PublicKey {
@ -35,18 +19,6 @@ func InnerRingNodes() []interop.PublicKey {
return roles.GetDesignatedByRole(roles.NeoFSAlphabet, uint32(blockHeight+1))
}
// InnerRingNodesFromNetmap gets a list of inner ring nodes through
// calling "innerRingList" method of smart contract.
// Work around for environments without notary support.
func InnerRingNodesFromNetmap(sc interop.Hash160) []interop.PublicKey {
nodes := contract.Call(sc, irListMethod, contract.ReadOnly).([]IRNode)
pubs := []interop.PublicKey{}
for i := range nodes {
pubs = append(pubs, nodes[i].PublicKey)
}
return pubs
}
// AlphabetNodes returns a list of alphabet nodes from committee in the sidechain.
func AlphabetNodes() []interop.PublicKey {
return neo.GetCommittee()

View file

@ -5,15 +5,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
)
func GetList(ctx storage.Context, key interface{}) [][]byte {
data := storage.Get(ctx, key)
if data != nil {
return std.Deserialize(data.([]byte)).([][]byte)
}
return [][]byte{}
}
// SetSerialized serializes data and puts it into contract storage.
func SetSerialized(ctx storage.Context, key interface{}, value interface{}) {
data := std.Serialize(value)

View file

@ -4,15 +4,15 @@ import "github.com/nspcc-dev/neo-go/pkg/interop/native/std"
const (
major = 0
minor = 16
minor = 18
patch = 0
// Versions from which an update should be performed.
// These should be used in a group (so prevMinor can be equal to minor if there are
// any migration routines.
prevMajor = 0
prevMinor = 15
prevPatch = 4
prevMinor = 16
prevPatch = 0
Version = major*1_000_000 + minor*1_000 + patch

View file

@ -1,149 +0,0 @@
package common
import (
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/native/crypto"
"github.com/nspcc-dev/neo-go/pkg/interop/native/ledger"
"github.com/nspcc-dev/neo-go/pkg/interop/native/std"
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
"github.com/nspcc-dev/neo-go/pkg/interop/util"
)
type Ballot struct {
// ID of the voting decision.
ID []byte
// Public keys of the already voted inner ring nodes.
Voters []interop.PublicKey
// Height of block with the last vote.
Height int
}
const voteKey = "ballots"
const blockDiff = 20 // change base on performance evaluation
func InitVote(ctx storage.Context) {
SetSerialized(ctx, voteKey, []Ballot{})
}
// Vote adds ballot for the decision with a specific 'id' and returns the amount
// of unique voters for that decision.
func Vote(ctx storage.Context, id, from []byte) int {
var (
newCandidates []Ballot
candidates = getBallots(ctx)
found = -1
blockHeight = ledger.CurrentIndex()
)
for i := 0; i < len(candidates); i++ {
cnd := candidates[i]
if blockHeight-cnd.Height > blockDiff {
continue
}
if BytesEqual(cnd.ID, id) {
voters := cnd.Voters
for j := range voters {
if BytesEqual(voters[j], from) {
return len(voters)
}
}
voters = append(voters, from)
cnd = Ballot{ID: id, Voters: voters, Height: blockHeight}
found = len(voters)
}
newCandidates = append(newCandidates, cnd)
}
if found < 0 {
voters := []interop.PublicKey{from}
newCandidates = append(newCandidates, Ballot{
ID: id,
Voters: voters,
Height: blockHeight})
found = 1
}
SetSerialized(ctx, voteKey, newCandidates)
return found
}
// RemoveVotes clears ballots of the decision that has been accepted by
// inner ring nodes.
func RemoveVotes(ctx storage.Context, id []byte) {
var (
candidates = getBallots(ctx)
index int
)
for i := 0; i < len(candidates); i++ {
cnd := candidates[i]
if BytesEqual(cnd.ID, id) {
index = i
break
}
}
util.Remove(candidates, index)
SetSerialized(ctx, voteKey, candidates)
}
// getBallots returns a deserialized slice of vote ballots.
func getBallots(ctx storage.Context) []Ballot {
data := storage.Get(ctx, voteKey)
if data != nil {
return std.Deserialize(data.([]byte)).([]Ballot)
}
return []Ballot{}
}
// BytesEqual compares two slices of bytes by wrapping them into strings,
// which is necessary with new util.Equals interop behaviour, see neo-go#1176.
func BytesEqual(a []byte, b []byte) bool {
return util.Equals(string(a), string(b))
}
// InvokeID returns hashed value of prefix and args concatenation. Iy is used to
// identify different ballots.
func InvokeID(args []interface{}, prefix []byte) []byte {
for i := range args {
arg := args[i].([]byte)
prefix = append(prefix, arg...)
}
return crypto.Sha256(prefix)
}
/*
Check if the invocation is made from known container or audit contracts.
This is necessary because calls from these contracts require to do transfer
without signature collection (1 invoke transfer).
IR1, IR2, IR3, IR4 -(4 invokes)-> [ Container Contract ] -(1 invoke)-> [ Balance Contract ]
We can do 1 invoke transfer if:
- invokation has happened from inner ring node,
- it is indirect invocation from another smart-contract.
However, there is a possible attack, when a malicious inner ring node creates
a malicious smart-contract in the morph chain to do indirect call.
MaliciousIR -(1 invoke)-> [ Malicious Contract ] -(1 invoke)-> [ Balance Contract ]
To prevent that, we have to allow 1 invoke transfer from authorised well-known
smart-contracts, that will be set up at `Init` method.
*/
func FromKnownContract(ctx storage.Context, caller interop.Hash160, key string) bool {
addr := storage.Get(ctx, key).(interop.Hash160)
return BytesEqual(caller, addr)
}

View file

@ -16,8 +16,8 @@ var (
// CheckAlphabetWitness checks witness of the passed caller.
// It panics with ErrAlphabetWitnessFailed message on fail.
func CheckAlphabetWitness(caller []byte) {
checkWitnessWithPanic(caller, ErrAlphabetWitnessFailed)
func CheckAlphabetWitness() {
checkWitnessWithPanic(AlphabetAddress(), ErrAlphabetWitnessFailed)
}
// CheckOwnerWitness checks witness of the passed caller.

View file

@ -1,47 +1,19 @@
name: "NeoFS Container"
safemethods: ["count", "get", "owner", "list", "eACL", "getContainerSize", "listContainerSizes", "version"]
name: "Container"
safemethods: ["count", "containersOf", "get", "owner", "list", "eACL", "getContainerSize", "listContainerSizes", "iterateContainerSizes", "version"]
permissions:
- methods: ["update", "addKey", "transferX",
"register", "addRecord", "deleteRecords"]
events:
- name: containerPut
parameters:
- name: container
type: ByteArray
- name: signature
type: Signature
- name: publicKey
type: PublicKey
- name: token
type: ByteArray
- name: PutSuccess
parameters:
- name: containerID
type: Hash256
- name: publicKey
type: PublicKey
- name: containerDelete
parameters:
- name: containerID
type: ByteArray
- name: signature
type: Signature
- name: token
type: ByteArray
- name: DeleteSuccess
parameters:
- name: containerID
type: ByteArray
- name: setEACL
parameters:
- name: eACL
type: ByteArray
- name: signature
type: Signature
- name: publicKey
type: PublicKey
- name: token
type: ByteArray
- name: SetEACLSuccess
parameters:
- name: containerID

View file

@ -1,6 +1,7 @@
package container
import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/convert"
@ -10,7 +11,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/interop/native/std"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
"github.com/nspcc-dev/neofs-contract/common"
)
type (
@ -44,13 +44,13 @@ type (
)
const (
neofsIDContractKey = "identityScriptHash"
balanceContractKey = "balanceScriptHash"
netmapContractKey = "netmapScriptHash"
nnsContractKey = "nnsScriptHash"
nnsRootKey = "nnsRoot"
nnsHasAliasKey = "nnsHasAlias"
notaryDisabledKey = "notary"
frostfsIDContractKey = "identityScriptHash"
balanceContractKey = "balanceScriptHash"
netmapContractKey = "netmapScriptHash"
nnsContractKey = "nnsScriptHash"
nnsRootKey = "nnsRoot"
nnsHasAliasKey = "nnsHasAlias"
notaryDisabledKey = "notary"
// RegistrationFeeKey is a key in netmap config which contains fee for container registration.
RegistrationFeeKey = "ContainerFee"
@ -62,6 +62,8 @@ const (
singleEstimatePrefix = "est"
estimateKeyPrefix = "cnr"
containerKeyPrefix = 'x'
ownerKeyPrefix = 'o'
estimatePostfixSize = 10
// CleanupDelta contains the number of the last epochs for which container estimations are present.
CleanupDelta = 3
@ -74,10 +76,10 @@ const (
NotFoundError = "container does not exist"
// default SOA record field values
defaultRefresh = 3600 // 1 hour
defaultRetry = 600 // 10 min
defaultExpire = 604800 // 1 week
defaultTTL = 3600 // 1 hour
defaultRefresh = 3600 // 1 hour
defaultRetry = 600 // 10 min
defaultExpire = 3600 * 24 * 365 * 10 // 10 years
defaultTTL = 3600 // 1 hour
)
var (
@ -90,13 +92,37 @@ func OnNEP11Payment(a interop.Hash160, b int, c []byte, d interface{}) {
func _deploy(data interface{}, isUpdate bool) {
ctx := storage.GetContext()
common.RmAndCheckNotaryDisabledKey(data, notaryDisabledKey)
if isUpdate {
args := data.([]interface{})
common.CheckVersion(args[len(args)-1].(int))
it := storage.Find(ctx, []byte{}, storage.None)
for iterator.Next(it) {
item := iterator.Value(it).(struct {
key []byte
value []byte
})
// Migrate container.
if len(item.key) == containerIDSize {
storage.Delete(ctx, item.key)
storage.Put(ctx, append([]byte{containerKeyPrefix}, item.key...), item.value)
}
// Migrate owner-cid map.
if len(item.key) == 25 /* owner id size */ +containerIDSize {
storage.Delete(ctx, item.key)
storage.Put(ctx, append([]byte{ownerKeyPrefix}, item.key...), item.value)
}
}
return
}
args := data.(struct {
//TODO(@acid-ant): #9 remove notaryDisabled in future version
notaryDisabled bool
addrNetmap interop.Hash160
addrBalance interop.Hash160
@ -113,17 +139,10 @@ func _deploy(data interface{}, isUpdate bool) {
storage.Put(ctx, netmapContractKey, args.addrNetmap)
storage.Put(ctx, balanceContractKey, args.addrBalance)
storage.Put(ctx, neofsIDContractKey, args.addrID)
storage.Put(ctx, frostfsIDContractKey, args.addrID)
storage.Put(ctx, nnsContractKey, args.addrNNS)
storage.Put(ctx, nnsRootKey, args.nnsRoot)
// initialize the way to collect signatures
storage.Put(ctx, notaryDisabledKey, args.notaryDisabled)
if args.notaryDisabled {
common.InitVote(ctx)
runtime.Log("container contract notary disabled")
}
// add NNS root for container alias domains
registerNiceNameTLD(args.addrNNS, args.nnsRoot)
@ -138,7 +157,7 @@ func registerNiceNameTLD(addrNNS interop.Hash160, nnsRoot string) {
}
res := contract.Call(addrNNS, "register", contract.All,
nnsRoot, runtime.GetExecutingScriptHash(), "ops@nspcc.ru",
nnsRoot, runtime.GetExecutingScriptHash(), "ops@frostfs.info",
defaultRefresh, defaultRetry, defaultExpire, defaultTTL).(bool)
if !res {
panic("can't register NNS TLD")
@ -158,7 +177,7 @@ func Update(script []byte, manifest []byte, data interface{}) {
}
// Put method creates a new container if it has been invoked by Alphabet nodes
// of the Inner Ring. Otherwise, it produces containerPut notification.
// of the Inner Ring.
//
// Container should be a stable marshaled Container structure from API.
// Signature is a RFC6979 signature of the Container.
@ -175,11 +194,10 @@ func PutNamed(container []byte, signature interop.Signature,
publicKey interop.PublicKey, token []byte,
name, zone string) {
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
ownerID := ownerFromBinaryContainer(container)
containerID := crypto.Sha256(container)
neofsIDContractAddr := storage.Get(ctx, neofsIDContractKey).(interop.Hash160)
frostfsIDContractAddr := storage.Get(ctx, frostfsIDContractKey).(interop.Hash160)
cnr := Container{
value: container,
sig: signature,
@ -216,26 +234,7 @@ func PutNamed(container []byte, signature interop.Signature,
panic("insufficient balance to create container")
}
if notaryDisabled {
nodeKey := common.InnerRingInvoker(alphabet)
if len(nodeKey) == 0 {
runtime.Notify("containerPut", container, signature, publicKey, token)
return
}
threshold := len(alphabet)*2/3 + 1
id := common.InvokeID([]interface{}{container, signature, publicKey}, []byte("put"))
n := common.Vote(ctx, id, nodeKey)
if n < threshold {
return
}
common.RemoveVotes(ctx, id)
} else {
multiaddr := common.AlphabetAddress()
common.CheckAlphabetWitness(multiaddr)
}
common.CheckAlphabetWitness()
// todo: check if new container with unique container id
details := common.ContainerFeeTransferDetails(containerID)
@ -258,7 +257,7 @@ func PutNamed(container []byte, signature interop.Signature,
if name != "" {
if needRegister {
res := contract.Call(nnsContractAddr, "register", contract.All,
domain, runtime.GetExecutingScriptHash(), "ops@nspcc.ru",
domain, runtime.GetExecutingScriptHash(), "ops@frostfs.info",
defaultRefresh, defaultRetry, defaultExpire, defaultTTL).(bool)
if !res {
panic("can't register the domain " + domain)
@ -272,7 +271,7 @@ func PutNamed(container []byte, signature interop.Signature,
}
if len(token) == 0 { // if container created directly without session
contract.Call(neofsIDContractAddr, "addKey", contract.All, ownerID, [][]byte{publicKey})
contract.Call(frostfsIDContractAddr, "addKey", contract.All, ownerID, [][]byte{publicKey})
}
runtime.Log("added new container")
@ -304,8 +303,7 @@ func checkNiceNameAvailable(nnsContractAddr interop.Hash160, domain string) bool
}
// Delete method removes a container from the contract storage if it has been
// invoked by Alphabet nodes of the Inner Ring. Otherwise, it produces
// containerDelete notification.
// invoked by Alphabet nodes of the Inner Ring.
//
// Signature is a RFC6979 signature of the container ID.
// Token is optional and should be a stable marshaled SessionToken structure from
@ -314,34 +312,13 @@ func checkNiceNameAvailable(nnsContractAddr interop.Hash160, domain string) bool
// If the container doesn't exist, it panics with NotFoundError.
func Delete(containerID []byte, signature interop.Signature, token []byte) {
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
ownerID := getOwnerByID(ctx, containerID)
if ownerID == nil {
return
}
if notaryDisabled {
alphabet := common.AlphabetNodes()
nodeKey := common.InnerRingInvoker(alphabet)
if len(nodeKey) == 0 {
runtime.Notify("containerDelete", containerID, signature, token)
return
}
threshold := len(alphabet)*2/3 + 1
id := common.InvokeID([]interface{}{containerID, signature}, []byte("delete"))
n := common.Vote(ctx, id, nodeKey)
if n < threshold {
return
}
common.RemoveVotes(ctx, id)
} else {
multiaddr := common.AlphabetAddress()
common.CheckAlphabetWitness(multiaddr)
}
common.CheckAlphabetWitness()
key := append([]byte(nnsHasAliasKey), containerID...)
domain := storage.Get(ctx, key).(string)
@ -391,17 +368,24 @@ func Owner(containerID []byte) []byte {
func Count() int {
count := 0
ctx := storage.GetReadOnlyContext()
it := storage.Find(ctx, []byte{}, storage.KeysOnly)
it := storage.Find(ctx, []byte{containerKeyPrefix}, storage.KeysOnly)
for iterator.Next(it) {
key := iterator.Value(it).([]byte)
// V2 format
if len(key) == containerIDSize {
count++
}
count++
}
return count
}
// ContainersOf iterates over all container IDs owned by the specified owner.
// If owner is nil, it iterates over all containers.
func ContainersOf(owner []byte) iterator.Iterator {
ctx := storage.GetReadOnlyContext()
key := []byte{ownerKeyPrefix}
if len(owner) != 0 {
key = append(key, owner...)
}
return storage.Find(ctx, key, storage.ValuesOnly)
}
// List method returns a list of all container IDs owned by the specified owner.
func List(owner []byte) [][]byte {
ctx := storage.GetReadOnlyContext()
@ -412,7 +396,7 @@ func List(owner []byte) [][]byte {
var list [][]byte
it := storage.Find(ctx, owner, storage.ValuesOnly)
it := storage.Find(ctx, append([]byte{ownerKeyPrefix}, owner...), storage.ValuesOnly)
for iterator.Next(it) {
id := iterator.Value(it).([]byte)
list = append(list, id)
@ -422,8 +406,7 @@ func List(owner []byte) [][]byte {
}
// SetEACL method sets a new extended ACL table related to the contract
// if it was invoked by Alphabet nodes of the Inner Ring. Otherwise, it produces
// setEACL notification.
// if it was invoked by Alphabet nodes of the Inner Ring.
//
// EACL should be a stable marshaled EACLTable structure from API.
// Signature is a RFC6979 signature of the Container.
@ -434,7 +417,6 @@ func List(owner []byte) [][]byte {
// If the container doesn't exist, it panics with NotFoundError.
func SetEACL(eACL []byte, signature interop.Signature, publicKey interop.PublicKey, token []byte) {
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
// V2 format
// get container ID
@ -447,27 +429,7 @@ func SetEACL(eACL []byte, signature interop.Signature, publicKey interop.PublicK
panic(NotFoundError)
}
if notaryDisabled {
alphabet := common.AlphabetNodes()
nodeKey := common.InnerRingInvoker(alphabet)
if len(nodeKey) == 0 {
runtime.Notify("setEACL", eACL, signature, publicKey, token)
return
}
threshold := len(alphabet)*2/3 + 1
id := common.InvokeID([]interface{}{eACL}, []byte("setEACL"))
n := common.Vote(ctx, id, nodeKey)
if n < threshold {
return
}
common.RemoveVotes(ctx, id)
} else {
multiaddr := common.AlphabetAddress()
common.CheckAlphabetWitness(multiaddr)
}
common.CheckAlphabetWitness()
rule := ExtendedACL{
value: eACL,
@ -551,7 +513,7 @@ func GetContainerSize(id []byte) containerSizes {
}
// ListContainerSizes method returns the IDs of container size estimations
// that has been registered for the specified epoch.
// that have been registered for the specified epoch.
func ListContainerSizes(epoch int) [][]byte {
ctx := storage.GetReadOnlyContext()
@ -582,25 +544,25 @@ func ListContainerSizes(epoch int) [][]byte {
return result
}
// IterateContainerSizes method returns iterator over container size estimations
// that have been registered for the specified epoch.
func IterateContainerSizes(epoch int) iterator.Iterator {
ctx := storage.GetReadOnlyContext()
var buf interface{} = epoch
key := []byte(estimateKeyPrefix)
key = append(key, buf.([]byte)...)
return storage.Find(ctx, key, storage.DeserializeValues)
}
// NewEpoch method removes all container size estimations from epoch older than
// epochNum + 3. It can be invoked only by NewEpoch method of the Netmap contract.
func NewEpoch(epochNum int) {
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
if notaryDisabled {
indirectCall := common.FromKnownContract(
ctx,
runtime.GetCallingScriptHash(),
netmapContractKey,
)
if !indirectCall {
panic("method must be invoked by inner ring")
}
} else {
multiaddr := common.AlphabetAddress()
common.CheckAlphabetWitness(multiaddr)
}
common.CheckAlphabetWitness()
cleanupContainers(ctx, epochNum)
}
@ -608,36 +570,7 @@ func NewEpoch(epochNum int) {
// StartContainerEstimation method produces StartEstimation notification.
// It can be invoked only by Alphabet nodes of the Inner Ring.
func StartContainerEstimation(epoch int) {
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
var ( // for invocation collection without notary
alphabet []interop.PublicKey
nodeKey []byte
)
if notaryDisabled {
alphabet = common.AlphabetNodes()
nodeKey = common.InnerRingInvoker(alphabet)
if len(nodeKey) == 0 {
panic("method must be invoked by inner ring")
}
} else {
multiaddr := common.AlphabetAddress()
common.CheckAlphabetWitness(multiaddr)
}
if notaryDisabled {
threshold := len(alphabet)*2/3 + 1
id := common.InvokeID([]interface{}{epoch}, []byte("startEstimation"))
n := common.Vote(ctx, id, nodeKey)
if n < threshold {
return
}
common.RemoveVotes(ctx, id)
}
common.CheckAlphabetWitness()
runtime.Notify("StartEstimation", epoch)
runtime.Log("notification has been produced")
@ -646,36 +579,7 @@ func StartContainerEstimation(epoch int) {
// StopContainerEstimation method produces StopEstimation notification.
// It can be invoked only by Alphabet nodes of the Inner Ring.
func StopContainerEstimation(epoch int) {
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
var ( // for invocation collection without notary
alphabet []interop.PublicKey
nodeKey []byte
)
if notaryDisabled {
alphabet = common.AlphabetNodes()
nodeKey = common.InnerRingInvoker(alphabet)
if len(nodeKey) == 0 {
panic("method must be invoked by inner ring")
}
} else {
multiaddr := common.AlphabetAddress()
common.CheckAlphabetWitness(multiaddr)
}
if notaryDisabled {
threshold := len(alphabet)*2/3 + 1
id := common.InvokeID([]interface{}{epoch}, []byte("stopEstimation"))
n := common.Vote(ctx, id, nodeKey)
if n < threshold {
return
}
common.RemoveVotes(ctx, id)
}
common.CheckAlphabetWitness()
runtime.Notify("StopEstimation", epoch)
runtime.Log("notification has been produced")
@ -687,29 +591,30 @@ func Version() int {
}
func addContainer(ctx storage.Context, id, owner []byte, container Container) {
containerListKey := append(owner, id...)
containerListKey := append([]byte{ownerKeyPrefix}, owner...)
containerListKey = append(containerListKey, id...)
storage.Put(ctx, containerListKey, id)
common.SetSerialized(ctx, id, container)
idKey := append([]byte{containerKeyPrefix}, id...)
common.SetSerialized(ctx, idKey, container)
}
func removeContainer(ctx storage.Context, id []byte, owner []byte) {
containerListKey := append(owner, id...)
containerListKey := append([]byte{ownerKeyPrefix}, owner...)
containerListKey = append(containerListKey, id...)
storage.Delete(ctx, containerListKey)
storage.Delete(ctx, id)
storage.Delete(ctx, append([]byte{containerKeyPrefix}, id...))
}
func getAllContainers(ctx storage.Context) [][]byte {
var list [][]byte
it := storage.Find(ctx, []byte{}, storage.KeysOnly)
it := storage.Find(ctx, []byte{containerKeyPrefix}, storage.KeysOnly|storage.RemovePrefix)
for iterator.Next(it) {
key := iterator.Value(it).([]byte) // it MUST BE `storage.KeysOnly`
// V2 format
if len(key) == containerIDSize {
list = append(list, key)
}
list = append(list, key)
}
return list
@ -726,7 +631,7 @@ func getEACL(ctx storage.Context, cid []byte) ExtendedACL {
}
func getContainer(ctx storage.Context, cid []byte) Container {
data := storage.Get(ctx, cid)
data := storage.Get(ctx, append([]byte{containerKeyPrefix}, cid...))
if data != nil {
return std.Deserialize(data.([]byte)).(Container)
}

View file

@ -1,5 +1,5 @@
/*
Container contract is a contract deployed in NeoFS sidechain.
Container contract is a contract deployed in FrostFS sidechain.
Container contract stores and manages containers, extended ACLs and container
size estimations. Contract does not perform sanity or signature checks of
@ -7,62 +7,38 @@ containers or extended ACLs, it is done by Alphabet nodes of the Inner Ring.
Alphabet nodes approve it by invoking the same Put or SetEACL methods with
the same arguments.
Contract notifications
containerPut notification. This notification is produced when a user wants to
create a new container. Alphabet nodes of the Inner Ring catch the notification and
validate container data, signature and token if present.
containerPut:
- name: container
type: ByteArray
- name: signature
type: Signature
- name: publicKey
type: PublicKey
- name: token
type: ByteArray
containerDelete notification. This notification is produced when a container owner
wants to delete a container. Alphabet nodes of the Inner Ring catch the notification
and validate container ownership, signature and token if present.
containerDelete:
- name: containerID
type: ByteArray
- name: signature
type: Signature
- name: token
type: ByteArray
setEACL notification. This notification is produced when a container owner wants
to update an extended ACL of a container. Alphabet nodes of the Inner Ring catch
the notification and validate container ownership, signature and token if
present.
setEACL:
- name: eACL
type: ByteArray
- name: signature
type: Signature
- name: publicKey
type: PublicKey
- name: token
type: ByteArray
# Contract notifications
StartEstimation notification. This notification is produced when Storage nodes
should exchange estimation values of container sizes among other Storage nodes.
StartEstimation:
- name: epoch
type: Integer
StartEstimation:
- name: epoch
type: Integer
StopEstimation notification. This notification is produced when Storage nodes
should calculate average container size based on received estimations and store
it in Container contract.
StopEstimation:
- name: epoch
type: Integer
StopEstimation:
- name: epoch
type: Integer
# Contract storage scheme
| Key | Value | Description |
|-----------------------------------------------------------------------------------------------------|
| `netmapScriptHash` | Hash160 | netmap contract hash |
| `balanceScriptHash` | Hash160 | balance contract hash |
| `identityScriptHash` | Hash160 | frostfsID contract hash |
| `nnsContractKey` | Hash160 | nns contract hash |
| `nnsRoot` | string | default value for domain zone |
| `cnr` + epoch + containerID + publicKeyHash[:10] | ByteArray | estimated container size |
| `est` + containerID + publicKeyHash | ByteArray | serialized epochs array |
| `o` + ownerID + containerID | ByteArray | container ID |
| `x` + containerID | ByteArray | serialized container struct |
| `nnsHasAlias` + containerID | string | domain name |
*/
package container

5
debian/changelog vendored Normal file
View file

@ -0,0 +1,5 @@
frostfs-contract (0.0.0) stable; urgency=medium
* Initial release
-- TrueCloudLab <tech@frostfs.info> Wed, 24 Aug 2022 18:29:49 +0300

33
debian/control vendored Normal file
View file

@ -0,0 +1,33 @@
Source: frostfs-contract
Section: misc
Priority: optional
Maintainer: FrostFS <tech@frostfs.info>
Build-Depends: debhelper-compat (= 13), git, devscripts, neo-go
Standards-Version: 4.5.1
Homepage: https://fs.neo.org/
Vcs-Git: https://git.frostfs.info/TrueCloudLab/frostfs-contract.git
Vcs-Browser: https://git.frostfs.info/TrueCloudLab/frostfs-contract
Package: frostfs-contract
Architecture: all
Depends: ${misc:Depends}
Description: FrostFS-Contract contains all FrostFS related contracts.
Contracts are written for neo-go compiler.
These contracts are deployed both in the mainchain and the sidechain.
.
Mainchain contracts:
.
- frostfs
- processing
.
Sidechain contracts:
.
- alphabet
- audit
- balance
- container
- frostfsid
- netmap
- nns
- proxy
- reputation

23
debian/copyright vendored Normal file
View file

@ -0,0 +1,23 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: frostfs-contract
Upstream-Contact: tech@frostfs.info
Source: https://git.frostfs.info/TrueCloudLab/frostfs-contract
Files: *
Copyright: 2022 TrueCloudLab (@TrueCloudLab)
Copyright: 2018-2022 NeoSPCC (@nspcc-dev)
License: GPL-3
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published
by the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program or at /usr/share/common-licenses/GPL-3.
If not, see <http://www.gnu.org/licenses/>.

1
debian/neofs-contract.docs vendored Normal file
View file

@ -0,0 +1 @@
README*

39
debian/postinst.ex vendored Normal file
View file

@ -0,0 +1,39 @@
#!/bin/sh
# postinst script for frostfs-contract
#
# see: dh_installdeb(1)
set -e
# summary of how this script can be called:
# * <postinst> `configure' <most-recently-configured-version>
# * <old-postinst> `abort-upgrade' <new version>
# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
# <new-version>
# * <postinst> `abort-remove'
# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
# <failed-install-package> <version> `removing'
# <conflicting-package> <version>
# for details, see https://www.debian.org/doc/debian-policy/ or
# the debian-policy package
case "$1" in
configure)
;;
abort-upgrade|abort-remove|abort-deconfigure)
;;
*)
echo "postinst called with unknown argument \`$1'" >&2
exit 1
;;
esac
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
#DEBHELPER#
exit 0

37
debian/postrm.ex vendored Normal file
View file

@ -0,0 +1,37 @@
#!/bin/sh
# postrm script for frostfs-contract
#
# see: dh_installdeb(1)
set -e
# summary of how this script can be called:
# * <postrm> `remove'
# * <postrm> `purge'
# * <old-postrm> `upgrade' <new-version>
# * <new-postrm> `failed-upgrade' <old-version>
# * <new-postrm> `abort-install'
# * <new-postrm> `abort-install' <old-version>
# * <new-postrm> `abort-upgrade' <old-version>
# * <disappearer's-postrm> `disappear' <overwriter>
# <overwriter-version>
# for details, see https://www.debian.org/doc/debian-policy/ or
# the debian-policy package
case "$1" in
purge|remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
;;
*)
echo "postrm called with unknown argument \`$1'" >&2
exit 1
;;
esac
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
#DEBHELPER#
exit 0

35
debian/preinst.ex vendored Normal file
View file

@ -0,0 +1,35 @@
#!/bin/sh
# preinst script for frostfs-contract
#
# see: dh_installdeb(1)
set -e
# summary of how this script can be called:
# * <new-preinst> `install'
# * <new-preinst> `install' <old-version>
# * <new-preinst> `upgrade' <old-version>
# * <old-preinst> `abort-upgrade' <new-version>
# for details, see https://www.debian.org/doc/debian-policy/ or
# the debian-policy package
case "$1" in
install|upgrade)
;;
abort-upgrade)
;;
*)
echo "preinst called with unknown argument \`$1'" >&2
exit 1
;;
esac
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
#DEBHELPER#
exit 0

38
debian/prerm.ex vendored Normal file
View file

@ -0,0 +1,38 @@
#!/bin/sh
# prerm script for frostfs-contract
#
# see: dh_installdeb(1)
set -e
# summary of how this script can be called:
# * <prerm> `remove'
# * <old-prerm> `upgrade' <new-version>
# * <new-prerm> `failed-upgrade' <old-version>
# * <conflictor's-prerm> `remove' `in-favour' <package> <new-version>
# * <deconfigured's-prerm> `deconfigure' `in-favour'
# <package-being-installed> <version> `removing'
# <conflicting-package> <version>
# for details, see https://www.debian.org/doc/debian-policy/ or
# the debian-policy package
case "$1" in
remove|upgrade|deconfigure)
;;
failed-upgrade)
;;
*)
echo "prerm called with unknown argument \`$1'" >&2
exit 1
;;
esac
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
#DEBHELPER#
exit 0

20
debian/rules vendored Executable file
View file

@ -0,0 +1,20 @@
#!/usr/bin/make -f
SERVICE = frostfs-contract
export NEOGO ?= $(shell command -v neo-go)
%:
dh $@
override_dh_auto_build:
make all
override_dh_auto_install:
install -D -m 0750 -d debian/$(SERVICE)/var/lib/frostfs/contract
find . -maxdepth 2 \( -name '*.nef' -o -name 'config.json' \) -exec cp --parents \{\} debian/$(SERVICE)/var/lib/frostfs/contract \;
override_dh_installchangelogs:
dh_installchangelogs -k CHANGELOG.md

1
debian/source/format vendored Normal file
View file

@ -0,0 +1 @@
3.0 (quilt)

View file

@ -1,5 +1,5 @@
name: "NeoFS"
safemethods: ["alphabetList", "alphabetAddress", "innerRingCandidates", "config", "listConfig", "version"]
name: "FrostFS"
safemethods: ["alphabetAddress", "innerRingCandidates", "config", "listConfig", "version"]
permissions:
- methods: ["update", "transfer"]
events:

94
frostfs/doc.go Normal file
View file

@ -0,0 +1,94 @@
/*
FrostFS contract is a contract deployed in FrostFS mainchain.
FrostFS contract is an entry point to FrostFS users. This contract stores all FrostFS
related GAS, registers new Inner Ring candidates and produces notifications
to control the sidechain.
While mainchain committee controls the list of Alphabet nodes in native
RoleManagement contract, FrostFS can't change more than 1\3 keys at a time.
FrostFS contract contains the actual list of Alphabet nodes in the sidechain.
Network configuration is also stored in FrostFS contract. All changes in
configuration are mirrored in the sidechain with notifications.
# Contract notifications
Deposit notification. This notification is produced when user transfers native
GAS to the FrostFS contract address. The same amount of FROSTFS token will be
minted in Balance contract in the sidechain.
Deposit:
- name: from
type: Hash160
- name: amount
type: Integer
- name: receiver
type: Hash160
- name: txHash
type: Hash256
Withdraw notification. This notification is produced when a user wants to
withdraw GAS from the internal FrostFS balance and has paid fee for that.
Withdraw:
- name: user
type: Hash160
- name: amount
type: Integer
- name: txHash
type: Hash256
Cheque notification. This notification is produced when FrostFS contract
has successfully transferred assets back to the user after withdraw.
Cheque:
- name: id
type: ByteArray
- name: user
type: Hash160
- name: amount
type: Integer
- name: lockAccount
type: ByteArray
Bind notification. This notification is produced when a user wants to bind
public keys with the user account (OwnerID). Keys argument is an array of ByteArray.
Bind:
- name: user
type: ByteArray
- name: keys
type: Array
Unbind notification. This notification is produced when a user wants to unbind
public keys with the user account (OwnerID). Keys argument is an array of ByteArray.
Unbind:
- name: user
type: ByteArray
- name: keys
type: Array
SetConfig notification. This notification is produced when Alphabet nodes update
FrostFS network configuration value.
SetConfig
- name: id
type: ByteArray
- name: key
type: ByteArray
- name: value
type: ByteArray
# Contract storage scheme
| Key | Value | Description |
|-----------------------------------------------------------------------------|
| `processingScriptHash` | Hash160 | processing contract hash |
| `candidates` + candidateKey | ByteArray | it flags inner ring candidate |
| `config` + postfix | ByteArray | serialized config data |
*/
package frostfs

View file

@ -1,10 +1,10 @@
package neofs
package frostfs
import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
"github.com/nspcc-dev/neo-go/pkg/interop/native/crypto"
"github.com/nspcc-dev/neo-go/pkg/interop/native/gas"
"github.com/nspcc-dev/neo-go/pkg/interop/native/ledger"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
@ -12,7 +12,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/interop/native/std"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
"github.com/nspcc-dev/neofs-contract/common"
)
type (
@ -48,6 +47,8 @@ var (
func _deploy(data interface{}, isUpdate bool) {
ctx := storage.GetContext()
common.RmAndCheckNotaryDisabledKey(data, notaryDisabledKey)
if isUpdate {
args := data.([]interface{})
common.CheckVersion(args[len(args)-1].(int))
@ -55,6 +56,7 @@ func _deploy(data interface{}, isUpdate bool) {
}
args := data.(struct {
//TODO(@acid-ant): #9 remove notaryDisabled in future version
notaryDisabled bool
addrProc interop.Hash160
keys []interop.PublicKey
@ -81,13 +83,6 @@ func _deploy(data interface{}, isUpdate bool) {
storage.Put(ctx, processingContractKey, args.addrProc)
// initialize the way to collect signatures
storage.Put(ctx, notaryDisabledKey, args.notaryDisabled)
if args.notaryDisabled {
common.InitVote(ctx)
runtime.Log("neofs contract notary disabled")
}
ln := len(args.config)
if ln%2 != 0 {
panic("bad configuration")
@ -100,7 +95,7 @@ func _deploy(data interface{}, isUpdate bool) {
setConfig(ctx, key, val)
}
runtime.Log("neofs: contract initialized")
runtime.Log("frostfs: contract initialized")
}
// Update method updates contract source code and manifest. It can be invoked
@ -110,23 +105,13 @@ func Update(script []byte, manifest []byte, data interface{}) {
alphabetKeys := roles.GetDesignatedByRole(roles.NeoFSAlphabet, uint32(blockHeight+1))
alphabetCommittee := common.Multiaddress(alphabetKeys, true)
common.CheckAlphabetWitness(alphabetCommittee)
if !runtime.CheckWitness(alphabetCommittee) {
panic(common.ErrAlphabetWitnessFailed)
}
contract.Call(interop.Hash160(management.Hash), "update",
contract.All, script, manifest, common.AppendVersion(data))
runtime.Log("neofs contract updated")
}
// AlphabetList returns an array of alphabet node keys. It is used in sidechain notary
// disabled environment.
func AlphabetList() []common.IRNode {
ctx := storage.GetReadOnlyContext()
pubs := getAlphabetNodes(ctx)
nodes := []common.IRNode{}
for i := range pubs {
nodes = append(nodes, common.IRNode{PublicKey: pubs[i]})
}
return nodes
runtime.Log("frostfs contract updated")
}
// AlphabetAddress returns 2\3n+1 multisignature address of alphabet nodes.
@ -156,43 +141,16 @@ func InnerRingCandidates() []common.IRNode {
// This method does not return fee back to the candidate.
func InnerRingCandidateRemove(key interop.PublicKey) {
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
var ( // for invocation collection without notary
alphabet []interop.PublicKey
nodeKey []byte
)
keyOwner := runtime.CheckWitness(key)
if !keyOwner {
if notaryDisabled {
alphabet = getAlphabetNodes(ctx)
nodeKey = common.InnerRingInvoker(alphabet)
if len(nodeKey) == 0 {
panic("this method must be invoked by candidate or alphabet")
}
} else {
multiaddr := AlphabetAddress()
if !runtime.CheckWitness(multiaddr) {
panic("this method must be invoked by candidate or alphabet")
}
multiaddr := AlphabetAddress()
if !runtime.CheckWitness(multiaddr) {
panic("this method must be invoked by candidate or alphabet")
}
}
if notaryDisabled && !keyOwner {
threshold := len(alphabet)*2/3 + 1
id := append(key, []byte("delete")...)
hashID := crypto.Sha256(id)
n := common.Vote(ctx, hashID, nodeKey)
if n < threshold {
return
}
common.RemoveVotes(ctx, hashID)
}
prefix := []byte(candidatesKey)
stKey := append(prefix, key...)
if storage.Get(ctx, stKey) != nil {
@ -205,7 +163,7 @@ func InnerRingCandidateRemove(key interop.PublicKey) {
// It can be invoked only by the candidate itself.
//
// This method transfers fee from a candidate to the contract account.
// Fee value is specified in NeoFS network config with the key InnerRingCandidateFee.
// Fee value is specified in FrostFS network config with the key InnerRingCandidateFee.
func InnerRingCandidateAdd(key interop.PublicKey) {
ctx := storage.GetContext()
@ -231,7 +189,7 @@ func InnerRingCandidateAdd(key interop.PublicKey) {
// OnNEP17Payment is a callback for NEP-17 compatible native GAS contract.
// It takes no more than 9000.0 GAS. Native GAS has precision 8, and
// NeoFS balance contract has precision 12. Values bigger than 9000.0 can
// FrostFS balance contract has precision 12. Values bigger than 9000.0 can
// break JSON limits for integers when precision is converted.
func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) {
rcv := data.(interop.Hash160)
@ -264,13 +222,13 @@ func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) {
runtime.Notify("Deposit", from, amount, rcv, tx.Hash)
}
// Withdraw initializes gas asset withdraw from NeoFS. It can be invoked only
// Withdraw initializes gas asset withdraw from FrostFS. It can be invoked only
// by the specified user.
//
// This method produces Withdraw notification to lock assets in the sidechain and
// transfers withdraw fee from a user account to each Alphabet node. If notary
// is enabled in the mainchain, fee is transferred to Processing contract.
// Fee value is specified in NeoFS network config with the key WithdrawFee.
// Fee value is specified in FrostFS network config with the key WithdrawFee.
func Withdraw(user interop.Hash160, amount int) {
if !runtime.CheckWitness(user) {
panic("you should be the owner of the wallet")
@ -285,28 +243,15 @@ func Withdraw(user interop.Hash160, amount int) {
}
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
// transfer fee to proxy contract to pay cheque invocation
fee := getConfig(ctx, withdrawFeeConfigKey).(int)
if notaryDisabled {
alphabet := getAlphabetNodes(ctx)
for _, node := range alphabet {
processingAddr := contract.CreateStandardAccount(node)
processingAddr := storage.Get(ctx, processingContractKey).(interop.Hash160)
transferred := gas.Transfer(user, processingAddr, fee, []byte{})
if !transferred {
panic("failed to transfer withdraw fee, aborting")
}
}
} else {
processingAddr := storage.Get(ctx, processingContractKey).(interop.Hash160)
transferred := gas.Transfer(user, processingAddr, fee, []byte{})
if !transferred {
panic("failed to transfer withdraw fee, aborting")
}
transferred := gas.Transfer(user, processingAddr, fee, []byte{})
if !transferred {
panic("failed to transfer withdraw fee, aborting")
}
// notify alphabet nodes
@ -317,43 +262,15 @@ func Withdraw(user interop.Hash160, amount int) {
}
// Cheque transfers GAS back to the user from the contract account, if assets were
// successfully locked in NeoFS balance contract. It can be invoked only by
// successfully locked in FrostFS balance contract. It can be invoked only by
// Alphabet nodes.
//
// This method produces Cheque notification to burn assets in sidechain.
func Cheque(id []byte, user interop.Hash160, amount int, lockAcc []byte) {
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
var ( // for invocation collection without notary
alphabet []interop.PublicKey
nodeKey []byte
)
if notaryDisabled {
alphabet = getAlphabetNodes(ctx)
nodeKey = common.InnerRingInvoker(alphabet)
if len(nodeKey) == 0 {
panic("this method must be invoked by alphabet")
}
} else {
multiaddr := AlphabetAddress()
common.CheckAlphabetWitness(multiaddr)
}
common.CheckAlphabetWitness()
from := runtime.GetExecutingScriptHash()
if notaryDisabled {
threshold := len(alphabet)*2/3 + 1
n := common.Vote(ctx, id, nodeKey)
if n < threshold {
return
}
common.RemoveVotes(ctx, id)
}
transferred := gas.Transfer(from, user, amount, nil)
if !transferred {
panic("failed to transfer funds, aborting")
@ -363,7 +280,7 @@ func Cheque(id []byte, user interop.Hash160, amount int, lockAcc []byte) {
runtime.Notify("Cheque", id, user, amount, lockAcc)
}
// Bind method produces notification to bind the specified public keys in NeoFSID
// Bind method produces notification to bind the specified public keys in FrostFSID
// contract in the sidechain. It can be invoked only by specified user.
//
// This method produces Bind notification. This method panics if keys are not
@ -383,7 +300,7 @@ func Bind(user []byte, keys []interop.PublicKey) {
runtime.Notify("Bind", user, keys)
}
// Unbind method produces notification to unbind the specified public keys in NeoFSID
// Unbind method produces notification to unbind the specified public keys in FrostFSID
// contract in the sidechain. It can be invoked only by the specified user.
//
// This method produces Unbind notification. This method panics if keys are not
@ -403,102 +320,19 @@ func Unbind(user []byte, keys []interop.PublicKey) {
runtime.Notify("Unbind", user, keys)
}
// AlphabetUpdate updates a list of alphabet nodes with the provided list of
// public keys. It can be invoked only by alphabet nodes.
//
// This method is used in notary disabled sidechain environment. In this case,
// the actual alphabet list should be stored in the NeoFS contract.
func AlphabetUpdate(id []byte, args []interop.PublicKey) {
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
if len(args) == 0 {
panic("bad arguments")
}
var ( // for invocation collection without notary
alphabet []interop.PublicKey
nodeKey []byte
)
if notaryDisabled {
alphabet = getAlphabetNodes(ctx)
nodeKey = common.InnerRingInvoker(alphabet)
if len(nodeKey) == 0 {
panic("this method must be invoked by alphabet")
}
} else {
multiaddr := AlphabetAddress()
common.CheckAlphabetWitness(multiaddr)
}
newAlphabet := []interop.PublicKey{}
for i := 0; i < len(args); i++ {
pubKey := args[i]
if len(pubKey) != interop.PublicKeyCompressedLen {
panic("invalid public key in alphabet list")
}
newAlphabet = append(newAlphabet, pubKey)
}
if notaryDisabled {
threshold := len(alphabet)*2/3 + 1
n := common.Vote(ctx, id, nodeKey)
if n < threshold {
return
}
common.RemoveVotes(ctx, id)
}
common.SetSerialized(ctx, alphabetKey, newAlphabet)
runtime.Notify("AlphabetUpdate", id, newAlphabet)
runtime.Log("alphabet list has been updated")
}
// Config returns configuration value of NeoFS configuration. If the key does
// Config returns configuration value of FrostFS configuration. If the key does
// not exists, returns nil.
func Config(key []byte) interface{} {
ctx := storage.GetReadOnlyContext()
return getConfig(ctx, key)
}
// SetConfig key-value pair as a NeoFS runtime configuration value. It can be invoked
// SetConfig key-value pair as a FrostFS runtime configuration value. It can be invoked
// only by Alphabet nodes.
func SetConfig(id, key, val []byte) {
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
var ( // for invocation collection without notary
alphabet []interop.PublicKey
nodeKey []byte
)
if notaryDisabled {
alphabet = getAlphabetNodes(ctx)
nodeKey = common.InnerRingInvoker(alphabet)
if len(key) == 0 {
panic("this method must be invoked by alphabet")
}
} else {
multiaddr := AlphabetAddress()
common.CheckAlphabetWitness(multiaddr)
}
if notaryDisabled {
threshold := len(alphabet)*2/3 + 1
n := common.Vote(ctx, id, nodeKey)
if n < threshold {
return
}
common.RemoveVotes(ctx, id)
}
common.CheckAlphabetWitness()
setConfig(ctx, key, val)
@ -507,7 +341,7 @@ func SetConfig(id, key, val []byte) {
}
// ListConfig returns an array of structures that contain a key and a value of all
// NeoFS configuration records. Key and value are both byte arrays.
// FrostFS configuration records. Key and value are both byte arrays.
func ListConfig() []record {
ctx := storage.GetReadOnlyContext()
@ -542,7 +376,7 @@ func getAlphabetNodes(ctx storage.Context) []interop.PublicKey {
return []interop.PublicKey{}
}
// getConfig returns the installed neofs configuration value or nil if it is not set.
// getConfig returns the installed frostfs configuration value or nil if it is not set.
func getConfig(ctx storage.Context, key interface{}) interface{} {
postfix := key.([]byte)
storageKey := append(configPrefix, postfix...)
@ -550,7 +384,7 @@ func getConfig(ctx storage.Context, key interface{}) interface{} {
return storage.Get(ctx, storageKey)
}
// setConfig sets a neofs configuration value in the contract storage.
// setConfig sets a frostfs configuration value in the contract storage.
func setConfig(ctx storage.Context, key, val interface{}) {
postfix := key.([]byte)
storageKey := append(configPrefix, postfix...)

View file

@ -1,4 +1,4 @@
name: "NeoFS ID"
name: "Identity"
safemethods: ["key", "version"]
permissions:
- methods: ["update"]

29
frostfsid/doc.go Normal file
View file

@ -0,0 +1,29 @@
/*
FrostFSID contract is a contract deployed in FrostFS sidechain.
FrostFSID contract is used to store connection between an OwnerID and its public keys.
OwnerID is a 25-byte N3 wallet address that can be produced from a public key.
It is one-way conversion. In simple cases, FrostFS verifies ownership by checking
signature and relation between a public key and an OwnerID.
In more complex cases, a user can use public keys unrelated to the OwnerID to maintain
secure access to the data. FrostFSID contract stores relation between an OwnerID and
arbitrary public keys. Data owner can bind a public key with its account or unbind it
by invoking Bind or Unbind methods of FrostFS contract in the mainchain. After that,
Alphabet nodes produce multisigned AddKey and RemoveKey invocations of FrostFSID
contract.
# Contract notifications
FrostFSID contract does not produce notifications to process.
# Contract storage scheme
| Key | Value | Description |
|-----------------------------|------------|----------------------------------|
| `processingScriptHash` | Hash160 | netmap contract hash |
| `containerScriptHash` | Hash160 | container contract hash |
| `o` + ownerID + publicKey | ByteArray | it flags owner's public key |
*/
package frostfsid

View file

@ -1,14 +1,13 @@
package neofsid
package frostfsid
import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
"github.com/nspcc-dev/neo-go/pkg/interop/native/crypto"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
"github.com/nspcc-dev/neofs-contract/common"
)
type (
@ -31,6 +30,8 @@ const (
func _deploy(data interface{}, isUpdate bool) {
ctx := storage.GetContext()
common.RmAndCheckNotaryDisabledKey(data, notaryDisabledKey)
if isUpdate {
args := data.([]interface{})
common.CheckVersion(args[len(args)-1].(int))
@ -38,6 +39,7 @@ func _deploy(data interface{}, isUpdate bool) {
}
args := data.(struct {
//TODO(@acid-ant): #9 remove notaryDisabled in future version
notaryDisabled bool
addrNetmap interop.Hash160
addrContainer interop.Hash160
@ -50,14 +52,7 @@ func _deploy(data interface{}, isUpdate bool) {
storage.Put(ctx, netmapContractKey, args.addrNetmap)
storage.Put(ctx, containerContractKey, args.addrContainer)
// initialize the way to collect signatures
storage.Put(ctx, notaryDisabledKey, args.notaryDisabled)
if args.notaryDisabled {
common.InitVote(ctx)
runtime.Log("neofsid contract notary disabled")
}
runtime.Log("neofsid contract initialized")
runtime.Log("frostfsid contract initialized")
}
// Update method updates contract source code and manifest. It can be invoked
@ -69,7 +64,7 @@ func Update(script []byte, manifest []byte, data interface{}) {
contract.Call(interop.Hash160(management.Hash), "update",
contract.All, script, manifest, common.AppendVersion(data))
runtime.Log("neofsid contract updated")
runtime.Log("frostfsid contract updated")
}
// AddKey binds a list of the provided public keys to the OwnerID. It can be invoked only by
@ -90,41 +85,8 @@ func AddKey(owner []byte, keys []interop.PublicKey) {
}
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
var ( // for invocation collection without notary
alphabet []interop.PublicKey
nodeKey []byte
indirectCall bool
)
if notaryDisabled {
alphabet = common.AlphabetNodes()
nodeKey = common.InnerRingInvoker(alphabet)
if len(nodeKey) == 0 {
panic("invocation from non inner ring node")
}
indirectCall = common.FromKnownContract(
ctx,
runtime.GetCallingScriptHash(),
containerContractKey)
if indirectCall {
threshold := len(alphabet)*2/3 + 1
id := invokeIDKeys(owner, keys, []byte("add"))
n := common.Vote(ctx, id, nodeKey)
if n < threshold {
return
}
common.RemoveVotes(ctx, id)
}
} else {
multiaddr := common.AlphabetAddress()
common.CheckAlphabetWitness(multiaddr)
}
common.CheckAlphabetWitness()
ownerKey := append([]byte{ownerKeysPrefix}, owner...)
for i := range keys {
@ -153,34 +115,10 @@ func RemoveKey(owner []byte, keys []interop.PublicKey) {
}
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
var ( // for invocation collection without notary
alphabet []interop.PublicKey
nodeKey []byte
)
if notaryDisabled {
alphabet = common.AlphabetNodes()
nodeKey = common.InnerRingInvoker(alphabet)
if len(nodeKey) == 0 {
panic("invocation from non inner ring node")
}
threshold := len(alphabet)*2/3 + 1
id := invokeIDKeys(owner, keys, []byte("remove"))
n := common.Vote(ctx, id, nodeKey)
if n < threshold {
return
}
common.RemoveVotes(ctx, id)
} else {
multiaddr := common.AlphabetAddress()
if !runtime.CheckWitness(multiaddr) {
panic("invocation from non inner ring node")
}
multiaddr := common.AlphabetAddress()
if !runtime.CheckWitness(multiaddr) {
panic("invocation from non inner ring node")
}
ownerKey := append([]byte{ownerKeysPrefix}, owner...)
@ -222,12 +160,3 @@ func getUserInfo(ctx storage.Context, key interface{}) UserInfo {
return UserInfo{Keys: pubs}
}
func invokeIDKeys(owner []byte, keys []interop.PublicKey, prefix []byte) []byte {
prefix = append(prefix, owner...)
for i := range keys {
prefix = append(prefix, keys[i]...)
}
return crypto.Sha256(prefix)
}

6
go.mod
View file

@ -1,10 +1,10 @@
module github.com/nspcc-dev/neofs-contract
module git.frostfs.info/TrueCloudLab/frostfs-contract
go 1.14
require (
github.com/mr-tron/base58 v1.2.0
github.com/nspcc-dev/neo-go v0.99.2
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20220809123759-3094d3e0c14b
github.com/nspcc-dev/neo-go v0.99.4
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20220927123257-24c107e3a262
github.com/stretchr/testify v1.7.0
)

25
go.sum
View file

@ -75,8 +75,11 @@ github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tj
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
@ -102,6 +105,7 @@ github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHj
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M=
github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss=
github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
@ -209,6 +213,7 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
@ -246,23 +251,29 @@ github.com/nspcc-dev/dbft v0.0.0-20191209120240-0d6b7568d9ae/go.mod h1:3FjXOoHmA
github.com/nspcc-dev/dbft v0.0.0-20200117124306-478e5cfbf03a/go.mod h1:/YFK+XOxxg0Bfm6P92lY5eDSLYfp06XOdL8KAVgXjVk=
github.com/nspcc-dev/dbft v0.0.0-20200219114139-199d286ed6c1/go.mod h1:O0qtn62prQSqizzoagHmuuKoz8QMkU3SzBoKdEvm3aQ=
github.com/nspcc-dev/dbft v0.0.0-20210721160347-1b03241391ac/go.mod h1:U8MSnEShH+o5hexfWJdze6uMFJteP0ko7J2frO7Yu1Y=
github.com/nspcc-dev/dbft v0.0.0-20220629112714-fd49ca59d354/go.mod h1:U8MSnEShH+o5hexfWJdze6uMFJteP0ko7J2frO7Yu1Y=
github.com/nspcc-dev/dbft v0.0.0-20220902113116-58a5e763e647 h1:handGBjqVzRx7HD6152zsP8ZRxw083zCMbN0IlUaPQk=
github.com/nspcc-dev/dbft v0.0.0-20220902113116-58a5e763e647/go.mod h1:g9xisXmX9NP9MjioaTe862n9SlZTrP+6PVUWLBYOr98=
github.com/nspcc-dev/go-ordered-json v0.0.0-20210915112629-e1b6cce73d02/go.mod h1:79bEUDEviBHJMFV6Iq6in57FEOCMcRhfQnfaf0ETA5U=
github.com/nspcc-dev/go-ordered-json v0.0.0-20220111165707-25110be27d22 h1:n4ZaFCKt1pQJd7PXoMJabZWK9ejjbLOVrkl/lOUmshg=
github.com/nspcc-dev/go-ordered-json v0.0.0-20220111165707-25110be27d22/go.mod h1:79bEUDEviBHJMFV6Iq6in57FEOCMcRhfQnfaf0ETA5U=
github.com/nspcc-dev/hrw v1.0.9 h1:17VcAuTtrstmFppBjfRiia4K2wA/ukXZhLFS8Y8rz5Y=
github.com/nspcc-dev/hrw v1.0.9/go.mod h1:l/W2vx83vMQo6aStyx2AuZrJ+07lGv2JQGlVkPG06MU=
github.com/nspcc-dev/neo-go v0.73.1-pre.0.20200303142215-f5a1b928ce09/go.mod h1:pPYwPZ2ks+uMnlRLUyXOpLieaDQSEaf4NM3zHVbRjmg=
github.com/nspcc-dev/neo-go v0.98.0/go.mod h1:E3cc1x6RXSXrJb2nDWXTXjnXk3rIqVN8YdFyWv+FrqM=
github.com/nspcc-dev/neo-go v0.99.2 h1:Fq79FI6BJkj/XkgWtrURSdXgXIeBHCgbKauBw3LOvZ4=
github.com/nspcc-dev/neo-go v0.99.2/go.mod h1:9P0yWqhZX7i/ChJ+zjtiStO1uPTolPFUM+L5oNznU8E=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20220809123759-3094d3e0c14b h1:J7QZNmnO84esVuPbBo88fwAG4XVnDjlSTiO1ewLNCkQ=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20220809123759-3094d3e0c14b/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=
github.com/nspcc-dev/neo-go v0.99.4 h1:8Y+SdRxksC72a4PNkcGCh/aaQinh9Gu+c5LilbcsXOI=
github.com/nspcc-dev/neo-go v0.99.4/go.mod h1:mKTolfRUfKjFso5HPvGSQtUZc70n0VKBMs16eGuC5gA=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20220927123257-24c107e3a262 h1:UTmSLZw5OpD/JPE1B5Vf98GF0zu2/Hsqq1lGLtStTUE=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20220927123257-24c107e3a262/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=
github.com/nspcc-dev/neofs-api-go/v2 v2.11.0-pre.0.20211201134523-3604d96f3fe1/go.mod h1:oS8dycEh8PPf2Jjp6+8dlwWyEv2Dy77h/XhhcdxYEFs=
github.com/nspcc-dev/neofs-api-go/v2 v2.11.1 h1:SVqc523pZsSaS9vnPS1mm3VV6b6xY0gvdA0uYJ/GWZQ=
github.com/nspcc-dev/neofs-api-go/v2 v2.11.1/go.mod h1:oS8dycEh8PPf2Jjp6+8dlwWyEv2Dy77h/XhhcdxYEFs=
github.com/nspcc-dev/neofs-crypto v0.2.0/go.mod h1:F/96fUzPM3wR+UGsPi3faVNmFlA9KAEAUQR7dMxZmNA=
github.com/nspcc-dev/neofs-crypto v0.2.3/go.mod h1:8w16GEJbH6791ktVqHN9YRNH3s9BEEKYxGhlFnp0cDw=
github.com/nspcc-dev/neofs-crypto v0.3.0/go.mod h1:8w16GEJbH6791ktVqHN9YRNH3s9BEEKYxGhlFnp0cDw=
github.com/nspcc-dev/neofs-crypto v0.4.0 h1:5LlrUAM5O0k1+sH/sktBtrgfWtq1pgpDs09fZo+KYi4=
github.com/nspcc-dev/neofs-crypto v0.4.0/go.mod h1:6XJ8kbXgOfevbI2WMruOtI+qUJXNwSGM/E9eClXxPHs=
github.com/nspcc-dev/neofs-sdk-go v0.0.0-20211201182451-a5b61c4f6477/go.mod h1:dfMtQWmBHYpl9Dez23TGtIUKiFvCIxUZq/CkSIhEpz4=
github.com/nspcc-dev/neofs-sdk-go v0.0.0-20220113123743-7f3162110659 h1:rpMCoRa7expLc9gMiOP724gz6YSykZzmMALR/CmiwnU=
github.com/nspcc-dev/neofs-sdk-go v0.0.0-20220113123743-7f3162110659/go.mod h1:/jay1lr3w7NQd/VDBkEhkJmDmyPNsu4W+QV2obsUV40=
github.com/nspcc-dev/rfc6979 v0.1.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso=
github.com/nspcc-dev/rfc6979 v0.2.0 h1:3e1WNxrN60/6N0DW7+UYisLeZJyfqZTNOjeV/toYvOE=
@ -281,6 +292,7 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -327,6 +339,7 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -649,6 +662,7 @@ google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEY
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
@ -664,6 +678,7 @@ google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.41.0 h1:f+PlOh7QV4iIJkPrx5NQ7qaNGFQ3OTse67yaDHfju4E=
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=

View file

@ -1,95 +0,0 @@
/*
NeoFS contract is a contract deployed in NeoFS mainchain.
NeoFS contract is an entry point to NeoFS users. This contract stores all NeoFS
related GAS, registers new Inner Ring candidates and produces notifications
to control the sidechain.
While mainchain committee controls the list of Alphabet nodes in native
RoleManagement contract, NeoFS can't change more than 1\3 keys at a time.
NeoFS contract contains the actual list of Alphabet nodes in the sidechain.
Network configuration is also stored in NeoFS contract. All changes in
configuration are mirrored in the sidechain with notifications.
Contract notifications
Deposit notification. This notification is produced when user transfers native
GAS to the NeoFS contract address. The same amount of NEOFS token will be
minted in Balance contract in the sidechain.
Deposit:
- name: from
type: Hash160
- name: amount
type: Integer
- name: receiver
type: Hash160
- name: txHash
type: Hash256
Withdraw notification. This notification is produced when a user wants to
withdraw GAS from the internal NeoFS balance and has paid fee for that.
Withdraw:
- name: user
type: Hash160
- name: amount
type: Integer
- name: txHash
type: Hash256
Cheque notification. This notification is produced when NeoFS contract
has successfully transferred assets back to the user after withdraw.
Cheque:
- name: id
type: ByteArray
- name: user
type: Hash160
- name: amount
type: Integer
- name: lockAccount
type: ByteArray
Bind notification. This notification is produced when a user wants to bind
public keys with the user account (OwnerID). Keys argument is an array of ByteArray.
Bind:
- name: user
type: ByteArray
- name: keys
type: Array
Unbind notification. This notification is produced when a user wants to unbind
public keys with the user account (OwnerID). Keys argument is an array of ByteArray.
Unbind:
- name: user
type: ByteArray
- name: keys
type: Array
AlphabetUpdate notification. This notification is produced when Alphabet nodes
have updated their lists in the contract. Alphabet argument is an array of ByteArray. It
contains public keys of new alphabet nodes.
AlphabetUpdate:
- name: id
type: ByteArray
- name: alphabet
type: Array
SetConfig notification. This notification is produced when Alphabet nodes update
NeoFS network configuration value.
SetConfig
- name: id
type: ByteArray
- name: key
type: ByteArray
- name: value
type: ByteArray
*/
package neofs

View file

@ -1,20 +0,0 @@
/*
NeoFSID contract is a contract deployed in NeoFS sidechain.
NeoFSID contract is used to store connection between an OwnerID and its public keys.
OwnerID is a 25-byte N3 wallet address that can be produced from a public key.
It is one-way conversion. In simple cases, NeoFS verifies ownership by checking
signature and relation between a public key and an OwnerID.
In more complex cases, a user can use public keys unrelated to the OwnerID to maintain
secure access to the data. NeoFSID contract stores relation between an OwnerID and
arbitrary public keys. Data owner can bind a public key with its account or unbind it
by invoking Bind or Unbind methods of NeoFS contract in the mainchain. After that,
Alphabet nodes produce multisigned AddKey and RemoveKey invocations of NeoFSID
contract.
Contract notifications
NeoFSID contract does not produce notifications to process.
*/
package neofsid

View file

@ -1,5 +1,5 @@
name: "NeoFS Netmap"
safemethods: ["innerRingList", "epoch", "netmap", "netmapCandidates", "snapshot", "snapshotByEpoch", "config", "listConfig", "version"]
name: "Netmap"
safemethods: ["epoch", "netmap", "netmapCandidates", "snapshot", "snapshotByEpoch", "config", "listConfig", "version"]
permissions:
- methods: ["update", "newEpoch"]
events:

View file

@ -1,34 +1,46 @@
/*
Netmap contract is a contract deployed in NeoFS sidechain.
Netmap contract is a contract deployed in FrostFS sidechain.
Netmap contract stores and manages NeoFS network map, Storage node candidates
and epoch number counter. In notary disabled environment, contract also stores
a list of Inner Ring node keys.
Netmap contract stores and manages FrostFS network map, Storage node candidates
and epoch number counter.
Contract notifications
# Contract notifications
AddPeer notification. This notification is produced when a Storage node sends
a bootstrap request by invoking AddPeer method.
AddPeer
- name: nodeInfo
type: ByteArray
AddPeer
- name: nodeInfo
type: ByteArray
UpdateState notification. This notification is produced when a Storage node wants
to change its state (go offline) by invoking UpdateState method. Supported
states: (2) -- offline.
UpdateState
- name: state
type: Integer
- name: publicKey
type: PublicKey
UpdateState
- name: state
type: Integer
- name: publicKey
type: PublicKey
NewEpoch notification. This notification is produced when a new epoch is applied
in the network by invoking NewEpoch method.
NewEpoch
- name: epoch
type: Integer
NewEpoch
- name: epoch
type: Integer
# Contract storage scheme
| Key | Value | Description |
|-----------------------------|------------|-----------------------------------|
| `snapshotCount` | int | snapshot count |
| `snapshotEpoch` | int | snapshot epoch |
| `snapshotBlock` | int | snapshot block |
| `snapshot_` + snapshotNum | ByteArray | serialized '[]Node' array |
| `snapshotCurrent` | int | current snapshot |
| `balanceScriptHash` | Hash160 | balance contract hash |
| `containerScriptHash` | Hash160 | container contract hash |
*/
package netmap

View file

@ -1,16 +1,15 @@
package netmap
import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
"github.com/nspcc-dev/neo-go/pkg/interop/native/crypto"
"github.com/nspcc-dev/neo-go/pkg/interop/native/ledger"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
"github.com/nspcc-dev/neo-go/pkg/interop/native/std"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
"github.com/nspcc-dev/neofs-contract/common"
)
// NodeState is an enumeration for node states.
@ -32,10 +31,10 @@ const (
NodeStateMaintenance
)
// Node groups data related to NeoFS storage nodes registered in the NeoFS
// Node groups data related to FrostFS storage nodes registered in the FrostFS
// network. The information is stored in the current contract.
type Node struct {
// Information about the node encoded according to the NeoFS binary
// Information about the node encoded according to the FrostFS binary
// protocol.
BLOB []byte
@ -71,7 +70,10 @@ var (
func _deploy(data interface{}, isUpdate bool) {
ctx := storage.GetContext()
common.RmAndCheckNotaryDisabledKey(data, notaryDisabledKey)
var args = data.(struct {
//TODO(@acid-ant): #9 remove notaryDisabled in future version
notaryDisabled bool
addrBalance interop.Hash160
addrContainer interop.Hash160
@ -94,25 +96,6 @@ func _deploy(data interface{}, isUpdate bool) {
if isUpdate {
common.CheckVersion(args.version)
count := getSnapshotCount(ctx)
prefix := []byte(snapshotKeyPrefix)
for i := 0; i < count; i++ {
key := append(prefix, byte(i))
data := storage.Get(ctx, key)
if data != nil {
nodes := std.Deserialize(data.([]byte)).([]Node)
for i := range nodes {
// Old structure contains only the first field,
// second is implicitly assumed to be Online.
nodes[i] = Node{
BLOB: nodes[i].BLOB,
State: NodeStateOnline,
}
}
common.SetSerialized(ctx, key, nodes)
}
}
return
}
@ -134,14 +117,6 @@ func _deploy(data interface{}, isUpdate bool) {
storage.Put(ctx, balanceContractKey, args.addrBalance)
storage.Put(ctx, containerContractKey, args.addrContainer)
// initialize the way to collect signatures
storage.Put(ctx, notaryDisabledKey, args.notaryDisabled)
if args.notaryDisabled {
common.SetSerialized(ctx, innerRingKey, args.keys)
common.InitVote(ctx)
runtime.Log("netmap contract notary disabled")
}
runtime.Log("netmap contract initialized")
}
@ -157,62 +132,6 @@ func Update(script []byte, manifest []byte, data interface{}) {
runtime.Log("netmap contract updated")
}
// InnerRingList method returns a slice of structures that contains the public key of
// an Inner Ring node. It should be used in notary disabled environment only.
//
// If notary is enabled, look to NeoFSAlphabet role in native RoleManagement
// contract of the sidechain.
func InnerRingList() []common.IRNode {
ctx := storage.GetReadOnlyContext()
pubs := getIRNodes(ctx)
nodes := []common.IRNode{}
for i := range pubs {
nodes = append(nodes, common.IRNode{PublicKey: pubs[i]})
}
return nodes
}
// UpdateInnerRing method updates a list of Inner Ring node keys. It should be used
// only in notary disabled environment. It can be invoked only by Alphabet nodes.
//
// If notary is enabled, update NeoFSAlphabet role in native RoleManagement
// contract of the sidechain. Use notary service to collect multisignature.
func UpdateInnerRing(keys []interop.PublicKey) {
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
var ( // for invocation collection without notary
alphabet []interop.PublicKey
nodeKey []byte
)
if notaryDisabled {
alphabet = common.AlphabetNodes()
nodeKey = common.InnerRingInvoker(alphabet)
if len(nodeKey) == 0 {
panic("this method must be invoked by alphabet nodes")
}
} else {
multiaddr := common.AlphabetAddress()
common.CheckAlphabetWitness(multiaddr)
}
if notaryDisabled {
threshold := len(alphabet)*2/3 + 1
id := keysID(keys, []byte("updateIR"))
n := common.Vote(ctx, id, nodeKey)
if n < threshold {
return
}
common.RemoveVotes(ctx, id)
}
runtime.Log("inner ring list updated")
common.SetSerialized(ctx, innerRingKey, keys)
}
// AddPeerIR accepts Alphabet calls in the notary-enabled contract setting and
// behaves similar to AddPeer in the notary-disabled one.
//
@ -220,12 +139,8 @@ func UpdateInnerRing(keys []interop.PublicKey) {
// AddPeerIR MUST be called by the Alphabet member only.
func AddPeerIR(nodeInfo []byte) {
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
if notaryDisabled {
panic("AddPeerIR should only be called in notary-enabled environment")
}
common.CheckAlphabetWitness(common.AlphabetAddress())
common.CheckAlphabetWitness()
publicKey := nodeInfo[2:35] // V2 format: offset:2, len:33
@ -235,78 +150,14 @@ func AddPeerIR(nodeInfo []byte) {
})
}
// AddPeer accepts information about the network map candidate in the NeoFS
// binary protocol format, identifies the caller and behaves depending on different
// conditions listed below.
//
// Contract settings:
//
// (1) notary-enabled
// (2) notary-disabled
//
// Callers:
//
// (a) candidate himself, if node's public key corresponds to the signer
// (b) Alphabet member
// (c) others
//
// AddPeer case-by-case behavior:
//
// (1a) does nothing
// (1b) panics. Notice that AddPeerIR MUST be used for this purpose.
// (2a) throws AddPeer notification with the provided BLOB
// (2b) accepts Alphabet vote. If the threshold of votes is reached, adds
// new element to the candidate set, and throws AddPeerSuccess notification.
// (c) panics
//
// Candidate MUST call AddPeer with "online" state in its descriptor. Alphabet
// members MUST NOT call AddPeer with any other states.
// AddPeer accepts information about the network map candidate in the FrostFS
// binary protocol format and does nothing. Keep method because storage node
// creates a notary transaction with this method, which produces a notary
// notification (implicit here).
func AddPeer(nodeInfo []byte) {
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
var ( // for invocation collection without notary
alphabet []interop.PublicKey
nodeKey []byte
)
if notaryDisabled {
alphabet = common.AlphabetNodes()
nodeKey = common.InnerRingInvoker(alphabet)
}
// V2 format
publicKey := nodeInfo[2:35] // offset:2, len:33
// If notary is enabled or caller is not an alphabet node,
// just emit the notification for alphabet.
if !notaryDisabled || len(nodeKey) == 0 {
common.CheckWitness(publicKey)
if notaryDisabled {
runtime.Notify("AddPeer", nodeInfo)
}
return
}
candidate := Node{
BLOB: nodeInfo,
State: NodeStateOnline,
}
if notaryDisabled {
threshold := len(alphabet)*2/3 + 1
rawCandidate := std.Serialize(candidate)
id := crypto.Sha256(rawCandidate)
n := common.Vote(ctx, id, nodeKey)
if n < threshold {
return
}
common.RemoveVotes(ctx, id)
}
addToNetmap(ctx, publicKey, candidate)
// V2 format - offset:2, len:33
common.CheckWitness(nodeInfo[2:35])
return
}
// updates state of the network map candidate by its public key in the contract
@ -329,13 +180,8 @@ func updateCandidateState(ctx storage.Context, publicKey interop.PublicKey, stat
}
// UpdateState accepts new state to be assigned to network map candidate
// identified by the given public key, identifies the signer and behaves
// depending on different conditions listed below.
//
// Contract settings:
//
// (1) notary-enabled
// (2) notary-disabled
// identified by the given public key, identifies the signer.
// Applicable only for notary-enabled environment.
//
// Signers:
//
@ -346,13 +192,10 @@ func updateCandidateState(ctx storage.Context, publicKey interop.PublicKey, stat
//
// UpdateState case-by-case behavior:
//
// (1a) panics
// (1b) like (1a)
// (1ab) updates candidate's state in the contract storage (*), and throws
// (a) panics
// (b) panics
// (ab) updates candidate's state in the contract storage (*), and throws
// UpdateStateSuccess with the provided key and new state
// (2a) throws UpdateState notification with the provided key and new state
// (2b) accepts Alphabet vote. If the threshold of votes is reached, behaves
// like (1ab).
// (c) panics
//
// (*) Candidate is removed from the candidate set if state is NodeStateOffline.
@ -368,33 +211,9 @@ func UpdateState(state NodeState, publicKey interop.PublicKey) {
}
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
if notaryDisabled {
alphabet := common.AlphabetNodes()
nodeKey := common.InnerRingInvoker(alphabet)
// If caller is not an alphabet node,
// just emit the notification for alphabet.
if len(nodeKey) == 0 {
common.CheckWitness(publicKey)
runtime.Notify("UpdateState", state, publicKey)
return
}
threshold := len(alphabet)*2/3 + 1
id := common.InvokeID([]interface{}{state, publicKey}, []byte("update"))
n := common.Vote(ctx, id, nodeKey)
if n < threshold {
return
}
common.RemoveVotes(ctx, id)
} else {
common.CheckWitness(publicKey)
common.CheckAlphabetWitness(common.AlphabetAddress())
}
common.CheckWitness(publicKey)
common.CheckAlphabetWitness()
updateCandidateState(ctx, publicKey, state)
}
@ -407,12 +226,8 @@ func UpdateState(state NodeState, publicKey interop.PublicKey) {
// UpdateStateIR MUST be called by the Alphabet member only.
func UpdateStateIR(state NodeState, publicKey interop.PublicKey) {
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
if notaryDisabled {
panic("UpdateStateIR should only be called in notary-enabled environment")
}
common.CheckAlphabetWitness(common.AlphabetAddress())
common.CheckAlphabetWitness()
updateCandidateState(ctx, publicKey, state)
}
@ -428,35 +243,8 @@ func UpdateStateIR(state NodeState, publicKey interop.PublicKey) {
// It produces NewEpoch notification.
func NewEpoch(epochNum int) {
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
var ( // for invocation collection without notary
alphabet []interop.PublicKey
nodeKey []byte
)
if notaryDisabled {
alphabet = common.AlphabetNodes()
nodeKey = common.InnerRingInvoker(alphabet)
if len(nodeKey) == 0 {
panic("this method must be invoked by inner ring nodes")
}
} else {
multiaddr := common.AlphabetAddress()
common.CheckAlphabetWitness(multiaddr)
}
if notaryDisabled {
threshold := len(alphabet)*2/3 + 1
id := common.InvokeID([]interface{}{epochNum}, []byte("epoch"))
n := common.Vote(ctx, id, nodeKey)
if n < threshold {
return
}
common.RemoveVotes(ctx, id)
}
common.CheckAlphabetWitness()
currentEpoch := storage.Get(ctx, snapshotEpoch).(int)
if epochNum <= currentEpoch {
@ -554,7 +342,7 @@ func getSnapshotCount(ctx storage.Context) int {
//
// Count MUST NOT be negative.
func UpdateSnapshotCount(count int) {
common.CheckAlphabetWitness(common.AlphabetAddress())
common.CheckAlphabetWitness()
if count < 0 {
panic("count must be positive")
}
@ -642,45 +430,19 @@ func SnapshotByEpoch(epoch int) []Node {
return Snapshot(currentEpoch - epoch)
}
// Config returns configuration value of NeoFS configuration. If key does
// Config returns configuration value of FrostFS configuration. If key does
// not exists, returns nil.
func Config(key []byte) interface{} {
ctx := storage.GetReadOnlyContext()
return getConfig(ctx, key)
}
// SetConfig key-value pair as a NeoFS runtime configuration value. It can be invoked
// SetConfig key-value pair as a FrostFS runtime configuration value. It can be invoked
// only by Alphabet nodes.
func SetConfig(id, key, val []byte) {
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
var ( // for invocation collection without notary
alphabet []interop.PublicKey
nodeKey []byte
)
if notaryDisabled {
alphabet = common.AlphabetNodes()
nodeKey = common.InnerRingInvoker(alphabet)
if len(nodeKey) == 0 {
panic("invoked by non inner ring node")
}
} else {
multiaddr := common.AlphabetAddress()
common.CheckAlphabetWitness(multiaddr)
}
if notaryDisabled {
threshold := len(alphabet)*2/3 + 1
n := common.Vote(ctx, id, nodeKey)
if n < threshold {
return
}
common.RemoveVotes(ctx, id)
}
common.CheckAlphabetWitness()
setConfig(ctx, key, val)
@ -693,7 +455,7 @@ type record struct {
}
// ListConfig returns an array of structures that contain key and value of all
// NeoFS configuration records. Key and value are both byte arrays.
// FrostFS configuration records. Key and value are both byte arrays.
func ListConfig() []record {
ctx := storage.GetReadOnlyContext()
@ -803,26 +565,3 @@ func cleanup(ctx storage.Context, epoch int) {
containerContractAddr := storage.Get(ctx, containerContractKey).(interop.Hash160)
contract.Call(containerContractAddr, cleanupEpochMethod, contract.All, epoch)
}
func getIRNodes(ctx storage.Context) []interop.PublicKey {
data := storage.Get(ctx, innerRingKey)
if data != nil {
return std.Deserialize(data.([]byte)).([]interop.PublicKey)
}
return []interop.PublicKey{}
}
func keysID(args []interop.PublicKey, prefix []byte) []byte {
var (
result []byte
)
result = append(result, prefix...)
for i := range args {
result = append(result, args[i]...)
}
return crypto.Sha256(result)
}

17
nns/doc.go Normal file
View file

@ -0,0 +1,17 @@
/*
# Contract storage scheme
| Key | Value | Description |
|--------------------------------------|------------|-----------------------------------|
| 0x0 | int | total supply of minted domains |
| 0x1 + accountAddr | int | account's balance |
| 0x2 + accountAddr + tokenKey | ByteArray | token ID |
| 0x10 | int | price for domain registration |
| 0x20 | int | set of roots |
| 0x21 + tokenKey | ByteArray | serialized NameState struct |
| 0x22 + tokenKey + Hash160(tokenName) | Hash160 | container contract hash |
*/
package nns

View file

@ -9,6 +9,7 @@ must be added by committee before a new domain name can be registered.
package nns
import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
@ -19,7 +20,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
"github.com/nspcc-dev/neo-go/pkg/interop/util"
"github.com/nspcc-dev/neofs-contract/common"
)
// Prefixes used for contract data storage.

View file

@ -1,4 +1,4 @@
name: "NeoFS Multi Signature Processing"
name: "Multi Signature Processing"
safemethods: ["verify", "version"]
permissions:
- methods: ["update"]

View file

@ -1,5 +1,5 @@
/*
Processing contract is a contract deployed in NeoFS mainchain.
Processing contract is a contract deployed in FrostFS mainchain.
Processing contract pays for all multisignature transaction executions when notary
service is enabled in the mainchain. Notary service prepares multisigned transactions,
@ -8,15 +8,22 @@ ask Alphabet nodes to pay for these transactions: nodes can change over time,
some nodes will spend sidechain GAS faster. It leads to economic instability.
Processing contract exists to solve this issue. At the Withdraw invocation of
NeoFS contract, a user pays fee directly to this contract. This fee is used to
pay for Cheque invocation of NeoFS contract that returns mainchain GAS back
FrostFS contract, a user pays fee directly to this contract. This fee is used to
pay for Cheque invocation of FrostFS contract that returns mainchain GAS back
to the user. The address of the Processing contract is used as the first signer in
the multisignature transaction. Therefore, NeoVM executes Verify method of the
contract and if invocation is verified, Processing contract pays for the
execution.
Contract notifications
# Contract notifications
Processing contract does not produce notifications to process.
# Contract storage scheme
| Key | Value | Description |
|-----------------------------|------------|----------------------------------|
| `frostfsScriptHash` | Hash160 | frostFS contract hash |
*/
package processing

View file

@ -1,6 +1,7 @@
package processing
import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/native/gas"
@ -9,11 +10,10 @@ import (
"github.com/nspcc-dev/neo-go/pkg/interop/native/roles"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
"github.com/nspcc-dev/neofs-contract/common"
)
const (
neofsContractKey = "neofsScriptHash"
frostfsContractKey = "frostfsScriptHash"
multiaddrMethod = "alphabetAddress"
)
@ -34,16 +34,16 @@ func _deploy(data interface{}, isUpdate bool) {
}
args := data.(struct {
addrNeoFS interop.Hash160
addrFrostFS interop.Hash160
})
ctx := storage.GetContext()
if len(args.addrNeoFS) != interop.Hash160Len {
if len(args.addrFrostFS) != interop.Hash160Len {
panic("incorrect length of contract script hash")
}
storage.Put(ctx, neofsContractKey, args.addrNeoFS)
storage.Put(ctx, frostfsContractKey, args.addrFrostFS)
runtime.Log("processing contract initialized")
}
@ -68,8 +68,8 @@ func Update(script []byte, manifest []byte, data interface{}) {
// Alphabet nodes of the Inner Ring.
func Verify() bool {
ctx := storage.GetContext()
neofsContractAddr := storage.Get(ctx, neofsContractKey).(interop.Hash160)
multiaddr := contract.Call(neofsContractAddr, multiaddrMethod, contract.ReadOnly).(interop.Hash160)
frostfsContractAddr := storage.Get(ctx, frostfsContractKey).(interop.Hash160)
multiaddr := contract.Call(frostfsContractAddr, multiaddrMethod, contract.ReadOnly).(interop.Hash160)
return runtime.CheckWitness(multiaddr)
}

View file

@ -1,4 +1,4 @@
name: "NeoFS Notary Proxy"
name: "Notary Proxy"
safemethods: ["verify", "version"]
permissions:
- methods: ["update"]

View file

@ -1,5 +1,5 @@
/*
Proxy contract is a contract deployed in NeoFS sidechain.
Proxy contract is a contract deployed in FrostFS sidechain.
Proxy contract pays for all multisignature transaction executions when notary
service is enabled in the sidechain. Notary service prepares multisigned transactions,
@ -14,8 +14,13 @@ Proxy contract is used as the first signer in a multisignature transaction.
Therefore, NeoVM executes Verify method of the contract; and if invocation is
verified, Proxy contract pays for the execution.
Contract notifications
# Contract notifications
Proxy contract does not produce notifications to process.
# Contract storage scheme
Proxy contract does not use storage
*/
package proxy

View file

@ -1,13 +1,13 @@
package proxy
import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/native/gas"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
"github.com/nspcc-dev/neo-go/pkg/interop/native/neo"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
"github.com/nspcc-dev/neofs-contract/common"
)
// OnNEP17Payment is a callback for NEP-17 compatible native GAS contract.

View file

@ -1,4 +1,4 @@
name: "NeoFS Reputation"
name: "Reputation"
safemethods: ["get", "getByID", "listByEpoch"]
permissions:
- methods: ["update"]

View file

@ -1,5 +1,5 @@
/*
Reputation contract is a contract deployed in NeoFS sidechain.
Reputation contract is a contract deployed in FrostFS sidechain.
Inner Ring nodes produce data audit for each container during each epoch. In the end,
nodes produce DataAuditResult structure that contains information about audit
@ -10,8 +10,16 @@ During settlement process, Alphabet nodes fetch all DataAuditResult structures
from the epoch and execute balance transfers from data owners to Storage and
Inner Ring nodes if data audit succeeds.
Contract notifications
# Contract notifications
Reputation contract does not produce notifications to process.
# Contract storage scheme
| Key | Value | Description |
|-----------------------------|------------|-----------------------------------|
| `c` + epoch + peerID | int | peer reputation count |
| `r` + count | ByteArray | serialized DataAuditResult struct |
*/
package reputation

View file

@ -1,6 +1,7 @@
package reputation
import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/convert"
@ -8,7 +9,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
"github.com/nspcc-dev/neofs-contract/common"
)
const (
@ -18,7 +18,7 @@ const (
)
func _deploy(data interface{}, isUpdate bool) {
ctx := storage.GetContext()
common.RmAndCheckNotaryDisabledKey(data, notaryDisabledKey)
if isUpdate {
args := data.([]interface{})
@ -26,17 +26,6 @@ func _deploy(data interface{}, isUpdate bool) {
return
}
args := data.(struct {
notaryDisabled bool
})
// initialize the way to collect signatures
storage.Put(ctx, notaryDisabledKey, args.notaryDisabled)
if args.notaryDisabled {
common.InitVote(ctx)
runtime.Log("reputation contract notary disabled")
}
runtime.Log("reputation contract initialized")
}
@ -60,40 +49,14 @@ func Update(script []byte, manifest []byte, data interface{}) {
// Value contains a stable marshaled structure of DataAuditResult.
func Put(epoch int, peerID []byte, value []byte) {
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
var ( // for invocation collection without notary
alphabet []interop.PublicKey
nodeKey []byte
alphabetCall bool
)
if notaryDisabled {
alphabet = common.AlphabetNodes()
nodeKey = common.InnerRingInvoker(alphabet)
alphabetCall = len(nodeKey) != 0
} else {
multiaddr := common.AlphabetAddress()
alphabetCall = runtime.CheckWitness(multiaddr)
}
if !alphabetCall {
multiaddr := common.AlphabetAddress()
if !runtime.CheckWitness(multiaddr) {
runtime.Notify("reputationPut", epoch, peerID, value)
return
}
id := storageID(epoch, peerID)
if notaryDisabled {
threshold := len(alphabet)*2/3 + 1
n := common.Vote(ctx, id, nodeKey)
if n < threshold {
return
}
common.RemoveVotes(ctx, id)
}
key := getReputationKey(reputationCountPrefix, id)
rawCnt := storage.Get(ctx, key)
cnt := 0

View file

@ -1,23 +0,0 @@
name: "NeoFS Subnet"
safemethods: ["version"]
permissions:
- methods: ["update"]
events:
- name: Put
parameters:
- name: id
type: ByteArray
- name: ownerKey
type: PublicKey
- name: info
type: ByteArray
- name: Delete
parameters:
- name: id
type: ByteArray
- name: RemoveNode
parameters:
- name: subnetID
type: ByteArray
- name: node
type: PublicKey

View file

@ -1,37 +0,0 @@
/*
Subnet contract is a contract deployed in NeoFS sidechain.
Subnet contract stores and manages NeoFS subnetwork states. It allows registering
and deleting subnetworks, limiting access to them, and defining a list of the Storage
Nodes that can be included in them.
Contract notifications
Put notification. This notification is produced when a new subnetwork is
registered by invoking Put method.
Put
- name: id
type: ByteArray
- name: ownerKey
type: PublicKey
- name: info
type: ByteArray
Delete notification. This notification is produced when some subnetwork is
deleted by invoking Delete method.
Delete
- name: id
type: ByteArray
RemoveNode notification. This notification is produced when some node is deleted
by invoking RemoveNode method.
RemoveNode
- name: subnetID
type: ByteArray
- name: node
type: PublicKey
*/
package subnet

View file

@ -1,593 +0,0 @@
package subnet
import (
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
"github.com/nspcc-dev/neofs-contract/common"
)
const (
// ErrInvalidSubnetID is thrown when subnet id is not a slice of 5 bytes.
ErrInvalidSubnetID = "invalid subnet ID"
// ErrInvalidGroupID is thrown when group id is not a slice of 5 bytes.
ErrInvalidGroupID = "invalid group ID"
// ErrInvalidOwner is thrown when owner has invalid format.
ErrInvalidOwner = "invalid owner"
// ErrInvalidAdmin is thrown when admin has invalid format.
ErrInvalidAdmin = "invalid administrator"
// ErrAlreadyExists is thrown when id already exists.
ErrAlreadyExists = "subnet id already exists"
// ErrNotExist is thrown when id doesn't exist.
ErrNotExist = "subnet id doesn't exist"
// ErrInvalidUser is thrown when user has invalid format.
ErrInvalidUser = "invalid user"
// ErrInvalidNode is thrown when node has invalid format.
ErrInvalidNode = "invalid node key"
// ErrNodeAdmNotExist is thrown when node admin is not found.
ErrNodeAdmNotExist = "node admin not found"
// ErrClientAdmNotExist is thrown when client admin is not found.
ErrClientAdmNotExist = "client admin not found"
// ErrNodeNotExist is thrown when node is not found.
ErrNodeNotExist = "node not found"
// ErrUserNotExist is thrown when user is not found.
ErrUserNotExist = "user not found"
// ErrAccessDenied is thrown when operation is denied for caller.
ErrAccessDenied = "access denied"
)
const (
nodeAdminPrefix = 'a'
infoPrefix = 'i'
clientAdminPrefix = 'm'
nodePrefix = 'n'
ownerPrefix = 'o'
userPrefix = 'u'
notaryDisabledKey = 'z'
)
const (
userIDSize = 27
subnetIDSize = 5
groupIDSize = 5
)
// _deploy function sets up initial list of inner ring public keys.
func _deploy(data interface{}, isUpdate bool) {
if isUpdate {
args := data.([]interface{})
common.CheckVersion(args[len(args)-1].(int))
return
}
args := data.(struct {
notaryDisabled bool
})
ctx := storage.GetContext()
storage.Put(ctx, []byte{notaryDisabledKey}, args.notaryDisabled)
}
// Update method updates contract source code and manifest. It can be invoked
// only by committee.
func Update(script []byte, manifest []byte, data interface{}) {
if !common.HasUpdateAccess() {
panic("only committee can update contract")
}
contract.Call(interop.Hash160(management.Hash), "update", contract.All,
script, manifest, common.AppendVersion(data))
runtime.Log("subnet contract updated")
}
// Put creates a new subnet with the specified owner and info.
func Put(id []byte, ownerKey interop.PublicKey, info []byte) {
// V2 format
if len(id) != subnetIDSize {
panic(ErrInvalidSubnetID)
}
if len(ownerKey) != interop.PublicKeyCompressedLen {
panic(ErrInvalidOwner)
}
ctx := storage.GetContext()
stKey := append([]byte{ownerPrefix}, id...)
if storage.Get(ctx, stKey) != nil {
panic(ErrAlreadyExists)
}
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
if notaryDisabled {
alphabet := common.AlphabetNodes()
nodeKey := common.InnerRingInvoker(alphabet)
if len(nodeKey) == 0 {
common.CheckWitness(ownerKey)
runtime.Notify("Put", id, ownerKey, info)
return
}
threshold := len(alphabet)*2/3 + 1
id := common.InvokeID([]interface{}{ownerKey, info}, []byte("put"))
n := common.Vote(ctx, id, nodeKey)
if n < threshold {
return
}
common.RemoveVotes(ctx, id)
} else {
common.CheckOwnerWitness(ownerKey)
multiaddr := common.AlphabetAddress()
common.CheckAlphabetWitness(multiaddr)
}
storage.Put(ctx, stKey, ownerKey)
stKey[0] = infoPrefix
storage.Put(ctx, stKey, info)
}
// Get returns info about the subnet with the specified id.
func Get(id []byte) []byte {
// V2 format
if len(id) != subnetIDSize {
panic(ErrInvalidSubnetID)
}
ctx := storage.GetReadOnlyContext()
key := append([]byte{infoPrefix}, id...)
raw := storage.Get(ctx, key)
if raw == nil {
panic(ErrNotExist)
}
return raw.([]byte)
}
// Delete deletes the subnet with the specified id.
func Delete(id []byte) {
// V2 format
if len(id) != subnetIDSize {
panic(ErrInvalidSubnetID)
}
ctx := storage.GetContext()
key := append([]byte{ownerPrefix}, id...)
raw := storage.Get(ctx, key)
if raw == nil {
return
}
owner := raw.([]byte)
common.CheckOwnerWitness(owner)
storage.Delete(ctx, key)
key[0] = infoPrefix
storage.Delete(ctx, key)
key[0] = nodeAdminPrefix
deleteByPrefix(ctx, key)
key[0] = nodePrefix
deleteByPrefix(ctx, key)
key[0] = clientAdminPrefix
deleteByPrefix(ctx, key)
key[0] = userPrefix
deleteByPrefix(ctx, key)
runtime.Notify("Delete", id)
}
// AddNodeAdmin adds a new node administrator to the specified subnetwork.
func AddNodeAdmin(subnetID []byte, adminKey interop.PublicKey) {
// V2 format
if len(subnetID) != subnetIDSize {
panic(ErrInvalidSubnetID)
}
if len(adminKey) != interop.PublicKeyCompressedLen {
panic(ErrInvalidAdmin)
}
ctx := storage.GetContext()
stKey := append([]byte{ownerPrefix}, subnetID...)
rawOwner := storage.Get(ctx, stKey)
if rawOwner == nil {
panic(ErrNotExist)
}
owner := rawOwner.([]byte)
common.CheckOwnerWitness(owner)
stKey[0] = nodeAdminPrefix
if keyInList(ctx, adminKey, stKey) {
return
}
putKeyInList(ctx, adminKey, stKey)
}
// RemoveNodeAdmin removes node administrator from the specified subnetwork.
// Must be called by the subnet owner only.
func RemoveNodeAdmin(subnetID []byte, adminKey interop.PublicKey) {
// V2 format
if len(subnetID) != subnetIDSize {
panic(ErrInvalidSubnetID)
}
if len(adminKey) != interop.PublicKeyCompressedLen {
panic(ErrInvalidAdmin)
}
ctx := storage.GetContext()
stKey := append([]byte{ownerPrefix}, subnetID...)
rawOwner := storage.Get(ctx, stKey)
if rawOwner == nil {
panic(ErrNotExist)
}
owner := rawOwner.([]byte)
common.CheckOwnerWitness(owner)
stKey[0] = nodeAdminPrefix
if !keyInList(ctx, adminKey, stKey) {
return
}
deleteKeyFromList(ctx, adminKey, stKey)
}
// AddNode adds a node to the specified subnetwork.
// Must be called by the subnet's owner or the node administrator
// only.
func AddNode(subnetID []byte, node interop.PublicKey) {
// V2 format
if len(subnetID) != subnetIDSize {
panic(ErrInvalidSubnetID)
}
if len(node) != interop.PublicKeyCompressedLen {
panic(ErrInvalidNode)
}
ctx := storage.GetContext()
stKey := append([]byte{ownerPrefix}, subnetID...)
rawOwner := storage.Get(ctx, stKey)
if rawOwner == nil {
panic(ErrNotExist)
}
stKey[0] = nodeAdminPrefix
owner := rawOwner.([]byte)
if !calledByOwnerOrAdmin(ctx, owner, stKey) {
panic(ErrAccessDenied)
}
stKey[0] = nodePrefix
if keyInList(ctx, node, stKey) {
return
}
putKeyInList(ctx, node, stKey)
}
// RemoveNode removes a node from the specified subnetwork.
// Must be called by the subnet's owner or the node administrator
// only.
func RemoveNode(subnetID []byte, node interop.PublicKey) {
// V2 format
if len(subnetID) != subnetIDSize {
panic(ErrInvalidSubnetID)
}
if len(node) != interop.PublicKeyCompressedLen {
panic(ErrInvalidNode)
}
ctx := storage.GetContext()
stKey := append([]byte{ownerPrefix}, subnetID...)
rawOwner := storage.Get(ctx, stKey)
if rawOwner == nil {
panic(ErrNotExist)
}
stKey[0] = nodeAdminPrefix
owner := rawOwner.([]byte)
if !calledByOwnerOrAdmin(ctx, owner, stKey) {
panic(ErrAccessDenied)
}
stKey[0] = nodePrefix
if !keyInList(ctx, node, stKey) {
return
}
storage.Delete(ctx, append(stKey, node...))
runtime.Notify("RemoveNode", subnetID, node)
}
// NodeAllowed checks if a node is included in the
// specified subnet.
func NodeAllowed(subnetID []byte, node interop.PublicKey) bool {
// V2 format
if len(subnetID) != subnetIDSize {
panic(ErrInvalidSubnetID)
}
if len(node) != interop.PublicKeyCompressedLen {
panic(ErrInvalidNode)
}
ctx := storage.GetReadOnlyContext()
stKey := append([]byte{ownerPrefix}, subnetID...)
rawOwner := storage.Get(ctx, stKey)
if rawOwner == nil {
panic(ErrNotExist)
}
stKey[0] = nodePrefix
return storage.Get(ctx, append(stKey, node...)) != nil
}
// AddClientAdmin adds a new client administrator of the specified group in the specified subnetwork.
// Must be called by the owner only.
func AddClientAdmin(subnetID []byte, groupID []byte, adminPublicKey interop.PublicKey) {
// V2 format
if len(subnetID) != subnetIDSize {
panic(ErrInvalidSubnetID)
}
// V2 format
if len(groupID) != groupIDSize {
panic(ErrInvalidGroupID)
}
if len(adminPublicKey) != interop.PublicKeyCompressedLen {
panic(ErrInvalidAdmin)
}
ctx := storage.GetContext()
stKey := append([]byte{ownerPrefix}, subnetID...)
rawOwner := storage.Get(ctx, stKey)
if rawOwner == nil {
panic(ErrNotExist)
}
owner := rawOwner.([]byte)
common.CheckOwnerWitness(owner)
stKey[0] = clientAdminPrefix
stKey = append(stKey, groupID...)
if keyInList(ctx, adminPublicKey, stKey) {
return
}
putKeyInList(ctx, adminPublicKey, stKey)
}
// RemoveClientAdmin removes client administrator from the
// specified group in the specified subnetwork.
// Must be called by the owner only.
func RemoveClientAdmin(subnetID []byte, groupID []byte, adminPublicKey interop.PublicKey) {
// V2 format
if len(subnetID) != subnetIDSize {
panic(ErrInvalidSubnetID)
}
// V2 format
if len(groupID) != groupIDSize {
panic(ErrInvalidGroupID)
}
if len(adminPublicKey) != interop.PublicKeyCompressedLen {
panic(ErrInvalidAdmin)
}
ctx := storage.GetContext()
stKey := append([]byte{ownerPrefix}, subnetID...)
rawOwner := storage.Get(ctx, stKey)
if rawOwner == nil {
panic(ErrNotExist)
}
owner := rawOwner.([]byte)
common.CheckOwnerWitness(owner)
stKey[0] = clientAdminPrefix
stKey = append(stKey, groupID...)
if !keyInList(ctx, adminPublicKey, stKey) {
return
}
deleteKeyFromList(ctx, adminPublicKey, stKey)
}
// AddUser adds user to the specified subnetwork and group.
// Must be called by the owner or the group's admin only.
func AddUser(subnetID []byte, groupID []byte, userID []byte) {
// V2 format
if len(subnetID) != subnetIDSize {
panic(ErrInvalidSubnetID)
}
// V2 format
if len(userID) != userIDSize {
panic(ErrInvalidUser)
}
// V2 format
if len(groupID) != groupIDSize {
panic(ErrInvalidGroupID)
}
ctx := storage.GetContext()
stKey := append([]byte{ownerPrefix}, subnetID...)
rawOwner := storage.Get(ctx, stKey)
if rawOwner == nil {
panic(ErrNotExist)
}
stKey[0] = clientAdminPrefix
stKey = append(stKey, groupID...)
owner := rawOwner.([]byte)
if !calledByOwnerOrAdmin(ctx, owner, stKey) {
panic(ErrAccessDenied)
}
stKey[0] = userPrefix
if keyInList(ctx, userID, stKey) {
return
}
putKeyInList(ctx, userID, stKey)
}
// RemoveUser removes a user from the specified subnetwork and group.
// Must be called by the owner or the group's admin only.
func RemoveUser(subnetID []byte, groupID []byte, userID []byte) {
// V2 format
if len(subnetID) != subnetIDSize {
panic(ErrInvalidSubnetID)
}
// V2 format
if len(groupID) != groupIDSize {
panic(ErrInvalidGroupID)
}
// V2 format
if len(userID) != userIDSize {
panic(ErrInvalidUser)
}
ctx := storage.GetContext()
stKey := append([]byte{ownerPrefix}, subnetID...)
rawOwner := storage.Get(ctx, stKey)
if rawOwner == nil {
panic(ErrNotExist)
}
stKey[0] = clientAdminPrefix
stKey = append(stKey, groupID...)
owner := rawOwner.([]byte)
if !calledByOwnerOrAdmin(ctx, owner, stKey) {
panic(ErrAccessDenied)
}
stKey[0] = userPrefix
if !keyInList(ctx, userID, stKey) {
return
}
deleteKeyFromList(ctx, userID, stKey)
}
// UserAllowed returns bool that indicates if a node is included in the
// specified subnet.
func UserAllowed(subnetID []byte, user []byte) bool {
// V2 format
if len(subnetID) != subnetIDSize {
panic(ErrInvalidSubnetID)
}
ctx := storage.GetContext()
stKey := append([]byte{ownerPrefix}, subnetID...)
if storage.Get(ctx, stKey) == nil {
panic(ErrNotExist)
}
stKey[0] = userPrefix
prefixLen := len(stKey) + groupIDSize
iter := storage.Find(ctx, stKey, storage.KeysOnly)
for iterator.Next(iter) {
key := iterator.Value(iter).([]byte)
if common.BytesEqual(user, key[prefixLen:]) {
return true
}
}
return false
}
// Version returns the version of the contract.
func Version() int {
return common.Version
}
func keyInList(ctx storage.Context, searchedKey interop.PublicKey, prefix []byte) bool {
return storage.Get(ctx, append(prefix, searchedKey...)) != nil
}
func putKeyInList(ctx storage.Context, keyToPut interop.PublicKey, prefix []byte) {
storage.Put(ctx, append(prefix, keyToPut...), []byte{1})
}
func deleteKeyFromList(ctx storage.Context, keyToDelete interop.PublicKey, prefix []byte) {
storage.Delete(ctx, append(prefix, keyToDelete...))
}
func deleteByPrefix(ctx storage.Context, prefix []byte) {
iter := storage.Find(ctx, prefix, storage.KeysOnly)
for iterator.Next(iter) {
k := iterator.Value(iter).([]byte)
storage.Delete(ctx, k)
}
}
func calledByOwnerOrAdmin(ctx storage.Context, owner []byte, adminPrefix []byte) bool {
if runtime.CheckWitness(owner) {
return true
}
iter := storage.Find(ctx, adminPrefix, storage.KeysOnly|storage.RemovePrefix)
for iterator.Next(iter) {
key := iterator.Value(iter).([]byte)
if runtime.CheckWitness(key) {
return true
}
}
return false
}

View file

@ -4,6 +4,8 @@ import (
"path"
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"git.frostfs.info/TrueCloudLab/frostfs-contract/container"
"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/neotest"
@ -11,8 +13,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/nspcc-dev/neofs-contract/common"
"github.com/nspcc-dev/neofs-contract/container"
"github.com/stretchr/testify/require"
)

View file

@ -3,17 +3,19 @@ package tests
import (
"bytes"
"crypto/sha256"
"math/big"
"path"
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"git.frostfs.info/TrueCloudLab/frostfs-contract/container"
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
"github.com/mr-tron/base58"
"github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neofs-contract/common"
"github.com/nspcc-dev/neofs-contract/container"
"github.com/nspcc-dev/neofs-contract/nns"
"github.com/stretchr/testify/require"
)
@ -31,7 +33,7 @@ func deployContainerContract(t *testing.T, e *neotest.Executor, addrNetmap, addr
args[2] = addrBalance
args[3] = util.Uint160{} // not needed for now
args[4] = addrNNS
args[5] = "neofs"
args[5] = "frostfs"
c := neotest.CompileFile(t, e.CommitteeHash, containerPath, path.Join(containerPath, "config.yml"))
e.DeployContract(t, c, args)
@ -101,15 +103,62 @@ func TestContainerCount(t *testing.T) {
cnt3 := dummyContainer(acc1)
balanceMint(t, cBal, acc1, containerFee*1, []byte{})
c.Invoke(t, stackitem.Null{}, "put", cnt3.value, cnt3.sig, cnt3.pub, cnt3.token)
checkContainerList(t, c, [][]byte{cnt1.id[:], cnt2.id[:], cnt3.id[:]})
c.Invoke(t, stackitem.Null{}, "delete", cnt1.id[:], cnt1.sig, cnt1.token)
checkCount(t, 2)
checkContainerList(t, c, [][]byte{cnt2.id[:], cnt3.id[:]})
c.Invoke(t, stackitem.Null{}, "delete", cnt2.id[:], cnt2.sig, cnt2.token)
checkCount(t, 1)
checkContainerList(t, c, [][]byte{cnt3.id[:]})
c.Invoke(t, stackitem.Null{}, "delete", cnt3.id[:], cnt3.sig, cnt3.token)
checkCount(t, 0)
checkContainerList(t, c, [][]byte{})
}
func checkContainerList(t *testing.T, c *neotest.ContractInvoker, expected [][]byte) {
t.Run("check with `list`", func(t *testing.T) {
s, err := c.TestInvoke(t, "list", nil)
require.NoError(t, err)
require.Equal(t, 1, s.Len())
if len(expected) == 0 {
_, ok := s.Top().Item().(stackitem.Null)
require.True(t, ok)
return
}
arr, ok := s.Top().Value().([]stackitem.Item)
require.True(t, ok)
require.Equal(t, len(expected), len(arr))
actual := make([][]byte, 0, len(expected))
for i := range arr {
id, ok := arr[i].Value().([]byte)
require.True(t, ok)
actual = append(actual, id)
}
require.ElementsMatch(t, expected, actual)
})
t.Run("check with `containersOf`", func(t *testing.T) {
s, err := c.TestInvoke(t, "containersOf", nil)
require.NoError(t, err)
require.Equal(t, 1, s.Len())
iter, ok := s.Top().Value().(*storage.Iterator)
require.True(t, ok)
actual := make([][]byte, 0, len(expected))
for iter.Next() {
id, ok := iter.Value().Value().([]byte)
require.True(t, ok)
actual = append(actual, id)
}
require.ElementsMatch(t, expected, actual)
})
}
func TestContainerPut(t *testing.T) {
@ -146,14 +195,14 @@ func TestContainerPut(t *testing.T) {
stackitem.NewByteArray([]byte(base58.Encode(cnt.id[:]))),
})
cNNS := c.CommitteeInvoker(nnsHash)
cNNS.Invoke(t, expected, "resolve", "mycnt.neofs", int64(nns.TXT))
cNNS.Invoke(t, expected, "resolve", "mycnt.frostfs", int64(nns.TXT))
t.Run("name is already taken", func(t *testing.T) {
c.InvokeFail(t, "name is already taken", "putNamed", putArgs...)
})
c.Invoke(t, stackitem.Null{}, "delete", cnt.id[:], cnt.sig, cnt.token)
cNNS.Invoke(t, stackitem.Null{}, "resolve", "mycnt.neofs", int64(nns.TXT))
cNNS.Invoke(t, stackitem.Null{}, "resolve", "mycnt.frostfs", int64(nns.TXT))
t.Run("register in advance", func(t *testing.T) {
cnt.value[len(cnt.value)-1] = 10
@ -366,6 +415,16 @@ type estimation struct {
}
func checkEstimations(t *testing.T, c *neotest.ContractInvoker, epoch int64, cnt testContainer, estimations ...estimation) {
// Check that listed estimations match expected
listEstimations := getListEstimations(t, c, epoch, cnt)
requireEstimationsMatch(t, estimations, listEstimations)
// Check that iterated estimations match expected
iterEstimations := getIterEstimations(t, c, epoch)
requireEstimationsMatch(t, estimations, iterEstimations)
}
func getListEstimations(t *testing.T, c *neotest.ContractInvoker, epoch int64, cnt testContainer) []estimation {
s, err := c.TestInvoke(t, "listContainerSizes", epoch)
require.NoError(t, err)
@ -375,9 +434,8 @@ func checkEstimations(t *testing.T, c *neotest.ContractInvoker, epoch int64, cnt
item := s.Top().Item()
switch it := item.(type) {
case stackitem.Null:
require.Equal(t, 0, len(estimations))
require.Equal(t, stackitem.Null{}, it)
return
return make([]estimation, 0)
case *stackitem.Array:
id, err = it.Value().([]stackitem.Item)[0].TryBytes()
require.NoError(t, err)
@ -388,25 +446,52 @@ func checkEstimations(t *testing.T, c *neotest.ContractInvoker, epoch int64, cnt
s, err = c.TestInvoke(t, "getContainerSize", id)
require.NoError(t, err)
// Here and below we assume that all estimations in the contract are related to our container
sizes := s.Top().Array()
require.Equal(t, cnt.id[:], sizes[0].Value())
actual := sizes[1].Value().([]stackitem.Item)
require.Equal(t, len(estimations), len(actual))
for i := range actual {
// type estimation struct {
// from interop.PublicKey
// size int
// }
est := actual[i].Value().([]stackitem.Item)
pub := est[0].Value().([]byte)
return convertStackToEstimations(sizes[1].Value().([]stackitem.Item))
}
func getIterEstimations(t *testing.T, c *neotest.ContractInvoker, epoch int64) []estimation {
iterStack, err := c.TestInvoke(t, "iterateContainerSizes", epoch)
require.NoError(t, err)
iter := iterStack.Pop().Value().(*storage.Iterator)
// Iterator contains pairs: key + estimation (as stack item), we extract estimations only
pairs := iteratorToArray(iter)
estimationItems := make([]stackitem.Item, len(pairs))
for i, pair := range pairs {
pairItems := pair.Value().([]stackitem.Item)
estimationItems[i] = pairItems[1]
}
return convertStackToEstimations(estimationItems)
}
func convertStackToEstimations(stackItems []stackitem.Item) []estimation {
estimations := make([]estimation, 0, len(stackItems))
for _, item := range stackItems {
value := item.Value().([]stackitem.Item)
from := value[0].Value().([]byte)
size := value[1].Value().(*big.Int)
estimation := estimation{from: from, size: size.Int64()}
estimations = append(estimations, estimation)
}
return estimations
}
func requireEstimationsMatch(t *testing.T, expected []estimation, actual []estimation) {
require.Equal(t, len(expected), len(actual))
for _, e := range expected {
found := false
for i := range estimations {
if found = bytes.Equal(estimations[i].from, pub); found {
require.Equal(t, stackitem.Make(estimations[i].size), est[1])
for _, a := range actual {
if found = bytes.Equal(e.from, a.from); found {
require.Equal(t, e.size, a.size)
break
}
}
require.True(t, found, "expected estimation from %x to be present", pub)
require.True(t, found, "expected estimation from %x to be present", e.from)
}
}

View file

@ -6,6 +6,7 @@ import (
"sort"
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfs"
"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/neotest"
@ -14,13 +15,12 @@ import (
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/nspcc-dev/neofs-contract/neofs"
"github.com/stretchr/testify/require"
)
const neofsPath = "../neofs"
const frostfsPath = "../frostfs"
func deployNeoFSContract(t *testing.T, e *neotest.Executor, addrProc util.Uint160,
func deployFrostFSContract(t *testing.T, e *neotest.Executor, addrProc util.Uint160,
pubs keys.PublicKeys, config ...interface{}) util.Uint160 {
args := make([]interface{}, 5)
args[0] = false
@ -33,12 +33,12 @@ func deployNeoFSContract(t *testing.T, e *neotest.Executor, addrProc util.Uint16
args[2] = arr
args[3] = append([]interface{}{}, config...)
c := neotest.CompileFile(t, e.CommitteeHash, neofsPath, path.Join(neofsPath, "config.yml"))
c := neotest.CompileFile(t, e.CommitteeHash, frostfsPath, path.Join(frostfsPath, "config.yml"))
e.DeployContract(t, c, args)
return c.Hash
}
func newNeoFSInvoker(t *testing.T, n int, config ...interface{}) (*neotest.ContractInvoker, neotest.Signer, keys.PublicKeys) {
func newFrostFSInvoker(t *testing.T, n int, config ...interface{}) (*neotest.ContractInvoker, neotest.Signer, keys.PublicKeys) {
e := newExecutor(t)
accounts := make([]*wallet.Account, n)
@ -66,7 +66,7 @@ func newNeoFSInvoker(t *testing.T, n int, config ...interface{}) (*neotest.Contr
}
alphabet := neotest.NewMultiSigner(accounts...)
h := deployNeoFSContract(t, e, util.Uint160{}, pubs, config...)
h := deployFrostFSContract(t, e, util.Uint160{}, pubs, config...)
gasHash, err := e.Chain.GetNativeContractScriptHash(nativenames.Gas)
require.NoError(t, err)
@ -79,22 +79,8 @@ func newNeoFSInvoker(t *testing.T, n int, config ...interface{}) (*neotest.Contr
return e.CommitteeInvoker(h).WithSigners(alphabet), alphabet, pubs
}
func TestNeoFS_AlphabetList(t *testing.T) {
const alphabetSize = 4
e, _, pubs := newNeoFSInvoker(t, alphabetSize)
arr := make([]stackitem.Item, len(pubs))
for i := range arr {
arr[i] = stackitem.NewStruct([]stackitem.Item{
stackitem.NewByteArray(pubs[i].Bytes()),
})
}
e.Invoke(t, stackitem.NewArray(arr), "alphabetList")
}
func TestNeoFS_InnerRingCandidate(t *testing.T) {
e, _, _ := newNeoFSInvoker(t, 4, neofs.CandidateFeeConfigKey, int64(10))
func TestFrostFS_InnerRingCandidate(t *testing.T) {
e, _, _ := newFrostFSInvoker(t, 4, frostfs.CandidateFeeConfigKey, int64(10))
const candidateCount = 3

View file

@ -6,30 +6,30 @@ import (
"sort"
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-contract/container"
"github.com/mr-tron/base58"
"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/neotest"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neofs-contract/container"
"github.com/stretchr/testify/require"
)
const neofsidPath = "../neofsid"
const frostfsidPath = "../frostfsid"
func deployNeoFSIDContract(t *testing.T, e *neotest.Executor, addrNetmap, addrContainer util.Uint160) util.Uint160 {
func deployFrostFSIDContract(t *testing.T, e *neotest.Executor, addrNetmap, addrContainer util.Uint160) util.Uint160 {
args := make([]interface{}, 5)
args[0] = false
args[1] = addrNetmap
args[2] = addrContainer
c := neotest.CompileFile(t, e.CommitteeHash, neofsidPath, path.Join(neofsidPath, "config.yml"))
c := neotest.CompileFile(t, e.CommitteeHash, frostfsidPath, path.Join(frostfsidPath, "config.yml"))
e.DeployContract(t, c, args)
return c.Hash
}
func newNeoFSIDInvoker(t *testing.T) *neotest.ContractInvoker {
func newFrostFSIDInvoker(t *testing.T) *neotest.ContractInvoker {
e := newExecutor(t)
ctrNNS := neotest.CompileFile(t, e.CommitteeHash, nnsPath, path.Join(nnsPath, "config.yml"))
@ -43,12 +43,12 @@ func newNeoFSIDInvoker(t *testing.T) *neotest.ContractInvoker {
container.AliasFeeKey, int64(containerAliasFee))
deployBalanceContract(t, e, ctrNetmap.Hash, ctrContainer.Hash)
deployContainerContract(t, e, ctrNetmap.Hash, ctrBalance.Hash, ctrNNS.Hash)
h := deployNeoFSIDContract(t, e, ctrNetmap.Hash, ctrContainer.Hash)
h := deployFrostFSIDContract(t, e, ctrNetmap.Hash, ctrContainer.Hash)
return e.CommitteeInvoker(h)
}
func TestNeoFSID_AddKey(t *testing.T) {
e := newNeoFSIDInvoker(t)
func TestFrostFSID_AddKey(t *testing.T) {
e := newFrostFSIDInvoker(t)
pubs := make([][]byte, 6)
for i := range pubs {

View file

@ -7,14 +7,14 @@ import (
"strings"
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"git.frostfs.info/TrueCloudLab/frostfs-contract/container"
"git.frostfs.info/TrueCloudLab/frostfs-contract/netmap"
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neofs-contract/common"
"github.com/nspcc-dev/neofs-contract/container"
"github.com/nspcc-dev/neofs-contract/netmap"
"github.com/stretchr/testify/require"
)

View file

@ -8,10 +8,10 @@ import (
"testing"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
"github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neofs-contract/nns"
"github.com/stretchr/testify/require"
)
@ -30,7 +30,7 @@ func newNNSInvoker(t *testing.T, addRoot bool) *neotest.ContractInvoker {
refresh, retry, expire, ttl := int64(101), int64(102), int64(msPerYear/1000*100), int64(104)
c.Invoke(t, true, "register",
"com", c.CommitteeHash,
"myemail@nspcc.ru", refresh, retry, expire, ttl)
"myemail@frostfs.info", refresh, retry, expire, ttl)
}
return c
}
@ -50,21 +50,21 @@ func TestNNSRegisterTLD(t *testing.T) {
c.InvokeFail(t, "invalid domain name format", "register",
"0com", c.CommitteeHash,
"email@nspcc.ru", refresh, retry, expire, ttl)
"email@frostfs.info", refresh, retry, expire, ttl)
acc := c.NewAccount(t)
cAcc := c.WithSigners(acc)
cAcc.InvokeFail(t, "not witnessed by committee", "register",
"com", acc.ScriptHash(),
"email@nspcc.ru", refresh, retry, expire, ttl)
"email@frostfs.info", refresh, retry, expire, ttl)
c.Invoke(t, true, "register",
"com", c.CommitteeHash,
"email@nspcc.ru", refresh, retry, expire, ttl)
"email@frostfs.info", refresh, retry, expire, ttl)
c.InvokeFail(t, "TLD already exists", "register",
"com", c.CommitteeHash,
"email@nspcc.ru", refresh, retry, expire, ttl)
"email@frostfs.info", refresh, retry, expire, ttl)
}
func TestNNSRegister(t *testing.T) {
@ -75,33 +75,33 @@ func TestNNSRegister(t *testing.T) {
c1 := c.WithSigners(c.Committee, accTop)
c1.Invoke(t, true, "register",
"com", accTop.ScriptHash(),
"myemail@nspcc.ru", refresh, retry, expire, ttl)
"myemail@frostfs.info", refresh, retry, expire, ttl)
acc := c.NewAccount(t)
c2 := c.WithSigners(c.Committee, acc)
c2.InvokeFail(t, "not witnessed by admin", "register",
"testdomain.com", acc.ScriptHash(),
"myemail@nspcc.ru", refresh, retry, expire, ttl)
"myemail@frostfs.info", refresh, retry, expire, ttl)
c3 := c.WithSigners(accTop, acc)
t.Run("domain names with hyphen", func(t *testing.T) {
c3.InvokeFail(t, "invalid domain name format", "register",
"-testdomain.com", acc.ScriptHash(),
"myemail@nspcc.ru", refresh, retry, expire, ttl)
"myemail@frostfs.info", refresh, retry, expire, ttl)
c3.InvokeFail(t, "invalid domain name format", "register",
"testdomain-.com", acc.ScriptHash(),
"myemail@nspcc.ru", refresh, retry, expire, ttl)
"myemail@frostfs.info", refresh, retry, expire, ttl)
c3.Invoke(t, true, "register",
"test-domain.com", acc.ScriptHash(),
"myemail@nspcc.ru", refresh, retry, expire, ttl)
"myemail@frostfs.info", refresh, retry, expire, ttl)
})
c3.Invoke(t, true, "register",
"testdomain.com", acc.ScriptHash(),
"myemail@nspcc.ru", refresh, retry, expire, ttl)
"myemail@frostfs.info", refresh, retry, expire, ttl)
b := c.TopBlock(t)
expected := stackitem.NewArray([]stackitem.Item{stackitem.NewBuffer(
[]byte(fmt.Sprintf("testdomain.com myemail@nspcc.ru %d %d %d %d %d",
[]byte(fmt.Sprintf("testdomain.com myemail@frostfs.info %d %d %d %d %d",
b.Timestamp, refresh, retry, expire, ttl)))})
c.Invoke(t, expected, "getRecords", "testdomain.com", int64(nns.SOA))
@ -195,18 +195,18 @@ func TestNNSUpdateSOA(t *testing.T) {
refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104)
c.Invoke(t, true, "register",
"testdomain.com", c.CommitteeHash,
"myemail@nspcc.ru", refresh, retry, expire, ttl)
"myemail@frostfs.info", refresh, retry, expire, ttl)
refresh *= 2
retry *= 2
expire *= 2
ttl *= 2
c.Invoke(t, stackitem.Null{}, "updateSOA",
"testdomain.com", "newemail@nspcc.ru", refresh, retry, expire, ttl)
"testdomain.com", "newemail@frostfs.info", refresh, retry, expire, ttl)
b := c.TopBlock(t)
expected := stackitem.NewArray([]stackitem.Item{stackitem.NewBuffer(
[]byte(fmt.Sprintf("testdomain.com newemail@nspcc.ru %d %d %d %d %d",
[]byte(fmt.Sprintf("testdomain.com newemail@frostfs.info %d %d %d %d %d",
b.Timestamp, refresh, retry, expire, ttl)))})
c.Invoke(t, expected, "getRecords", "testdomain.com", int64(nns.SOA))
}
@ -217,13 +217,13 @@ func TestNNSGetAllRecords(t *testing.T) {
refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104)
c.Invoke(t, true, "register",
"testdomain.com", c.CommitteeHash,
"myemail@nspcc.ru", refresh, retry, expire, ttl)
"myemail@frostfs.info", refresh, retry, expire, ttl)
c.Invoke(t, stackitem.Null{}, "addRecord", "testdomain.com", int64(nns.TXT), "first TXT record")
c.Invoke(t, stackitem.Null{}, "addRecord", "testdomain.com", int64(nns.A), "1.2.3.4")
b := c.TopBlock(t)
expSOA := fmt.Sprintf("testdomain.com myemail@nspcc.ru %d %d %d %d %d",
expSOA := fmt.Sprintf("testdomain.com myemail@frostfs.info %d %d %d %d %d",
b.Timestamp, refresh, retry, expire, ttl)
s, err := c.TestInvoke(t, "getAllRecords", "testdomain.com")
@ -257,7 +257,7 @@ func TestExpiration(t *testing.T) {
refresh, retry, expire, ttl := int64(101), int64(102), int64(msPerYear/1000*10), int64(104)
c.Invoke(t, true, "register",
"testdomain.com", c.CommitteeHash,
"myemail@nspcc.ru", refresh, retry, expire, ttl)
"myemail@frostfs.info", refresh, retry, expire, ttl)
checkProperties := func(t *testing.T, expiration uint64) {
expected := stackitem.NewMapWithValue([]stackitem.MapElement{
@ -295,7 +295,7 @@ func TestNNSSetAdmin(t *testing.T) {
refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104)
c.Invoke(t, true, "register",
"testdomain.com", c.CommitteeHash,
"myemail@nspcc.ru", refresh, retry, expire, ttl)
"myemail@frostfs.info", refresh, retry, expire, ttl)
acc := c.NewAccount(t)
cAcc := c.WithSigners(acc)
@ -318,7 +318,7 @@ func TestNNSIsAvailable(t *testing.T) {
refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104)
c.Invoke(t, true, "register",
"com", c.CommitteeHash,
"myemail@nspcc.ru", refresh, retry, expire, ttl)
"myemail@frostfs.info", refresh, retry, expire, ttl)
c.Invoke(t, false, "isAvailable", "com")
c.Invoke(t, true, "isAvailable", "domain.com")
@ -327,7 +327,7 @@ func TestNNSIsAvailable(t *testing.T) {
c1 := c.WithSigners(c.Committee, acc)
c1.Invoke(t, true, "register",
"domain.com", acc.ScriptHash(),
"myemail@nspcc.ru", refresh, retry, expire, ttl)
"myemail@frostfs.info", refresh, retry, expire, ttl)
c.Invoke(t, false, "isAvailable", "domain.com")
}
@ -340,7 +340,7 @@ func TestNNSRenew(t *testing.T) {
refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104)
c1.Invoke(t, true, "register",
"testdomain.com", c.CommitteeHash,
"myemail@nspcc.ru", refresh, retry, expire, ttl)
"myemail@frostfs.info", refresh, retry, expire, ttl)
const msPerYear = 365 * 24 * time.Hour / time.Millisecond
b := c.TopBlock(t)
@ -361,7 +361,7 @@ func TestNNSResolve(t *testing.T) {
refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104)
c.Invoke(t, true, "register",
"test.com", c.CommitteeHash,
"myemail@nspcc.ru", refresh, retry, expire, ttl)
"myemail@frostfs.info", refresh, retry, expire, ttl)
c.Invoke(t, stackitem.Null{}, "addRecord",
"test.com", int64(nns.TXT), "expected result")

View file

@ -11,21 +11,21 @@ import (
const processingPath = "../processing"
func deployProcessingContract(t *testing.T, e *neotest.Executor, addrNeoFS util.Uint160) util.Uint160 {
func deployProcessingContract(t *testing.T, e *neotest.Executor, addrFrostFS util.Uint160) util.Uint160 {
c := neotest.CompileFile(t, e.CommitteeHash, processingPath, path.Join(processingPath, "config.yml"))
args := make([]interface{}, 1)
args[0] = addrNeoFS
args[0] = addrFrostFS
e.DeployContract(t, c, args)
return c.Hash
}
func newProcessingInvoker(t *testing.T) (*neotest.ContractInvoker, neotest.Signer) {
neofsInvoker, irMultiAcc, _ := newNeoFSInvoker(t, 2)
hash := deployProcessingContract(t, neofsInvoker.Executor, neofsInvoker.Hash)
frostfsInvoker, irMultiAcc, _ := newFrostFSInvoker(t, 2)
hash := deployProcessingContract(t, frostfsInvoker.Executor, frostfsInvoker.Hash)
return neofsInvoker.CommitteeInvoker(hash), irMultiAcc
return frostfsInvoker.CommitteeInvoker(hash), irMultiAcc
}
func TestVerify_Processing(t *testing.T) {

View file

@ -1,330 +0,0 @@
package tests
import (
"encoding/binary"
"path"
"testing"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neofs-contract/common"
"github.com/nspcc-dev/neofs-contract/subnet"
"github.com/stretchr/testify/require"
)
const subnetPath = "../subnet"
func deploySubnetContract(t *testing.T, e *neotest.Executor) util.Uint160 {
c := neotest.CompileFile(t, e.CommitteeHash, subnetPath, path.Join(subnetPath, "config.yml"))
args := []interface{}{false}
e.DeployContract(t, c, args)
return c.Hash
}
func newSubnetInvoker(t *testing.T) *neotest.ContractInvoker {
e := newExecutor(t)
h := deploySubnetContract(t, e)
return e.CommitteeInvoker(h)
}
func TestSubnet_Version(t *testing.T) {
e := newSubnetInvoker(t)
e.Invoke(t, common.Version, "version")
}
func TestSubnet_Put(t *testing.T) {
e := newSubnetInvoker(t)
acc := e.NewAccount(t)
pub, ok := vm.ParseSignatureContract(acc.Script())
require.True(t, ok)
id := make([]byte, 5)
binary.LittleEndian.PutUint32(id, 123)
info := randomBytes(10)
e.InvokeFail(t, common.ErrWitnessFailed, "put", id, pub, info)
cAcc := e.WithSigners(acc)
cAcc.InvokeFail(t, common.ErrAlphabetWitnessFailed, "put", id, pub, info)
cBoth := e.WithSigners(e.Committee, acc)
cBoth.InvokeFail(t, subnet.ErrInvalidSubnetID, "put", []byte{1, 2, 3}, pub, info)
cBoth.InvokeFail(t, subnet.ErrInvalidOwner, "put", id, pub[10:], info)
cBoth.Invoke(t, stackitem.Null{}, "put", id, pub, info)
cAcc.Invoke(t, stackitem.NewBuffer(info), "get", id)
cBoth.InvokeFail(t, subnet.ErrAlreadyExists, "put", id, pub, info)
}
func TestSubnet_Delete(t *testing.T) {
e := newSubnetInvoker(t)
id, owner := createSubnet(t, e)
e.InvokeFail(t, common.ErrWitnessFailed, "delete", id)
cAcc := e.WithSigners(owner)
cAcc.InvokeFail(t, subnet.ErrInvalidSubnetID, "delete", []byte{1, 1, 1, 1})
cAcc.Invoke(t, stackitem.Null{}, "delete", []byte{1, 1, 1, 1, 1})
cAcc.Invoke(t, stackitem.Null{}, "delete", id)
cAcc.InvokeFail(t, subnet.ErrNotExist, "get", id)
}
func TestSubnet_AddNodeAdmin(t *testing.T) {
e := newSubnetInvoker(t)
id, owner := createSubnet(t, e)
adm := e.NewAccount(t)
admPub, ok := vm.ParseSignatureContract(adm.Script())
require.True(t, ok)
const method = "addNodeAdmin"
e.InvokeFail(t, subnet.ErrInvalidSubnetID, method, []byte{0, 0, 0, 0}, admPub)
e.InvokeFail(t, subnet.ErrInvalidAdmin, method, id, admPub[1:])
e.InvokeFail(t, subnet.ErrNotExist, method, []byte{0, 0, 0, 0, 0}, admPub)
cAdm := e.WithSigners(adm)
cAdm.InvokeFail(t, common.ErrOwnerWitnessFailed, method, id, admPub)
cOwner := e.WithSigners(owner)
cOwner.Invoke(t, stackitem.Null{}, method, id, admPub)
cOwner.Invoke(t, stackitem.Null{}, method, id, admPub)
}
func TestSubnet_RemoveNodeAdmin(t *testing.T) {
e := newSubnetInvoker(t)
id, owner := createSubnet(t, e)
adm := e.NewAccount(t)
admPub, ok := vm.ParseSignatureContract(adm.Script())
require.True(t, ok)
const method = "removeNodeAdmin"
e.InvokeFail(t, subnet.ErrInvalidSubnetID, method, []byte{0, 0, 0, 0}, admPub)
e.InvokeFail(t, subnet.ErrInvalidAdmin, method, id, admPub[1:])
e.InvokeFail(t, subnet.ErrNotExist, method, []byte{0, 0, 0, 0, 0}, admPub)
cAdm := e.WithSigners(adm)
cAdm.InvokeFail(t, common.ErrOwnerWitnessFailed, method, id, admPub)
cOwner := e.WithSigners(owner)
cOwner.Invoke(t, stackitem.Null{}, method, id, admPub)
cOwner.Invoke(t, stackitem.Null{}, "addNodeAdmin", id, admPub)
cOwner.Invoke(t, stackitem.Null{}, method, id, admPub)
cOwner.Invoke(t, stackitem.Null{}, method, id, admPub)
}
func TestSubnet_AddNode(t *testing.T) {
e := newSubnetInvoker(t)
id, owner := createSubnet(t, e)
node := e.NewAccount(t)
nodePub, ok := vm.ParseSignatureContract(node.Script())
require.True(t, ok)
const method = "addNode"
cOwn := e.WithSigners(owner)
cOwn.InvokeFail(t, subnet.ErrInvalidSubnetID, method, []byte{0, 0, 0, 0}, nodePub)
cOwn.InvokeFail(t, subnet.ErrInvalidNode, method, id, nodePub[1:])
cOwn.InvokeFail(t, subnet.ErrNotExist, method, []byte{0, 0, 0, 0, 0}, nodePub)
cOwn.Invoke(t, stackitem.Null{}, method, id, nodePub)
cOwn.Invoke(t, stackitem.Null{}, method, id, nodePub)
}
func TestSubnet_RemoveNode(t *testing.T) {
e := newSubnetInvoker(t)
id, owner := createSubnet(t, e)
node := e.NewAccount(t)
nodePub, ok := vm.ParseSignatureContract(node.Script())
require.True(t, ok)
adm := e.NewAccount(t)
admPub, ok := vm.ParseSignatureContract(adm.Script())
require.True(t, ok)
const method = "removeNode"
cOwn := e.WithSigners(owner)
cOwn.InvokeFail(t, subnet.ErrInvalidSubnetID, method, []byte{0, 0, 0, 0}, nodePub)
cOwn.InvokeFail(t, subnet.ErrInvalidNode, method, id, nodePub[1:])
cOwn.InvokeFail(t, subnet.ErrNotExist, method, []byte{0, 0, 0, 0, 0}, nodePub)
cOwn.Invoke(t, stackitem.Null{}, method, id, nodePub)
cOwn.Invoke(t, stackitem.Null{}, "addNode", id, nodePub)
cOwn.Invoke(t, stackitem.Null{}, method, id, nodePub)
cAdm := cOwn.WithSigners(adm)
cOwn.Invoke(t, stackitem.Null{}, "addNodeAdmin", id, admPub)
cAdm.Invoke(t, stackitem.Null{}, method, id, nodePub)
}
func TestSubnet_NodeAllowed(t *testing.T) {
e := newSubnetInvoker(t)
id, owner := createSubnet(t, e)
node := e.NewAccount(t)
nodePub, ok := vm.ParseSignatureContract(node.Script())
require.True(t, ok)
const method = "nodeAllowed"
cOwn := e.WithSigners(owner)
cOwn.InvokeFail(t, subnet.ErrInvalidSubnetID, method, []byte{0, 0, 0, 0}, nodePub)
cOwn.InvokeFail(t, subnet.ErrInvalidNode, method, id, nodePub[1:])
cOwn.InvokeFail(t, subnet.ErrNotExist, method, []byte{0, 0, 0, 0, 0}, nodePub)
cOwn.Invoke(t, stackitem.NewBool(false), method, id, nodePub)
cOwn.Invoke(t, stackitem.Null{}, "addNode", id, nodePub)
cOwn.Invoke(t, stackitem.NewBool(true), method, id, nodePub)
}
func TestSubnet_AddClientAdmin(t *testing.T) {
e := newSubnetInvoker(t)
id, owner := createSubnet(t, e)
adm := e.NewAccount(t)
admPub, ok := vm.ParseSignatureContract(adm.Script())
require.True(t, ok)
const method = "addClientAdmin"
groupId := randomBytes(5)
cOwn := e.WithSigners(owner)
cOwn.InvokeFail(t, subnet.ErrInvalidSubnetID, method, []byte{0, 0, 0, 0}, groupId, admPub)
cOwn.InvokeFail(t, subnet.ErrInvalidAdmin, method, id, groupId, admPub[1:])
cOwn.InvokeFail(t, subnet.ErrNotExist, method, []byte{0, 0, 0, 0, 0}, groupId, admPub)
cOwn.Invoke(t, stackitem.Null{}, method, id, groupId, admPub)
cOwn.Invoke(t, stackitem.Null{}, method, id, groupId, admPub)
}
func TestSubnet_RemoveClientAdmin(t *testing.T) {
e := newSubnetInvoker(t)
id, owner := createSubnet(t, e)
adm := e.NewAccount(t)
admPub, ok := vm.ParseSignatureContract(adm.Script())
require.True(t, ok)
const method = "removeClientAdmin"
groupId := randomBytes(5)
cOwn := e.WithSigners(owner)
cOwn.InvokeFail(t, subnet.ErrInvalidSubnetID, method, []byte{0, 0, 0, 0}, groupId, admPub)
cOwn.InvokeFail(t, subnet.ErrInvalidAdmin, method, id, groupId, admPub[1:])
cOwn.InvokeFail(t, subnet.ErrNotExist, method, []byte{0, 0, 0, 0, 0}, groupId, admPub)
cOwn.Invoke(t, stackitem.Null{}, method, id, groupId, admPub)
cOwn.Invoke(t, stackitem.Null{}, "addClientAdmin", id, groupId, admPub)
cOwn.Invoke(t, stackitem.Null{}, method, id, groupId, admPub)
}
func TestSubnet_AddUser(t *testing.T) {
e := newSubnetInvoker(t)
id, owner := createSubnet(t, e)
adm := e.NewAccount(t)
admPub, ok := vm.ParseSignatureContract(adm.Script())
require.True(t, ok)
user := randomBytes(27)
groupId := randomBytes(5)
const method = "addUser"
cOwn := e.WithSigners(owner)
cOwn.InvokeFail(t, subnet.ErrInvalidSubnetID, method, []byte{0, 0, 0, 0}, groupId, user)
cOwn.InvokeFail(t, subnet.ErrNotExist, method, []byte{0, 0, 0, 0, 0}, groupId, user)
cOwn.Invoke(t, stackitem.Null{}, "addClientAdmin", id, groupId, admPub)
cAdm := e.WithSigners(adm)
cAdm.Invoke(t, stackitem.Null{}, method, id, groupId, user)
cOwn.Invoke(t, stackitem.Null{}, method, id, groupId, user)
}
func TestSubnet_RemoveUser(t *testing.T) {
e := newSubnetInvoker(t)
id, owner := createSubnet(t, e)
groupId := randomBytes(5)
user := randomBytes(27)
adm := e.NewAccount(t)
admPub, ok := vm.ParseSignatureContract(adm.Script())
require.True(t, ok)
const method = "removeUser"
cOwn := e.WithSigners(owner)
cOwn.InvokeFail(t, subnet.ErrInvalidSubnetID, method, []byte{0, 0, 0, 0}, groupId, user)
cOwn.InvokeFail(t, subnet.ErrNotExist, method, []byte{0, 0, 0, 0, 0}, groupId, user)
cOwn.Invoke(t, stackitem.Null{}, method, id, groupId, user)
cOwn.Invoke(t, stackitem.Null{}, "addUser", id, groupId, user)
cOwn.Invoke(t, stackitem.Null{}, method, id, groupId, user)
cAdm := cOwn.WithSigners(adm)
cOwn.Invoke(t, stackitem.Null{}, "addClientAdmin", id, groupId, admPub)
cAdm.Invoke(t, stackitem.Null{}, method, id, groupId, user)
}
func TestSubnet_UserAllowed(t *testing.T) {
e := newSubnetInvoker(t)
id, owner := createSubnet(t, e)
groupId := randomBytes(5)
user := randomBytes(27)
const method = "userAllowed"
cOwn := e.WithSigners(owner)
cOwn.InvokeFail(t, subnet.ErrNotExist, method, []byte{0, 0, 0, 0, 0}, user)
cOwn.Invoke(t, stackitem.NewBool(false), method, id, user)
cOwn.Invoke(t, stackitem.Null{}, "addUser", id, groupId, user)
cOwn.Invoke(t, stackitem.NewBool(true), method, id, user)
}
func createSubnet(t *testing.T, e *neotest.ContractInvoker) (id []byte, owner neotest.Signer) {
var (
ok bool
pub []byte
)
owner = e.NewAccount(t)
pub, ok = vm.ParseSignatureContract(owner.Script())
require.True(t, ok)
id = make([]byte, 5)
binary.LittleEndian.PutUint32(id, 123)
info := randomBytes(10)
cBoth := e.WithSigners(e.Committee, owner)
cBoth.Invoke(t, stackitem.Null{}, "put", id, pub, info)
return
}

View file

@ -3,10 +3,20 @@ package tests
import (
"testing"
"github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
func iteratorToArray(iter *storage.Iterator) []stackitem.Item {
stackItems := make([]stackitem.Item, 0)
for iter.Next() {
stackItems = append(stackItems, iter.Value())
}
return stackItems
}
func newExecutor(t *testing.T) *neotest.Executor {
bc, acc := chain.NewSingle(t)
return neotest.NewExecutor(t, bc, acc, acc)

29
tests/version_test.go Normal file
View file

@ -0,0 +1,29 @@
package tests
import (
"os"
"strconv"
"strings"
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/stretchr/testify/require"
)
func TestVersion(t *testing.T) {
data, err := os.ReadFile("../VERSION")
require.NoError(t, err)
v := strings.TrimPrefix(string(data), "v")
parts := strings.Split(strings.TrimSpace(v), ".")
require.Len(t, parts, 3)
var ver [3]int
for i := range parts {
ver[i], err = strconv.Atoi(parts[i])
require.NoError(t, err)
}
require.Equal(t, common.Version, ver[0]*1_000_000+ver[1]*1_000+ver[2],
"version from common package is different from the one in VERSION file")
}