Compare commits

..

1 commit

Author SHA1 Message Date
Alex Vanin
72571349a8 Enable notary and remove all ballots
Signed-off-by: Alex Vanin <alexey@nspcc.ru>
2021-12-14 12:10:17 +03:00
80 changed files with 2719 additions and 3150 deletions

1
.github/CODEOWNERS vendored
View file

@ -1 +0,0 @@
* @carpawell @fyrchik @cthulhu-rider

View file

@ -1,45 +0,0 @@
---
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`):

View file

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

View file

@ -1,20 +0,0 @@
---
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. -->

187
.github/logo.svg vendored
View file

@ -1,70 +1,129 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 25.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> <svg
<svg version="1.1" id="Слой_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" xmlns:dc="http://purl.org/dc/elements/1.1/"
viewBox="0 0 184.2 51.8" style="enable-background:new 0 0 184.2 51.8;" xml:space="preserve"> xmlns:cc="http://creativecommons.org/ns#"
<style type="text/css"> xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
.st0{display:none;} xmlns:svg="http://www.w3.org/2000/svg"
.st1{display:inline;} xmlns="http://www.w3.org/2000/svg"
.st2{fill:#01E397;} xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
.st3{display:inline;fill:#010032;} xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
.st4{display:inline;fill:#00E599;} sodipodi:docname="logo_fs.svg"
.st5{display:inline;fill:#00AF92;} inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
.st6{fill:#00C3E5;} id="svg57"
</style> version="1.1"
<g id="Layer_2"> viewBox="0 0 105 25"
<g id="Layer_1-2" class="st0"> height="25mm"
<g class="st1"> width="105mm">
<path class="st2" d="M146.6,18.3v7.2h10.9V29h-10.9v10.7h-4V14.8h18v3.5H146.6z"/> <defs
<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 id="defs51">
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 <clipPath
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 clipPathUnits="userSpaceOnUse"
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 id="clipPath434">
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 <path
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"/> 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>
<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 <g
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 transform="matrix(0.35277777,0,0,-0.35277777,-315.43002,107.34005)"
c1.5-0.8,3.2-1.2,4.9-1.1C68.9,13.8,71.3,14.7,73.3,16.3z"/> id="g428">
<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 <g
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 id="g430"
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 clip-path="url(#clipPath434)">
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"/> <g
<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 id="g436"
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 transform="translate(1112.874,278.2981)">
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 <path
C119.9,17.2,117.7,18.2,116.2,19.9z"/> 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"
<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 "/> style="fill:#00e396;fill-opacity:1;fill-rule:nonzero;stroke:none"
<polygon class="st5" points="24.3,17.9 24.3,36.8 46.8,44.9 46.8,9.6 "/> id="path438" />
</g> </g>
<g> <g
<g> id="g440"
<path class="st6" d="M41.6,17.5H28.2v6.9h10.4v3.3H28.2v10.2h-3.9V14.2h17.2V17.5z"/> transform="translate(993.0239,277.5454)">
<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 <path
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 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"
c-0.7,0.4-1.3,1-1.8,1.8s-0.7,1.8-0.7,3v9.5H45.8z"/> style="fill:#000033;fill-opacity:1;fill-rule:nonzero;stroke:none"
<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 id="path442" />
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
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>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View file

@ -1,21 +0,0 @@
name: DCO check
on:
pull_request:
branches:
- master
jobs:
commits_check_job:
runs-on: ubuntu-latest
name: Commits Check
steps:
- name: Get PR Commits
id: 'get-pr-commits'
uses: tim-actions/get-pr-commits@master
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: DCO Check
uses: tim-actions/dco@master
with:
commits: ${{ steps.get-pr-commits.outputs.commits }}

4
.gitignore vendored
View file

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

View file

@ -1,128 +1,5 @@
# Changelog # Changelog
Changelog for FrostFS Contract Changelog for NeoFS Contract
## [Unreleased]
### Added
### Changed
### Removed
### 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
## [0.16.0] - 2022-10-17 - Anmado (안마도, 鞍馬島)
### Added
- Support `MAINTENANCE` state of storage nodes (#269)
### Changed
- `netmap.Snapshot` and all similar methods return (#269)
### Updated
- NNS contract now sets domain expiration based on `register` arguments (#262)
### Fixed
- NNS `renew` now can only be done by the domain owner
### Updating from v0.15.5
Update deployed `Netmap` contract using `Update` method: storage of the contract
has been incompatibly changed.
## [0.15.5] - 2022-08-23
### Updated
- Update neo-go to v0.99.2 (#261)
- Makefile now takes only `v*` tags into account (#255)
## [0.15.4] - 2022-07-27
Only a version bump to update manifest.
## [0.15.3] - 2022-07-22
### Added
- Allow to build archive from source (#250)
### Changed
- Update neo-go to the latest version
- Use proper type for integer constants (#248)
## [0.15.2] - 2022-06-07
### Added
- `container.Count` method (#242)
### Changed
- Update neo-go to v0.99.0 (#246)
## [0.15.1] - 2022-04-13
### Fixed
- Max domain name fragement length (#238)
### Added
- `netmap.UpdateSnapshotCount` method (#232)
- Notifications of successful container and storage node operations (#236)
### Changed
- Update neo-go to v0.98.2 (#234)
## [0.15.0] - 2022-03-23 - Heuksando (흑산도, 黑山島)
### Fixed
- Split `UpdateState` method to allow Alphabet nodes remove storage nodes from
network map based on consensus decision in notary-enabled environment (#225)
### Changed
- Increase from 2 to 10 stored network maps in netmap contract (#224)
- Use public keys instead of `IRNode` structures in neofs and netmap contracts
(#222)
## [0.14.2] - 2022-02-07
### Fixed
- Remove duplicate records in NNS contract (#196)
### Changed
- Evict container estimations on every put (#215)
- Update neo-go to v0.98.1
## [0.14.1] - 2022-01-24
### Fixed
- Remove migration routine for reputation contract update (#220)
- Remove version check for subnet contract update (#220)
### Added
- Append version to `Update` arguments for subnet contract (#220)
## [0.14.0] - 2022-01-14 - Geojedo (거제도, 巨濟島)
### Fixed
- Sync `Update` method signature in NNS contract (#197)
- Use current block index in all `GetDisgnatedByRole` invocations (#209)
### Added
- Version check during contract update (#204)
### Changed
- Use `storage.RemovePrefix` in subnet contract (#199)
### Removed
- Netmap contract hash usage in proxy contract (#205)
- Legacy contract owner records from contract storage (#202)
## [0.13.2] - 2021-12-14 ## [0.13.2] - 2021-12-14
@ -417,17 +294,6 @@ Preview4-testnet version of NeoFS contracts.
Preview4 compatible contracts. Preview4 compatible contracts.
[Unreleased]: https://github.com/nspcc-dev/neofs-contract/compare/v0.16.0...master
[0.16.0]: https://github.com/nspcc-dev/neofs-contract/compare/v0.15.5...v0.16.0
[0.15.5]: https://github.com/nspcc-dev/neofs-contract/compare/v0.15.4...v0.15.5
[0.15.4]: https://github.com/nspcc-dev/neofs-contract/compare/v0.15.3...v0.15.4
[0.15.3]: https://github.com/nspcc-dev/neofs-contract/compare/v0.15.2...v0.15.3
[0.15.2]: https://github.com/nspcc-dev/neofs-contract/compare/v0.15.1...v0.15.2
[0.15.1]: https://github.com/nspcc-dev/neofs-contract/compare/v0.15.0...v0.15.1
[0.15.0]: https://github.com/nspcc-dev/neofs-contract/compare/v0.14.2...v0.15.0
[0.14.2]: https://github.com/nspcc-dev/neofs-contract/compare/v0.14.1...v0.14.2
[0.14.1]: https://github.com/nspcc-dev/neofs-contract/compare/v0.14.0...v0.14.1
[0.14.0]: https://github.com/nspcc-dev/neofs-contract/compare/v0.13.2...v0.14.0
[0.13.2]: https://github.com/nspcc-dev/neofs-contract/compare/v0.13.1...v0.13.2 [0.13.2]: https://github.com/nspcc-dev/neofs-contract/compare/v0.13.1...v0.13.2
[0.13.1]: https://github.com/nspcc-dev/neofs-contract/compare/v0.13.0...v0.13.1 [0.13.1]: https://github.com/nspcc-dev/neofs-contract/compare/v0.13.0...v0.13.1
[0.13.0]: https://github.com/nspcc-dev/neofs-contract/compare/v0.12.2...v0.13.0 [0.13.0]: https://github.com/nspcc-dev/neofs-contract/compare/v0.12.2...v0.13.0

View file

@ -1,29 +1,19 @@
#!/usr/bin/make -f #!/usr/bin/make -f
SHELL=bash SHELL=bash
# GOBIN is used only to install neo-go and allows to override GOBIN ?= $(shell go env GOPATH)/bin
# the location of written binary.
export GOBIN ?= $(shell pwd)/bin
NEOGO ?= $(GOBIN)/cli NEOGO ?= $(GOBIN)/cli
VERSION ?= $(shell git describe --tags --dirty --match "v*" --always --abbrev=8 2>/dev/null || cat VERSION 2>/dev/null || echo "develop") VERSION?=$(shell git describe --tags)
# .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: all build clean test neo-go
.PHONY: alphabet mainnet morph nns sidechain .PHONY: alphabet mainnet morph nns sidechain
.PHONY: debpackage debclean
build: neo-go all build: neo-go all
all: sidechain mainnet all: sidechain mainnet
sidechain: alphabet morph nns sidechain: alphabet morph nns
alphabet_sc = alphabet alphabet_sc = alphabet
morph_sc = audit balance container frostfsid netmap proxy reputation subnet morph_sc = audit balance container neofsid netmap proxy reputation subnet
mainnet_sc = frostfs processing mainnet_sc = neofs processing
nns_sc = nns nns_sc = nns
define sc_template define sc_template
@ -55,7 +45,6 @@ test:
clean: clean:
find . -name '*.nef' -exec rm -rf {} \; find . -name '*.nef' -exec rm -rf {} \;
find . -name 'config.json' -exec rm -rf {} \; find . -name 'config.json' -exec rm -rf {} \;
rm -rf ./bin/
mr_proper: clean mr_proper: clean
for sc in $(alphabet_sc); do\ for sc in $(alphabet_sc); do\
@ -63,18 +52,6 @@ mr_proper: clean
done done
archive: build archive: build
@tar --transform "s|^./|frostfs-contract-$(VERSION)/|" \ @tar --transform "s|^./|neofs-contract-$(VERSION)/|" \
-czf frostfs-contract-$(VERSION).tar.gz \ -czf neofs-contract-$(VERSION).tar.gz \
$(shell find . -name '*.nef' -o -name 'config.json') $(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"> <p align="center">
<img src="./.github/logo.svg" width="500px" alt="FrostFS"> <img src="./.github/logo.svg" width="500px" alt="NeoFS">
</p> </p>
<p align="center"> <p align="center">
<a href="https://frostfs.info">FrostFS</a> related smart contracts. <a href="https://fs.neo.org">NeoFS</a> related smart contracts.
</p> </p>
--- ---
# Overview # Overview
FrostFS-Contract contains all FrostFS related contracts written for NeoFS-Contract contains all NeoFS related contracts written for
[neo-go](https://github.com/nspcc-dev/neo-go) compiler. These contracts [neo-go](https://github.com/nspcc-dev/neo-go) compiler. These contracts
are deployed both in the mainchain and the sidechain. are deployed both in main chain and side chain.
Main chain contracts: Main chain contracts:
- frostfs - neofs
- processing - processing
Side chain contracts: Side chain contracts:
@ -24,7 +24,7 @@ Sidechain contracts:
- audit - audit
- balance - balance
- container - container
- frostfsid - neofsid
- netmap - netmap
- nns - nns
- proxy - proxy
@ -37,11 +37,11 @@ Sidechain contracts:
To compile smart contracts you need: To compile smart contracts you need:
- [neo-go](https://github.com/nspcc-dev/neo-go) >= 0.99.2 - [neo-go](https://github.com/nspcc-dev/neo-go) >= 0.98.0
## Compilation ## Compilation
To build and compile smart contract, run `make all` command. Compiled contracts To build and compile smart contract run `make all` command. Compiled contracts
`*_contract.nef` and manifest `config.json` files are placed in the `*_contract.nef` and manifest `config.json` files are placed in the
corresponding directories. corresponding directories.
@ -51,13 +51,13 @@ $ 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 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 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 container -c container/config.yml -m container/config.json -o container/container_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 neofsid -c neofsid/config.yml -m neofsid/config.json -o neofsid/neofsid_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 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 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 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 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 nns -c nns/config.yml -m nns/config.json -o nns/nns_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 neofs -c neofs/config.yml -m neofs/config.json -o neofs/neofs_contract.nef
/home/user/go/bin/cli contract compile -i processing -c processing/config.yml -m processing/config.json -o processing/processing_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,24 +69,25 @@ $ NEOGO=/home/user/neo-go/bin/neo-go make all
Remove compiled files with `make clean` or `make mr_proper` command. 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 # Testing
Smartcontract tests reside in `tests/` directory. To execute test suite Smartcontract tests reside in `tests/` directory. To execute test suite
after applying changes, simply run `make test`. after applying changes simply run `make test`.
``` ```
$ make test $ make test
ok git.frostfs.info/TrueCloudLab/frostfs-contract/tests 0.462s ok github.com/nspcc-dev/neofs-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)|
# License # License
This project is licensed under the GPLv3 License - see the This project is licensed under the GPLv3 License - see the

View file

@ -1 +0,0 @@
v0.18.0

View file

@ -1,14 +1,15 @@
package alphabet package alphabet
import ( import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop" "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/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/gas"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management" "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/native/neo"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime" "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/storage"
"github.com/nspcc-dev/neofs-contract/common"
) )
const ( const (
@ -32,18 +33,18 @@ func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) {
} }
func _deploy(data interface{}, isUpdate bool) { func _deploy(data interface{}, isUpdate bool) {
ctx := storage.GetContext()
common.RmAndCheckNotaryDisabledKey(data, notaryDisabledKey)
if isUpdate { if isUpdate {
args := data.([]interface{}) ctx := storage.GetContext()
common.CheckVersion(args[len(args)-1].(int)) storage.Delete(ctx, "ballots")
storage.Put(ctx, notaryDisabledKey, false)
proxyContract := data.([]interface{})[0] // better to hardcode it
storage.Put(ctx, proxyKey, proxyContract)
return return
} }
args := data.(struct { args := data.(struct {
//TODO(@acid-ant): #9 remove notaryDisabled in future version
notaryDisabled bool notaryDisabled bool
addrNetmap interop.Hash160 addrNetmap interop.Hash160
addrProxy interop.Hash160 addrProxy interop.Hash160
@ -52,7 +53,9 @@ func _deploy(data interface{}, isUpdate bool) {
total int total int
}) })
if len(args.addrNetmap) != interop.Hash160Len || len(args.addrProxy) != interop.Hash160Len { ctx := storage.GetContext()
if len(args.addrNetmap) != interop.Hash160Len || !args.notaryDisabled && len(args.addrProxy) != interop.Hash160Len {
panic("incorrect length of contract script hash") panic("incorrect length of contract script hash")
} }
@ -62,10 +65,17 @@ func _deploy(data interface{}, isUpdate bool) {
storage.Put(ctx, indexKey, args.index) storage.Put(ctx, indexKey, args.index)
storage.Put(ctx, totalKey, args.total) 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") runtime.Log(args.name + " contract initialized")
} }
// Update method updates contract source code and manifest. It can be invoked // Update method updates contract source code and manifest. Can be invoked
// only by committee. // only by committee.
func Update(script []byte, manifest []byte, data interface{}) { func Update(script []byte, manifest []byte, data interface{}) {
if !common.HasUpdateAccess() { if !common.HasUpdateAccess() {
@ -77,12 +87,12 @@ func Update(script []byte, manifest []byte, data interface{}) {
runtime.Log("alphabet contract updated") runtime.Log("alphabet contract updated")
} }
// GAS returns the amount of the sidechain GAS stored in the contract account. // GAS returns amount of side chain GAS stored in contract account.
func Gas() int { func Gas() int {
return gas.BalanceOf(runtime.GetExecutingScriptHash()) return gas.BalanceOf(runtime.GetExecutingScriptHash())
} }
// NEO returns the amount of sidechain NEO stored in the contract account. // NEO returns amount of side chain NEO stored in contract account.
func Neo() int { func Neo() int {
return neo.BalanceOf(runtime.GetExecutingScriptHash()) return neo.BalanceOf(runtime.GetExecutingScriptHash())
} }
@ -100,7 +110,7 @@ func index(ctx storage.Context) int {
return storage.Get(ctx, indexKey).(int) return storage.Get(ctx, indexKey).(int)
} }
func checkPermission(ir []interop.PublicKey) bool { func checkPermission(ir []common.IRNode) bool {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
index := index(ctx) // read from contract memory index := index(ctx) // read from contract memory
@ -109,18 +119,22 @@ func checkPermission(ir []interop.PublicKey) bool {
} }
node := ir[index] node := ir[index]
return runtime.CheckWitness(node) return runtime.CheckWitness(node.PublicKey)
} }
// Emit method produces side chain GAS and distributes it among Inner Ring nodes // Emit method produces side chain GAS and distributes it among Inner Ring nodes
// and proxy contract. It can be invoked only by an Alphabet node of the Inner Ring. // and proxy contract. Can be invoked only by Alphabet node of the Inner Ring.
// //
// To produce GAS, an alphabet contract transfers all available NEO from the contract // To produce GAS, alphabet contract transfers all available NEO from contract
// account to itself. 50% of the GAS in the contract account // account to itself. If notary enabled, then 50% of the GAS in the contract account
// are transferred to proxy contract. 43.75% of the GAS are equally distributed // 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. // among all Inner Ring nodes. Remaining 6.25% of the GAS stays in the contract.
//
// If notary disabled, then 87.5% of the GAS are equally distributed among all
// Inner Ring nodes. Remaining 12.5% of the GAS stays in the contract.
func Emit() { func Emit() {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
alphabet := common.AlphabetNodes() alphabet := common.AlphabetNodes()
if !checkPermission(alphabet) { if !checkPermission(alphabet) {
@ -135,6 +149,7 @@ func Emit() {
gasBalance := gas.BalanceOf(contractHash) gasBalance := gas.BalanceOf(contractHash)
if !notaryDisabled {
proxyAddr := storage.Get(ctx, proxyKey).(interop.Hash160) proxyAddr := storage.Get(ctx, proxyKey).(interop.Hash160)
proxyGas := gasBalance / 2 proxyGas := gasBalance / 2
@ -149,14 +164,22 @@ func Emit() {
gasBalance -= proxyGas gasBalance -= proxyGas
runtime.Log("utility token has been emitted to proxy contract") runtime.Log("utility token has been emitted to proxy contract")
}
innerRing := common.InnerRingNodes() var innerRing []common.IRNode
if notaryDisabled {
netmapContract := storage.Get(ctx, netmapKey).(interop.Hash160)
innerRing = common.InnerRingNodesFromNetmap(netmapContract)
} else {
innerRing = common.InnerRingNodes()
}
gasPerNode := gasBalance * 7 / 8 / len(innerRing) gasPerNode := gasBalance * 7 / 8 / len(innerRing)
if gasPerNode != 0 { if gasPerNode != 0 {
for _, node := range innerRing { for _, node := range innerRing {
address := contract.CreateStandardAccount(node) address := contract.CreateStandardAccount(node.PublicKey)
if !gas.Transfer(contractHash, address, gasPerNode, nil) { if !gas.Transfer(contractHash, address, gasPerNode, nil) {
runtime.Log("could not transfer GAS to one of IR node") runtime.Log("could not transfer GAS to one of IR node")
} }
@ -166,19 +189,34 @@ func Emit() {
} }
} }
// Vote method votes for the sidechain committee. It requires multisignature from // Vote method votes for side chain committee. Requires multisignature from
// Alphabet nodes of the Inner Ring. // Alphabet nodes of the Inner Ring.
// //
// This method is used when governance changes the list of Alphabet nodes of the // This method is used when governance changes list of Alphabet nodes of the
// Inner Ring. Alphabet nodes share keys with side chain validators, therefore // Inner Ring. Alphabet nodes share keys with side chain validators, therefore
// it is required to change them as well. To do that, NEO holders (which are // it is required to change them as well. To do that NEO holders, which are
// alphabet contracts) should vote for a new committee. // alphabet contracts, should vote for new committee.
func Vote(epoch int, candidates []interop.PublicKey) { func Vote(epoch int, candidates []interop.PublicKey) {
ctx := storage.GetContext() ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
index := index(ctx) index := index(ctx)
name := name(ctx) name := name(ctx)
common.CheckAlphabetWitness() var ( // for invocation collection without notary
alphabet []common.IRNode
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)
}
curEpoch := currentEpoch(ctx) curEpoch := currentEpoch(ctx)
if epoch != curEpoch { if epoch != curEpoch {
@ -188,21 +226,50 @@ func Vote(epoch int, candidates []interop.PublicKey) {
candidate := candidates[index%len(candidates)] candidate := candidates[index%len(candidates)]
address := runtime.GetExecutingScriptHash() 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) ok := neo.Vote(address, candidate)
if ok { if ok {
runtime.Log(name + ": successfully voted for validator") runtime.Log(name + ": successfully voted for validator")
} else { } else {
runtime.Log(name + ": vote has been failed") runtime.Log(name + ": vote has been failed")
} }
return
} }
// Name returns the Glagolitic name of the contract. 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 Glagolitic name of the contract.
func Name() string { func Name() string {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
return name(ctx) return name(ctx)
} }
// Version returns the version of the contract. // Version returns version of the contract.
func Version() int { func Version() int {
return common.Version return common.Version
} }

View file

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

View file

@ -1,20 +1,20 @@
/* /*
Alphabet contract is a contract deployed in FrostFS sidechain. Alphabet contract is a contract deployed in NeoFS side chain.
Alphabet contract is designed to support GAS production and vote for new Alphabet contract is designed to support GAS producing and voting for new
validators in the side chain. NEO token is required to produce GAS and vote for validators in the side chain. NEO token is required to produce GAS and vote for
a new committee. It can be distributed among alphabet nodes of the Inner Ring. a new committee. If can be distributed among alphabet nodes of Inner Ring.
However, some of them may be malicious, and some NEO can be lost. It will destabilize However, some of them may be malicious and some NEO can be lost. It will lead
the economic of the sidechain. To avoid it, all 100,000,000 NEO are to side chain economic destabilization. To avoid it, all 100 000 000 NEO are
distributed among all alphabet contracts. distributed among all alphabet contracts.
To identify alphabet contracts, they are named with letters of the Glagolitic alphabet. To identify alphabet contracts, they are named with letters of the Glagolitic.
Names are set at contract deploy. Alphabet nodes of the Inner Ring communicate with Names are set at contract deploy. Alphabet nodes of Inner Ring communicate with
one of the alphabetical contracts to emit GAS. To vote for a new list of side 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 chain committee, alphabet nodes of Inner Ring create multisignature transactions
for each alphabet contract. for each alphabet contract.
# Contract notifications Contract notifications
Alphabet contract does not produce notifications to process. Alphabet contract does not produce notifications to process.
*/ */

View file

@ -1,7 +1,6 @@
package audit package audit
import ( import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop" "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/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator" "github.com/nspcc-dev/neo-go/pkg/interop/iterator"
@ -9,6 +8,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/interop/native/management" "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/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage" "github.com/nspcc-dev/neo-go/pkg/interop/storage"
"github.com/nspcc-dev/neofs-contract/common"
) )
type ( type (
@ -19,8 +19,8 @@ type (
} }
) )
// Audit key is a combination of the epoch, the container ID and the public key of the node that // Audit key is a combination of epoch, container ID and public key of node that
// has executed the audit. Together, it shouldn't be more than 64 bytes. We can't shrink // executed audit. Together it should be no more than 64 bytes. We can't shrink
// epoch and container ID since we iterate over these values. But we can shrink // epoch and container ID since we iterate over these values. But we can shrink
// public key by using first bytes of the hashed value. // public key by using first bytes of the hashed value.
@ -43,32 +43,37 @@ const (
) )
func _deploy(data interface{}, isUpdate bool) { func _deploy(data interface{}, isUpdate bool) {
ctx := storage.GetContext()
common.RmAndCheckNotaryDisabledKey(data, notaryDisabledKey)
if isUpdate { if isUpdate {
args := data.([]interface{}) ctx := storage.GetContext()
common.CheckVersion(args[len(args)-1].(int)) storage.Delete(ctx, "ballots")
storage.Put(ctx, notaryDisabledKey, false)
return return
} }
args := data.(struct { args := data.(struct {
//TODO(@acid-ant): #9 remove notaryDisabled in future version
notaryDisabled bool notaryDisabled bool
addrNetmap interop.Hash160 addrNetmap interop.Hash160
}) })
ctx := storage.GetContext()
if len(args.addrNetmap) != interop.Hash160Len { if len(args.addrNetmap) != interop.Hash160Len {
panic("incorrect length of contract script hash") panic("incorrect length of contract script hash")
} }
storage.Put(ctx, netmapContractKey, args.addrNetmap) 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") runtime.Log("audit contract initialized")
} }
// Update method updates contract source code and manifest. It can be invoked // Update method updates contract source code and manifest. Can be invoked
// only by committee. // only by committee.
func Update(script []byte, manifest []byte, data interface{}) { func Update(script []byte, manifest []byte, data interface{}) {
if !common.HasUpdateAccess() { if !common.HasUpdateAccess() {
@ -80,23 +85,31 @@ func Update(script []byte, manifest []byte, data interface{}) {
runtime.Log("audit contract updated") runtime.Log("audit contract updated")
} }
// Put method stores a stable marshalled `DataAuditResult` structure. It can be // Put method stores stable marshalled `DataAuditResult` structure. Can be
// invoked only by Inner Ring nodes. // invoked only by Inner Ring nodes.
// //
// Inner Ring nodes perform audit of containers and produce `DataAuditResult` // Inner Ring nodes perform audit of the containers and produce `DataAuditResult`
// structures. They are stored in audit contract and used for settlements // structures. They are being stored in audit contract and used for settlements
// in later epochs. // in later epochs.
func Put(rawAuditResult []byte) { func Put(rawAuditResult []byte) {
ctx := storage.GetContext() ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
innerRing := common.InnerRingNodes() var innerRing []common.IRNode
if notaryDisabled {
netmapContract := storage.Get(ctx, netmapContractKey).(interop.Hash160)
innerRing = common.InnerRingNodesFromNetmap(netmapContract)
} else {
innerRing = common.InnerRingNodes()
}
hdr := newAuditHeader(rawAuditResult) hdr := newAuditHeader(rawAuditResult)
presented := false presented := false
for i := range innerRing { for i := range innerRing {
ir := innerRing[i] ir := innerRing[i]
if common.BytesEqual(ir, hdr.from) { if common.BytesEqual(ir.PublicKey, hdr.from) {
presented = true presented = true
break break
@ -112,16 +125,16 @@ func Put(rawAuditResult []byte) {
runtime.Log("audit: result has been saved") runtime.Log("audit: result has been saved")
} }
// Get method returns a stable marshaled DataAuditResult structure. // Get method returns stable marshaled DataAuditResult structure.
// //
// The ID of the DataAuditResult can be obtained from listing methods. // ID of the DataAuditResult can be obtained from listing methods.
func Get(id []byte) []byte { func Get(id []byte) []byte {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
return storage.Get(ctx, id).([]byte) return storage.Get(ctx, id).([]byte)
} }
// List method returns a list of all available DataAuditResult IDs from // List method returns list of all available DataAuditResult IDs from
// the contract storage. // contract storage.
func List() [][]byte { func List() [][]byte {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
it := storage.Find(ctx, []byte{}, storage.KeysOnly) it := storage.Find(ctx, []byte{}, storage.KeysOnly)
@ -129,8 +142,8 @@ func List() [][]byte {
return list(it) return list(it)
} }
// ListByEpoch method returns a list of DataAuditResult IDs generated during // ListByEpoch method returns list of DataAuditResult IDs generated in
// the specified epoch. // specified epoch.
func ListByEpoch(epoch int) [][]byte { func ListByEpoch(epoch int) [][]byte {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
var buf interface{} = epoch var buf interface{} = epoch
@ -139,8 +152,8 @@ func ListByEpoch(epoch int) [][]byte {
return list(it) return list(it)
} }
// ListByCID method returns a list of DataAuditResult IDs generated during // ListByCID method returns list of DataAuditResult IDs generated in
// the specified epoch for the specified container. // specified epoch for specified container.
func ListByCID(epoch int, cid []byte) [][]byte { func ListByCID(epoch int, cid []byte) [][]byte {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
@ -152,8 +165,8 @@ func ListByCID(epoch int, cid []byte) [][]byte {
return list(it) return list(it)
} }
// ListByNode method returns a list of DataAuditResult IDs generated in // ListByNode method returns list of DataAuditResult IDs generated in
// the specified epoch for the specified container by the specified Inner Ring node. // specified epoch for specified container by specified Inner Ring node.
func ListByNode(epoch int, cid []byte, key interop.PublicKey) [][]byte { func ListByNode(epoch int, cid []byte, key interop.PublicKey) [][]byte {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
hdr := auditHeader{ hdr := auditHeader{
@ -172,6 +185,7 @@ func list(it iterator.Iterator) [][]byte {
ignore := [][]byte{ ignore := [][]byte{
[]byte(netmapContractKey), []byte(netmapContractKey),
[]byte(notaryDisabledKey),
} }
loop: loop:
@ -189,12 +203,12 @@ loop:
return result return result
} }
// Version returns the version of the contract. // Version returns version of the contract.
func Version() int { func Version() int {
return common.Version return common.Version
} }
// readNext reads the length from the first byte, and then reads data (max 127 bytes). // readNext reads length from first byte and then reads data (max 127 bytes).
func readNext(input []byte) ([]byte, int) { func readNext(input []byte) ([]byte, int) {
var buf interface{} = input[0] var buf interface{} = input[0]
ln := buf.(int) ln := buf.(int)

View file

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

View file

@ -1,22 +1,22 @@
/* /*
Audit contract is a contract deployed in FrostFS sidechain. Audit contract is a contract deployed in NeoFS side chain.
Inner Ring nodes perform audit of the registered containers during every epoch. Inner Ring nodes perform an audit of the registered containers in every epoch.
If a container contains StorageGroup objects, an Inner Ring node initializes If container contains StorageGroup objects, then the Inner Ring node initializes
a series of audit checks. Based on the results of these checks, the Inner Ring a series of audit checks. Based on the results of these checks, the Inner Ring
node creates a DataAuditResult structure for the container. The content of this node creates a DataAuditResult structure for the container. The content of this
structure makes it possible to determine which storage nodes have been examined and structure makes it possible to determine which storage nodes were examined and
see the status of these checks. Regarding this information, the container owner is the status of these checks. Based on this information, container owner is
charged for data storage. charged for data storage.
Audit contract is used as a reliable and verifiable storage for all Audit contract is used as reliable and verifiable storage for all
DataAuditResult structures. At the end of data audit routine, Inner Ring DataAuditResult structures. At the end of the data audit routine, the Inner Ring
nodes send a stable marshaled version of the DataAuditResult structure to the nodes send a stable marshaled version of the DataAuditResult structure to the
contract. When Alphabet nodes of the Inner Ring perform settlement operations, contract. When Alphabet nodes of the Inner Ring perform settlement operations,
they make a list and get these AuditResultStructures from the audit contract. they list and get these AuditResultStructures from the audit contract.
# Contract notifications Contract notifications
Audit contract does not produce notifications to process. Alphabet contract does not produce notifications to process.
*/ */
package audit package audit

View file

@ -1,7 +1,6 @@
package balance package balance
import ( import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop" "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/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator" "github.com/nspcc-dev/neo-go/pkg/interop/iterator"
@ -9,6 +8,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/interop/native/std" "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/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage" "github.com/nspcc-dev/neo-go/pkg/interop/storage"
"github.com/nspcc-dev/neofs-contract/common"
) )
type ( type (
@ -22,7 +22,7 @@ type (
CirculationKey string CirculationKey string
} }
// Account structure stores metadata of each FrostFS balance account. // Account structure stores metadata of each NeoFS balance account.
Account struct { Account struct {
// Active balance // Active balance
Balance int Balance int
@ -35,7 +35,7 @@ type (
) )
const ( const (
symbol = "FROSTFS" symbol = "NEOFS"
decimals = 12 decimals = 12
circulation = "MainnetGAS" circulation = "MainnetGAS"
@ -59,23 +59,22 @@ func init() {
} }
func _deploy(data interface{}, isUpdate bool) { func _deploy(data interface{}, isUpdate bool) {
ctx := storage.GetContext()
common.RmAndCheckNotaryDisabledKey(data, notaryDisabledKey)
if isUpdate { if isUpdate {
args := data.([]interface{}) ctx := storage.GetContext()
common.CheckVersion(args[len(args)-1].(int)) storage.Delete(ctx, "ballots")
storage.Put(ctx, notaryDisabledKey, false)
return return
} }
args := data.(struct { args := data.(struct {
//TODO(@acid-ant): #9 remove notaryDisabled in future version
notaryDisabled bool notaryDisabled bool
addrNetmap interop.Hash160 addrNetmap interop.Hash160
addrContainer interop.Hash160 addrContainer interop.Hash160
}) })
ctx := storage.GetContext()
if len(args.addrNetmap) != interop.Hash160Len || len(args.addrContainer) != interop.Hash160Len { if len(args.addrNetmap) != interop.Hash160Len || len(args.addrContainer) != interop.Hash160Len {
panic("incorrect length of contract script hash") panic("incorrect length of contract script hash")
} }
@ -83,10 +82,17 @@ func _deploy(data interface{}, isUpdate bool) {
storage.Put(ctx, netmapContractKey, args.addrNetmap) storage.Put(ctx, netmapContractKey, args.addrNetmap)
storage.Put(ctx, containerContractKey, args.addrContainer) 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") runtime.Log("balance contract initialized")
} }
// Update method updates contract source code and manifest. It can be invoked // Update method updates contract source code and manifest. Can be invoked
// only by committee. // only by committee.
func Update(script []byte, manifest []byte, data interface{}) { func Update(script []byte, manifest []byte, data interface{}) {
if !common.HasUpdateAccess() { if !common.HasUpdateAccess() {
@ -98,53 +104,87 @@ func Update(script []byte, manifest []byte, data interface{}) {
runtime.Log("balance contract updated") runtime.Log("balance contract updated")
} }
// Symbol is a NEP-17 standard method that returns FROSTFS token symbol. // Symbol is a NEP-17 standard method that returns NEOFS token symbol.
func Symbol() string { func Symbol() string {
return token.Symbol return token.Symbol
} }
// Decimals is a NEP-17 standard method that returns precision of FrostFS // Decimals is a NEP-17 standard method that returns precision of NeoFS
// balances. // balances.
func Decimals() int { func Decimals() int {
return token.Decimals return token.Decimals
} }
// TotalSupply is a NEP-17 standard method that returns total amount of main // TotalSupply is a NEP-17 standard method that returns total amount of main
// chain GAS in FrostFS network. // chain GAS in the NeoFS network.
func TotalSupply() int { func TotalSupply() int {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
return token.getSupply(ctx) return token.getSupply(ctx)
} }
// BalanceOf is a NEP-17 standard method that returns FrostFS balance of the specified // BalanceOf is a NEP-17 standard method that returns NeoFS balance of specified
// account. // account.
func BalanceOf(account interop.Hash160) int { func BalanceOf(account interop.Hash160) int {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
return token.balanceOf(ctx, account) return token.balanceOf(ctx, account)
} }
// Transfer is a NEP-17 standard method that transfers FrostFS balance from one // Transfer is a NEP-17 standard method that transfers NeoFS balance from one
// account to another. It can be invoked only by the account owner. // account to other. Can be invoked only by account owner.
// //
// It produces Transfer and TransferX notifications. TransferX notification // Produces Transfer and TransferX notifications. TransferX notification
// will have empty details field. // will have empty details field.
func Transfer(from, to interop.Hash160, amount int, data interface{}) bool { func Transfer(from, to interop.Hash160, amount int, data interface{}) bool {
ctx := storage.GetContext() ctx := storage.GetContext()
return token.transfer(ctx, from, to, amount, false, nil) return token.transfer(ctx, from, to, amount, false, nil)
} }
// TransferX is a method for FrostFS balance to be transferred from one account to // TransferX is a method for NeoFS balance transfers from one account to
// another. It can be invoked by the account owner or by Alphabet nodes. // another. Can be invoked by account owner or by Alphabet nodes.
// //
// It produces Transfer and TransferX notifications. // Produces Transfer and TransferX notifications.
// //
// TransferX method expands Transfer method by having extra details argument. // TransferX method expands Transfer method by having extra details argument.
// TransferX method also allows to transfer assets by Alphabet nodes of the // Also TransferX method allows to transfer assets by Alphabet nodes of the
// Inner Ring with multi signature. // Inner Ring with multi signature.
func TransferX(from, to interop.Hash160, amount int, details []byte) { func TransferX(from, to interop.Hash160, amount int, details []byte) {
ctx := storage.GetContext() ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
common.CheckAlphabetWitness() var ( // for invocation collection without notary
alphabet []common.IRNode
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)
}
result := token.transfer(ctx, from, to, amount, true, details) result := token.transfer(ctx, from, to, amount, true, details)
if !result { if !result {
@ -154,18 +194,33 @@ func TransferX(from, to interop.Hash160, amount int, details []byte) {
runtime.Log("successfully transferred assets") runtime.Log("successfully transferred assets")
} }
// Lock is a method that transfers assets from a user account to the lock account // Lock is a method that transfers assets from user account to lock account
// related to the user. It can be invoked only by Alphabet nodes of the Inner Ring. // related to the user. Can be invoked only by Alphabet nodes of the Inner Ring.
// //
// It produces Lock, Transfer and TransferX notifications. // Produces Lock, Transfer and TransferX notifications.
// //
// Lock method is invoked by Alphabet nodes of the Inner Ring when they process // Lock method invoked by Alphabet nodes of the Inner Ring when they process
// Withdraw notification from FrostFS contract. This should transfer assets // Withdraw notification from NeoFS contract. This should transfer assets
// to a new lock account that won't be used for anything beside Unlock and Burn. // to new lock account that won't be used for anything besides Unlock and Burn.
func Lock(txDetails []byte, from, to interop.Hash160, amount, until int) { func Lock(txDetails []byte, from, to interop.Hash160, amount, until int) {
ctx := storage.GetContext() ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
common.CheckAlphabetWitness() var ( // for invocation collection without notary
alphabet []common.IRNode
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)
}
details := common.LockTransferDetails(txDetails) details := common.LockTransferDetails(txDetails)
@ -175,6 +230,18 @@ func Lock(txDetails []byte, from, to interop.Hash160, amount, until int) {
Parent: from, 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) common.SetSerialized(ctx, to, lockAccount)
result := token.transfer(ctx, from, to, amount, true, details) result := token.transfer(ctx, from, to, amount, true, details)
@ -187,15 +254,28 @@ func Lock(txDetails []byte, from, to interop.Hash160, amount, until int) {
runtime.Notify("Lock", txDetails, from, to, amount, until) runtime.Notify("Lock", txDetails, from, to, amount, until)
} }
// NewEpoch is a method that checks timeout on lock accounts and returns assets // NewEpoch is a method that checks timeout on lock accounts and return assets
// if lock is not available anymore. It can be invoked only by NewEpoch method // if lock is not available anymore. Can be invoked only by NewEpoch method
// of Netmap contract. // of Netmap contract.
// //
// It produces Transfer and TransferX notifications. // Produces Transfer and TransferX notifications.
func NewEpoch(epochNum int) { func NewEpoch(epochNum int) {
ctx := storage.GetContext() ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
common.CheckAlphabetWitness() 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)
}
it := storage.Find(ctx, []byte{}, storage.KeysOnly) it := storage.Find(ctx, []byte{}, storage.KeysOnly)
for iterator.Next(it) { for iterator.Next(it) {
@ -217,22 +297,49 @@ func NewEpoch(epochNum int) {
} }
} }
// Mint is a method that transfers assets to a user account from an empty account. // Mint is a method that transfers assets to user account from empty account.
// It can be invoked only by Alphabet nodes of the Inner Ring. // Can be invoked only by Alphabet nodes of the Inner Ring.
// //
// It produces Mint, Transfer and TransferX notifications. // Produces Mint, Transfer and TransferX notifications.
// //
// Mint method is invoked by Alphabet nodes of the Inner Ring when they process // Mint method invoked by Alphabet nodes of the Inner Ring when they process
// Deposit notification from FrostFS contract. Before that, Alphabet nodes should // Deposit notification from NeoFS contract. Before that Alphabet nodes should
// synchronize precision of main chain GAS contract and Balance contract. // synchronize precision of main chain GAS contract and Balance contract.
// Mint increases total supply of NEP-17 compatible FrostFS token. // Mint increases total supply of NEP-17 compatible NeoFS token.
func Mint(to interop.Hash160, amount int, txDetails []byte) { func Mint(to interop.Hash160, amount int, txDetails []byte) {
ctx := storage.GetContext() ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
common.CheckAlphabetWitness() var ( // for invocation collection without notary
alphabet []common.IRNode
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)
}
details := common.MintTransferDetails(txDetails) 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) ok := token.transfer(ctx, nil, to, amount, true, details)
if !ok { if !ok {
panic("can't transfer assets") panic("can't transfer assets")
@ -245,24 +352,51 @@ func Mint(to interop.Hash160, amount int, txDetails []byte) {
runtime.Notify("Mint", to, amount) runtime.Notify("Mint", to, amount)
} }
// Burn is a method that transfers assets from a user account to an empty account. // Burn is a method that transfers assets from user account to empty account.
// It can be invoked only by Alphabet nodes of the Inner Ring. // Can be invoked only by Alphabet nodes of the Inner Ring.
// //
// It produces Burn, Transfer and TransferX notifications. // Produces Burn, Transfer and TransferX notifications.
// //
// Burn method is invoked by Alphabet nodes of the Inner Ring when they process // Burn method invoked by Alphabet nodes of the Inner Ring when they process
// Cheque notification from FrostFS contract. It means that locked assets have been // Cheque notification from NeoFS contract. It means that locked assets were
// transferred to the user in the mainchain, therefore the lock account should be destroyed. // transferred to user in main chain, therefore lock account should be destroyed.
// Before that, Alphabet nodes should synchronize precision of mainchain GAS // Before that Alphabet nodes should synchronize precision of main chain GAS
// contract and Balance contract. Burn decreases total supply of NEP-17 // contract and Balance contract. Burn decreases total supply of NEP-17
// compatible FrostFS token. // compatible NeoFS token.
func Burn(from interop.Hash160, amount int, txDetails []byte) { func Burn(from interop.Hash160, amount int, txDetails []byte) {
ctx := storage.GetContext() ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
common.CheckAlphabetWitness() var ( // for invocation collection without notary
alphabet []common.IRNode
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)
}
details := common.BurnTransferDetails(txDetails) 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) ok := token.transfer(ctx, from, nil, amount, true, details)
if !ok { if !ok {
panic("can't transfer assets") panic("can't transfer assets")
@ -279,7 +413,7 @@ func Burn(from interop.Hash160, amount int, txDetails []byte) {
runtime.Notify("Burn", from, amount) runtime.Notify("Burn", from, amount)
} }
// Version returns the version of the contract. // Version returns version of the contract.
func Version() int { func Version() int {
return common.Version return common.Version
} }
@ -353,7 +487,7 @@ func (t Token) canTransfer(ctx storage.Context, from, to interop.Hash160, amount
return amountFrom, true return amountFrom, true
} }
// isUsableAddress checks if the sender is either a correct NEO address or SC address. // isUsableAddress checks if the sender is either the correct NEO address or SC address.
func isUsableAddress(addr interop.Hash160) bool { func isUsableAddress(addr interop.Hash160) bool {
if len(addr) == 20 { if len(addr) == 20 {
if runtime.CheckWitness(addr) { if runtime.CheckWitness(addr) {

View file

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

View file

@ -1,23 +1,23 @@
/* /*
Balance contract is a contract deployed in FrostFS sidechain. Balance contract is a contract deployed in NeoFS side chain.
Balance contract stores all FrostFS account balances. It is a NEP-17 compatible Balance contract stores all NeoFS account balances. It is NEP-17 compatible
contract, so it can be tracked and controlled by N3 compatible network contract so in can be tracked and controlled by N3 compatible network
monitors and wallet software. monitors and wallet software.
This contract is used to store all micro transactions in the sidechain, such as This contract is used to store all micro transactions in the sidechain, such as
data audit settlements or container fee payments. It is inefficient to make such data audit settlements or container fee payments. It is inefficient to make such
small payment transactions in the mainchain. To process small transfers, balance small payment transactions in main chain. To process small transfers, balance
contract has higher (12) decimal precision than native GAS contract. contract has higher (12) decimal precision than native GAS contract.
FrostFS balances are synchronized with mainchain operations. Deposit produces NeoFS balances are synchronized with main chain operations. Deposit produce
minting of FROSTFS tokens in Balance contract. Withdraw locks some FROSTFS tokens minting of NEOFS tokens in Balance contract. Withdraw locks some NEOFS tokens
in a special lock account. When FrostFS contract transfers GAS assets back to the in special lock account. When NeoFS contract transfers GAS assets back to the
user, the lock account is destroyed with burn operation. user, lock account is destroyed with burn operation.
# Contract notifications Contract notifications
Transfer notification. This is a NEP-17 standard notification. Transfer notification. This is NEP-17 standard notification.
Transfer: Transfer:
- name: from - name: from
@ -27,7 +27,7 @@ Transfer notification. This is a NEP-17 standard notification.
- name: amount - name: amount
type: Integer type: Integer
TransferX notification. This is an enhanced transfer notification with details. TransferX notification. This is enhanced transfer notification with details.
TransferX: TransferX:
- name: from - name: from
@ -39,11 +39,11 @@ TransferX notification. This is an enhanced transfer notification with details.
- name: details - name: details
type: ByteArray type: ByteArray
Lock notification. This notification is produced when a lock account is Lock notification. This notification is produced when Lock account has been
created. It contains information about the mainchain transaction that has produced created. It contains information about main chain transaction that produced
the asset lock, the address of the lock account and the FrostFS epoch number until which the asset lock, address of lock account and NeoFS epoch number until lock account
lock account is valid. Alphabet nodes of the Inner Ring catch notification and initialize is valid. Alphabet nodes of the Inner Ring catch notification and initialize
Cheque method invocation of FrostFS contract. Cheque method invocation of the NeoFS contract.
Lock: Lock:
- name: txID - name: txID
@ -66,8 +66,9 @@ replenished from deposit in the mainchain.
- name: amount - name: amount
type: Integer type: Integer
Burn notification. This notification is produced after user balance is reduced Burn notification. This notification is produced after user balance is reduced
when FrostFS contract has transferred GAS assets back to the user. when NeoFS contract transferred GAS assets back to the user.
Burn: Burn:
- name: from - name: from

View file

@ -1,26 +0,0 @@
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,22 +6,47 @@ import (
"github.com/nspcc-dev/neo-go/pkg/interop/native/ledger" "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/neo"
"github.com/nspcc-dev/neo-go/pkg/interop/native/roles" "github.com/nspcc-dev/neo-go/pkg/interop/native/roles"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
) )
type IRNode struct { type IRNode struct {
PublicKey interop.PublicKey PublicKey interop.PublicKey
} }
// InnerRingNodes return a list of inner ring nodes from state validator role const irListMethod = "innerRingList"
// in the sidechain.
func InnerRingNodes() []interop.PublicKey { // InnerRingInvoker returns public key of inner ring node that invoked contract.
blockHeight := ledger.CurrentIndex() // Work around for environments without notary support.
return roles.GetDesignatedByRole(roles.NeoFSAlphabet, uint32(blockHeight+1)) func InnerRingInvoker(ir []IRNode) interop.PublicKey {
for i := 0; i < len(ir); i++ {
node := ir[i]
if runtime.CheckWitness(node.PublicKey) {
return node.PublicKey
}
} }
// AlphabetNodes returns a list of alphabet nodes from committee in the sidechain. return nil
func AlphabetNodes() []interop.PublicKey { }
return neo.GetCommittee()
// InnerRingNodes return list of inner ring nodes from state validator role
// in side chain.
func InnerRingNodes() []IRNode {
blockHeight := ledger.CurrentIndex()
list := roles.GetDesignatedByRole(roles.NeoFSAlphabet, uint32(blockHeight+1))
return keysToNodes(list)
}
// InnerRingNodesFromNetmap gets list of inner ring through
// calling "innerRingList" method of smart contract.
// Work around for environments without notary support.
func InnerRingNodesFromNetmap(sc interop.Hash160) []IRNode {
return contract.Call(sc, irListMethod, contract.ReadOnly).([]IRNode)
}
// AlphabetNodes return list of alphabet nodes from committee in side chain.
func AlphabetNodes() []IRNode {
list := neo.GetCommittee()
return keysToNodes(list)
} }
// AlphabetAddress returns multi address of alphabet public keys. // AlphabetAddress returns multi address of alphabet public keys.
@ -37,7 +62,7 @@ func CommitteeAddress() []byte {
} }
// Multiaddress returns default multi signature account address for N keys. // Multiaddress returns default multi signature account address for N keys.
// If committee set to true, it is `M = N/2+1` committee account. // If committee set to true, then it is `M = N/2+1` committee account.
func Multiaddress(n []interop.PublicKey, committee bool) []byte { func Multiaddress(n []interop.PublicKey, committee bool) []byte {
threshold := len(n)*2/3 + 1 threshold := len(n)*2/3 + 1
if committee { if committee {
@ -46,3 +71,15 @@ func Multiaddress(n []interop.PublicKey, committee bool) []byte {
return contract.CreateMultisigAccount(threshold, n) return contract.CreateMultisigAccount(threshold, n)
} }
func keysToNodes(list []interop.PublicKey) []IRNode {
result := []IRNode{}
for i := range list {
result = append(result, IRNode{
PublicKey: list[i],
})
}
return result
}

View file

@ -5,6 +5,15 @@ import (
"github.com/nspcc-dev/neo-go/pkg/interop/storage" "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. // SetSerialized serializes data and puts it into contract storage.
func SetSerialized(ctx storage.Context, key interface{}, value interface{}) { func SetSerialized(ctx storage.Context, key interface{}, value interface{}) {
data := std.Serialize(value) data := std.Serialize(value)

View file

@ -39,7 +39,7 @@ func ContainerFeeTransferDetails(cid []byte) []byte {
return append(containerFeePrefix, cid...) return append(containerFeePrefix, cid...)
} }
// AbortWithMessage calls `runtime.Log` with the passed message // AbortWithMessage calls `runtime.Log` with passed message
// and calls `ABORT` opcode. // and calls `ABORT` opcode.
func AbortWithMessage(msg string) { func AbortWithMessage(msg string) {
runtime.Log(msg) runtime.Log(msg)

View file

@ -4,9 +4,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
) )
// LegacyOwnerKey is storage key used to store contract owner.
const LegacyOwnerKey = "contractOwner"
// HasUpdateAccess returns true if contract can be updated. // HasUpdateAccess returns true if contract can be updated.
func HasUpdateAccess() bool { func HasUpdateAccess() bool {
return runtime.CheckWitness(CommitteeAddress()) return runtime.CheckWitness(CommitteeAddress())

View file

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

147
common/vote.go Normal file
View file

@ -0,0 +1,147 @@
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 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 specific 'id' and returns amount
// on 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 (
newCandidates []Ballot
candidates = getBallots(ctx)
)
for i := 0; i < len(candidates); i++ {
cnd := candidates[i]
if !BytesEqual(cnd.ID, id) {
newCandidates = append(newCandidates, cnd)
}
}
SetSerialized(ctx, voteKey, newCandidates)
}
// getBallots returns 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 slice of bytes by wrapping them into strings,
// which is necessary with new util.Equal 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. 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 invocation 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:
- invoke happened from inner ring node,
- it is indirect invocation from other smart-contract.
However there is a possible attack, when malicious inner ring node creates
malicious smart-contract in 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

@ -3,31 +3,31 @@ package common
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
var ( var (
// ErrAlphabetWitnessFailed appears when the method must be // ErrAlphabetWitnessFailed appears when method must be
// called by the Alphabet but was not. // called by Alphabet but was not.
ErrAlphabetWitnessFailed = "alphabet witness check failed" ErrAlphabetWitnessFailed = "alphabet witness check failed"
// ErrOwnerWitnessFailed appears when the method must be called // ErrOwnerWitnessFailed appears when method must be called
// by an owner of some assets but was not. // by owner of some assets but was not.
ErrOwnerWitnessFailed = "owner witness check failed" ErrOwnerWitnessFailed = "owner witness check failed"
// ErrWitnessFailed appears when the method must be called // ErrWitnessFailed appears when method must be called
// using certain public key but was not. // using certain public key but was not.
ErrWitnessFailed = "witness check failed" ErrWitnessFailed = "witness check failed"
) )
// CheckAlphabetWitness checks witness of the passed caller. // CheckAlphabetWitness checks witness of the passed caller.
// It panics with ErrAlphabetWitnessFailed message on fail. // Panics with ErrAlphabetWitnessFailed message on fail.
func CheckAlphabetWitness() { func CheckAlphabetWitness(caller []byte) {
checkWitnessWithPanic(AlphabetAddress(), ErrAlphabetWitnessFailed) checkWitnessWithPanic(caller, ErrAlphabetWitnessFailed)
} }
// CheckOwnerWitness checks witness of the passed caller. // CheckOwnerWitness checks witness of the passed caller.
// It panics with ErrOwnerWitnessFailed message on fail. // Panics with ErrOwnerWitnessFailed message on fail.
func CheckOwnerWitness(caller []byte) { func CheckOwnerWitness(caller []byte) {
checkWitnessWithPanic(caller, ErrOwnerWitnessFailed) checkWitnessWithPanic(caller, ErrOwnerWitnessFailed)
} }
// CheckWitness checks witness of the passed caller. // CheckWitness checks witness of the passed caller.
// It panics with ErrWitnessFailed message on fail. // Panics with ErrWitnessFailed message on fail.
func CheckWitness(caller []byte) { func CheckWitness(caller []byte) {
checkWitnessWithPanic(caller, ErrWitnessFailed) checkWitnessWithPanic(caller, ErrWitnessFailed)
} }

View file

@ -1,8 +1,8 @@
name: "Container" name: "NeoFS Container"
safemethods: ["count", "containersOf", "get", "owner", "list", "eACL", "getContainerSize", "listContainerSizes", "iterateContainerSizes", "version"] safemethods: ["get", "owner", "list", "eACL", "getContainerSize", "listContainerSizes", "version"]
permissions: permissions:
- methods: ["update", "addKey", "transferX", - methods: ["update", "addKey", "transferX",
"register", "addRecord", "deleteRecords"] "addRoot", "register", "addRecord", "deleteRecords"]
events: events:
- name: containerPut - name: containerPut
parameters: parameters:
@ -14,12 +14,6 @@ events:
type: PublicKey type: PublicKey
- name: token - name: token
type: ByteArray type: ByteArray
- name: PutSuccess
parameters:
- name: containerID
type: Hash256
- name: publicKey
type: PublicKey
- name: containerDelete - name: containerDelete
parameters: parameters:
- name: containerID - name: containerID
@ -28,10 +22,6 @@ events:
type: Signature type: Signature
- name: token - name: token
type: ByteArray type: ByteArray
- name: DeleteSuccess
parameters:
- name: containerID
type: ByteArray
- name: setEACL - name: setEACL
parameters: parameters:
- name: eACL - name: eACL
@ -42,12 +32,6 @@ events:
type: PublicKey type: PublicKey
- name: token - name: token
type: ByteArray type: ByteArray
- name: SetEACLSuccess
parameters:
- name: containerID
type: ByteArray
- name: publicKey
type: PublicKey
- name: StartEstimation - name: StartEstimation
parameters: parameters:
- name: epoch - name: epoch

View file

@ -1,16 +1,15 @@
package container package container
import ( import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop" "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/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/convert"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator" "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/crypto"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management" "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/native/std"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime" "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/storage"
"github.com/nspcc-dev/neofs-contract/common"
) )
type ( type (
@ -44,7 +43,7 @@ type (
) )
const ( const (
frostfsIDContractKey = "identityScriptHash" neofsIDContractKey = "identityScriptHash"
balanceContractKey = "balanceScriptHash" balanceContractKey = "balanceScriptHash"
netmapContractKey = "netmapScriptHash" netmapContractKey = "netmapScriptHash"
nnsContractKey = "nnsScriptHash" nnsContractKey = "nnsScriptHash"
@ -60,17 +59,9 @@ const (
// V2 format // V2 format
containerIDSize = 32 // SHA256 size containerIDSize = 32 // SHA256 size
singleEstimatePrefix = "est"
estimateKeyPrefix = "cnr" estimateKeyPrefix = "cnr"
containerKeyPrefix = 'x'
ownerKeyPrefix = 'o'
estimatePostfixSize = 10 estimatePostfixSize = 10
// CleanupDelta contains the number of the last epochs for which container estimations are present. cleanupDelta = 3
CleanupDelta = 3
// TotalCleanupDelta contains the number of the epochs after which estimation
// will be removed by epoch tick cleanup if any of the nodes hasn't updated
// container size and/or container has been removed. It must be greater than CleanupDelta.
TotalCleanupDelta = CleanupDelta + 1
// NotFoundError is returned if container is missing. // NotFoundError is returned if container is missing.
NotFoundError = "container does not exist" NotFoundError = "container does not exist"
@ -78,7 +69,7 @@ const (
// default SOA record field values // default SOA record field values
defaultRefresh = 3600 // 1 hour defaultRefresh = 3600 // 1 hour
defaultRetry = 600 // 10 min defaultRetry = 600 // 10 min
defaultExpire = 3600 * 24 * 365 * 10 // 10 years defaultExpire = 604800 // 1 week
defaultTTL = 3600 // 1 hour defaultTTL = 3600 // 1 hour
) )
@ -86,43 +77,14 @@ var (
eACLPrefix = []byte("eACL") eACLPrefix = []byte("eACL")
) )
// OnNEP11Payment is needed for registration with contract as the owner to work. // OnNEP11Payment is needed for registration with contract as owner to work.
func OnNEP11Payment(a interop.Hash160, b int, c []byte, d interface{}) { func OnNEP11Payment(a interop.Hash160, b int, c []byte, d interface{}) {
} }
func _deploy(data interface{}, isUpdate bool) { func _deploy(data interface{}, isUpdate bool) {
ctx := storage.GetContext() 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 { args := data.(struct {
//TODO(@acid-ant): #9 remove notaryDisabled in future version
notaryDisabled bool notaryDisabled bool
addrNetmap interop.Hash160 addrNetmap interop.Hash160
addrBalance interop.Hash160 addrBalance interop.Hash160
@ -131,6 +93,13 @@ func _deploy(data interface{}, isUpdate bool) {
nnsRoot string nnsRoot string
}) })
if isUpdate {
storage.Delete(ctx, "ballots")
storage.Put(ctx, notaryDisabledKey, false)
return
}
if len(args.addrNetmap) != interop.Hash160Len || if len(args.addrNetmap) != interop.Hash160Len ||
len(args.addrBalance) != interop.Hash160Len || len(args.addrBalance) != interop.Hash160Len ||
len(args.addrID) != interop.Hash160Len { len(args.addrID) != interop.Hash160Len {
@ -139,10 +108,17 @@ func _deploy(data interface{}, isUpdate bool) {
storage.Put(ctx, netmapContractKey, args.addrNetmap) storage.Put(ctx, netmapContractKey, args.addrNetmap)
storage.Put(ctx, balanceContractKey, args.addrBalance) storage.Put(ctx, balanceContractKey, args.addrBalance)
storage.Put(ctx, frostfsIDContractKey, args.addrID) storage.Put(ctx, neofsIDContractKey, args.addrID)
storage.Put(ctx, nnsContractKey, args.addrNNS) storage.Put(ctx, nnsContractKey, args.addrNNS)
storage.Put(ctx, nnsRootKey, args.nnsRoot) 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 // add NNS root for container alias domains
registerNiceNameTLD(args.addrNNS, args.nnsRoot) registerNiceNameTLD(args.addrNNS, args.nnsRoot)
@ -164,8 +140,8 @@ func registerNiceNameTLD(addrNNS interop.Hash160, nnsRoot string) {
} }
} }
// Update method updates contract source code and manifest. It can be invoked // Update method updates contract source code and manifest. Can be invoked
// by committee only. // only by committee.
func Update(script []byte, manifest []byte, data interface{}) { func Update(script []byte, manifest []byte, data interface{}) {
if !common.HasUpdateAccess() { if !common.HasUpdateAccess() {
panic("only committee can update contract") panic("only committee can update contract")
@ -176,13 +152,13 @@ func Update(script []byte, manifest []byte, data interface{}) {
runtime.Log("container contract updated") runtime.Log("container contract updated")
} }
// Put method creates a new container if it has been invoked by Alphabet nodes // Put method creates new container if it was invoked by Alphabet nodes
// of the Inner Ring. Otherwise, it produces containerPut notification. // of the Inner Ring. Otherwise it produces containerPut notification.
// //
// Container should be a stable marshaled Container structure from API. // Container should be stable marshaled Container structure from API.
// Signature is a RFC6979 signature of the Container. // Signature is a RFC6979 signature of Container.
// PublicKey contains the public key of the signer. // PublicKey contains public key of the signer.
// Token is optional and should be a stable marshaled SessionToken structure from // Token is optional and should be stable marshaled SessionToken structure from
// API. // API.
func Put(container []byte, signature interop.Signature, publicKey interop.PublicKey, token []byte) { func Put(container []byte, signature interop.Signature, publicKey interop.PublicKey, token []byte) {
PutNamed(container, signature, publicKey, token, "", "") PutNamed(container, signature, publicKey, token, "", "")
@ -194,10 +170,11 @@ func PutNamed(container []byte, signature interop.Signature,
publicKey interop.PublicKey, token []byte, publicKey interop.PublicKey, token []byte,
name, zone string) { name, zone string) {
ctx := storage.GetContext() ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
ownerID := ownerFromBinaryContainer(container) ownerID := ownerFromBinaryContainer(container)
containerID := crypto.Sha256(container) containerID := crypto.Sha256(container)
frostfsIDContractAddr := storage.Get(ctx, frostfsIDContractKey).(interop.Hash160) neofsIDContractAddr := storage.Get(ctx, neofsIDContractKey).(interop.Hash160)
cnr := Container{ cnr := Container{
value: container, value: container,
sig: signature, sig: signature,
@ -234,14 +211,33 @@ func PutNamed(container []byte, signature interop.Signature,
panic("insufficient balance to create container") panic("insufficient balance to create container")
} }
common.CheckAlphabetWitness() 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)
}
// todo: check if new container with unique container id // todo: check if new container with unique container id
details := common.ContainerFeeTransferDetails(containerID) details := common.ContainerFeeTransferDetails(containerID)
for i := 0; i < len(alphabet); i++ { for i := 0; i < len(alphabet); i++ {
node := alphabet[i] node := alphabet[i]
to := contract.CreateStandardAccount(node) to := contract.CreateStandardAccount(node.PublicKey)
contract.Call(balanceContractAddr, "transferX", contract.Call(balanceContractAddr, "transferX",
contract.All, contract.All,
@ -271,14 +267,13 @@ func PutNamed(container []byte, signature interop.Signature,
} }
if len(token) == 0 { // if container created directly without session if len(token) == 0 { // if container created directly without session
contract.Call(frostfsIDContractAddr, "addKey", contract.All, ownerID, [][]byte{publicKey}) contract.Call(neofsIDContractAddr, "addKey", contract.All, ownerID, [][]byte{publicKey})
} }
runtime.Log("added new container") runtime.Log("added new container")
runtime.Notify("PutSuccess", containerID, publicKey)
} }
// checkNiceNameAvailable checks if the nice name is available for the container. // checkNiceNameAvailable checks if nice name is available for the container.
// It panics if the name is taken. Returned value specifies if new domain registration is needed. // It panics if the name is taken. Returned value specifies if new domain registration is needed.
func checkNiceNameAvailable(nnsContractAddr interop.Hash160, domain string) bool { func checkNiceNameAvailable(nnsContractAddr interop.Hash160, domain string) bool {
isAvail := contract.Call(nnsContractAddr, "isAvailable", isAvail := contract.Call(nnsContractAddr, "isAvailable",
@ -302,32 +297,53 @@ func checkNiceNameAvailable(nnsContractAddr interop.Hash160, domain string) bool
return false return false
} }
// Delete method removes a container from the contract storage if it has been // Delete method removes container from contract storage if it was
// invoked by Alphabet nodes of the Inner Ring. Otherwise, it produces // invoked by Alphabet nodes of the Inner Ring. Otherwise it produces
// containerDelete notification. // containerDelete notification.
// //
// Signature is a RFC6979 signature of the container ID. // Signature is a RFC6979 signature of container ID.
// Token is optional and should be a stable marshaled SessionToken structure from // Token is optional and should be stable marshaled SessionToken structure from
// API. // API.
// //
// If the container doesn't exist, it panics with NotFoundError. // If a container doesn't exist it panics with NotFoundError.
func Delete(containerID []byte, signature interop.Signature, token []byte) { func Delete(containerID []byte, signature interop.Signature, token []byte) {
ctx := storage.GetContext() ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
ownerID := getOwnerByID(ctx, containerID) ownerID := getOwnerByID(ctx, containerID)
if ownerID == nil { if ownerID == nil {
return return
} }
common.CheckAlphabetWitness() 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)
}
key := append([]byte(nnsHasAliasKey), containerID...) key := append([]byte(nnsHasAliasKey), containerID...)
domain := storage.Get(ctx, key).(string) domain := storage.Get(ctx, key).(string)
if len(domain) != 0 { if len(domain) != 0 {
storage.Delete(ctx, key) storage.Delete(ctx, key)
// We should do `getRecord` first because NNS record could be deleted // We should do `getRecord` first because NNS record could be deleted
// by other means (expiration, manual), thus leading to failing `deleteRecord` // by other means (expiration, manual) thus leading to failing `deleteRecord`
// and inability to delete a container. We should also check if we own the record in case. // and inability to delete container. We should also check that we own the record in case.
nnsContractAddr := storage.Get(ctx, nnsContractKey).(interop.Hash160) nnsContractAddr := storage.Get(ctx, nnsContractKey).(interop.Hash160)
res := contract.Call(nnsContractAddr, "getRecords", contract.ReadStates|contract.AllowCall, domain, 16 /* TXT */) res := contract.Call(nnsContractAddr, "getRecords", contract.ReadStates|contract.AllowCall, domain, 16 /* TXT */)
if res != nil && std.Base58Encode(containerID) == string(res.([]interface{})[0].(string)) { if res != nil && std.Base58Encode(containerID) == string(res.([]interface{})[0].(string)) {
@ -336,14 +352,13 @@ func Delete(containerID []byte, signature interop.Signature, token []byte) {
} }
removeContainer(ctx, containerID, ownerID) removeContainer(ctx, containerID, ownerID)
runtime.Log("remove container") runtime.Log("remove container")
runtime.Notify("DeleteSuccess", containerID)
} }
// Get method returns a structure that contains a stable marshaled Container structure, // Get method returns structure that contains stable marshaled Container structure,
// the signature, the public key of the container creator and a stable marshaled SessionToken // signature, public key of the container creator and stable marshaled SessionToken
// structure if it was provided. // structure if it was provided.
// //
// If the container doesn't exist, it panics with NotFoundError. // If a container doesn't exist it panics with NotFoundError.
func Get(containerID []byte) Container { func Get(containerID []byte) Container {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
cnt := getContainer(ctx, containerID) cnt := getContainer(ctx, containerID)
@ -353,9 +368,9 @@ func Get(containerID []byte) Container {
return cnt return cnt
} }
// Owner method returns a 25 byte Owner ID of the container. // Owner method returns 25 byte Owner ID of the container.
// //
// If the container doesn't exist, it panics with NotFoundError. // If a container doesn't exist it panics with NotFoundError.
func Owner(containerID []byte) []byte { func Owner(containerID []byte) []byte {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
owner := getOwnerByID(ctx, containerID) owner := getOwnerByID(ctx, containerID)
@ -365,29 +380,7 @@ func Owner(containerID []byte) []byte {
return owner return owner
} }
// Count method returns the number of registered containers. // List method returns list of all container IDs owned by specified owner.
func Count() int {
count := 0
ctx := storage.GetReadOnlyContext()
it := storage.Find(ctx, []byte{containerKeyPrefix}, storage.KeysOnly)
for iterator.Next(it) {
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 { func List(owner []byte) [][]byte {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
@ -397,7 +390,7 @@ func List(owner []byte) [][]byte {
var list [][]byte var list [][]byte
it := storage.Find(ctx, append([]byte{ownerKeyPrefix}, owner...), storage.ValuesOnly) it := storage.Find(ctx, owner, storage.ValuesOnly)
for iterator.Next(it) { for iterator.Next(it) {
id := iterator.Value(it).([]byte) id := iterator.Value(it).([]byte)
list = append(list, id) list = append(list, id)
@ -406,19 +399,20 @@ func List(owner []byte) [][]byte {
return list return list
} }
// SetEACL method sets a new extended ACL table related to the contract // SetEACL method sets new extended ACL table related to the contract
// if it was invoked by Alphabet nodes of the Inner Ring. Otherwise, it produces // if it was invoked by Alphabet nodes of the Inner Ring. Otherwise it produces
// setEACL notification. // setEACL notification.
// //
// EACL should be a stable marshaled EACLTable structure from API. // EACL should be stable marshaled EACLTable structure from API.
// Signature is a RFC6979 signature of the Container. // Signature is a RFC6979 signature of Container.
// PublicKey contains the public key of the signer. // PublicKey contains public key of the signer.
// Token is optional and should be a stable marshaled SessionToken structure from // Token is optional and should be stable marshaled SessionToken structure from
// API. // API.
// //
// If the container doesn't exist, it panics with NotFoundError. // If a container doesn't exist it panics with NotFoundError.
func SetEACL(eACL []byte, signature interop.Signature, publicKey interop.PublicKey, token []byte) { func SetEACL(eACL []byte, signature interop.Signature, publicKey interop.PublicKey, token []byte) {
ctx := storage.GetContext() ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
// V2 format // V2 format
// get container ID // get container ID
@ -431,7 +425,27 @@ func SetEACL(eACL []byte, signature interop.Signature, publicKey interop.PublicK
panic(NotFoundError) panic(NotFoundError)
} }
common.CheckAlphabetWitness() 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)
}
rule := ExtendedACL{ rule := ExtendedACL{
value: eACL, value: eACL,
@ -445,14 +459,13 @@ func SetEACL(eACL []byte, signature interop.Signature, publicKey interop.PublicK
common.SetSerialized(ctx, key, rule) common.SetSerialized(ctx, key, rule)
runtime.Log("success") runtime.Log("success")
runtime.Notify("SetEACLSuccess", containerID, publicKey)
} }
// EACL method returns a structure that contains a stable marshaled EACLTable structure, // EACL method returns structure that contains stable marshaled EACLTable structure,
// the signature, the public key of the extended ACL setter and a stable marshaled SessionToken // signature, public key of the extended ACL setter and stable marshaled SessionToken
// structure if it was provided. // structure if it was provided.
// //
// If the container doesn't exist, it panics with NotFoundError. // If a container doesn't exist it panics with NotFoundError.
func EACL(containerID []byte) ExtendedACL { func EACL(containerID []byte) ExtendedACL {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
@ -465,10 +478,10 @@ func EACL(containerID []byte) ExtendedACL {
} }
// PutContainerSize method saves container size estimation in contract // PutContainerSize method saves container size estimation in contract
// memory. It can be invoked only by Storage nodes from the network map. This method // memory. Can be invoked only by Storage nodes from the network map. Method
// checks witness based on the provided public key of the Storage node. // checks witness based on the provided public key of the Storage node.
// //
// If the container doesn't exist, it panics with NotFoundError. // If a container doesn't exist it panics with NotFoundError.
func PutContainerSize(epoch int, cid []byte, usedSize int, pubKey interop.PublicKey) { func PutContainerSize(epoch int, cid []byte, usedSize int, pubKey interop.PublicKey) {
ctx := storage.GetContext() ctx := storage.GetContext()
@ -490,18 +503,17 @@ func PutContainerSize(epoch int, cid []byte, usedSize int, pubKey interop.Public
} }
storage.Put(ctx, key, std.Serialize(s)) storage.Put(ctx, key, std.Serialize(s))
updateEstimations(ctx, epoch, cid, pubKey, false)
runtime.Log("saved container size estimation") runtime.Log("saved container size estimation")
} }
// GetContainerSize method returns the container ID and a slice of container // GetContainerSize method returns container ID and slice of container
// estimations. Container estimation includes the public key of the Storage Node // estimations. Container estimation includes public key of the Storage Node
// that registered estimation and value of estimation. // that registered estimation and value of estimation.
// //
// Use the ID obtained from ListContainerSizes method. Estimations are removed // Use ID obtained from ListContainerSizes method. Estimations are removed
// from contract storage every epoch, see NewEpoch method; therefore, this method // from contract storage every epoch, see NewEpoch method, therefore method
// can return different results during different epochs. // can return different results in different epochs.
func GetContainerSize(id []byte) containerSizes { func GetContainerSize(id []byte) containerSizes {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
@ -514,8 +526,8 @@ func GetContainerSize(id []byte) containerSizes {
return getContainerSizeEstimation(ctx, id, cid) return getContainerSizeEstimation(ctx, id, cid)
} }
// ListContainerSizes method returns the IDs of container size estimations // ListContainerSizes method returns IDs of container size estimations
// that have been registered for the specified epoch. // that has been registered for specified epoch.
func ListContainerSizes(epoch int) [][]byte { func ListContainerSizes(epoch int) [][]byte {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
@ -546,78 +558,138 @@ func ListContainerSizes(epoch int) [][]byte {
return result 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 // 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. // epochNum + 3. Can be invoked only by NewEpoch method of the Netmap contract.
func NewEpoch(epochNum int) { func NewEpoch(epochNum int) {
ctx := storage.GetContext() ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
common.CheckAlphabetWitness() 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)
}
cleanupContainers(ctx, epochNum) candidates := keysToDelete(ctx, epochNum)
for _, candidate := range candidates {
storage.Delete(ctx, candidate)
}
} }
// StartContainerEstimation method produces StartEstimation notification. // StartContainerEstimation method produces StartEstimation notification.
// It can be invoked only by Alphabet nodes of the Inner Ring. // Can be invoked only by Alphabet nodes of the Inner Ring.
func StartContainerEstimation(epoch int) { func StartContainerEstimation(epoch int) {
common.CheckAlphabetWitness() ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
var ( // for invocation collection without notary
alphabet []common.IRNode
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)
}
runtime.Notify("StartEstimation", epoch) runtime.Notify("StartEstimation", epoch)
runtime.Log("notification has been produced") runtime.Log("notification has been produced")
} }
// StopContainerEstimation method produces StopEstimation notification. // StopContainerEstimation method produces StopEstimation notification.
// It can be invoked only by Alphabet nodes of the Inner Ring. // Can be invoked only by Alphabet nodes of the Inner Ring.
func StopContainerEstimation(epoch int) { func StopContainerEstimation(epoch int) {
common.CheckAlphabetWitness() ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
var ( // for invocation collection without notary
alphabet []common.IRNode
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)
}
runtime.Notify("StopEstimation", epoch) runtime.Notify("StopEstimation", epoch)
runtime.Log("notification has been produced") runtime.Log("notification has been produced")
} }
// Version returns the version of the contract. // Version returns version of the contract.
func Version() int { func Version() int {
return common.Version return common.Version
} }
func addContainer(ctx storage.Context, id, owner []byte, container Container) { func addContainer(ctx storage.Context, id, owner []byte, container Container) {
containerListKey := append([]byte{ownerKeyPrefix}, owner...) containerListKey := append(owner, id...)
containerListKey = append(containerListKey, id...)
storage.Put(ctx, containerListKey, id) storage.Put(ctx, containerListKey, id)
idKey := append([]byte{containerKeyPrefix}, id...) common.SetSerialized(ctx, id, container)
common.SetSerialized(ctx, idKey, container)
} }
func removeContainer(ctx storage.Context, id []byte, owner []byte) { func removeContainer(ctx storage.Context, id []byte, owner []byte) {
containerListKey := append([]byte{ownerKeyPrefix}, owner...) containerListKey := append(owner, id...)
containerListKey = append(containerListKey, id...)
storage.Delete(ctx, containerListKey) storage.Delete(ctx, containerListKey)
storage.Delete(ctx, append([]byte{containerKeyPrefix}, id...)) storage.Delete(ctx, id)
} }
func getAllContainers(ctx storage.Context) [][]byte { func getAllContainers(ctx storage.Context) [][]byte {
var list [][]byte var list [][]byte
it := storage.Find(ctx, []byte{containerKeyPrefix}, storage.KeysOnly|storage.RemovePrefix) it := storage.Find(ctx, []byte{}, storage.KeysOnly)
for iterator.Next(it) { for iterator.Next(it) {
key := iterator.Value(it).([]byte) // it MUST BE `storage.KeysOnly` key := iterator.Value(it).([]byte) // it MUST BE `storage.KeysOnly`
// V2 format // V2 format
if len(key) == containerIDSize {
list = append(list, key) list = append(list, key)
} }
}
return list return list
} }
@ -633,7 +705,7 @@ func getEACL(ctx storage.Context, cid []byte) ExtendedACL {
} }
func getContainer(ctx storage.Context, cid []byte) Container { func getContainer(ctx storage.Context, cid []byte) Container {
data := storage.Get(ctx, append([]byte{containerKeyPrefix}, cid...)) data := storage.Get(ctx, cid)
if data != nil { if data != nil {
return std.Deserialize(data.([]byte)).(Container) return std.Deserialize(data.([]byte)).(Container)
} }
@ -685,7 +757,7 @@ func getContainerSizeEstimation(ctx storage.Context, key, cid []byte) containerS
} }
// isStorageNode looks into _previous_ epoch network map, because storage node // isStorageNode looks into _previous_ epoch network map, because storage node
// announces container size estimation of the previous epoch. // announce container size estimation of previous epoch.
func isStorageNode(ctx storage.Context, key interop.PublicKey) bool { func isStorageNode(ctx storage.Context, key interop.PublicKey) bool {
netmapContractAddr := storage.Get(ctx, netmapContractKey).(interop.Hash160) netmapContractAddr := storage.Get(ctx, netmapContractKey).(interop.Hash160)
snapshot := contract.Call(netmapContractAddr, "snapshot", contract.ReadOnly, 1).([]storageNode) snapshot := contract.Call(netmapContractAddr, "snapshot", contract.ReadOnly, 1).([]storageNode)
@ -703,33 +775,9 @@ func isStorageNode(ctx storage.Context, key interop.PublicKey) bool {
return false return false
} }
func updateEstimations(ctx storage.Context, epoch int, cid []byte, pub interop.PublicKey, isUpdate bool) { func keysToDelete(ctx storage.Context, epoch int) [][]byte {
h := crypto.Ripemd160(pub) results := [][]byte{}
estKey := append([]byte(singleEstimatePrefix), cid...)
estKey = append(estKey, h...)
var newEpochs []int
rawList := storage.Get(ctx, estKey).([]byte)
if rawList != nil {
epochs := std.Deserialize(rawList).([]int)
for _, oldEpoch := range epochs {
if !isUpdate && epoch-oldEpoch > CleanupDelta {
key := append([]byte(estimateKeyPrefix), convert.ToBytes(oldEpoch)...)
key = append(key, cid...)
key = append(key, h[:estimatePostfixSize]...)
storage.Delete(ctx, key)
} else {
newEpochs = append(newEpochs, oldEpoch)
}
}
}
newEpochs = append(newEpochs, epoch)
common.SetSerialized(ctx, estKey, newEpochs)
}
func cleanupContainers(ctx storage.Context, epoch int) {
it := storage.Find(ctx, []byte(estimateKeyPrefix), storage.KeysOnly) it := storage.Find(ctx, []byte(estimateKeyPrefix), storage.KeysOnly)
for iterator.Next(it) { for iterator.Next(it) {
k := iterator.Value(it).([]byte) k := iterator.Value(it).([]byte)
@ -738,8 +786,10 @@ func cleanupContainers(ctx storage.Context, epoch int) {
var n interface{} = nbytes var n interface{} = nbytes
if epoch-n.(int) > TotalCleanupDelta { if epoch-n.(int) > cleanupDelta {
storage.Delete(ctx, k) results = append(results, k)
} }
} }
return results
} }

View file

@ -1,17 +1,17 @@
/* /*
Container contract is a contract deployed in FrostFS sidechain. Container contract is a contract deployed in NeoFS side chain.
Container contract stores and manages containers, extended ACLs and container Container contract stores and manages containers, extended ACLs and container
size estimations. Contract does not perform sanity or signature checks of size estimations. Contract does not perform sanity or signature checks of the
containers or extended ACLs, it is done by Alphabet nodes of the Inner Ring. 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 Alphabet nodes approve it by invoking the same Put or SetEACL methods with
the same arguments. the same arguments.
# Contract notifications Contract notifications
containerPut notification. This notification is produced when a user wants to containerPut notification. This notification is produced when user wants to
create a new container. Alphabet nodes of the Inner Ring catch the notification and create new container. Alphabet nodes of the Inner Ring catch notification and
validate container data, signature and token if present. validate container data, signature and token if it is present.
containerPut: containerPut:
- name: container - name: container
@ -23,9 +23,9 @@ validate container data, signature and token if present.
- name: token - name: token
type: ByteArray type: ByteArray
containerDelete notification. This notification is produced when a container owner containerDelete notification. This notification is produced when container owner
wants to delete a container. Alphabet nodes of the Inner Ring catch the notification wants to delete container. Alphabet nodes of the Inner Ring catch notification
and validate container ownership, signature and token if present. and validate container ownership, signature and token if it is present.
containerDelete: containerDelete:
- name: containerID - name: containerID
@ -35,9 +35,9 @@ and validate container ownership, signature and token if present.
- name: token - name: token
type: ByteArray type: ByteArray
setEACL notification. This notification is produced when a container owner wants setEACL notification. This notification is produced when container owner wants
to update an extended ACL of a container. Alphabet nodes of the Inner Ring catch to update extended ACL of the container. Alphabet nodes of the Inner Ring catch
the notification and validate container ownership, signature and token if notification and validate container ownership, signature and token if it is
present. present.
setEACL: setEACL:

5
debian/changelog vendored
View file

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

34
debian/control vendored
View file

@ -1,34 +0,0 @@
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
- subnet

23
debian/copyright vendored
View file

@ -1,23 +0,0 @@
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/>.

View file

@ -1 +0,0 @@
README*

39
debian/postinst.ex vendored
View file

@ -1,39 +0,0 @@
#!/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
View file

@ -1,37 +0,0 @@
#!/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
View file

@ -1,35 +0,0 @@
#!/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
View file

@ -1,38 +0,0 @@
#!/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
View file

@ -1,20 +0,0 @@
#!/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

View file

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

View file

@ -1,84 +0,0 @@
/*
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
*/
package frostfs

View file

@ -1,401 +0,0 @@
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/gas"
"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/roles"
"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"
)
type (
record struct {
key []byte
val []byte
}
)
const (
// CandidateFeeConfigKey contains fee for a candidate registration.
CandidateFeeConfigKey = "InnerRingCandidateFee"
withdrawFeeConfigKey = "WithdrawFee"
alphabetKey = "alphabet"
candidatesKey = "candidates"
notaryDisabledKey = "notary"
processingContractKey = "processingScriptHash"
maxBalanceAmount = 9000 // Max integer of Fixed12 in JSON bound (2**53-1)
maxBalanceAmountGAS = int64(maxBalanceAmount) * 1_0000_0000
// hardcoded value to ignore deposit notification in onReceive
ignoreDepositNotification = "\x57\x0b"
)
var (
configPrefix = []byte("config")
)
// _deploy sets up initial alphabet node keys.
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))
return
}
args := data.(struct {
//TODO(@acid-ant): #9 remove notaryDisabled in future version
notaryDisabled bool
addrProc interop.Hash160
keys []interop.PublicKey
config [][]byte
})
if len(args.keys) == 0 {
panic("at least one alphabet key must be provided")
}
if len(args.addrProc) != interop.Hash160Len {
panic("incorrect length of contract script hash")
}
for i := 0; i < len(args.keys); i++ {
pub := args.keys[i]
if len(pub) != interop.PublicKeyCompressedLen {
panic("incorrect public key length")
}
}
// initialize all storage slices
common.SetSerialized(ctx, alphabetKey, args.keys)
storage.Put(ctx, processingContractKey, args.addrProc)
ln := len(args.config)
if ln%2 != 0 {
panic("bad configuration")
}
for i := 0; i < ln/2; i++ {
key := args.config[i*2]
val := args.config[i*2+1]
setConfig(ctx, key, val)
}
runtime.Log("frostfs: contract initialized")
}
// Update method updates contract source code and manifest. It can be invoked
// only by sidechain committee.
func Update(script []byte, manifest []byte, data interface{}) {
blockHeight := ledger.CurrentIndex()
alphabetKeys := roles.GetDesignatedByRole(roles.NeoFSAlphabet, uint32(blockHeight+1))
alphabetCommittee := common.Multiaddress(alphabetKeys, true)
if !runtime.CheckWitness(alphabetCommittee) {
panic(common.ErrAlphabetWitnessFailed)
}
contract.Call(interop.Hash160(management.Hash), "update",
contract.All, script, manifest, common.AppendVersion(data))
runtime.Log("frostfs contract updated")
}
// AlphabetAddress returns 2\3n+1 multisignature address of alphabet nodes.
// It is used in sidechain notary disabled environment.
func AlphabetAddress() interop.Hash160 {
ctx := storage.GetReadOnlyContext()
return multiaddress(getAlphabetNodes(ctx))
}
// InnerRingCandidates returns an array of structures that contain an Inner Ring
// candidate node key.
func InnerRingCandidates() []common.IRNode {
ctx := storage.GetReadOnlyContext()
nodes := []common.IRNode{}
it := storage.Find(ctx, candidatesKey, storage.KeysOnly|storage.RemovePrefix)
for iterator.Next(it) {
pub := iterator.Value(it).([]byte)
nodes = append(nodes, common.IRNode{PublicKey: pub})
}
return nodes
}
// InnerRingCandidateRemove removes a key from a list of Inner Ring candidates.
// It can be invoked by Alphabet nodes or the candidate itself.
//
// This method does not return fee back to the candidate.
func InnerRingCandidateRemove(key interop.PublicKey) {
ctx := storage.GetContext()
keyOwner := runtime.CheckWitness(key)
if !keyOwner {
multiaddr := AlphabetAddress()
if !runtime.CheckWitness(multiaddr) {
panic("this method must be invoked by candidate or alphabet")
}
}
prefix := []byte(candidatesKey)
stKey := append(prefix, key...)
if storage.Get(ctx, stKey) != nil {
storage.Delete(ctx, stKey)
runtime.Log("candidate has been removed")
}
}
// InnerRingCandidateAdd adds a key to a list of Inner Ring candidates.
// 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 FrostFS network config with the key InnerRingCandidateFee.
func InnerRingCandidateAdd(key interop.PublicKey) {
ctx := storage.GetContext()
common.CheckWitness(key)
stKey := append([]byte(candidatesKey), key...)
if storage.Get(ctx, stKey) != nil {
panic("candidate already in the list")
}
from := contract.CreateStandardAccount(key)
to := runtime.GetExecutingScriptHash()
fee := getConfig(ctx, CandidateFeeConfigKey).(int)
transferred := gas.Transfer(from, to, fee, []byte(ignoreDepositNotification))
if !transferred {
panic("failed to transfer funds, aborting")
}
storage.Put(ctx, stKey, []byte{1})
runtime.Log("candidate has been added")
}
// 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
// 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)
if common.BytesEqual(rcv, []byte(ignoreDepositNotification)) {
return
}
if amount <= 0 {
common.AbortWithMessage("amount must be positive")
} else if maxBalanceAmountGAS < int64(amount) {
common.AbortWithMessage("out of max amount limit")
}
caller := runtime.GetCallingScriptHash()
if !common.BytesEqual(caller, interop.Hash160(gas.Hash)) {
common.AbortWithMessage("only GAS can be accepted for deposit")
}
switch len(rcv) {
case 20:
case 0:
rcv = from
default:
common.AbortWithMessage("invalid data argument, expected Hash160")
}
runtime.Log("funds have been transferred")
tx := runtime.GetScriptContainer()
runtime.Notify("Deposit", from, amount, rcv, tx.Hash)
}
// 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 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")
}
if amount < 0 {
panic("non positive amount number")
}
if amount > maxBalanceAmount {
panic("out of max amount limit")
}
ctx := storage.GetContext()
// transfer fee to proxy contract to pay cheque invocation
fee := getConfig(ctx, withdrawFeeConfigKey).(int)
processingAddr := storage.Get(ctx, processingContractKey).(interop.Hash160)
transferred := gas.Transfer(user, processingAddr, fee, []byte{})
if !transferred {
panic("failed to transfer withdraw fee, aborting")
}
// notify alphabet nodes
amount = amount * 100000000
tx := runtime.GetScriptContainer()
runtime.Notify("Withdraw", user, amount, tx.Hash)
}
// Cheque transfers GAS back to the user from the contract account, if assets were
// 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) {
common.CheckAlphabetWitness()
from := runtime.GetExecutingScriptHash()
transferred := gas.Transfer(from, user, amount, nil)
if !transferred {
panic("failed to transfer funds, aborting")
}
runtime.Log("funds have been transferred")
runtime.Notify("Cheque", id, user, amount, lockAcc)
}
// 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
// 33 byte long. User argument must be a valid 20 byte script hash.
func Bind(user []byte, keys []interop.PublicKey) {
if !runtime.CheckWitness(user) {
panic("you should be the owner of the wallet")
}
for i := 0; i < len(keys); i++ {
pubKey := keys[i]
if len(pubKey) != interop.PublicKeyCompressedLen {
panic("incorrect public key size")
}
}
runtime.Notify("Bind", user, keys)
}
// 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
// 33 byte long. User argument must be a valid 20 byte script hash.
func Unbind(user []byte, keys []interop.PublicKey) {
if !runtime.CheckWitness(user) {
panic("you should be the owner of the wallet")
}
for i := 0; i < len(keys); i++ {
pubKey := keys[i]
if len(pubKey) != interop.PublicKeyCompressedLen {
panic("incorrect public key size")
}
}
runtime.Notify("Unbind", user, keys)
}
// 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 FrostFS runtime configuration value. It can be invoked
// only by Alphabet nodes.
func SetConfig(id, key, val []byte) {
ctx := storage.GetContext()
common.CheckAlphabetWitness()
setConfig(ctx, key, val)
runtime.Notify("SetConfig", id, key, val)
runtime.Log("configuration has been updated")
}
// ListConfig returns an array of structures that contain a key and a value of all
// FrostFS configuration records. Key and value are both byte arrays.
func ListConfig() []record {
ctx := storage.GetReadOnlyContext()
var config []record
it := storage.Find(ctx, configPrefix, storage.None)
for iterator.Next(it) {
pair := iterator.Value(it).(struct {
key []byte
val []byte
})
r := record{key: pair.key[len(configPrefix):], val: pair.val}
config = append(config, r)
}
return config
}
// Version returns version of the contract.
func Version() int {
return common.Version
}
// getAlphabetNodes returns a deserialized slice of nodes from storage.
func getAlphabetNodes(ctx storage.Context) []interop.PublicKey {
data := storage.Get(ctx, alphabetKey)
if data != nil {
return std.Deserialize(data.([]byte)).([]interop.PublicKey)
}
return []interop.PublicKey{}
}
// 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...)
return storage.Get(ctx, storageKey)
}
// 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...)
storage.Put(ctx, storageKey, val)
}
// multiaddress returns a multisignature address from the list of IRNode structures
// with m = 2/3n+1.
func multiaddress(keys []interop.PublicKey) []byte {
threshold := len(keys)*2/3 + 1
return contract.CreateMultisigAccount(threshold, keys)
}

View file

@ -1,20 +0,0 @@
/*
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.
*/
package frostfsid

5
go.mod
View file

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

342
go.sum
View file

@ -1,38 +1,6 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/CityOfZion/neo-go v0.62.1-pre.0.20191114145240-e740fbe708f8/go.mod h1:MJCkWUBhi9pn/CrYO1Q3P687y2KeahrOPS9BD9LDGb0= github.com/CityOfZion/neo-go v0.62.1-pre.0.20191114145240-e740fbe708f8/go.mod h1:MJCkWUBhi9pn/CrYO1Q3P687y2KeahrOPS9BD9LDGb0=
github.com/CityOfZion/neo-go v0.70.1-pre.0.20191209120015-fccb0085941e/go.mod h1:0enZl0az8xA6PVkwzEOwPWVJGqlt/GO4hA4kmQ5Xzig= github.com/CityOfZion/neo-go v0.70.1-pre.0.20191209120015-fccb0085941e/go.mod h1:0enZl0az8xA6PVkwzEOwPWVJGqlt/GO4hA4kmQ5Xzig=
github.com/CityOfZion/neo-go v0.70.1-pre.0.20191212173117-32ac01130d4c/go.mod h1:JtlHfeqLywZLswKIKFnAp+yzezY4Dji9qlfQKB2OD/I= github.com/CityOfZion/neo-go v0.70.1-pre.0.20191212173117-32ac01130d4c/go.mod h1:JtlHfeqLywZLswKIKFnAp+yzezY4Dji9qlfQKB2OD/I=
@ -72,21 +40,15 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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/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/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/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/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= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@ -105,45 +67,28 @@ 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.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= 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/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/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.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-redis/redis v6.10.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis v6.10.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
@ -152,54 +97,33 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM= github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM=
github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
@ -208,14 +132,8 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
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.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 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= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@ -240,7 +158,6 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
@ -251,30 +168,21 @@ 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-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-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-20210721160347-1b03241391ac/go.mod h1:U8MSnEShH+o5hexfWJdze6uMFJteP0ko7J2frO7Yu1Y=
github.com/nspcc-dev/dbft v0.0.0-20220902113116-58a5e763e647 h1:handGBjqVzRx7HD6152zsP8ZRxw083zCMbN0IlUaPQk= github.com/nspcc-dev/go-ordered-json v0.0.0-20210915112629-e1b6cce73d02 h1:JgRx27vfGw5WV5QbaNDy0iy2WD1XJO964wwAapaYKLg=
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-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 h1:17VcAuTtrstmFppBjfRiia4K2wA/ukXZhLFS8Y8rz5Y=
github.com/nspcc-dev/hrw v1.0.9/go.mod h1:l/W2vx83vMQo6aStyx2AuZrJ+07lGv2JQGlVkPG06MU= 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.73.1-pre.0.20200303142215-f5a1b928ce09/go.mod h1:pPYwPZ2ks+uMnlRLUyXOpLieaDQSEaf4NM3zHVbRjmg=
github.com/nspcc-dev/neo-go v0.98.0 h1:yyW4sgY88/pLf0949qmgfkQXzRKC3CI/WyhqXNnwMd8=
github.com/nspcc-dev/neo-go v0.98.0/go.mod h1:E3cc1x6RXSXrJb2nDWXTXjnXk3rIqVN8YdFyWv+FrqM= github.com/nspcc-dev/neo-go v0.98.0/go.mod h1:E3cc1x6RXSXrJb2nDWXTXjnXk3rIqVN8YdFyWv+FrqM=
github.com/nspcc-dev/neo-go v0.99.4 h1:8Y+SdRxksC72a4PNkcGCh/aaQinh9Gu+c5LilbcsXOI= github.com/nspcc-dev/neofs-api-go/v2 v2.11.0-pre.0.20211201134523-3604d96f3fe1 h1:CGA56mhLLduWRuMHcWujP5Ek+gAnXHk0WuIWkG65G1s=
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.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.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.2.3/go.mod h1:8w16GEJbH6791ktVqHN9YRNH3s9BEEKYxGhlFnp0cDw=
github.com/nspcc-dev/neofs-crypto v0.3.0 h1:zlr3pgoxuzrmGCxc5W8dGVfA9Rro8diFvVnBg0L4ifM=
github.com/nspcc-dev/neofs-crypto v0.3.0/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-sdk-go v0.0.0-20211201182451-a5b61c4f6477 h1:JC+jt4ARpMV/L3OqPHBKxAmbMabU7RYl/L4KgBz3yPs=
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-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.1.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso=
github.com/nspcc-dev/rfc6979 v0.2.0 h1:3e1WNxrN60/6N0DW7+UYisLeZJyfqZTNOjeV/toYvOE= github.com/nspcc-dev/rfc6979 v0.2.0 h1:3e1WNxrN60/6N0DW7+UYisLeZJyfqZTNOjeV/toYvOE=
github.com/nspcc-dev/rfc6979 v0.2.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso= github.com/nspcc-dev/rfc6979 v0.2.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso=
@ -292,7 +200,6 @@ 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.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 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 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/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.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -304,10 +211,8 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU=
github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@ -316,20 +221,15 @@ github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6T
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
@ -359,21 +259,12 @@ github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 h1:JwtAtbp7r/7QSyGz8mKUbYJBg2+6Cd7OjM8o/GNOcVo= github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 h1:JwtAtbp7r/7QSyGz8mKUbYJBg2+6Cd7OjM8o/GNOcVo=
github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74/go.mod h1:RmMWU37GKR2s6pgrIEB4ixgpVCt/cf7dnJv3fuH1J1c= github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74/go.mod h1:RmMWU37GKR2s6pgrIEB4ixgpVCt/cf7dnJv3fuH1J1c=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/gopher-lua v0.0.0-20190514113301-1cd887cd7036/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= github.com/yuin/gopher-lua v0.0.0-20190514113301-1cd887cd7036/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
github.com/yuin/gopher-lua v0.0.0-20191128022950-c6266f4fe8d7/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= github.com/yuin/gopher-lua v0.0.0-20191128022950-c6266f4fe8d7/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
@ -390,8 +281,6 @@ go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@ -400,38 +289,13 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -441,56 +305,27 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -498,184 +333,63 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210429154555-c04ba851c2a4 h1:UPou2i3GzKgi6igR+/0C5XyHKBngHxBp/CL5CQ0p3Zk=
golang.org/x/term v0.0.0-20210429154555-c04ba851c2a4/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210429154555-c04ba851c2a4/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180318012157-96caea41033d/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180318012157-96caea41033d/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9 h1:sEvmEcJVKBNUvgCUClbUQeHOAa9U0I2Ce1BooMvVCY4=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w=
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
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.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
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.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 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 h1:f+PlOh7QV4iIJkPrx5NQ7qaNGFQ3OTse67yaDHfju4E=
@ -688,13 +402,10 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/abiosoft/ishell.v2 v2.0.0/go.mod h1:sFp+cGtH6o4s1FtpVPTMcHq2yue+c4DGOVohJCPUzwY= gopkg.in/abiosoft/ishell.v2 v2.0.0/go.mod h1:sFp+cGtH6o4s1FtpVPTMcHq2yue+c4DGOVohJCPUzwY=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -715,16 +426,7 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View file

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

95
neofs/doc.go Normal file
View file

@ -0,0 +1,95 @@
/*
NeoFS contract is a contract deployed in NeoFS main chain.
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 side chain.
While main chain committee controls list of Alphabet nodes in native
RoleManagement contract, NeoFS can't change more than 1\3 keys at a time.
NeoFS contract contains actual list of Alphabet nodes in the side chain.
Network configuration also stored in NeoFS contract. All the changes in
configuration are mirrored in side chain 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 side chain.
Deposit:
- name: from
type: Hash160
- name: amount
type: Integer
- name: receiver
type: Hash160
- name: txHash
type: Hash256
Withdraw notification. This notification is produced when user wants to
withdraw GAS from internal NeoFS balance and has payed 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
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 user wants to bind
public keys with user account (OwnerID). Keys argument is array of ByteArray.
Bind:
- name: user
type: ByteArray
- name: keys
type: Array
Unbind notification. This notification is produced when user wants to unbind
public keys with 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
updated it's list 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

579
neofs/neofs_contract.go Normal file
View file

@ -0,0 +1,579 @@
package neofs
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/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"
"github.com/nspcc-dev/neo-go/pkg/interop/native/roles"
"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 (
record struct {
key []byte
val []byte
}
)
const (
// CandidateFeeConfigKey contains fee for a candidate registration.
CandidateFeeConfigKey = "InnerRingCandidateFee"
withdrawFeeConfigKey = "WithdrawFee"
alphabetKey = "alphabet"
candidatesKey = "candidates"
notaryDisabledKey = "notary"
processingContractKey = "processingScriptHash"
maxBalanceAmount = 9000 // Max integer of Fixed12 in JSON bound (2**53-1)
maxBalanceAmountGAS = maxBalanceAmount * 1_0000_0000
// hardcoded value to ignore deposit notification in onReceive
ignoreDepositNotification = "\x57\x0b"
)
var (
configPrefix = []byte("config")
)
// _deploy sets up initial alphabet node keys.
func _deploy(data interface{}, isUpdate bool) {
if isUpdate {
ctx := storage.GetContext()
nodes := getNodes(ctx, candidatesKey)
storage.Delete(ctx, candidatesKey)
for i := range nodes {
key := append([]byte(candidatesKey), nodes[i].PublicKey...)
storage.Put(ctx, key, []byte{1})
}
return
}
args := data.(struct {
notaryDisabled bool
addrProc interop.Hash160
keys []interop.PublicKey
config [][]byte
})
ctx := storage.GetContext()
var irList []common.IRNode
if len(args.keys) == 0 {
panic("at least one alphabet key must be provided")
}
if len(args.addrProc) != interop.Hash160Len {
panic("incorrect length of contract script hash")
}
for i := 0; i < len(args.keys); i++ {
pub := args.keys[i]
if len(pub) != interop.PublicKeyCompressedLen {
panic("incorrect public key length")
}
irList = append(irList, common.IRNode{PublicKey: pub})
}
// initialize all storage slices
common.SetSerialized(ctx, alphabetKey, irList)
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")
}
for i := 0; i < ln/2; i++ {
key := args.config[i*2]
val := args.config[i*2+1]
setConfig(ctx, key, val)
}
runtime.Log("neofs: contract initialized")
}
// Update method updates contract source code and manifest. Can be invoked
// only by side chain committee.
func Update(script []byte, manifest []byte, data interface{}) {
blockHeight := ledger.CurrentIndex()
alphabetKeys := roles.GetDesignatedByRole(roles.NeoFSAlphabet, uint32(blockHeight))
alphabetCommittee := common.Multiaddress(alphabetKeys, true)
common.CheckAlphabetWitness(alphabetCommittee)
contract.Call(interop.Hash160(management.Hash), "update",
contract.All, script, manifest, common.AppendVersion(data))
runtime.Log("neofs contract updated")
}
// AlphabetList returns array of alphabet node keys. Use in side chain notary
// disabled environment.
func AlphabetList() []common.IRNode {
ctx := storage.GetReadOnlyContext()
return getNodes(ctx, alphabetKey)
}
// AlphabetAddress returns 2\3n+1 multi signature address of alphabet nodes.
// Used in side chain notary disabled environment.
func AlphabetAddress() interop.Hash160 {
ctx := storage.GetReadOnlyContext()
return multiaddress(getNodes(ctx, alphabetKey))
}
// InnerRingCandidates returns array of structures that contain Inner Ring
// candidate node key.
func InnerRingCandidates() []common.IRNode {
ctx := storage.GetReadOnlyContext()
nodes := []common.IRNode{}
it := storage.Find(ctx, candidatesKey, storage.KeysOnly|storage.RemovePrefix)
for iterator.Next(it) {
pub := iterator.Value(it).([]byte)
nodes = append(nodes, common.IRNode{PublicKey: pub})
}
return nodes
}
// InnerRingCandidateRemove removes key from the list of Inner Ring candidates.
// Can be invoked by Alphabet nodes or candidate itself.
//
// 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 []common.IRNode
nodeKey []byte
)
keyOwner := runtime.CheckWitness(key)
if !keyOwner {
if notaryDisabled {
alphabet = getNodes(ctx, alphabetKey)
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")
}
}
}
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 {
storage.Delete(ctx, stKey)
runtime.Log("candidate has been removed")
}
}
// InnerRingCandidateAdd adds key to the list of Inner Ring candidates.
// Can be invoked only by candidate itself.
//
// This method transfers fee from candidate to contract account.
// Fee value specified in NeoFS network config with the key InnerRingCandidateFee.
func InnerRingCandidateAdd(key interop.PublicKey) {
ctx := storage.GetContext()
common.CheckWitness(key)
stKey := append([]byte(candidatesKey), key...)
if storage.Get(ctx, stKey) != nil {
panic("candidate already in the list")
}
from := contract.CreateStandardAccount(key)
to := runtime.GetExecutingScriptHash()
fee := getConfig(ctx, CandidateFeeConfigKey).(int)
transferred := gas.Transfer(from, to, fee, []byte(ignoreDepositNotification))
if !transferred {
panic("failed to transfer funds, aborting")
}
storage.Put(ctx, stKey, []byte{1})
runtime.Log("candidate has been added")
}
// 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
// break JSON limits for integers when precision is converted.
func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) {
rcv := data.(interop.Hash160)
if common.BytesEqual(rcv, []byte(ignoreDepositNotification)) {
return
}
if amount <= 0 {
common.AbortWithMessage("amount must be positive")
} else if maxBalanceAmountGAS < amount {
common.AbortWithMessage("out of max amount limit")
}
caller := runtime.GetCallingScriptHash()
if !common.BytesEqual(caller, interop.Hash160(gas.Hash)) {
common.AbortWithMessage("only GAS can be accepted for deposit")
}
switch len(rcv) {
case 20:
case 0:
rcv = from
default:
common.AbortWithMessage("invalid data argument, expected Hash160")
}
runtime.Log("funds have been transferred")
tx := runtime.GetScriptContainer()
runtime.Notify("Deposit", from, amount, rcv, tx.Hash)
}
// Withdraw initialize gas asset withdraw from NeoFS. Can be invoked only
// by the specified user.
//
// This method produces Withdraw notification to lock assets in side chain and
// transfers withdraw fee from user account to each Alphabet node. If notary
// is enabled in main chain, fee is transferred to Processing contract.
// Fee value specified in NeoFS 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")
}
if amount < 0 {
panic("non positive amount number")
}
if amount > maxBalanceAmount {
panic("out of max amount limit")
}
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 := getNodes(ctx, alphabetKey)
for _, node := range alphabet {
processingAddr := contract.CreateStandardAccount(node.PublicKey)
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")
}
}
// notify alphabet nodes
amount = amount * 100000000
tx := runtime.GetScriptContainer()
runtime.Notify("Withdraw", user, amount, tx.Hash)
}
// Cheque transfers GAS back to the user from contract account, if assets were
// successfully locked in NeoFS balance contract. Can be invoked only by
// Alphabet nodes.
//
// This method produces Cheque notification to burn assets in side chain.
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 []common.IRNode
nodeKey []byte
)
if notaryDisabled {
alphabet = getNodes(ctx, alphabetKey)
nodeKey = common.InnerRingInvoker(alphabet)
if len(nodeKey) == 0 {
panic("this method must be invoked by alphabet")
}
} else {
multiaddr := AlphabetAddress()
common.CheckAlphabetWitness(multiaddr)
}
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")
}
runtime.Log("funds have been transferred")
runtime.Notify("Cheque", id, user, amount, lockAcc)
}
// Bind method produces notification to bind specified public keys in NeoFSID
// contract in side chain. Can be invoked only by specified user.
//
// This method produces Bind notification. Method panics if keys are not
// 33 byte long. User argument must be valid 20 byte script hash.
func Bind(user []byte, keys []interop.PublicKey) {
if !runtime.CheckWitness(user) {
panic("you should be the owner of the wallet")
}
for i := 0; i < len(keys); i++ {
pubKey := keys[i]
if len(pubKey) != interop.PublicKeyCompressedLen {
panic("incorrect public key size")
}
}
runtime.Notify("Bind", user, keys)
}
// Unbind method produces notification to unbind specified public keys in NeoFSID
// contract in side chain. Can be invoked only by specified user.
//
// This method produces Unbind notification. Method panics if keys are not
// 33 byte long. User argument must be valid 20 byte script hash.
func Unbind(user []byte, keys []interop.PublicKey) {
if !runtime.CheckWitness(user) {
panic("you should be the owner of the wallet")
}
for i := 0; i < len(keys); i++ {
pubKey := keys[i]
if len(pubKey) != interop.PublicKeyCompressedLen {
panic("incorrect public key size")
}
}
runtime.Notify("Unbind", user, keys)
}
// AlphabetUpdate updates list of alphabet nodes with provided list of
// public keys. Can be invoked only by alphabet nodes.
//
// This method used in notary disabled side chain environment. In this case
// 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 []common.IRNode
nodeKey []byte
)
if notaryDisabled {
alphabet = getNodes(ctx, alphabetKey)
nodeKey = common.InnerRingInvoker(alphabet)
if len(nodeKey) == 0 {
panic("this method must be invoked by alphabet")
}
} else {
multiaddr := AlphabetAddress()
common.CheckAlphabetWitness(multiaddr)
}
newAlphabet := []common.IRNode{}
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, common.IRNode{
PublicKey: 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 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. 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 []common.IRNode
nodeKey []byte
)
if notaryDisabled {
alphabet = getNodes(ctx, alphabetKey)
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)
}
setConfig(ctx, key, val)
runtime.Notify("SetConfig", id, key, val)
runtime.Log("configuration has been updated")
}
// ListConfig returns array of structures that contain key and value of all
// NeoFS configuration records. Key and value are both byte arrays.
func ListConfig() []record {
ctx := storage.GetReadOnlyContext()
var config []record
it := storage.Find(ctx, configPrefix, storage.None)
for iterator.Next(it) {
pair := iterator.Value(it).(struct {
key []byte
val []byte
})
r := record{key: pair.key[len(configPrefix):], val: pair.val}
config = append(config, r)
}
return config
}
// Version returns version of the contract.
func Version() int {
return common.Version
}
// getNodes returns deserialized slice of nodes from storage.
func getNodes(ctx storage.Context, key string) []common.IRNode {
data := storage.Get(ctx, key)
if data != nil {
return std.Deserialize(data.([]byte)).([]common.IRNode)
}
return []common.IRNode{}
}
// getConfig returns installed neofs configuration value or nil if it is not set.
func getConfig(ctx storage.Context, key interface{}) interface{} {
postfix := key.([]byte)
storageKey := append(configPrefix, postfix...)
return storage.Get(ctx, storageKey)
}
// setConfig sets neofs configuration value in the contract storage.
func setConfig(ctx storage.Context, key, val interface{}) {
postfix := key.([]byte)
storageKey := append(configPrefix, postfix...)
storage.Put(ctx, storageKey, val)
}
// multiaddress returns multi signature address from list of IRNode structures
// with m = 2/3n+1.
func multiaddress(n []common.IRNode) []byte {
threshold := len(n)*2/3 + 1
keys := []interop.PublicKey{}
for _, node := range n {
key := node.PublicKey
keys = append(keys, key)
}
return contract.CreateMultisigAccount(threshold, keys)
}

View file

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

20
neofsid/doc.go Normal file
View file

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

View file

@ -1,13 +1,14 @@
package frostfsid package neofsid
import ( import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop" "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/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator" "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/native/management"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime" "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/storage"
"github.com/nspcc-dev/neofs-contract/common"
) )
type ( type (
@ -30,16 +31,14 @@ const (
func _deploy(data interface{}, isUpdate bool) { func _deploy(data interface{}, isUpdate bool) {
ctx := storage.GetContext() ctx := storage.GetContext()
common.RmAndCheckNotaryDisabledKey(data, notaryDisabledKey)
if isUpdate { if isUpdate {
args := data.([]interface{}) storage.Delete(ctx, "ballots")
common.CheckVersion(args[len(args)-1].(int)) storage.Put(ctx, notaryDisabledKey, false)
return return
} }
args := data.(struct { args := data.(struct {
//TODO(@acid-ant): #9 remove notaryDisabled in future version
notaryDisabled bool notaryDisabled bool
addrNetmap interop.Hash160 addrNetmap interop.Hash160
addrContainer interop.Hash160 addrContainer interop.Hash160
@ -52,10 +51,17 @@ func _deploy(data interface{}, isUpdate bool) {
storage.Put(ctx, netmapContractKey, args.addrNetmap) storage.Put(ctx, netmapContractKey, args.addrNetmap)
storage.Put(ctx, containerContractKey, args.addrContainer) storage.Put(ctx, containerContractKey, args.addrContainer)
runtime.Log("frostfsid contract initialized") // initialize the way to collect signatures
storage.Put(ctx, notaryDisabledKey, args.notaryDisabled)
if args.notaryDisabled {
common.InitVote(ctx)
runtime.Log("neofsid contract notary disabled")
} }
// Update method updates contract source code and manifest. It can be invoked runtime.Log("neofsid contract initialized")
}
// Update method updates contract source code and manifest. Can be invoked
// only by committee. // only by committee.
func Update(script []byte, manifest []byte, data interface{}) { func Update(script []byte, manifest []byte, data interface{}) {
if !common.HasUpdateAccess() { if !common.HasUpdateAccess() {
@ -64,14 +70,14 @@ func Update(script []byte, manifest []byte, data interface{}) {
contract.Call(interop.Hash160(management.Hash), "update", contract.Call(interop.Hash160(management.Hash), "update",
contract.All, script, manifest, common.AppendVersion(data)) contract.All, script, manifest, common.AppendVersion(data))
runtime.Log("frostfsid contract updated") runtime.Log("neofsid contract updated")
} }
// AddKey binds a list of the provided public keys to the OwnerID. It can be invoked only by // AddKey binds list of provided public keys to OwnerID. Can be invoked only by
// Alphabet nodes. // Alphabet nodes.
// //
// This method panics if the OwnerID is not an ownerSize byte or the public key is not 33 byte long. // This method panics if OwnerID is not ownerSize byte or public key is not 33 byte long.
// If the key is already bound, the method ignores it. // If key is already bound, ignores it.
func AddKey(owner []byte, keys []interop.PublicKey) { func AddKey(owner []byte, keys []interop.PublicKey) {
// V2 format // V2 format
if len(owner) != ownerSize { if len(owner) != ownerSize {
@ -85,8 +91,41 @@ func AddKey(owner []byte, keys []interop.PublicKey) {
} }
ctx := storage.GetContext() ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
common.CheckAlphabetWitness() var ( // for invocation collection without notary
alphabet []common.IRNode
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)
}
ownerKey := append([]byte{ownerKeysPrefix}, owner...) ownerKey := append([]byte{ownerKeysPrefix}, owner...)
for i := range keys { for i := range keys {
@ -97,11 +136,11 @@ func AddKey(owner []byte, keys []interop.PublicKey) {
runtime.Log("key bound to the owner") runtime.Log("key bound to the owner")
} }
// RemoveKey unbinds the provided public keys from the OwnerID. It can be invoked only by // RemoveKey unbinds provided public keys from OwnerID. Can be invoked only by
// Alphabet nodes. // Alphabet nodes.
// //
// This method panics if the OwnerID is not an ownerSize byte or the public key is not 33 byte long. // This method panics if OwnerID is not ownerSize byte or public key is not 33 byte long.
// If the key is already unbound, the method ignores it. // If key is already unbound, ignores it.
func RemoveKey(owner []byte, keys []interop.PublicKey) { func RemoveKey(owner []byte, keys []interop.PublicKey) {
// V2 format // V2 format
if len(owner) != ownerSize { if len(owner) != ownerSize {
@ -115,11 +154,35 @@ func RemoveKey(owner []byte, keys []interop.PublicKey) {
} }
ctx := storage.GetContext() ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
var ( // for invocation collection without notary
alphabet []common.IRNode
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() multiaddr := common.AlphabetAddress()
if !runtime.CheckWitness(multiaddr) { if !runtime.CheckWitness(multiaddr) {
panic("invocation from non inner ring node") panic("invocation from non inner ring node")
} }
}
ownerKey := append([]byte{ownerKeysPrefix}, owner...) ownerKey := append([]byte{ownerKeysPrefix}, owner...)
for i := range keys { for i := range keys {
@ -128,9 +191,9 @@ func RemoveKey(owner []byte, keys []interop.PublicKey) {
} }
} }
// Key method returns a list of 33-byte public keys bound with the OwnerID. // Key method returns list of 33-byte public keys bound with OwnerID.
// //
// This method panics if the owner is not ownerSize byte long. // This method panics if owner is not ownerSize byte long.
func Key(owner []byte) [][]byte { func Key(owner []byte) [][]byte {
// V2 format // V2 format
if len(owner) != ownerSize { if len(owner) != ownerSize {
@ -145,7 +208,7 @@ func Key(owner []byte) [][]byte {
return info.Keys return info.Keys
} }
// Version returns the version of the contract. // Version returns version of the contract.
func Version() int { func Version() int {
return common.Version return common.Version
} }
@ -160,3 +223,12 @@ func getUserInfo(ctx storage.Context, key interface{}) UserInfo {
return UserInfo{Keys: pubs} 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)
}

View file

@ -1,5 +1,5 @@
name: "Netmap" name: "NeoFS Netmap"
safemethods: ["epoch", "netmap", "netmapCandidates", "snapshot", "snapshotByEpoch", "config", "listConfig", "version"] safemethods: ["innerRingList", "epoch", "netmap", "netmapCandidates", "snapshot", "snapshotByEpoch", "config", "listConfig", "version"]
permissions: permissions:
- methods: ["update", "newEpoch"] - methods: ["update", "newEpoch"]
events: events:
@ -7,22 +7,12 @@ events:
parameters: parameters:
- name: nodeInfo - name: nodeInfo
type: ByteArray type: ByteArray
- name: AddPeerSuccess
parameters:
- name: publicKey
type: PublicKey
- name: UpdateState - name: UpdateState
parameters: parameters:
- name: state - name: state
type: Integer type: Integer
- name: publicKey - name: publicKey
type: PublicKey type: PublicKey
- name: UpdateStateSuccess
parameters:
- name: publicKey
type: PublicKey
- name: state
type: Integer
- name: NewEpoch - name: NewEpoch
parameters: parameters:
- name: epoch - name: epoch

View file

@ -1,20 +1,21 @@
/* /*
Netmap contract is a contract deployed in FrostFS sidechain. Netmap contract is a contract deployed in NeoFS side chain.
Netmap contract stores and manages FrostFS network map, Storage node candidates Netmap contract stores and manages NeoFS network map, Storage node candidates
and epoch number counter. and epoch number counter. In notary disabled environment, contract also stores
list of Inner Ring node keys.
# Contract notifications Contract notifications
AddPeer notification. This notification is produced when a Storage node sends AddPeer notification. This notification is produced when Storage node sends
a bootstrap request by invoking AddPeer method. bootstrap request by invoking AddPeer method.
AddPeer AddPeer
- name: nodeInfo - name: nodeInfo
type: ByteArray type: ByteArray
UpdateState notification. This notification is produced when a Storage node wants UpdateState notification. This notification is produced when Storage node wants
to change its state (go offline) by invoking UpdateState method. Supported to change it's state (go offline) by invoking UpdateState method. Supported
states: (2) -- offline. states: (2) -- offline.
UpdateState UpdateState
@ -23,7 +24,7 @@ states: (2) -- offline.
- name: publicKey - name: publicKey
type: PublicKey type: PublicKey
NewEpoch notification. This notification is produced when a new epoch is applied NewEpoch notification. This notification is produced when new epoch is applied
in the network by invoking NewEpoch method. in the network by invoking NewEpoch method.
NewEpoch NewEpoch

View file

@ -1,57 +1,42 @@
package netmap package netmap
import ( import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop" "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/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator" "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/ledger"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management" "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/native/std"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime" "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/storage"
"github.com/nspcc-dev/neofs-contract/common"
) )
// NodeState is an enumeration for node states. type (
type NodeState int storageNode struct {
info []byte
// Various Node states
const (
_ NodeState = iota
// NodeStateOnline stands for nodes that are in full network and
// operational availability.
NodeStateOnline
// NodeStateOffline stands for nodes that are in network unavailability.
NodeStateOffline
// NodeStateMaintenance stands for nodes under maintenance with partial
// network availability.
NodeStateMaintenance
)
// 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 FrostFS binary
// protocol.
BLOB []byte
// Current node state.
State NodeState
} }
netmapNode struct {
node storageNode
state nodeState
}
nodeState int
record struct {
key []byte
val []byte
}
)
const ( const (
notaryDisabledKey = "notary" notaryDisabledKey = "notary"
innerRingKey = "innerring" innerRingKey = "innerring"
// DefaultSnapshotCount contains the number of previous snapshots stored by this contract. snapshot0Key = "snapshotCurrent"
// Must be less than 255. snapshot1Key = "snapshotPrevious"
DefaultSnapshotCount = 10
snapshotCountKey = "snapshotCount"
snapshotKeyPrefix = "snapshot_"
snapshotCurrentIDKey = "snapshotCurrent"
snapshotEpoch = "snapshotEpoch" snapshotEpoch = "snapshotEpoch"
snapshotBlockKey = "snapshotBlock" snapshotBlockKey = "snapshotBlock"
@ -61,6 +46,13 @@ const (
cleanupEpochMethod = "newEpoch" cleanupEpochMethod = "newEpoch"
) )
const (
// V2 format
_ nodeState = iota
onlineState
offlineState
)
var ( var (
configPrefix = []byte("config") configPrefix = []byte("config")
candidatePrefix = []byte("candidate") candidatePrefix = []byte("candidate")
@ -70,16 +62,12 @@ var (
func _deploy(data interface{}, isUpdate bool) { func _deploy(data interface{}, isUpdate bool) {
ctx := storage.GetContext() ctx := storage.GetContext()
common.RmAndCheckNotaryDisabledKey(data, notaryDisabledKey)
var args = data.(struct { var args = data.(struct {
//TODO(@acid-ant): #9 remove notaryDisabled in future version
notaryDisabled bool notaryDisabled bool
addrBalance interop.Hash160 addrBalance interop.Hash160
addrContainer interop.Hash160 addrContainer interop.Hash160
keys []interop.PublicKey keys []interop.PublicKey
config [][]byte config [][]byte
version int
}) })
ln := len(args.config) ln := len(args.config)
@ -95,7 +83,9 @@ func _deploy(data interface{}, isUpdate bool) {
} }
if isUpdate { if isUpdate {
common.CheckVersion(args.version) storage.Delete(ctx, "ballots")
storage.Put(ctx, notaryDisabledKey, false)
return return
} }
@ -104,23 +94,34 @@ func _deploy(data interface{}, isUpdate bool) {
} }
// epoch number is a little endian int, it doesn't need to be serialized // epoch number is a little endian int, it doesn't need to be serialized
storage.Put(ctx, snapshotCountKey, DefaultSnapshotCount)
storage.Put(ctx, snapshotEpoch, 0) storage.Put(ctx, snapshotEpoch, 0)
storage.Put(ctx, snapshotBlockKey, 0) storage.Put(ctx, snapshotBlockKey, 0)
prefix := []byte(snapshotKeyPrefix) common.SetSerialized(ctx, snapshot0Key, []netmapNode{})
for i := 0; i < DefaultSnapshotCount; i++ { common.SetSerialized(ctx, snapshot1Key, []netmapNode{})
common.SetSerialized(ctx, append(prefix, byte(i)), []Node{})
}
storage.Put(ctx, snapshotCurrentIDKey, 0)
storage.Put(ctx, balanceContractKey, args.addrBalance) storage.Put(ctx, balanceContractKey, args.addrBalance)
storage.Put(ctx, containerContractKey, args.addrContainer) storage.Put(ctx, containerContractKey, args.addrContainer)
// initialize the way to collect signatures
storage.Put(ctx, notaryDisabledKey, args.notaryDisabled)
if args.notaryDisabled {
var irList []common.IRNode
for i := 0; i < len(args.keys); i++ {
key := args.keys[i]
irList = append(irList, common.IRNode{PublicKey: key})
}
common.SetSerialized(ctx, innerRingKey, irList)
common.InitVote(ctx)
runtime.Log("netmap contract notary disabled")
}
runtime.Log("netmap contract initialized") runtime.Log("netmap contract initialized")
} }
// Update method updates contract source code and manifest. It can be invoked // Update method updates contract source code and manifest. Can be invoked
// only by committee. // only by committee.
func Update(script []byte, manifest []byte, data interface{}) { func Update(script []byte, manifest []byte, data interface{}) {
if !common.HasUpdateAccess() { if !common.HasUpdateAccess() {
@ -132,126 +133,234 @@ func Update(script []byte, manifest []byte, data interface{}) {
runtime.Log("netmap contract updated") runtime.Log("netmap contract updated")
} }
// AddPeerIR accepts Alphabet calls in the notary-enabled contract setting and // InnerRingList method returns slice of structures that contains public key of
// behaves similar to AddPeer in the notary-disabled one. // Inner Ring node. Should be used only in notary disabled environment.
// //
// AddPeerIR MUST NOT be called in notary-disabled contract setting. // If notary enabled, then look to NeoFSAlphabet role in native RoleManagement
// AddPeerIR MUST be called by the Alphabet member only. // contract of the side chain.
func AddPeerIR(nodeInfo []byte) { func InnerRingList() []common.IRNode {
ctx := storage.GetContext() ctx := storage.GetReadOnlyContext()
return getIRNodes(ctx)
common.CheckAlphabetWitness()
publicKey := nodeInfo[2:35] // V2 format: offset:2, len:33
addToNetmap(ctx, publicKey, Node{
BLOB: nodeInfo,
State: NodeStateOnline,
})
} }
// AddPeer accepts information about the network map candidate in the FrostFS // UpdateInnerRing method updates list of Inner Ring node keys. Should be used
// binary protocol format and does nothing. Keep method because storage node // only in notary disabled environment. Can be invoked only by Alphabet nodes.
// creates a notary transaction with this method, which produces a notary //
// notification (implicit here). // If notary enabled, then update NeoFSAlphabet role in native RoleManagement
func AddPeer(nodeInfo []byte) { // contract of the side chain. Use notary service to collect multi signature.
// V2 format - offset:2, len:33 func UpdateInnerRing(keys []interop.PublicKey) {
common.CheckWitness(nodeInfo[2:35]) ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
var ( // for invocation collection without notary
alphabet []common.IRNode
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)
}
var irList []common.IRNode
for i := 0; i < len(keys); i++ {
key := keys[i]
irList = append(irList, common.IRNode{PublicKey: key})
}
if notaryDisabled {
threshold := len(alphabet)*2/3 + 1
id := keysID(keys, []byte("updateIR"))
n := common.Vote(ctx, id, nodeKey)
if n < threshold {
return return
} }
// updates state of the network map candidate by its public key in the contract common.RemoveVotes(ctx, id)
// storage, and throws UpdateStateSuccess notification after this.
//
// State MUST be from the NodeState enum.
func updateCandidateState(ctx storage.Context, publicKey interop.PublicKey, state NodeState) {
switch state {
case NodeStateOffline:
removeFromNetmap(ctx, publicKey)
runtime.Log("remove storage node from the network map")
case NodeStateOnline, NodeStateMaintenance:
updateNetmapState(ctx, publicKey, state)
runtime.Log("update state of the network map candidate")
default:
panic("unsupported state")
} }
runtime.Notify("UpdateStateSuccess", publicKey, state) runtime.Log("inner ring list updated")
common.SetSerialized(ctx, innerRingKey, irList)
} }
// UpdateState accepts new state to be assigned to network map candidate // Register method tries to add new candidate to the network map by
// identified by the given public key, identifies the signer. // emitting AddPeer notification. Should be invoked by the registree.
// Applicable only for notary-enabled environment. func Register(nodeInfo []byte) {
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
if notaryDisabled {
panic("Register should only be called in notary-enabled environment")
}
common.CheckAlphabetWitness(common.AlphabetAddress())
addToNetmap(ctx, storageNode{info: nodeInfo})
return
}
// AddPeer method adds new candidate to the next network map if it was invoked
// by Alphabet node. If it was invoked by node candidate, it produces AddPeer
// notification. Otherwise method throws panic.
// //
// Signers: // If the candidate already exists, it's info is updated.
// NodeInfo argument contains stable marshaled version of netmap.NodeInfo
// structure.
func AddPeer(nodeInfo []byte) {
ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
var ( // for invocation collection without notary
alphabet []common.IRNode
nodeKey []byte
)
if notaryDisabled {
alphabet = common.AlphabetNodes()
nodeKey = common.InnerRingInvoker(alphabet)
}
// If notary is enabled or caller is not an alphabet node,
// just emit the notification for alphabet.
if !notaryDisabled || len(nodeKey) == 0 {
// V2 format
publicKey := nodeInfo[2:35] // offset:2, len:33
common.CheckWitness(publicKey)
runtime.Notify("AddPeer", nodeInfo)
return
}
candidate := storageNode{
info: nodeInfo,
}
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, candidate)
}
// UpdateState method updates state of node from the network map candidate list
// if it was invoked by Alphabet node. If it was invoked by public key owner,
// then it produces UpdateState notification. Otherwise method throws panic.
// //
// (a) candidate himself only, if provided public key corresponds to the signer // State argument defines node state. The only supported state now is (2) --
// (b) Alphabet member only // offline state. Node is removed from network map candidate list.
// (ab) both candidate and Alphabet member
// (c) others
// //
// UpdateState case-by-case behavior: // Method panics when invoked with unsupported states.
// func UpdateState(state int, publicKey interop.PublicKey) {
// (a) panics
// (b) panics
// (ab) updates candidate's state in the contract storage (*), and throws
// UpdateStateSuccess with the provided key and new state
// (c) panics
//
// (*) Candidate is removed from the candidate set if state is NodeStateOffline.
// Any other state is written into candidate's descriptor in the contract storage.
// If requested candidate is missing, panic occurs. Throws UpdateStateSuccess
// notification on success.
//
// State MUST be from the NodeState enum. Public key MUST be
// interop.PublicKeyCompressedLen bytes.
func UpdateState(state NodeState, publicKey interop.PublicKey) {
if len(publicKey) != interop.PublicKeyCompressedLen { if len(publicKey) != interop.PublicKeyCompressedLen {
panic("incorrect public key") panic("incorrect public key")
} }
ctx := storage.GetContext() ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
var ( // for invocation collection without notary
alphabet []common.IRNode
nodeKey []byte
)
if notaryDisabled {
alphabet = common.AlphabetNodes()
nodeKey = common.InnerRingInvoker(alphabet)
if len(nodeKey) == 0 {
common.CheckWitness(publicKey) common.CheckWitness(publicKey)
common.CheckAlphabetWitness()
updateCandidateState(ctx, publicKey, state) runtime.Notify("UpdateState", state, publicKey)
return
} }
// UpdateStateIR accepts Alphabet calls in the notary-enabled contract setting threshold := len(alphabet)*2/3 + 1
// and behaves similar to UpdateState, but does not require candidate's id := common.InvokeID([]interface{}{state, publicKey}, []byte("update"))
// signature presence.
//
// UpdateStateIR MUST NOT be called in notary-disabled contract setting.
// UpdateStateIR MUST be called by the Alphabet member only.
func UpdateStateIR(state NodeState, publicKey interop.PublicKey) {
ctx := storage.GetContext()
common.CheckAlphabetWitness() n := common.Vote(ctx, id, nodeKey)
if n < threshold {
updateCandidateState(ctx, publicKey, state) return
} }
// NewEpoch method changes the epoch number up to the provided epochNum argument. It can common.RemoveVotes(ctx, id)
// be invoked only by Alphabet nodes. If provided epoch number is less than the } else {
// current epoch number or equals it, the method throws panic. multiaddr := common.AlphabetAddress()
common.CheckWitness(publicKey)
common.CheckAlphabetWitness(multiaddr)
}
switch nodeState(state) {
case offlineState:
removeFromNetmap(ctx, publicKey)
runtime.Log("remove storage node from the network map")
default:
panic("unsupported state")
}
}
// NewEpoch method changes epoch number up to provided epochNum argument. Can
// be invoked only by Alphabet nodes. If provided epoch number is less or equal
// current epoch number, method throws panic.
// //
// When epoch number is updated, the contract sets storage node candidates as the current // When epoch number updated, contract sets storage node candidates as current
// network map. The contract also invokes NewEpoch method on Balance and Container // network map. Also contract invokes NewEpoch method on Balance and Container
// contracts. // contracts.
// //
// It produces NewEpoch notification. // Produces NewEpoch notification.
func NewEpoch(epochNum int) { func NewEpoch(epochNum int) {
ctx := storage.GetContext() ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
common.CheckAlphabetWitness() var ( // for invocation collection without notary
alphabet []common.IRNode
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)
}
currentEpoch := storage.Get(ctx, snapshotEpoch).(int) currentEpoch := storage.Get(ctx, snapshotEpoch).(int)
if epochNum <= currentEpoch { if epochNum <= currentEpoch {
panic("invalid epoch") // ignore invocations with invalid epoch panic("invalid epoch") // ignore invocations with invalid epoch
} }
dataOnlineState := filterNetmap(ctx) data0snapshot := getSnapshot(ctx, snapshot0Key)
dataOnlineState := filterNetmap(ctx, onlineState)
runtime.Log("process new epoch") runtime.Log("process new epoch")
@ -259,12 +368,11 @@ func NewEpoch(epochNum int) {
storage.Put(ctx, snapshotEpoch, epochNum) storage.Put(ctx, snapshotEpoch, epochNum)
storage.Put(ctx, snapshotBlockKey, ledger.CurrentIndex()) storage.Put(ctx, snapshotBlockKey, ledger.CurrentIndex())
id := storage.Get(ctx, snapshotCurrentIDKey).(int) // put actual snapshot into previous snapshot
id = (id + 1) % getSnapshotCount(ctx) common.SetSerialized(ctx, snapshot1Key, data0snapshot)
storage.Put(ctx, snapshotCurrentIDKey, id)
// put netmap into actual snapshot // put netmap into actual snapshot
common.SetSerialized(ctx, snapshotKeyPrefix+string([]byte{byte(id)}), dataOnlineState) common.SetSerialized(ctx, snapshot0Key, dataOnlineState)
// make clean up routines in other contracts // make clean up routines in other contracts
cleanup(ctx, epochNum) cleanup(ctx, epochNum)
@ -272,190 +380,116 @@ func NewEpoch(epochNum int) {
runtime.Notify("NewEpoch", epochNum) runtime.Notify("NewEpoch", epochNum)
} }
// Epoch method returns the current epoch number. // Epoch method returns current epoch number.
func Epoch() int { func Epoch() int {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
return storage.Get(ctx, snapshotEpoch).(int) return storage.Get(ctx, snapshotEpoch).(int)
} }
// LastEpochBlock method returns the block number when the current epoch was applied. // LastEpochBlock method returns block number when current epoch was applied.
func LastEpochBlock() int { func LastEpochBlock() int {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
return storage.Get(ctx, snapshotBlockKey).(int) return storage.Get(ctx, snapshotBlockKey).(int)
} }
// Netmap returns set of information about the storage nodes representing a network // Netmap method returns list of structures that contain byte array of stable
// map in the current epoch. // marshalled netmap.NodeInfo structure. These structure contain Storage nodes
// // of current epoch.
// Current state of each node is represented in the State field. It MAY differ func Netmap() []storageNode {
// with the state encoded into BLOB field, in this case binary encoded state
// MUST NOT be processed.
func Netmap() []Node {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
id := storage.Get(ctx, snapshotCurrentIDKey).(int) return getSnapshot(ctx, snapshot0Key)
return getSnapshot(ctx, snapshotKeyPrefix+string([]byte{byte(id)}))
} }
// NetmapCandidates returns set of information about the storage nodes // Snapshot method returns list of structures that contain node state
// representing candidates for the network map in the coming epoch. // and byte array of stable marshalled netmap.NodeInfo structure.
// // These structure contain Storage node candidates for next epoch.
// Current state of each node is represented in the State field. It MAY differ func NetmapCandidates() []netmapNode {
// with the state encoded into BLOB field, in this case binary encoded state
// MUST NOT be processed.
func NetmapCandidates() []Node {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
return getNetmapNodes(ctx) return getNetmapNodes(ctx)
} }
// Snapshot returns set of information about the storage nodes representing a network // Snapshot method returns list of structures that contain node state
// map in (current-diff)-th epoch. // (online: 1) and byte array of stable marshalled netmap.NodeInfo structure.
// These structure contain Storage nodes of specified epoch.
// //
// Diff MUST NOT be negative. Diff MUST be less than maximum number of network // Netmap contract contains only two recent network map snapshot: current and
// map snapshots stored in the contract. The limit is a contract setting, // previous epoch. For diff bigger than 1 or less than 0 method throws panic.
// DefaultSnapshotCount by default. See UpdateSnapshotCount for details. func Snapshot(diff int) []storageNode {
// var key string
// Current state of each node is represented in the State field. It MAY differ
// with the state encoded into BLOB field, in this case binary encoded state switch diff {
// MUST NOT be processed. case 0:
func Snapshot(diff int) []Node { key = snapshot0Key
ctx := storage.GetReadOnlyContext() case 1:
count := getSnapshotCount(ctx) key = snapshot1Key
if diff < 0 || count <= diff { default:
panic("incorrect diff") panic("incorrect diff")
} }
id := storage.Get(ctx, snapshotCurrentIDKey).(int) ctx := storage.GetReadOnlyContext()
needID := (id - diff + count) % count
key := snapshotKeyPrefix + string([]byte{byte(needID)})
return getSnapshot(ctx, key) return getSnapshot(ctx, key)
} }
func getSnapshotCount(ctx storage.Context) int { // SnapshotByEpoch method returns list of structures that contain node state
return storage.Get(ctx, snapshotCountKey).(int) // (online: 1) and byte array of stable marshalled netmap.NodeInfo structure.
} // These structure contain Storage nodes of specified epoch.
// UpdateSnapshotCount updates the number of the stored snapshots.
// If a new number is less than the old one, old snapshots are removed.
// Otherwise, history is extended with empty snapshots, so
// `Snapshot` method can return invalid results for `diff = new-old` epochs
// until `diff` epochs have passed.
// //
// Count MUST NOT be negative. // Netmap contract contains only two recent network map snapshot: current and
func UpdateSnapshotCount(count int) { // previous epoch. For all others epoch method throws panic.
common.CheckAlphabetWitness() func SnapshotByEpoch(epoch int) []storageNode {
if count < 0 {
panic("count must be positive")
}
ctx := storage.GetContext()
curr := getSnapshotCount(ctx)
if curr == count {
panic("count has not changed")
}
storage.Put(ctx, snapshotCountKey, count)
id := storage.Get(ctx, snapshotCurrentIDKey).(int)
var delStart, delFinish int
if curr < count {
// Increase history size.
//
// Old state (N = count, K = curr, E = current index, C = current epoch)
// KEY INDEX: 0 | 1 | ... | E | E+1 | ... | K-1 | ... | N-1
// EPOCH : C-E | C-E+1 | ... | C | C-K+1 | ... | C-E-1 |
//
// New state:
// KEY INDEX: 0 | 1 | ... | E | E+1 | ... | K-1 | ... | N-1
// EPOCH : C-E | C-E+1 | ... | C | nil | ... | . | ... | C-E-1
//
// So we need to move tail snapshots N-K keys forward,
// i.e. from E+1 .. K to N-K+E+1 .. N
diff := count - curr
lower := diff + id + 1
for k := count - 1; k >= lower; k-- {
moveSnapshot(ctx, k-diff, k)
}
delStart, delFinish = id+1, id+1+diff
if curr < delFinish {
delFinish = curr
}
} else {
// Decrease history size.
//
// Old state (N = curr, K = count)
// KEY INDEX: 0 | 1 | ... K1 ... | E | E+1 | ... K2-1 ... | N-1
// EPOCH : C-E | C-E+1 | ... .. ... | C | C-N+1 | ... ... ... | C-E-1
var step, start int
if id < count {
// K2 case, move snapshots from E+1+N-K .. N-1 range to E+1 .. K-1
// New state:
// KEY INDEX: 0 | 1 | ... | E | E+1 | ... | K-1
// EPOCH : C-E | C-E+1 | ... | C | C-K+1 | ... | C-E-1
step = curr - count
start = id + 1
} else {
// New state:
// KEY INDEX: 0 | 1 | ... | K-1
// EPOCH : C-K+1 | C-K+2 | ... | C
// K1 case, move snapshots from E-K+1 .. E range to 0 .. K-1
// AND replace current id with K-1
step = id - count + 1
storage.Put(ctx, snapshotCurrentIDKey, count-1)
}
for k := start; k < count; k++ {
moveSnapshot(ctx, k+step, k)
}
delStart, delFinish = count, curr
}
for k := delStart; k < delFinish; k++ {
key := snapshotKeyPrefix + string([]byte{byte(k)})
storage.Delete(ctx, key)
}
}
func moveSnapshot(ctx storage.Context, from, to int) {
keyFrom := snapshotKeyPrefix + string([]byte{byte(from)})
keyTo := snapshotKeyPrefix + string([]byte{byte(to)})
data := storage.Get(ctx, keyFrom)
storage.Put(ctx, keyTo, data)
}
// SnapshotByEpoch returns set of information about the storage nodes representing
// a network map in the given epoch.
//
// Behaves like Snapshot: it is called after difference with the current epoch is
// calculated.
func SnapshotByEpoch(epoch int) []Node {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
currentEpoch := storage.Get(ctx, snapshotEpoch).(int) currentEpoch := storage.Get(ctx, snapshotEpoch).(int)
return Snapshot(currentEpoch - epoch) return Snapshot(currentEpoch - epoch)
} }
// Config returns configuration value of FrostFS configuration. If key does // Config returns configuration value of NeoFS configuration. If key does
// not exists, returns nil. // not exists, returns nil.
func Config(key []byte) interface{} { func Config(key []byte) interface{} {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
return getConfig(ctx, key) return getConfig(ctx, key)
} }
// SetConfig key-value pair as a FrostFS runtime configuration value. It can be invoked // SetConfig key-value pair as a NeoFS runtime configuration value. Can be invoked
// only by Alphabet nodes. // only by Alphabet nodes.
func SetConfig(id, key, val []byte) { func SetConfig(id, key, val []byte) {
ctx := storage.GetContext() ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
common.CheckAlphabetWitness() var ( // for invocation collection without notary
alphabet []common.IRNode
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)
}
setConfig(ctx, key, val) setConfig(ctx, key, val)
runtime.Log("configuration has been updated") runtime.Log("configuration has been updated")
} }
type record struct { // ListConfig returns array of structures that contain key and value of all
key []byte // NeoFS configuration records. Key and value are both byte arrays.
val []byte
}
// ListConfig returns an array of structures that contain key and value of all
// FrostFS configuration records. Key and value are both byte arrays.
func ListConfig() []record { func ListConfig() []record {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
@ -475,20 +509,24 @@ func ListConfig() []record {
return config return config
} }
// Version returns the version of the contract. // Version returns version of the contract.
func Version() int { func Version() int {
return common.Version return common.Version
} }
// serializes and stores the given Node by its public key in the contract storage, func addToNetmap(ctx storage.Context, n storageNode) {
// and throws AddPeerSuccess notification after this. var (
// newNode = n.info
// Public key MUST match the one encoded in BLOB field. newNodeKey = newNode[2:35]
func addToNetmap(ctx storage.Context, publicKey []byte, node Node) { storageKey = append(candidatePrefix, newNodeKey...)
storageKey := append(candidatePrefix, publicKey...)
storage.Put(ctx, storageKey, std.Serialize(node))
runtime.Notify("AddPeerSuccess", interop.PublicKey(publicKey)) node = netmapNode{
node: n,
state: onlineState,
}
)
storage.Put(ctx, storageKey, std.Serialize(node))
} }
func removeFromNetmap(ctx storage.Context, key interop.PublicKey) { func removeFromNetmap(ctx storage.Context, key interop.PublicKey) {
@ -496,52 +534,41 @@ func removeFromNetmap(ctx storage.Context, key interop.PublicKey) {
storage.Delete(ctx, storageKey) storage.Delete(ctx, storageKey)
} }
func updateNetmapState(ctx storage.Context, key interop.PublicKey, state NodeState) { func filterNetmap(ctx storage.Context, st nodeState) []storageNode {
storageKey := append(candidatePrefix, key...)
raw := storage.Get(ctx, storageKey).([]byte)
if raw == nil {
panic("peer is missing")
}
node := std.Deserialize(raw).(Node)
node.State = state
storage.Put(ctx, storageKey, std.Serialize(node))
}
func filterNetmap(ctx storage.Context) []Node {
var ( var (
netmap = getNetmapNodes(ctx) netmap = getNetmapNodes(ctx)
result = []Node{} result = []storageNode{}
) )
for i := 0; i < len(netmap); i++ { for i := 0; i < len(netmap); i++ {
item := netmap[i] item := netmap[i]
if item.State != NodeStateOffline { if item.state == st {
result = append(result, item) result = append(result, item.node)
} }
} }
return result return result
} }
func getNetmapNodes(ctx storage.Context) []Node { func getNetmapNodes(ctx storage.Context) []netmapNode {
result := []Node{} result := []netmapNode{}
it := storage.Find(ctx, candidatePrefix, storage.ValuesOnly|storage.DeserializeValues) it := storage.Find(ctx, candidatePrefix, storage.ValuesOnly|storage.DeserializeValues)
for iterator.Next(it) { for iterator.Next(it) {
node := iterator.Value(it).(Node) node := iterator.Value(it).(netmapNode)
result = append(result, node) result = append(result, node)
} }
return result return result
} }
func getSnapshot(ctx storage.Context, key string) []Node { func getSnapshot(ctx storage.Context, key string) []storageNode {
data := storage.Get(ctx, key) data := storage.Get(ctx, key)
if data != nil { if data != nil {
return std.Deserialize(data.([]byte)).([]Node) return std.Deserialize(data.([]byte)).([]storageNode)
} }
return []Node{} return []storageNode{}
} }
func getConfig(ctx storage.Context, key interface{}) interface{} { func getConfig(ctx storage.Context, key interface{}) interface{} {
@ -565,3 +592,26 @@ func cleanup(ctx storage.Context, epoch int) {
containerContractAddr := storage.Get(ctx, containerContractKey).(interop.Hash160) containerContractAddr := storage.Get(ctx, containerContractKey).(interop.Hash160)
contract.Call(containerContractAddr, cleanupEpochMethod, contract.All, epoch) contract.Call(containerContractAddr, cleanupEpochMethod, contract.All, epoch)
} }
func getIRNodes(ctx storage.Context) []common.IRNode {
data := storage.Get(ctx, innerRingKey)
if data != nil {
return std.Deserialize(data.([]byte)).([]common.IRNode)
}
return []common.IRNode{}
}
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)
}

View file

@ -9,13 +9,13 @@ import (
type NameState struct { type NameState struct {
Owner interop.Hash160 Owner interop.Hash160
Name string Name string
Expiration int64 Expiration int
Admin interop.Hash160 Admin interop.Hash160
} }
// ensureNotExpired panics if domain name is expired. // ensureNotExpired panics if domain name is expired.
func (n NameState) ensureNotExpired() { func (n NameState) ensureNotExpired() {
if int64(runtime.GetTime()) >= n.Expiration { if runtime.GetTime() >= n.Expiration {
panic("name has expired") panic("name has expired")
} }
} }

View file

@ -4,12 +4,11 @@ implementation. This token is a compatible analogue of C# Neo Name Service
token and is aimed to serve as a domain name service for Neo smart-contracts, token and is aimed to serve as a domain name service for Neo smart-contracts,
thus it's NeoNameService. This token can be minted with new domain name thus it's NeoNameService. This token can be minted with new domain name
registration, the domain name itself is your NFT. Corresponding domain root registration, the domain name itself is your NFT. Corresponding domain root
must be added by committee before a new domain name can be registered. must be added by the committee before new domain name can be registered.
*/ */
package nns package nns
import ( import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop" "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/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator" "github.com/nspcc-dev/neo-go/pkg/interop/iterator"
@ -20,13 +19,14 @@ import (
"github.com/nspcc-dev/neo-go/pkg/interop/runtime" "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/storage"
"github.com/nspcc-dev/neo-go/pkg/interop/util" "github.com/nspcc-dev/neo-go/pkg/interop/util"
"github.com/nspcc-dev/neofs-contract/common"
) )
// Prefixes used for contract data storage. // Prefixes used for contract data storage.
const ( const (
// prefixTotalSupply contains total supply of minted domains. // prefixTotalSupply contains total supply of minted domains.
prefixTotalSupply byte = 0x00 prefixTotalSupply byte = 0x00
// prefixBalance contains map from the owner to their balance. // prefixBalance contains map from owner to his balance.
prefixBalance byte = 0x01 prefixBalance byte = 0x01
// prefixAccountToken contains map from (owner + token key) to token ID, // prefixAccountToken contains map from (owner + token key) to token ID,
// where token key = hash160(token ID) and token ID = domain name. // where token key = hash160(token ID) and token ID = domain name.
@ -46,11 +46,11 @@ const (
// Values constraints. // Values constraints.
const ( const (
// maxRegisterPrice is the maximum price of register method. // maxRegisterPrice is the maximum price of register method.
maxRegisterPrice = int64(1_0000_0000_0000) maxRegisterPrice = 1_0000_0000_0000
// maxRootLength is the maximum domain root length. // maxRootLength is the maximum domain root length.
maxRootLength = 16 maxRootLength = 16
// maxDomainNameFragmentLength is the maximum length of the domain name fragment. // maxDomainNameFragmentLength is the maximum length of the domain name fragment.
maxDomainNameFragmentLength = 63 maxDomainNameFragmentLength = 62
// minDomainNameLength is minimum domain length. // minDomainNameLength is minimum domain length.
minDomainNameLength = 3 minDomainNameLength = 3
// maxDomainNameLength is maximum domain length. // maxDomainNameLength is maximum domain length.
@ -64,7 +64,7 @@ const (
// defaultRegisterPrice is the default price for new domain registration. // defaultRegisterPrice is the default price for new domain registration.
defaultRegisterPrice = 10_0000_0000 defaultRegisterPrice = 10_0000_0000
// millisecondsInYear is amount of milliseconds per year. // millisecondsInYear is amount of milliseconds per year.
millisecondsInYear = int64(365 * 24 * 3600 * 1000) millisecondsInYear = 365 * 24 * 3600 * 1000
) )
// RecordState is a type that registered entities are saved to. // RecordState is a type that registered entities are saved to.
@ -76,22 +76,61 @@ type RecordState struct {
} }
// Update updates NameService contract. // Update updates NameService contract.
func Update(nef []byte, manifest string, data interface{}) { func Update(nef []byte, manifest string) {
checkCommittee() checkCommittee()
// Calculating keys and serializing requires calling // Calculating keys and serializing requires calling
// std and crypto contracts. This can be helpful on update // std and crypto contracts. This can be helpful on update
// thus we provide `AllowCall` to management.Update. // thus we provide `AllowCall` to management.Update.
// management.Update(nef, []byte(manifest)) // management.Update(nef, []byte(manifest))
contract.Call(interop.Hash160(management.Hash), "update", contract.Call(interop.Hash160(management.Hash), "update",
contract.All, nef, manifest, common.AppendVersion(data)) contract.All, nef, manifest, common.AppendVersion(nil))
runtime.Log("nns contract updated")
} }
// _deploy initializes defaults (total supply and registration price) on contract deploy. // _deploy initializes defaults (total supply and registration price) on contract deploy.
func _deploy(data interface{}, isUpdate bool) { func _deploy(data interface{}, isUpdate bool) {
if isUpdate { if isUpdate {
args := data.([]interface{}) ctx := storage.GetContext()
common.CheckVersion(args[len(args)-1].(int)) committee := common.CommitteeAddress()
it := storage.Find(ctx, []byte{prefixRoot}, storage.KeysOnly|storage.RemovePrefix)
for iterator.Next(it) {
name := iterator.Value(it).(string)
if name != "neofs" {
continue
}
ns := NameState{
Owner: committee,
Name: name,
Expiration: runtime.GetTime() + millisecondsInYear,
}
tokenKey := getTokenKey([]byte(name))
putNameStateWithKey(ctx, tokenKey, ns)
putSoaRecord(ctx, name, "ops@nspcc.ru",
3600 /* 1 hour */, 600, /* 10 min */
604800 /* 1 week */, 3600 /* 1 hour */)
}
r := GetRecords("container.neofs", TXT)
owner := shBEFromLEHex(r[0])
it = storage.Find(ctx, []byte{prefixRoot}, storage.KeysOnly|storage.RemovePrefix)
for iterator.Next(it) {
name := iterator.Value(it).(string)
if name == "neofs" {
continue
}
ns := NameState{
Owner: owner,
Name: name,
Expiration: runtime.GetTime() + millisecondsInYear,
}
tokenKey := getTokenKey([]byte(name))
putNameStateWithKey(ctx, tokenKey, ns)
putSoaRecord(ctx, name, "ops@nspcc.ru",
3600 /* 1 hour */, 600, /* 10 min */
604800 /* 1 week */, 3600 /* 1 hour */)
}
return return
} }
@ -100,6 +139,39 @@ func _deploy(data interface{}, isUpdate bool) {
storage.Put(ctx, []byte{prefixRegisterPrice}, defaultRegisterPrice) storage.Put(ctx, []byte{prefixRegisterPrice}, defaultRegisterPrice)
} }
// remove after v0.13.1 upgrade
func shBEFromLEHex(s string) interop.Hash160 {
res := make([]byte, interop.Hash160Len)
ln := len(s) / 2
for i := 0; i < ln; i++ {
a := hexToNum(s[i*2])
b := hexToNum(s[i*2+1])
var n interface{} = a*16 + b
res[ln-1-i] = n.([]byte)[0]
}
return res
}
// remove after v0.13.1 upgrade
func hexToNum(s uint8) int {
if s >= '0' && s <= '9' {
return int(s) - 48
}
if s >= 'A' && s <= 'F' {
return int(s) - 55
}
if s >= 'a' && s <= 'f' {
return int(s) - 87
}
panic("invalid hex")
}
// Symbol returns NeoNameService symbol. // Symbol returns NeoNameService symbol.
func Symbol() string { func Symbol() string {
return "NNS" return "NNS"
@ -110,25 +182,25 @@ func Decimals() int {
return 0 return 0
} }
// Version returns the version of the contract. // Version returns version of the contract.
func Version() int { func Version() int {
return common.Version return common.Version
} }
// TotalSupply returns the overall number of domains minted by NeoNameService contract. // TotalSupply returns overall number of domains minted by the NeoNameService contract.
func TotalSupply() int { func TotalSupply() int {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
return getTotalSupply(ctx) return getTotalSupply(ctx)
} }
// OwnerOf returns the owner of the specified domain. // OwnerOf returns owner of the specified domain.
func OwnerOf(tokenID []byte) interop.Hash160 { func OwnerOf(tokenID []byte) interop.Hash160 {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
ns := getNameState(ctx, tokenID) ns := getNameState(ctx, tokenID)
return ns.Owner return ns.Owner
} }
// Properties returns a domain name and an expiration date of the specified domain. // Properties returns domain name and expiration date of the specified domain.
func Properties(tokenID []byte) map[string]interface{} { func Properties(tokenID []byte) map[string]interface{} {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
ns := getNameState(ctx, tokenID) ns := getNameState(ctx, tokenID)
@ -138,7 +210,7 @@ func Properties(tokenID []byte) map[string]interface{} {
} }
} }
// BalanceOf returns the overall number of domains owned by the specified owner. // BalanceOf returns overall number of domains owned by the specified owner.
func BalanceOf(owner interop.Hash160) int { func BalanceOf(owner interop.Hash160) int {
if !isValid(owner) { if !isValid(owner) {
panic(`invalid owner`) panic(`invalid owner`)
@ -166,7 +238,7 @@ func TokensOf(owner interop.Hash160) iterator.Iterator {
return storage.Find(ctx, append([]byte{prefixAccountToken}, owner...), storage.ValuesOnly) return storage.Find(ctx, append([]byte{prefixAccountToken}, owner...), storage.ValuesOnly)
} }
// Transfer transfers the domain with the specified name to a new owner. // Transfer transfers domain with the specified name to new owner.
func Transfer(to interop.Hash160, tokenID []byte, data interface{}) bool { func Transfer(to interop.Hash160, tokenID []byte, data interface{}) bool {
if !isValid(to) { if !isValid(to) {
panic(`invalid receiver`) panic(`invalid receiver`)
@ -203,7 +275,7 @@ func Roots() iterator.Iterator {
} }
// SetPrice sets the domain registration price. // SetPrice sets the domain registration price.
func SetPrice(price int64) { func SetPrice(price int) {
checkCommittee() checkCommittee()
if price < 0 || price > maxRegisterPrice { if price < 0 || price > maxRegisterPrice {
panic("The price is out of range.") panic("The price is out of range.")
@ -218,7 +290,7 @@ func GetPrice() int {
return storage.Get(ctx, []byte{prefixRegisterPrice}).(int) return storage.Get(ctx, []byte{prefixRegisterPrice}).(int)
} }
// IsAvailable checks whether the provided domain name is available. // IsAvailable checks whether provided domain name is available.
func IsAvailable(name string) bool { func IsAvailable(name string) bool {
fragments := splitAndCheck(name, false) fragments := splitAndCheck(name, false)
if fragments == nil { if fragments == nil {
@ -235,10 +307,10 @@ func IsAvailable(name string) bool {
return parentExpired(ctx, 0, fragments) return parentExpired(ctx, 0, fragments)
} }
// parentExpired returns true if any domain from fragments doesn't exist or is expired. // parentExpired returns true if any domain from fragments doesn't exist or expired.
// first denotes the deepest subdomain to check. // first denotes the deepest subdomain to check.
func parentExpired(ctx storage.Context, first int, fragments []string) bool { func parentExpired(ctx storage.Context, first int, fragments []string) bool {
now := int64(runtime.GetTime()) now := runtime.GetTime()
last := len(fragments) - 1 last := len(fragments) - 1
name := fragments[last] name := fragments[last]
for i := last; i >= first; i-- { for i := last; i >= first; i-- {
@ -257,7 +329,7 @@ func parentExpired(ctx storage.Context, first int, fragments []string) bool {
return false return false
} }
// Register registers a new domain with the specified owner and name if it's available. // Register registers new domain with the specified owner and name if it's available.
func Register(name string, owner interop.Hash160, email string, refresh, retry, expire, ttl int) bool { func Register(name string, owner interop.Hash160, email string, refresh, retry, expire, ttl int) bool {
fragments := splitAndCheck(name, true) fragments := splitAndCheck(name, true)
if fragments == nil { if fragments == nil {
@ -310,7 +382,7 @@ func Register(name string, owner interop.Hash160, email string, refresh, retry,
nsBytes := storage.Get(ctx, append([]byte{prefixName}, tokenKey...)) nsBytes := storage.Get(ctx, append([]byte{prefixName}, tokenKey...))
if nsBytes != nil { if nsBytes != nil {
ns := std.Deserialize(nsBytes.([]byte)).(NameState) ns := std.Deserialize(nsBytes.([]byte)).(NameState)
if int64(runtime.GetTime()) < ns.Expiration { if runtime.GetTime() < ns.Expiration {
return false return false
} }
oldOwner = ns.Owner oldOwner = ns.Owner
@ -321,8 +393,7 @@ func Register(name string, owner interop.Hash160, email string, refresh, retry,
ns := NameState{ ns := NameState{
Owner: owner, Owner: owner,
Name: name, Name: name,
// NNS expiration is in milliseconds Expiration: runtime.GetTime() + millisecondsInYear,
Expiration: int64(runtime.GetTime() + expire*1000),
} }
putNameStateWithKey(ctx, tokenKey, ns) putNameStateWithKey(ctx, tokenKey, ns)
putSoaRecord(ctx, name, email, refresh, retry, expire, ttl) putSoaRecord(ctx, name, email, refresh, retry, expire, ttl)
@ -332,20 +403,19 @@ func Register(name string, owner interop.Hash160, email string, refresh, retry,
} }
// Renew increases domain expiration date. // Renew increases domain expiration date.
func Renew(name string) int64 { func Renew(name string) int {
if len(name) > maxDomainNameLength { if len(name) > maxDomainNameLength {
panic("invalid domain name format") panic("invalid domain name format")
} }
runtime.BurnGas(GetPrice()) runtime.BurnGas(GetPrice())
ctx := storage.GetContext() ctx := storage.GetContext()
ns := getNameState(ctx, []byte(name)) ns := getNameState(ctx, []byte(name))
ns.checkAdmin()
ns.Expiration += millisecondsInYear ns.Expiration += millisecondsInYear
putNameState(ctx, ns) putNameState(ctx, ns)
return ns.Expiration return ns.Expiration
} }
// UpdateSOA updates soa record. // UpdateSOA update soa record.
func UpdateSOA(name, email string, refresh, retry, expire, ttl int) { func UpdateSOA(name, email string, refresh, retry, expire, ttl int) {
if len(name) > maxDomainNameLength { if len(name) > maxDomainNameLength {
panic("invalid domain name format") panic("invalid domain name format")
@ -371,7 +441,7 @@ func SetAdmin(name string, admin interop.Hash160) {
putNameState(ctx, ns) putNameState(ctx, ns)
} }
// SetRecord adds a new record of the specified type to the provided domain. // SetRecord adds new record of the specified type to the provided domain.
func SetRecord(name string, typ RecordType, id byte, data string) { func SetRecord(name string, typ RecordType, id byte, data string) {
tokenID := []byte(tokenIDFromName(name)) tokenID := []byte(tokenIDFromName(name))
if !checkBaseRecords(typ, data) { if !checkBaseRecords(typ, data) {
@ -399,7 +469,7 @@ func checkBaseRecords(typ RecordType, data string) bool {
} }
} }
// AddRecord adds a new record of the specified type to the provided domain. // AddRecord adds new record of the specified type to the provided domain.
func AddRecord(name string, typ RecordType, data string) { func AddRecord(name string, typ RecordType, data string) {
tokenID := []byte(tokenIDFromName(name)) tokenID := []byte(tokenIDFromName(name))
if !checkBaseRecords(typ, data) { if !checkBaseRecords(typ, data) {
@ -445,7 +515,7 @@ func Resolve(name string, typ RecordType) []string {
return resolve(ctx, nil, name, typ, 2) return resolve(ctx, nil, name, typ, 2)
} }
// GetAllRecords returns an Iterator with RecordState items for the given name. // GetAllRecords returns an Iterator with RecordState items for given name.
func GetAllRecords(name string) iterator.Iterator { func GetAllRecords(name string) iterator.Iterator {
tokenID := []byte(tokenIDFromName(name)) tokenID := []byte(tokenIDFromName(name))
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
@ -492,7 +562,7 @@ func getTotalSupply(ctx storage.Context) int {
return val.(int) return val.(int)
} }
// updateTotalSupply adds the specified diff to the total supply. // updateTotalSupply adds specified diff to the total supply.
func updateTotalSupply(ctx storage.Context, diff int) { func updateTotalSupply(ctx storage.Context, diff int) {
tsKey := []byte{prefixTotalSupply} tsKey := []byte{prefixTotalSupply}
ts := getTotalSupply(ctx) ts := getTotalSupply(ctx)
@ -589,7 +659,7 @@ func addRecord(ctx storage.Context, tokenId []byte, name string, typ RecordType,
storeRecord(ctx, recordKey, name, typ, id, data) storeRecord(ctx, recordKey, name, typ, id, data)
} }
// storeRecord puts record to storage. // storeRecord put record to storage.
func storeRecord(ctx storage.Context, recordKey []byte, name string, typ RecordType, id byte, data string) { func storeRecord(ctx storage.Context, recordKey []byte, name string, typ RecordType, id byte, data string) {
rs := RecordState{ rs := RecordState{
Name: name, Name: name,
@ -646,19 +716,19 @@ func updateSoaSerial(ctx storage.Context, tokenId []byte) {
storage.Put(ctx, recordKey, recBytes) storage.Put(ctx, recordKey, recBytes)
} }
// getRecordsKey returns the prefix used to store domain records of different types. // getRecordsKey returns prefix used to store domain records of different types.
func getRecordsKey(tokenId []byte, name string) []byte { func getRecordsKey(tokenId []byte, name string) []byte {
recordKey := append([]byte{prefixRecord}, getTokenKey(tokenId)...) recordKey := append([]byte{prefixRecord}, getTokenKey(tokenId)...)
return append(recordKey, getTokenKey([]byte(name))...) return append(recordKey, getTokenKey([]byte(name))...)
} }
// getRecordsKeyByType returns the key used to store domain records. // getRecordsKeyByType returns key used to store domain records.
func getRecordsKeyByType(tokenId []byte, name string, typ RecordType) []byte { func getRecordsKeyByType(tokenId []byte, name string, typ RecordType) []byte {
recordKey := getRecordsKey(tokenId, name) recordKey := getRecordsKey(tokenId, name)
return append(recordKey, byte(typ)) return append(recordKey, byte(typ))
} }
// getIdRecordKey returns the key used to store domain records. // getIdRecordKey returns key used to store domain records.
func getIdRecordKey(tokenId []byte, name string, typ RecordType, id byte) []byte { func getIdRecordKey(tokenId []byte, name string, typ RecordType, id byte) []byte {
recordKey := getRecordsKey(tokenId, name) recordKey := getRecordsKey(tokenId, name)
return append(recordKey, byte(typ), id) return append(recordKey, byte(typ), id)
@ -684,7 +754,7 @@ func checkCommittee() {
// checkFragment validates root or a part of domain name. // checkFragment validates root or a part of domain name.
// 1. Root domain must start with a letter. // 1. Root domain must start with a letter.
// 2. All other fragments must start and end with a letter or a digit. // 2. All other fragments must start and end in a letter or a digit.
func checkFragment(v string, isRoot bool) bool { func checkFragment(v string, isRoot bool) bool {
maxLength := maxDomainNameFragmentLength maxLength := maxDomainNameFragmentLength
if isRoot { if isRoot {
@ -845,7 +915,7 @@ func checkIPv6(data string) bool {
return true return true
} }
// tokenIDFromName returns token ID (domain.root) from the provided name. // tokenIDFromName returns token ID (domain.root) from provided name.
func tokenIDFromName(name string) string { func tokenIDFromName(name string) string {
fragments := splitAndCheck(name, true) fragments := splitAndCheck(name, true)
if fragments == nil { if fragments == nil {
@ -861,7 +931,7 @@ func tokenIDFromName(name string) string {
nsBytes := storage.Get(ctx, nameKey) nsBytes := storage.Get(ctx, nameKey)
if nsBytes != nil { if nsBytes != nil {
ns := std.Deserialize(nsBytes.([]byte)).(NameState) ns := std.Deserialize(nsBytes.([]byte)).(NameState)
if int64(runtime.GetTime()) < ns.Expiration { if runtime.GetTime() < ns.Expiration {
return name[sum:] return name[sum:]
} }
} }
@ -870,7 +940,7 @@ func tokenIDFromName(name string) string {
return name return name
} }
// resolve resolves the provided name using record with the specified type and given // resolve resolves provided name using record with the specified type and given
// maximum redirections constraint. // maximum redirections constraint.
func resolve(ctx storage.Context, res []string, name string, typ RecordType, redirect int) []string { func resolve(ctx storage.Context, res []string, name string, typ RecordType, redirect int) []string {
if redirect < 0 { if redirect < 0 {

View file

@ -3,7 +3,7 @@ package nns
// RecordType is domain name service record types. // RecordType is domain name service record types.
type RecordType byte type RecordType byte
// Record types are defined in [RFC 1035](https://tools.ietf.org/html/rfc1035) // Record types defined in [RFC 1035](https://tools.ietf.org/html/rfc1035)
const ( const (
// A represents address record type. // A represents address record type.
A RecordType = 1 A RecordType = 1
@ -15,7 +15,7 @@ const (
TXT RecordType = 16 TXT RecordType = 16
) )
// Record types are defined in [RFC 3596](https://tools.ietf.org/html/rfc3596) // Record types defined in [RFC 3596](https://tools.ietf.org/html/rfc3596)
const ( const (
// AAAA represents IPv6 address record type. // AAAA represents IPv6 address record type.
AAAA RecordType = 28 AAAA RecordType = 28

View file

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

View file

@ -1,21 +1,21 @@
/* /*
Processing contract is a contract deployed in FrostFS mainchain. Processing contract is a contract deployed in NeoFS main chain.
Processing contract pays for all multi signature transaction executions when notary Processing contract pays for all multi signature transaction executions when notary
service is enabled in the mainchain. Notary service prepares multisigned transactions, service enabled in main chain. Notary service prepares multi signed transaction,
however they should contain side chain GAS to be executed. It is inconvenient to however they should contain side chain GAS to be executed. It is inconvenient to
ask Alphabet nodes to pay for these transactions: nodes can change over time, 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. some nodes will spend side chain GAS faster, it creates economic instability.
Processing contract exists to solve this issue. At the Withdraw invocation of Processing contract exists to solve this issue. At the Withdraw invocation of
FrostFS contract, a user pays fee directly to this contract. This fee is used to NeoFS contract, user pays fee directly to this contract. This fee is used to
pay for Cheque invocation of FrostFS contract that returns mainchain GAS back pay for Cheque invocation of NeoFS contract that returns main chain GAS back
to the user. The address of the Processing contract is used as the first signer in to the user. Address of the Processing contract is uses as the first signer in
the multisignature transaction. Therefore, NeoVM executes Verify method of the the multi signature transaction. Therefore NeoVM executes Verify method of the
contract and if invocation is verified, Processing contract pays for the contract and if invocation is verified, then Processing contract pays for the
execution. execution.
# Contract notifications Contract notifications
Processing contract does not produce notifications to process. Processing contract does not produce notifications to process.
*/ */

View file

@ -1,7 +1,6 @@
package processing package processing
import ( import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop" "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/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/native/gas" "github.com/nspcc-dev/neo-go/pkg/interop/native/gas"
@ -10,10 +9,11 @@ import (
"github.com/nspcc-dev/neo-go/pkg/interop/native/roles" "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/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage" "github.com/nspcc-dev/neo-go/pkg/interop/storage"
"github.com/nspcc-dev/neofs-contract/common"
) )
const ( const (
frostfsContractKey = "frostfsScriptHash" neofsContractKey = "neofsScriptHash"
multiaddrMethod = "alphabetAddress" multiaddrMethod = "alphabetAddress"
) )
@ -28,31 +28,29 @@ func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) {
func _deploy(data interface{}, isUpdate bool) { func _deploy(data interface{}, isUpdate bool) {
if isUpdate { if isUpdate {
args := data.([]interface{})
common.CheckVersion(args[len(args)-1].(int))
return return
} }
args := data.(struct { args := data.(struct {
addrFrostFS interop.Hash160 addrNeoFS interop.Hash160
}) })
ctx := storage.GetContext() ctx := storage.GetContext()
if len(args.addrFrostFS) != interop.Hash160Len { if len(args.addrNeoFS) != interop.Hash160Len {
panic("incorrect length of contract script hash") panic("incorrect length of contract script hash")
} }
storage.Put(ctx, frostfsContractKey, args.addrFrostFS) storage.Put(ctx, neofsContractKey, args.addrNeoFS)
runtime.Log("processing contract initialized") runtime.Log("processing contract initialized")
} }
// Update method updates contract source code and manifest. It can be invoked // Update method updates contract source code and manifest. Can be invoked
// only by the sidechain committee. // only by side chain committee.
func Update(script []byte, manifest []byte, data interface{}) { func Update(script []byte, manifest []byte, data interface{}) {
blockHeight := ledger.CurrentIndex() blockHeight := ledger.CurrentIndex()
alphabetKeys := roles.GetDesignatedByRole(roles.NeoFSAlphabet, uint32(blockHeight+1)) alphabetKeys := roles.GetDesignatedByRole(roles.NeoFSAlphabet, uint32(blockHeight))
alphabetCommittee := common.Multiaddress(alphabetKeys, true) alphabetCommittee := common.Multiaddress(alphabetKeys, true)
if !runtime.CheckWitness(alphabetCommittee) { if !runtime.CheckWitness(alphabetCommittee) {
@ -68,13 +66,13 @@ func Update(script []byte, manifest []byte, data interface{}) {
// Alphabet nodes of the Inner Ring. // Alphabet nodes of the Inner Ring.
func Verify() bool { func Verify() bool {
ctx := storage.GetContext() ctx := storage.GetContext()
frostfsContractAddr := storage.Get(ctx, frostfsContractKey).(interop.Hash160) neofsContractAddr := storage.Get(ctx, neofsContractKey).(interop.Hash160)
multiaddr := contract.Call(frostfsContractAddr, multiaddrMethod, contract.ReadOnly).(interop.Hash160) multiaddr := contract.Call(neofsContractAddr, multiaddrMethod, contract.ReadOnly).(interop.Hash160)
return runtime.CheckWitness(multiaddr) return runtime.CheckWitness(multiaddr)
} }
// Version returns the version of the contract. // Version returns version of the contract.
func Version() int { func Version() int {
return common.Version return common.Version
} }

View file

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

View file

@ -1,20 +1,20 @@
/* /*
Proxy contract is a contract deployed in FrostFS sidechain. Proxy contract is a contract deployed in NeoFS side chain.
Proxy contract pays for all multi signature transaction executions when notary Proxy contract pays for all multi signature transaction executions when notary
service is enabled in the sidechain. Notary service prepares multisigned transactions, service enabled in side chain. Notary service prepares multi signed transaction,
however they should contain side chain GAS to be executed. It is inconvenient to however they should contain side chain GAS to be executed. It is inconvenient to
ask Alphabet nodes to pay for these transactions: nodes can change over time, 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. some nodes will spend side chain GAS faster, it creates economic instability.
Proxy contract exists to solve this issue. While Alphabet contracts hold all Proxy contract exists to solve this issue. While Alphabet contracts hold all
side chain NEO, proxy contract holds most of the side chain GAS. Alphabet side chain NEO, proxy contract holds most of the side chain GAS. Alphabet
contracts emit half of the available GAS to the proxy contract. The address of the contracts emits half of the available GAS to the proxy contract. Address of the
Proxy contract is used as the first signer in a multisignature transaction. Proxy contract is used as the first signer in the multi signature transaction.
Therefore, NeoVM executes Verify method of the contract; and if invocation is Therefore NeoVM executes Verify method of the contract and if invocation is
verified, Proxy contract pays for the execution. verified, then Proxy contract pays for the execution.
# Contract notifications Contract notifications
Proxy contract does not produce notifications to process. Proxy contract does not produce notifications to process.
*/ */

View file

@ -1,13 +1,18 @@
package proxy package proxy
import ( import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop" "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/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/native/gas" "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/management"
"github.com/nspcc-dev/neo-go/pkg/interop/native/neo" "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/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
"github.com/nspcc-dev/neofs-contract/common"
)
const (
netmapContractKey = "netmapScriptHash"
) )
// OnNEP17Payment is a callback for NEP-17 compatible native GAS contract. // OnNEP17Payment is a callback for NEP-17 compatible native GAS contract.
@ -20,15 +25,25 @@ func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) {
func _deploy(data interface{}, isUpdate bool) { func _deploy(data interface{}, isUpdate bool) {
if isUpdate { if isUpdate {
args := data.([]interface{})
common.CheckVersion(args[len(args)-1].(int))
return return
} }
args := data.(struct {
addrNetmap interop.Hash160
})
ctx := storage.GetContext()
if len(args.addrNetmap) != interop.Hash160Len {
panic("incorrect length of contract script hash")
}
storage.Put(ctx, netmapContractKey, args.addrNetmap)
runtime.Log("proxy contract initialized") runtime.Log("proxy contract initialized")
} }
// Update method updates contract source code and manifest. It can be invoked // Update method updates contract source code and manifest. Can be invoked
// only by committee. // only by committee.
func Update(script []byte, manifest []byte, data interface{}) { func Update(script []byte, manifest []byte, data interface{}) {
if !common.HasUpdateAccess() { if !common.HasUpdateAccess() {
@ -54,7 +69,7 @@ func Verify() bool {
return true return true
} }
// Version returns the version of the contract. // Version returns version of the contract.
func Version() int { func Version() int {
return common.Version return common.Version
} }

View file

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

View file

@ -1,16 +1,16 @@
/* /*
Reputation contract is a contract deployed in FrostFS sidechain. Reputation contract is a contract deployed in NeoFS side chain.
Inner Ring nodes produce data audit for each container during each epoch. In the end, Inner Ring nodes produce data audit for each container in each epoch. In the end
nodes produce DataAuditResult structure that contains information about audit nodes produce DataAuditResult structure that contains information about audit
progress. Reputation contract provides storage for such structures and simple progress. Reputation contract provides storage for such structures and simple
interface to iterate over available DataAuditResults on specified epoch. interface to iterate over available DataAuditResults on specified epoch.
During settlement process, Alphabet nodes fetch all DataAuditResult structures During settlement process, Alphabet nodes fetch all DataAuditResult structures
from the epoch and execute balance transfers from data owners to Storage and from the epoch and execute balance transfers from data owners to Storage and
Inner Ring nodes if data audit succeeds. Inner Ring nodes, if data audit succeed.
# Contract notifications Contract notifications
Reputation contract does not produce notifications to process. Reputation contract does not produce notifications to process.
*/ */

View file

@ -1,7 +1,6 @@
package reputation package reputation
import ( import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop" "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/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/convert" "github.com/nspcc-dev/neo-go/pkg/interop/convert"
@ -9,6 +8,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/interop/native/management" "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/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage" "github.com/nspcc-dev/neo-go/pkg/interop/storage"
"github.com/nspcc-dev/neofs-contract/common"
) )
const ( const (
@ -18,18 +18,30 @@ const (
) )
func _deploy(data interface{}, isUpdate bool) { func _deploy(data interface{}, isUpdate bool) {
common.RmAndCheckNotaryDisabledKey(data, notaryDisabledKey) ctx := storage.GetContext()
if isUpdate { if isUpdate {
args := data.([]interface{}) storage.Delete(ctx, "ballots")
common.CheckVersion(args[len(args)-1].(int)) storage.Put(ctx, notaryDisabledKey, false)
return 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") runtime.Log("reputation contract initialized")
} }
// Update method updates contract source code and manifest. It can be invoked // Update method updates contract source code and manifest. Can be invoked
// only by committee. // only by committee.
func Update(script []byte, manifest []byte, data interface{}) { func Update(script []byte, manifest []byte, data interface{}) {
if !common.HasUpdateAccess() { if !common.HasUpdateAccess() {
@ -41,22 +53,48 @@ func Update(script []byte, manifest []byte, data interface{}) {
runtime.Log("reputation contract updated") runtime.Log("reputation contract updated")
} }
// Put method saves DataAuditResult in contract storage. It can be invoked only by // Put method saves DataAuditResult in contract storage. Can be invoked only by
// Inner Ring nodes. It does not require multisignature invocations. // Inner Ring nodes. Does not require multi signature invocations.
// //
// Epoch is the epoch number when DataAuditResult structure was generated. // Epoch is an epoch number when DataAuditResult structure was generated.
// PeerID contains public keys of the Inner Ring node that has produced DataAuditResult. // PeerID contains public keys of Inner Ring node that produced DataAuditResult.
// Value contains a stable marshaled structure of DataAuditResult. // Value contains stable marshaled structure of DataAuditResult.
func Put(epoch int, peerID []byte, value []byte) { func Put(epoch int, peerID []byte, value []byte) {
ctx := storage.GetContext() ctx := storage.GetContext()
notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool)
var ( // for invocation collection without notary
alphabet []common.IRNode
nodeKey []byte
alphabetCall bool
)
if notaryDisabled {
alphabet = common.AlphabetNodes()
nodeKey = common.InnerRingInvoker(alphabet)
alphabetCall = len(nodeKey) != 0
} else {
multiaddr := common.AlphabetAddress() multiaddr := common.AlphabetAddress()
if !runtime.CheckWitness(multiaddr) { alphabetCall = runtime.CheckWitness(multiaddr)
}
if !alphabetCall {
runtime.Notify("reputationPut", epoch, peerID, value) runtime.Notify("reputationPut", epoch, peerID, value)
return return
} }
id := storageID(epoch, peerID) 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) key := getReputationKey(reputationCountPrefix, id)
rawCnt := storage.Get(ctx, key) rawCnt := storage.Get(ctx, key)
cnt := 0 cnt := 0
@ -71,15 +109,15 @@ func Put(epoch int, peerID []byte, value []byte) {
storage.Put(ctx, key, value) storage.Put(ctx, key, value)
} }
// Get method returns a list of all stable marshaled DataAuditResult structures // Get method returns list of all stable marshaled DataAuditResult structures
// produced by the specified Inner Ring node during the specified epoch. // produced by specified Inner Ring node in specified epoch.
func Get(epoch int, peerID []byte) [][]byte { func Get(epoch int, peerID []byte) [][]byte {
id := storageID(epoch, peerID) id := storageID(epoch, peerID)
return GetByID(id) return GetByID(id)
} }
// GetByID method returns a list of all stable marshaled DataAuditResult with // GetByID method returns list of all stable marshaled DataAuditResult with
// the specified id. Use ListByEpoch method to obtain the id. // specified id. Use ListByEpoch method to obtain id.
func GetByID(id []byte) [][]byte { func GetByID(id []byte) [][]byte {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
@ -96,7 +134,7 @@ func getReputationKey(prefix byte, id []byte) []byte {
return append([]byte{prefix}, id...) return append([]byte{prefix}, id...)
} }
// ListByEpoch returns a list of IDs that may be used to get reputation data // ListByEpoch returns list of IDs that may be used to get reputation data
// with GetByID method. // with GetByID method.
func ListByEpoch(epoch int) [][]byte { func ListByEpoch(epoch int) [][]byte {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
@ -113,7 +151,7 @@ func ListByEpoch(epoch int) [][]byte {
return result return result
} }
// Version returns the version of the contract. // Version returns version of the contract.
func Version() int { func Version() int {
return common.Version return common.Version
} }

View file

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

View file

@ -1,13 +1,13 @@
/* /*
Subnet contract is a contract deployed in FrostFS sidechain. Subnet contract is a contract deployed in NeoFS side chain.
Subnet contract stores and manages FrostFS subnetwork states. It allows registering 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 and deleting subnetworks, limiting access to them and defining a list of the Storage
Nodes that can be included in them. Nodes that can be included in them.
# Contract notifications Contract notifications
Put notification. This notification is produced when a new subnetwork is Put notification. This notification is produced when new subnetwork is
registered by invoking Put method. registered by invoking Put method.
Put Put

View file

@ -1,13 +1,13 @@
package subnet package subnet
import ( import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop" "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/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator" "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/native/management"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime" "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/storage"
"github.com/nspcc-dev/neofs-contract/common"
) )
const ( const (
@ -27,6 +27,14 @@ const (
ErrInvalidUser = "invalid user" ErrInvalidUser = "invalid user"
// ErrInvalidNode is thrown when node has invalid format. // ErrInvalidNode is thrown when node has invalid format.
ErrInvalidNode = "invalid node key" 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 is thrown when operation is denied for caller.
ErrAccessDenied = "access denied" ErrAccessDenied = "access denied"
) )
@ -49,28 +57,34 @@ const (
// _deploy function sets up initial list of inner ring public keys. // _deploy function sets up initial list of inner ring public keys.
func _deploy(data interface{}, isUpdate bool) { func _deploy(data interface{}, isUpdate bool) {
common.RmAndCheckNotaryDisabledKey(data, notaryDisabledKey)
if isUpdate { if isUpdate {
args := data.([]interface{}) ctx := storage.GetContext()
common.CheckVersion(args[len(args)-1].(int)) storage.Delete(ctx, "ballots")
storage.Put(ctx, notaryDisabledKey, false)
return 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 // Update method updates contract source code and manifest. Can be invoked
// only by committee. // only by committee.
func Update(script []byte, manifest []byte, data interface{}) { func Update(script []byte, manifest []byte, data interface{}) {
if !common.HasUpdateAccess() { if !common.HasUpdateAccess() {
panic("only committee can update contract") panic("only committee can update contract")
} }
contract.Call(interop.Hash160(management.Hash), "update", contract.All, contract.Call(interop.Hash160(management.Hash), "update", contract.All, script, manifest, data)
script, manifest, common.AppendVersion(data))
runtime.Log("subnet contract updated") runtime.Log("subnet contract updated")
} }
// Put creates a new subnet with the specified owner and info. // Put creates new subnet with the specified owner and info.
func Put(id []byte, ownerKey interop.PublicKey, info []byte) { func Put(id []byte, ownerKey interop.PublicKey, info []byte) {
// V2 format // V2 format
if len(id) != subnetIDSize { if len(id) != subnetIDSize {
@ -86,16 +100,37 @@ func Put(id []byte, ownerKey interop.PublicKey, info []byte) {
panic(ErrAlreadyExists) 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) common.CheckOwnerWitness(ownerKey)
common.CheckAlphabetWitness() multiaddr := common.AlphabetAddress()
common.CheckAlphabetWitness(multiaddr)
}
storage.Put(ctx, stKey, ownerKey) storage.Put(ctx, stKey, ownerKey)
stKey[0] = infoPrefix stKey[0] = infoPrefix
storage.Put(ctx, stKey, info) storage.Put(ctx, stKey, info)
} }
// Get returns info about the subnet with the specified id. // Get returns info about subnet with the specified id.
func Get(id []byte) []byte { func Get(id []byte) []byte {
// V2 format // V2 format
if len(id) != subnetIDSize { if len(id) != subnetIDSize {
@ -111,7 +146,7 @@ func Get(id []byte) []byte {
return raw.([]byte) return raw.([]byte)
} }
// Delete deletes the subnet with the specified id. // Delete deletes subnet with the specified id.
func Delete(id []byte) { func Delete(id []byte) {
// V2 format // V2 format
if len(id) != subnetIDSize { if len(id) != subnetIDSize {
@ -148,7 +183,7 @@ func Delete(id []byte) {
runtime.Notify("Delete", id) runtime.Notify("Delete", id)
} }
// AddNodeAdmin adds a new node administrator to the specified subnetwork. // AddNodeAdmin adds new node administrator to the specified subnetwork.
func AddNodeAdmin(subnetID []byte, adminKey interop.PublicKey) { func AddNodeAdmin(subnetID []byte, adminKey interop.PublicKey) {
// V2 format // V2 format
if len(subnetID) != subnetIDSize { if len(subnetID) != subnetIDSize {
@ -181,7 +216,7 @@ func AddNodeAdmin(subnetID []byte, adminKey interop.PublicKey) {
} }
// RemoveNodeAdmin removes node administrator from the specified subnetwork. // RemoveNodeAdmin removes node administrator from the specified subnetwork.
// Must be called by the subnet owner only. // Must be called by subnet owner only.
func RemoveNodeAdmin(subnetID []byte, adminKey interop.PublicKey) { func RemoveNodeAdmin(subnetID []byte, adminKey interop.PublicKey) {
// V2 format // V2 format
if len(subnetID) != subnetIDSize { if len(subnetID) != subnetIDSize {
@ -213,8 +248,8 @@ func RemoveNodeAdmin(subnetID []byte, adminKey interop.PublicKey) {
deleteKeyFromList(ctx, adminKey, stKey) deleteKeyFromList(ctx, adminKey, stKey)
} }
// AddNode adds a node to the specified subnetwork. // AddNode adds node to the specified subnetwork.
// Must be called by the subnet's owner or the node administrator // Must be called by subnet's owner or node administrator
// only. // only.
func AddNode(subnetID []byte, node interop.PublicKey) { func AddNode(subnetID []byte, node interop.PublicKey) {
// V2 format // V2 format
@ -252,8 +287,8 @@ func AddNode(subnetID []byte, node interop.PublicKey) {
putKeyInList(ctx, node, stKey) putKeyInList(ctx, node, stKey)
} }
// RemoveNode removes a node from the specified subnetwork. // RemoveNode removes node from the specified subnetwork.
// Must be called by the subnet's owner or the node administrator // Must be called by subnet's owner or node administrator
// only. // only.
func RemoveNode(subnetID []byte, node interop.PublicKey) { func RemoveNode(subnetID []byte, node interop.PublicKey) {
// V2 format // V2 format
@ -293,8 +328,8 @@ func RemoveNode(subnetID []byte, node interop.PublicKey) {
runtime.Notify("RemoveNode", subnetID, node) runtime.Notify("RemoveNode", subnetID, node)
} }
// NodeAllowed checks if a node is included in the // NodeAllowed checks if node is included in the
// specified subnet. // specified subnet or not.
func NodeAllowed(subnetID []byte, node interop.PublicKey) bool { func NodeAllowed(subnetID []byte, node interop.PublicKey) bool {
// V2 format // V2 format
if len(subnetID) != subnetIDSize { if len(subnetID) != subnetIDSize {
@ -319,8 +354,8 @@ func NodeAllowed(subnetID []byte, node interop.PublicKey) bool {
return storage.Get(ctx, append(stKey, node...)) != nil return storage.Get(ctx, append(stKey, node...)) != nil
} }
// AddClientAdmin adds a new client administrator of the specified group in the specified subnetwork. // AddClientAdmin adds new client administrator of the specified group in the specified subnetwork.
// Must be called by the owner only. // Must be called by owner only.
func AddClientAdmin(subnetID []byte, groupID []byte, adminPublicKey interop.PublicKey) { func AddClientAdmin(subnetID []byte, groupID []byte, adminPublicKey interop.PublicKey) {
// V2 format // V2 format
if len(subnetID) != subnetIDSize { if len(subnetID) != subnetIDSize {
@ -360,7 +395,7 @@ func AddClientAdmin(subnetID []byte, groupID []byte, adminPublicKey interop.Publ
// RemoveClientAdmin removes client administrator from the // RemoveClientAdmin removes client administrator from the
// specified group in the specified subnetwork. // specified group in the specified subnetwork.
// Must be called by the owner only. // Must be called by owner only.
func RemoveClientAdmin(subnetID []byte, groupID []byte, adminPublicKey interop.PublicKey) { func RemoveClientAdmin(subnetID []byte, groupID []byte, adminPublicKey interop.PublicKey) {
// V2 format // V2 format
if len(subnetID) != subnetIDSize { if len(subnetID) != subnetIDSize {
@ -443,7 +478,7 @@ func AddUser(subnetID []byte, groupID []byte, userID []byte) {
putKeyInList(ctx, userID, stKey) putKeyInList(ctx, userID, stKey)
} }
// RemoveUser removes a user from the specified subnetwork and group. // RemoveUser removes user from the specified subnetwork and group.
// Must be called by the owner or the group's admin only. // Must be called by the owner or the group's admin only.
func RemoveUser(subnetID []byte, groupID []byte, userID []byte) { func RemoveUser(subnetID []byte, groupID []byte, userID []byte) {
// V2 format // V2 format
@ -488,8 +523,8 @@ func RemoveUser(subnetID []byte, groupID []byte, userID []byte) {
deleteKeyFromList(ctx, userID, stKey) deleteKeyFromList(ctx, userID, stKey)
} }
// UserAllowed returns bool that indicates if a node is included in the // UserAllowed returns bool that indicates if node is included in the
// specified subnet. // specified subnet or not.
func UserAllowed(subnetID []byte, user []byte) bool { func UserAllowed(subnetID []byte, user []byte) bool {
// V2 format // V2 format
if len(subnetID) != subnetIDSize { if len(subnetID) != subnetIDSize {
@ -517,7 +552,7 @@ func UserAllowed(subnetID []byte, user []byte) bool {
return false return false
} }
// Version returns the version of the contract. // Version returns version of the contract.
func Version() int { func Version() int {
return common.Version return common.Version
} }
@ -547,10 +582,12 @@ func calledByOwnerOrAdmin(ctx storage.Context, owner []byte, adminPrefix []byte)
return true return true
} }
iter := storage.Find(ctx, adminPrefix, storage.KeysOnly|storage.RemovePrefix) prefixLen := len(adminPrefix)
iter := storage.Find(ctx, adminPrefix, storage.KeysOnly)
for iterator.Next(iter) { for iterator.Next(iter) {
key := iterator.Value(iter).([]byte) key := iterator.Value(iter).([]byte)
if runtime.CheckWitness(key) { if runtime.CheckWitness(key[prefixLen:]) {
return true return true
} }
} }

View file

@ -4,8 +4,6 @@ import (
"path" "path"
"testing" "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/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
"github.com/nspcc-dev/neo-go/pkg/neotest" "github.com/nspcc-dev/neo-go/pkg/neotest"
@ -13,11 +11,18 @@ import (
"github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/wallet" "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" "github.com/stretchr/testify/require"
) )
const alphabetPath = "../alphabet" const alphabetPath = "../alphabet"
// FIXME: delete after https://github.com/nspcc-dev/neo-go/issues/2297
const singleValidatorWIF = "KxyjQ8eUa4FHt3Gvioyt1Wz29cTUrE4eTqX3yFSk1YFCsPL8uNsY"
var committeeAcc, _ = wallet.NewAccountFromWIF(singleValidatorWIF)
func deployAlphabetContract(t *testing.T, e *neotest.Executor, addrNetmap, addrProxy util.Uint160, name string, index, total int64) util.Uint160 { func deployAlphabetContract(t *testing.T, e *neotest.Executor, addrNetmap, addrProxy util.Uint160, name string, index, total int64) util.Uint160 {
c := neotest.CompileFile(t, e.CommitteeHash, alphabetPath, path.Join(alphabetPath, "config.yml")) c := neotest.CompileFile(t, e.CommitteeHash, alphabetPath, path.Join(alphabetPath, "config.yml"))
@ -51,9 +56,7 @@ func newAlphabetInvoker(t *testing.T) (*neotest.Executor, *neotest.ContractInvok
deployProxyContract(t, e, ctrNetmap.Hash) deployProxyContract(t, e, ctrNetmap.Hash)
hash := deployAlphabetContract(t, e, ctrNetmap.Hash, ctrProxy.Hash, "Az", 0, 1) hash := deployAlphabetContract(t, e, ctrNetmap.Hash, ctrProxy.Hash, "Az", 0, 1)
alphabet := getAlphabetAcc(t, e) setAlphabetRole(t, e, committeeAcc.PrivateKey().PublicKey().Bytes())
setAlphabetRole(t, e, alphabet.PrivateKey().PublicKey().Bytes())
return e, e.CommitteeInvoker(hash) return e, e.CommitteeInvoker(hash)
} }
@ -63,9 +66,7 @@ func TestEmit(t *testing.T) {
const method = "emit" const method = "emit"
alphabet := getAlphabetAcc(t, c.Executor) cCommittee := c.WithSigners(neotest.NewSingleSigner(committeeAcc))
cCommittee := c.WithSigners(neotest.NewSingleSigner(alphabet))
cCommittee.InvokeFail(t, "no gas to emit", method) cCommittee.InvokeFail(t, "no gas to emit", method)
transferNeoToContract(t, c) transferNeoToContract(t, c)
@ -112,7 +113,7 @@ func TestVote(t *testing.T) {
c.Invoke(t, stackitem.Null{}, method, int64(0), []interface{}{newAlphabetPub}) c.Invoke(t, stackitem.Null{}, method, int64(0), []interface{}{newAlphabetPub})
// wait one block util // wait one block util
// a new committee is accepted // new committee is accepted
c.AddNewBlock(t) c.AddNewBlock(t)
cNewAlphabet.Invoke(t, stackitem.Null{}, "emit") cNewAlphabet.Invoke(t, stackitem.Null{}, "emit")
@ -141,10 +142,3 @@ func setAlphabetRole(t *testing.T, e *neotest.Executor, new []byte) {
// set committee as NeoFSAlphabet // set committee as NeoFSAlphabet
designInvoker.Invoke(t, stackitem.Null{}, "designateAsRole", int64(noderoles.NeoFSAlphabet), []interface{}{new}) designInvoker.Invoke(t, stackitem.Null{}, "designateAsRole", int64(noderoles.NeoFSAlphabet), []interface{}{new})
} }
func getAlphabetAcc(t *testing.T, e *neotest.Executor) *wallet.Account {
multi, ok := e.Committee.(neotest.MultiSigner)
require.True(t, ok)
return multi.Single(0).Account()
}

View file

@ -1,22 +1,18 @@
package tests package tests
import ( import (
"bytes"
"crypto/sha256" "crypto/sha256"
"math/big"
"path" "path"
"testing" "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/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/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/neotest" "github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require" "github.com/nspcc-dev/neofs-contract/common"
"github.com/nspcc-dev/neofs-contract/container"
"github.com/nspcc-dev/neofs-contract/nns"
) )
const containerPath = "../container" const containerPath = "../container"
@ -33,14 +29,14 @@ func deployContainerContract(t *testing.T, e *neotest.Executor, addrNetmap, addr
args[2] = addrBalance args[2] = addrBalance
args[3] = util.Uint160{} // not needed for now args[3] = util.Uint160{} // not needed for now
args[4] = addrNNS args[4] = addrNNS
args[5] = "frostfs" args[5] = "neofs"
c := neotest.CompileFile(t, e.CommitteeHash, containerPath, path.Join(containerPath, "config.yml")) c := neotest.CompileFile(t, e.CommitteeHash, containerPath, path.Join(containerPath, "config.yml"))
e.DeployContract(t, c, args) e.DeployContract(t, c, args)
return c.Hash return c.Hash
} }
func newContainerInvoker(t *testing.T) (*neotest.ContractInvoker, *neotest.ContractInvoker, *neotest.ContractInvoker) { func newContainerInvoker(t *testing.T) (*neotest.ContractInvoker, *neotest.ContractInvoker) {
e := newExecutor(t) e := newExecutor(t)
ctrNNS := neotest.CompileFile(t, e.CommitteeHash, nnsPath, path.Join(nnsPath, "config.yml")) ctrNNS := neotest.CompileFile(t, e.CommitteeHash, nnsPath, path.Join(nnsPath, "config.yml"))
@ -54,7 +50,7 @@ func newContainerInvoker(t *testing.T) (*neotest.ContractInvoker, *neotest.Contr
container.AliasFeeKey, int64(containerAliasFee)) container.AliasFeeKey, int64(containerAliasFee))
deployBalanceContract(t, e, ctrNetmap.Hash, ctrContainer.Hash) deployBalanceContract(t, e, ctrNetmap.Hash, ctrContainer.Hash)
deployContainerContract(t, e, ctrNetmap.Hash, ctrBalance.Hash, ctrNNS.Hash) deployContainerContract(t, e, ctrNetmap.Hash, ctrBalance.Hash, ctrNNS.Hash)
return e.CommitteeInvoker(ctrContainer.Hash), e.CommitteeInvoker(ctrBalance.Hash), e.CommitteeInvoker(ctrNetmap.Hash) return e.CommitteeInvoker(ctrContainer.Hash), e.CommitteeInvoker(ctrBalance.Hash)
} }
func setContainerOwner(c []byte, acc neotest.Signer) { func setContainerOwner(c []byte, acc neotest.Signer) {
@ -81,88 +77,8 @@ func dummyContainer(owner neotest.Signer) testContainer {
} }
} }
func TestContainerCount(t *testing.T) {
c, cBal, _ := newContainerInvoker(t)
checkCount := func(t *testing.T, expected int64) {
s, err := c.TestInvoke(t, "count")
require.NoError(t, err)
bi := s.Pop().BigInt()
require.True(t, bi.IsInt64())
require.Equal(t, int64(expected), bi.Int64())
}
checkCount(t, 0)
acc1, cnt1 := addContainer(t, c, cBal)
checkCount(t, 1)
_, cnt2 := addContainer(t, c, cBal)
checkCount(t, 2)
// Same owner.
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) { func TestContainerPut(t *testing.T) {
c, cBal, _ := newContainerInvoker(t) c, cBal := newContainerInvoker(t)
acc := c.NewAccount(t) acc := c.NewAccount(t)
cnt := dummyContainer(acc) cnt := dummyContainer(acc)
@ -195,14 +111,14 @@ func TestContainerPut(t *testing.T) {
stackitem.NewByteArray([]byte(base58.Encode(cnt.id[:]))), stackitem.NewByteArray([]byte(base58.Encode(cnt.id[:]))),
}) })
cNNS := c.CommitteeInvoker(nnsHash) cNNS := c.CommitteeInvoker(nnsHash)
cNNS.Invoke(t, expected, "resolve", "mycnt.frostfs", int64(nns.TXT)) cNNS.Invoke(t, expected, "resolve", "mycnt.neofs", int64(nns.TXT))
t.Run("name is already taken", func(t *testing.T) { t.Run("name is already taken", func(t *testing.T) {
c.InvokeFail(t, "name is already taken", "putNamed", putArgs...) c.InvokeFail(t, "name is already taken", "putNamed", putArgs...)
}) })
c.Invoke(t, stackitem.Null{}, "delete", cnt.id[:], cnt.sig, cnt.token) c.Invoke(t, stackitem.Null{}, "delete", cnt.id[:], cnt.sig, cnt.token)
cNNS.Invoke(t, stackitem.Null{}, "resolve", "mycnt.frostfs", int64(nns.TXT)) cNNS.Invoke(t, stackitem.Null{}, "resolve", "mycnt.neofs", int64(nns.TXT))
t.Run("register in advance", func(t *testing.T) { t.Run("register in advance", func(t *testing.T) {
cnt.value[len(cnt.value)-1] = 10 cnt.value[len(cnt.value)-1] = 10
@ -210,11 +126,11 @@ func TestContainerPut(t *testing.T) {
cNNS.Invoke(t, true, "register", cNNS.Invoke(t, true, "register",
"cdn", c.CommitteeHash, "cdn", c.CommitteeHash,
"whateveriwant@world.com", int64(0), int64(0), int64(100_000), int64(0)) "whateveriwant@world.com", int64(0), int64(0), int64(0), int64(0))
cNNS.Invoke(t, true, "register", cNNS.Invoke(t, true, "register",
"domain.cdn", c.CommitteeHash, "domain.cdn", c.CommitteeHash,
"whateveriwant@world.com", int64(0), int64(0), int64(100_000), int64(0)) "whateveriwant@world.com", int64(0), int64(0), int64(0), int64(0))
balanceMint(t, cBal, acc, (containerFee+containerAliasFee)*1, []byte{}) balanceMint(t, cBal, acc, (containerFee+containerAliasFee)*1, []byte{})
@ -229,19 +145,15 @@ func TestContainerPut(t *testing.T) {
}) })
} }
func addContainer(t *testing.T, c, cBal *neotest.ContractInvoker) (neotest.Signer, testContainer) { func TestContainerDelete(t *testing.T) {
c, cBal := newContainerInvoker(t)
acc := c.NewAccount(t) acc := c.NewAccount(t)
cnt := dummyContainer(acc) cnt := dummyContainer(acc)
balanceMint(t, cBal, acc, containerFee*1, []byte{}) balanceMint(t, cBal, acc, containerFee*1, []byte{})
c.Invoke(t, stackitem.Null{}, "put", cnt.value, cnt.sig, cnt.pub, cnt.token) c.Invoke(t, stackitem.Null{}, "put", cnt.value, cnt.sig, cnt.pub, cnt.token)
return acc, cnt
}
func TestContainerDelete(t *testing.T) {
c, cBal, _ := newContainerInvoker(t)
acc, cnt := addContainer(t, c, cBal)
cAcc := c.WithSigners(acc) cAcc := c.WithSigners(acc)
cAcc.InvokeFail(t, common.ErrAlphabetWitnessFailed, "delete", cAcc.InvokeFail(t, common.ErrAlphabetWitnessFailed, "delete",
cnt.id[:], cnt.sig, cnt.token) cnt.id[:], cnt.sig, cnt.token)
@ -258,9 +170,13 @@ func TestContainerDelete(t *testing.T) {
} }
func TestContainerOwner(t *testing.T) { func TestContainerOwner(t *testing.T) {
c, cBal, _ := newContainerInvoker(t) c, cBal := newContainerInvoker(t)
acc, cnt := addContainer(t, c, cBal) acc := c.NewAccount(t)
cnt := dummyContainer(acc)
balanceMint(t, cBal, acc, containerFee*1, []byte{})
c.Invoke(t, stackitem.Null{}, "put", cnt.value, cnt.sig, cnt.pub, cnt.token)
t.Run("missing container", func(t *testing.T) { t.Run("missing container", func(t *testing.T) {
id := cnt.id id := cnt.id
@ -273,9 +189,14 @@ func TestContainerOwner(t *testing.T) {
} }
func TestContainerGet(t *testing.T) { func TestContainerGet(t *testing.T) {
c, cBal, _ := newContainerInvoker(t) c, cBal := newContainerInvoker(t)
_, cnt := addContainer(t, c, cBal) acc := c.NewAccount(t)
cnt := dummyContainer(acc)
balanceMint(t, cBal, acc, containerFee*1, []byte{})
c.Invoke(t, stackitem.Null{}, "put", cnt.value, cnt.sig, cnt.pub, cnt.token)
t.Run("missing container", func(t *testing.T) { t.Run("missing container", func(t *testing.T) {
id := cnt.id id := cnt.id
@ -311,9 +232,13 @@ func dummyEACL(containerID [32]byte) eacl {
} }
func TestContainerSetEACL(t *testing.T) { func TestContainerSetEACL(t *testing.T) {
c, cBal, _ := newContainerInvoker(t) c, cBal := newContainerInvoker(t)
acc, cnt := addContainer(t, c, cBal) acc := c.NewAccount(t)
cnt := dummyContainer(acc)
balanceMint(t, cBal, acc, containerFee*1, []byte{})
c.Invoke(t, stackitem.Null{}, "put", cnt.value, cnt.sig, cnt.pub, cnt.token)
t.Run("missing container", func(t *testing.T) { t.Run("missing container", func(t *testing.T) {
id := cnt.id id := cnt.id
@ -337,161 +262,3 @@ func TestContainerSetEACL(t *testing.T) {
}) })
c.Invoke(t, expected, "eACL", cnt.id[:]) c.Invoke(t, expected, "eACL", cnt.id[:])
} }
func TestContainerSizeEstimation(t *testing.T) {
c, cBal, cNm := newContainerInvoker(t)
_, cnt := addContainer(t, c, cBal)
nodes := []testNodeInfo{
newStorageNode(t, c),
newStorageNode(t, c),
newStorageNode(t, c),
}
for i := range nodes {
cNm.WithSigners(nodes[i].signer).Invoke(t, stackitem.Null{}, "addPeer", nodes[i].raw)
cNm.Invoke(t, stackitem.Null{}, "addPeerIR", nodes[i].raw)
}
// putContainerSize retrieves storage nodes from the previous snapshot,
// so epoch must be incremented twice.
cNm.Invoke(t, stackitem.Null{}, "newEpoch", int64(1))
cNm.Invoke(t, stackitem.Null{}, "newEpoch", int64(2))
t.Run("must be witnessed by key in the argument", func(t *testing.T) {
c.WithSigners(nodes[1].signer).InvokeFail(t, common.ErrWitnessFailed, "putContainerSize",
int64(2), cnt.id[:], int64(123), nodes[0].pub)
})
c.WithSigners(nodes[0].signer).Invoke(t, stackitem.Null{}, "putContainerSize",
int64(2), cnt.id[:], int64(123), nodes[0].pub)
estimations := []estimation{{nodes[0].pub, 123}}
checkEstimations(t, c, 2, cnt, estimations...)
c.WithSigners(nodes[1].signer).Invoke(t, stackitem.Null{}, "putContainerSize",
int64(2), cnt.id[:], int64(42), nodes[1].pub)
estimations = append(estimations, estimation{nodes[1].pub, int64(42)})
checkEstimations(t, c, 2, cnt, estimations...)
t.Run("add estimation for a different epoch", func(t *testing.T) {
c.WithSigners(nodes[2].signer).Invoke(t, stackitem.Null{}, "putContainerSize",
int64(1), cnt.id[:], int64(777), nodes[2].pub)
checkEstimations(t, c, 1, cnt, estimation{nodes[2].pub, 777})
checkEstimations(t, c, 2, cnt, estimations...)
})
c.WithSigners(nodes[2].signer).Invoke(t, stackitem.Null{}, "putContainerSize",
int64(3), cnt.id[:], int64(888), nodes[2].pub)
checkEstimations(t, c, 3, cnt, estimation{nodes[2].pub, 888})
// Remove old estimations.
for i := int64(1); i <= container.CleanupDelta; i++ {
cNm.Invoke(t, stackitem.Null{}, "newEpoch", 2+i)
checkEstimations(t, c, 2, cnt, estimations...)
checkEstimations(t, c, 3, cnt, estimation{nodes[2].pub, 888})
}
epoch := int64(2 + container.CleanupDelta + 1)
cNm.Invoke(t, stackitem.Null{}, "newEpoch", epoch)
checkEstimations(t, c, 2, cnt, estimations...) // not yet removed
checkEstimations(t, c, 3, cnt, estimation{nodes[2].pub, 888})
c.WithSigners(nodes[1].signer).Invoke(t, stackitem.Null{}, "putContainerSize",
epoch, cnt.id[:], int64(999), nodes[1].pub)
checkEstimations(t, c, 2, cnt, estimations[:1]...)
checkEstimations(t, c, epoch, cnt, estimation{nodes[1].pub, int64(999)})
// Estimation from node 0 should be cleaned during epoch tick.
for i := int64(1); i <= container.TotalCleanupDelta-container.CleanupDelta; i++ {
cNm.Invoke(t, stackitem.Null{}, "newEpoch", epoch+i)
}
checkEstimations(t, c, 2, cnt)
checkEstimations(t, c, epoch, cnt, estimation{nodes[1].pub, int64(999)})
}
type estimation struct {
from []byte
size int64
}
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)
var id []byte
// When there are no estimations, listContainerSizes can also return nothing.
item := s.Top().Item()
switch it := item.(type) {
case stackitem.Null:
require.Equal(t, stackitem.Null{}, it)
return make([]estimation, 0)
case *stackitem.Array:
id, err = it.Value().([]stackitem.Item)[0].TryBytes()
require.NoError(t, err)
default:
require.FailNow(t, "invalid return type for listContainerSizes")
}
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())
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 _, 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", e.from)
}
}

View file

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

View file

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

View file

@ -4,17 +4,15 @@ import (
"math/big" "math/big"
"math/rand" "math/rand"
"path" "path"
"strings"
"testing" "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/encoding/bigint"
"github.com/nspcc-dev/neo-go/pkg/neotest" "github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "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/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -58,30 +56,13 @@ func TestDeploySetConfig(t *testing.T) {
"config", container.AliasFeeKey) "config", container.AliasFeeKey)
} }
type testNodeInfo struct { func dummyNodeInfo(acc neotest.Signer) []byte {
signer neotest.SingleSigner
pub []byte
raw []byte
state netmap.NodeState
}
func dummyNodeInfo(acc neotest.Signer) testNodeInfo {
ni := make([]byte, 66) ni := make([]byte, 66)
rand.Read(ni) rand.Read(ni)
s := acc.(neotest.SingleSigner) pub, _ := vm.ParseSignatureContract(acc.Script())
pub := s.Account().PrivateKey().PublicKey().Bytes()
copy(ni[2:], pub) copy(ni[2:], pub)
return testNodeInfo{ return ni
signer: s,
pub: pub,
raw: ni,
state: netmap.NodeStateOnline,
}
}
func newStorageNode(t *testing.T, c *neotest.ContractInvoker) testNodeInfo {
return dummyNodeInfo(c.NewAccount(t))
} }
func TestAddPeer(t *testing.T) { func TestAddPeer(t *testing.T) {
@ -93,322 +74,47 @@ func TestAddPeer(t *testing.T) {
acc1 := c.NewAccount(t) acc1 := c.NewAccount(t)
cAcc1 := c.WithSigners(acc1) cAcc1 := c.WithSigners(acc1)
cAcc1.InvokeFail(t, common.ErrWitnessFailed, "addPeer", dummyInfo.raw) cAcc1.InvokeFail(t, common.ErrWitnessFailed, "addPeer", dummyInfo)
h := cAcc.Invoke(t, stackitem.Null{}, "addPeer", dummyInfo.raw) h := cAcc.Invoke(t, stackitem.Null{}, "addPeer", dummyInfo)
aer := cAcc.CheckHalt(t, h) aer := cAcc.CheckHalt(t, h)
require.Equal(t, 0, len(aer.Events)) require.Equal(t, 1, len(aer.Events))
require.Equal(t, "AddPeer", aer.Events[0].Name)
require.Equal(t, stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(dummyInfo)}),
aer.Events[0].Item)
dummyInfo.raw[0] ^= 0xFF dummyInfo[0] ^= 0xFF
h = cAcc.Invoke(t, stackitem.Null{}, "addPeer", dummyInfo.raw) h = cAcc.Invoke(t, stackitem.Null{}, "addPeer", dummyInfo)
aer = cAcc.CheckHalt(t, h) aer = cAcc.CheckHalt(t, h)
require.Equal(t, 0, len(aer.Events)) require.Equal(t, 1, len(aer.Events))
require.Equal(t, "AddPeer", aer.Events[0].Name)
require.Equal(t, stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(dummyInfo)}),
aer.Events[0].Item)
c.InvokeFail(t, common.ErrWitnessFailed, "addPeer", dummyInfo.raw) c.InvokeFail(t, common.ErrWitnessFailed, "addPeer", dummyInfo)
c.Invoke(t, stackitem.Null{}, "addPeerIR", dummyInfo.raw) c.Invoke(t, stackitem.Null{}, "register", dummyInfo)
}
func TestNewEpoch(t *testing.T) {
rand.Seed(42)
const epochCount = netmap.DefaultSnapshotCount * 2
cNm := newNetmapInvoker(t)
nodes := make([][]testNodeInfo, epochCount)
for i := range nodes {
size := rand.Int()%5 + 1
arr := make([]testNodeInfo, size)
for j := 0; j < size; j++ {
arr[j] = newStorageNode(t, cNm)
}
nodes[i] = arr
}
for i := 0; i < epochCount; i++ {
for _, tn := range nodes[i] {
cNm.WithSigners(tn.signer).Invoke(t, stackitem.Null{}, "addPeer", tn.raw)
cNm.Invoke(t, stackitem.Null{}, "addPeerIR", tn.raw)
}
if i > 0 {
// Remove random nodes from the previous netmap.
current := make([]testNodeInfo, 0, len(nodes[i])+len(nodes[i-1]))
current = append(current, nodes[i]...)
for j := range nodes[i-1] {
if rand.Int()%3 == 0 {
cNm.Invoke(t, stackitem.Null{}, "updateStateIR",
int64(netmap.NodeStateOffline), nodes[i-1][j].pub)
} else {
current = append(current, nodes[i-1][j])
}
}
nodes[i] = current
}
cNm.Invoke(t, stackitem.Null{}, "newEpoch", i+1)
t.Logf("Epoch: %d, Netmap()", i)
s, err := cNm.TestInvoke(t, "netmap")
require.NoError(t, err)
require.Equal(t, 1, s.Len())
checkSnapshot(t, s, nodes[i])
for j := 0; j <= i && j < netmap.DefaultSnapshotCount; j++ {
t.Logf("Epoch: %d, diff: %d", i, j)
checkSnapshotAt(t, j, cNm, nodes[i-j])
}
_, err = cNm.TestInvoke(t, "snapshot", netmap.DefaultSnapshotCount)
require.Error(t, err)
require.True(t, strings.Contains(err.Error(), "incorrect diff"))
_, err = cNm.TestInvoke(t, "snapshot", -1)
require.Error(t, err)
require.True(t, strings.Contains(err.Error(), "incorrect diff"))
}
}
func TestUpdateSnapshotCount(t *testing.T) {
rand.Seed(42)
require.True(t, netmap.DefaultSnapshotCount > 5) // sanity check, adjust tests if false.
prepare := func(t *testing.T, cNm *neotest.ContractInvoker, epochCount int) [][]testNodeInfo {
nodes := make([][]testNodeInfo, epochCount)
nodes[0] = []testNodeInfo{newStorageNode(t, cNm)}
cNm.Invoke(t, stackitem.Null{}, "addPeerIR", nodes[0][0].raw)
cNm.Invoke(t, stackitem.Null{}, "newEpoch", 1)
for i := 1; i < len(nodes); i++ {
sn := newStorageNode(t, cNm)
nodes[i] = append(nodes[i-1], sn)
cNm.Invoke(t, stackitem.Null{}, "addPeerIR", sn.raw)
cNm.Invoke(t, stackitem.Null{}, "newEpoch", i+1)
}
return nodes
}
t.Run("increase size, extend with nil", func(t *testing.T) {
// Before: S-old .. S
// After : S-old .. S nil nil ...
const epochCount = netmap.DefaultSnapshotCount / 2
cNm := newNetmapInvoker(t)
nodes := prepare(t, cNm, epochCount)
const newCount = netmap.DefaultSnapshotCount + 3
cNm.Invoke(t, stackitem.Null{}, "updateSnapshotCount", newCount)
s, err := cNm.TestInvoke(t, "netmap")
require.NoError(t, err)
require.Equal(t, 1, s.Len())
checkSnapshot(t, s, nodes[epochCount-1])
for i := 0; i < epochCount; i++ {
checkSnapshotAt(t, i, cNm, nodes[epochCount-i-1])
}
for i := epochCount; i < newCount; i++ {
checkSnapshotAt(t, i, cNm, nil)
}
_, err = cNm.TestInvoke(t, "snapshot", int64(newCount))
require.Error(t, err)
})
t.Run("increase size, copy old snapshots", func(t *testing.T) {
// Before: S-x .. S S-old ...
// After : S-x .. S nil nil S-old ...
const epochCount = netmap.DefaultSnapshotCount + netmap.DefaultSnapshotCount/2
cNm := newNetmapInvoker(t)
nodes := prepare(t, cNm, epochCount)
const newCount = netmap.DefaultSnapshotCount + 3
cNm.Invoke(t, stackitem.Null{}, "updateSnapshotCount", newCount)
s, err := cNm.TestInvoke(t, "netmap")
require.NoError(t, err)
require.Equal(t, 1, s.Len())
checkSnapshot(t, s, nodes[epochCount-1])
for i := 0; i < newCount-3; i++ {
checkSnapshotAt(t, i, cNm, nodes[epochCount-i-1])
}
for i := newCount - 3; i < newCount; i++ {
checkSnapshotAt(t, i, cNm, nil)
}
_, err = cNm.TestInvoke(t, "snapshot", int64(newCount))
require.Error(t, err)
})
t.Run("decrease size, small decrease", func(t *testing.T) {
// Before: S-x .. S S-old ... ...
// After : S-x .. S S-new ...
const epochCount = netmap.DefaultSnapshotCount + netmap.DefaultSnapshotCount/2
cNm := newNetmapInvoker(t)
nodes := prepare(t, cNm, epochCount)
const newCount = netmap.DefaultSnapshotCount/2 + 2
cNm.Invoke(t, stackitem.Null{}, "updateSnapshotCount", newCount)
s, err := cNm.TestInvoke(t, "netmap")
require.NoError(t, err)
require.Equal(t, 1, s.Len())
checkSnapshot(t, s, nodes[epochCount-1])
for i := 0; i < newCount; i++ {
checkSnapshotAt(t, i, cNm, nodes[epochCount-i-1])
}
_, err = cNm.TestInvoke(t, "snapshot", int64(newCount))
require.Error(t, err)
})
t.Run("decrease size, big decrease", func(t *testing.T) {
// Before: S-x ... ... S S-old ... ...
// After : S-new ... S
const epochCount = netmap.DefaultSnapshotCount + netmap.DefaultSnapshotCount/2
cNm := newNetmapInvoker(t)
nodes := prepare(t, cNm, epochCount)
const newCount = netmap.DefaultSnapshotCount/2 - 2
cNm.Invoke(t, stackitem.Null{}, "updateSnapshotCount", newCount)
s, err := cNm.TestInvoke(t, "netmap")
require.NoError(t, err)
require.Equal(t, 1, s.Len())
checkSnapshot(t, s, nodes[epochCount-1])
for i := 0; i < newCount; i++ {
checkSnapshotAt(t, i, cNm, nodes[epochCount-i-1])
}
_, err = cNm.TestInvoke(t, "snapshot", int64(newCount))
require.Error(t, err)
})
}
func checkSnapshotAt(t *testing.T, epoch int, cNm *neotest.ContractInvoker, nodes []testNodeInfo) {
s, err := cNm.TestInvoke(t, "snapshot", int64(epoch))
require.NoError(t, err)
require.Equal(t, 1, s.Len())
checkSnapshot(t, s, nodes)
}
func checkSnapshot(t *testing.T, s *vm.Stack, nodes []testNodeInfo) {
arr, ok := s.Pop().Value().([]stackitem.Item)
require.True(t, ok, "expected array")
require.Equal(t, len(nodes), len(arr), "expected %d nodes", len(nodes))
actual := make([]netmap.Node, len(nodes))
expected := make([]netmap.Node, len(nodes))
for i := range nodes {
n, ok := arr[i].Value().([]stackitem.Item)
require.True(t, ok, "expected node struct")
require.Equalf(t, 2, len(n), "expected %d field(s)", 2)
require.IsType(t, []byte{}, n[0].Value())
state, err := n[1].TryInteger()
require.NoError(t, err)
actual[i].BLOB = n[0].Value().([]byte)
actual[i].State = netmap.NodeState(state.Int64())
expected[i].BLOB = nodes[i].raw
expected[i].State = nodes[i].state
}
require.ElementsMatch(t, expected, actual, "snapshot is different")
}
func TestUpdateStateIR(t *testing.T) {
cNm := newNetmapInvoker(t)
acc := cNm.NewAccount(t)
pub := acc.(neotest.SingleSigner).Account().PrivateKey().PublicKey().Bytes()
t.Run("can't move online, need addPeerIR", func(t *testing.T) {
cNm.InvokeFail(t, "peer is missing", "updateStateIR", int64(netmap.NodeStateOnline), pub)
})
dummyInfo := dummyNodeInfo(acc)
cNm.Invoke(t, stackitem.Null{}, "addPeerIR", dummyInfo.raw)
acc1 := cNm.NewAccount(t)
dummyInfo1 := dummyNodeInfo(acc1)
cNm.Invoke(t, stackitem.Null{}, "addPeerIR", dummyInfo1.raw)
t.Run("must be signed by the alphabet", func(t *testing.T) {
cAcc := cNm.WithSigners(acc)
cAcc.InvokeFail(t, common.ErrAlphabetWitnessFailed, "updateStateIR", int64(netmap.NodeStateOffline), pub)
})
t.Run("invalid state", func(t *testing.T) {
cNm.InvokeFail(t, "unsupported state", "updateStateIR", int64(42), pub)
})
checkNetmapCandidates(t, cNm, 2)
// Move the first node offline.
cNm.Invoke(t, stackitem.Null{}, "updateStateIR", int64(netmap.NodeStateOffline), pub)
checkNetmapCandidates(t, cNm, 1)
checkState := func(expected netmap.NodeState) {
arr := checkNetmapCandidates(t, cNm, 1)
nn := arr[0].Value().([]stackitem.Item)
state, err := nn[1].TryInteger()
require.NoError(t, err)
require.Equal(t, int64(expected), state.Int64())
}
// Move the second node in the maintenance state.
pub1 := acc1.(neotest.SingleSigner).Account().PrivateKey().PublicKey().Bytes()
t.Run("maintenance -> add peer", func(t *testing.T) {
cNm.Invoke(t, stackitem.Null{}, "updateStateIR", int64(netmap.NodeStateMaintenance), pub1)
checkState(netmap.NodeStateMaintenance)
cNm.Invoke(t, stackitem.Null{}, "addPeerIR", dummyInfo1.raw)
checkState(netmap.NodeStateOnline)
})
t.Run("maintenance -> online", func(t *testing.T) {
cNm.Invoke(t, stackitem.Null{}, "updateStateIR", int64(netmap.NodeStateMaintenance), pub1)
checkState(netmap.NodeStateMaintenance)
cNm.Invoke(t, stackitem.Null{}, "updateStateIR", int64(netmap.NodeStateOnline), pub1)
checkState(netmap.NodeStateOnline)
})
} }
func TestUpdateState(t *testing.T) { func TestUpdateState(t *testing.T) {
cNm := newNetmapInvoker(t) e := newNetmapInvoker(t)
accs := []neotest.Signer{cNm.NewAccount(t), cNm.NewAccount(t)} acc := e.NewAccount(t)
pubs := make([][]byte, len(accs)) cAcc := e.WithSigners(acc)
for i := range accs { cBoth := e.WithSigners(e.Committee, acc)
dummyInfo := dummyNodeInfo(accs[i]) dummyInfo := dummyNodeInfo(acc)
cNm.Invoke(t, stackitem.Null{}, "addPeerIR", dummyInfo.raw)
pubs[i] = accs[i].(neotest.SingleSigner).Account().PrivateKey().PublicKey().Bytes() cBoth.Invoke(t, stackitem.Null{}, "addPeer", dummyInfo)
}
pub, ok := vm.ParseSignatureContract(acc.Script())
require.True(t, ok)
t.Run("missing witness", func(t *testing.T) { t.Run("missing witness", func(t *testing.T) {
cAcc := cNm.WithSigners(accs[0]) cAcc.InvokeFail(t, common.ErrAlphabetWitnessFailed,
cNm.InvokeFail(t, common.ErrWitnessFailed, "updateState", int64(2), pubs[0]) "updateState", int64(2), pub)
cAcc.InvokeFail(t, common.ErrAlphabetWitnessFailed, "updateState", int64(2), pubs[0]) e.InvokeFail(t, common.ErrWitnessFailed,
cAcc.InvokeFail(t, common.ErrWitnessFailed, "updateState", int64(2), pubs[1]) "updateState", int64(2), pub)
}) })
checkNetmapCandidates(t, cNm, 2) cBoth.Invoke(t, stackitem.Null{}, "updateState", int64(2), pub)
cAcc.Invoke(t, stackitem.NewArray([]stackitem.Item{}), "netmapCandidates")
cBoth := cNm.WithSigners(accs[0], cNm.Committee)
cBoth.Invoke(t, stackitem.Null{}, "updateState", int64(2), pubs[0])
checkNetmapCandidates(t, cNm, 1)
t.Run("remove already removed node", func(t *testing.T) {
cBoth.Invoke(t, stackitem.Null{}, "updateState", int64(2), pubs[0])
checkNetmapCandidates(t, cNm, 1)
})
cBoth = cNm.WithSigners(accs[1], cNm.Committee)
cBoth.Invoke(t, stackitem.Null{}, "updateState", int64(2), pubs[1])
checkNetmapCandidates(t, cNm, 0)
}
func checkNetmapCandidates(t *testing.T, c *neotest.ContractInvoker, size int) []stackitem.Item {
s, err := c.TestInvoke(t, "netmapCandidates")
require.NoError(t, err)
require.Equal(t, 1, s.Len())
arr, ok := s.Pop().Value().([]stackitem.Item)
require.True(t, ok)
require.Equal(t, size, len(arr))
return arr
} }

View file

@ -4,14 +4,13 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"path" "path"
"strings"
"testing" "testing"
"time" "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/core/interop/storage"
"github.com/nspcc-dev/neo-go/pkg/neotest" "github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neofs-contract/nns"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -26,8 +25,7 @@ func newNNSInvoker(t *testing.T, addRoot bool) *neotest.ContractInvoker {
c := e.CommitteeInvoker(ctr.Hash) c := e.CommitteeInvoker(ctr.Hash)
if addRoot { if addRoot {
// Set expiration big enough to pass all tests. refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104)
refresh, retry, expire, ttl := int64(101), int64(102), int64(msPerYear/1000*100), int64(104)
c.Invoke(t, true, "register", c.Invoke(t, true, "register",
"com", c.CommitteeHash, "com", c.CommitteeHash,
"myemail@nspcc.ru", refresh, retry, expire, ttl) "myemail@nspcc.ru", refresh, retry, expire, ttl)
@ -254,36 +252,14 @@ func TestNNSGetAllRecords(t *testing.T) {
func TestExpiration(t *testing.T) { func TestExpiration(t *testing.T) {
c := newNNSInvoker(t, true) c := newNNSInvoker(t, true)
refresh, retry, expire, ttl := int64(101), int64(102), int64(msPerYear/1000*10), int64(104) refresh, retry, expire, ttl := int64(101), int64(102), int64(103), int64(104)
c.Invoke(t, true, "register", c.Invoke(t, true, "register",
"testdomain.com", c.CommitteeHash, "testdomain.com", c.CommitteeHash,
"myemail@nspcc.ru", refresh, retry, expire, ttl) "myemail@nspcc.ru", refresh, retry, expire, ttl)
checkProperties := func(t *testing.T, expiration uint64) {
expected := stackitem.NewMapWithValue([]stackitem.MapElement{
{Key: stackitem.Make("name"), Value: stackitem.Make("testdomain.com")},
{Key: stackitem.Make("expiration"), Value: stackitem.Make(expiration)}})
s, err := c.TestInvoke(t, "properties", "testdomain.com")
require.NoError(t, err)
require.Equal(t, expected.Value(), s.Top().Item().Value())
}
top := c.TopBlock(t)
expiration := top.Timestamp + uint64(expire*1000)
checkProperties(t, expiration)
b := c.NewUnsignedBlock(t) b := c.NewUnsignedBlock(t)
b.Timestamp = expiration - 2 // test invoke is done with +1 timestamp b.Timestamp = c.TopBlock(t).Timestamp + uint64(msPerYear) - 1
require.NoError(t, c.Chain.AddBlock(c.SignBlock(b))) require.NoError(t, c.Chain.AddBlock(c.SignBlock(b)))
checkProperties(t, expiration)
b = c.NewUnsignedBlock(t)
b.Timestamp = expiration - 1
require.NoError(t, c.Chain.AddBlock(c.SignBlock(b)))
_, err := c.TestInvoke(t, "properties", "testdomain.com")
require.Error(t, err)
require.True(t, strings.Contains(err.Error(), "name has expired"))
c.InvokeFail(t, "name has expired", "getAllRecords", "testdomain.com") c.InvokeFail(t, "name has expired", "getAllRecords", "testdomain.com")
c.InvokeFail(t, "name has expired", "ownerOf", "testdomain.com") c.InvokeFail(t, "name has expired", "ownerOf", "testdomain.com")
@ -344,14 +320,13 @@ func TestNNSRenew(t *testing.T) {
const msPerYear = 365 * 24 * time.Hour / time.Millisecond const msPerYear = 365 * 24 * time.Hour / time.Millisecond
b := c.TopBlock(t) b := c.TopBlock(t)
ts := b.Timestamp + uint64(expire*1000) + uint64(msPerYear) ts := b.Timestamp + 2*uint64(msPerYear)
cAcc := c.WithSigners(acc) cAcc := c.WithSigners(acc)
cAcc.InvokeFail(t, "not witnessed by admin", "renew", "testdomain.com") cAcc.Invoke(t, ts, "renew", "testdomain.com")
c1.Invoke(t, ts, "renew", "testdomain.com")
expected := stackitem.NewMapWithValue([]stackitem.MapElement{ expected := stackitem.NewMapWithValue([]stackitem.MapElement{
{Key: stackitem.Make("name"), Value: stackitem.Make("testdomain.com")}, {stackitem.Make("name"), stackitem.Make("testdomain.com")},
{Key: stackitem.Make("expiration"), Value: stackitem.Make(ts)}}) {stackitem.Make("expiration"), stackitem.Make(ts)}})
cAcc.Invoke(t, expected, "properties", "testdomain.com") cAcc.Invoke(t, expected, "properties", "testdomain.com")
} }

View file

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

View file

@ -5,12 +5,12 @@ import (
"path" "path"
"testing" "testing"
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"git.frostfs.info/TrueCloudLab/frostfs-contract/subnet"
"github.com/nspcc-dev/neo-go/pkg/neotest" "github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "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" "github.com/stretchr/testify/require"
) )

View file

@ -3,20 +3,10 @@ package tests
import ( import (
"testing" "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"
"github.com/nspcc-dev/neo-go/pkg/neotest/chain" "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 { func newExecutor(t *testing.T) *neotest.Executor {
bc, acc := chain.NewSingle(t) bc, acc := chain.NewSingle(t)
return neotest.NewExecutor(t, bc, acc, acc) return neotest.NewExecutor(t, bc, acc, acc)

View file

@ -1,29 +0,0 @@
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")
}