forked from TrueCloudLab/frostfs-node
Compare commits
1 commit
master
...
neofs-adm-
Author | SHA1 | Date | |
---|---|---|---|
|
6daf5cc438 |
1474 changed files with 56934 additions and 99355 deletions
|
@ -1,19 +1,19 @@
|
|||
FROM golang:1.22 as builder
|
||||
FROM golang:1.17 as builder
|
||||
ARG BUILD=now
|
||||
ARG VERSION=dev
|
||||
ARG REPO=repository
|
||||
WORKDIR /src
|
||||
COPY . /src
|
||||
|
||||
RUN make bin/frostfs-adm
|
||||
RUN make bin/neofs-adm
|
||||
|
||||
# Executable image
|
||||
FROM alpine AS frostfs-adm
|
||||
FROM alpine AS neofs-adm
|
||||
RUN apk add --no-cache bash
|
||||
|
||||
WORKDIR /
|
||||
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
COPY --from=builder /src/bin/frostfs-adm /bin/frostfs-adm
|
||||
COPY --from=builder /src/bin/neofs-adm /bin/neofs-adm
|
||||
|
||||
CMD ["frostfs-adm"]
|
||||
CMD ["neofs-adm"]
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
FROM golang:1.22
|
||||
|
||||
WORKDIR /tmp
|
||||
|
||||
# Install apt packages
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||
pip \
|
||||
&& apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Dash → Bash
|
||||
RUN echo "dash dash/sh boolean false" | debconf-set-selections
|
||||
RUN DEBIAN_FRONTEND=noninteractive dpkg-reconfigure dash
|
||||
|
||||
RUN useradd -u 1234 -d /home/ci -m ci
|
||||
USER ci
|
||||
|
||||
ENV PATH="$PATH:/home/ci/.local/bin"
|
||||
|
||||
COPY .pre-commit-config.yaml .
|
||||
|
||||
RUN pip install "pre-commit==3.1.1" \
|
||||
&& git init . \
|
||||
&& pre-commit install-hooks \
|
||||
&& rm -rf /tmp/*
|
|
@ -1,19 +1,19 @@
|
|||
FROM golang:1.22 as builder
|
||||
FROM golang:1.17 as builder
|
||||
ARG BUILD=now
|
||||
ARG VERSION=dev
|
||||
ARG REPO=repository
|
||||
WORKDIR /src
|
||||
COPY . /src
|
||||
|
||||
RUN make bin/frostfs-cli
|
||||
RUN make bin/neofs-cli
|
||||
|
||||
# Executable image
|
||||
FROM alpine AS frostfs-cli
|
||||
FROM alpine AS neofs-cli
|
||||
RUN apk add --no-cache bash
|
||||
|
||||
WORKDIR /
|
||||
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
COPY --from=builder /src/bin/frostfs-cli /bin/frostfs-cli
|
||||
COPY --from=builder /src/bin/neofs-cli /bin/neofs-cli
|
||||
|
||||
CMD ["frostfs-cli"]
|
||||
CMD ["neofs-cli"]
|
||||
|
|
|
@ -3,6 +3,6 @@ RUN apk add --no-cache bash ca-certificates
|
|||
|
||||
WORKDIR /
|
||||
|
||||
COPY bin/frostfs-adm /bin/frostfs-adm
|
||||
COPY bin/neofs-adm /bin/neofs-adm
|
||||
|
||||
CMD ["frostfs-adm"]
|
||||
CMD ["neofs-adm"]
|
||||
|
|
|
@ -3,6 +3,6 @@ RUN apk add --no-cache bash ca-certificates
|
|||
|
||||
WORKDIR /
|
||||
|
||||
COPY bin/frostfs-cli /bin/frostfs-cli
|
||||
COPY bin/neofs-cli /bin/neofs-cli
|
||||
|
||||
CMD ["frostfs-cli"]
|
||||
CMD ["neofs-cli"]
|
||||
|
|
|
@ -3,6 +3,6 @@ RUN apk add --no-cache bash ca-certificates
|
|||
|
||||
WORKDIR /
|
||||
|
||||
COPY bin/frostfs-ir /bin/frostfs-ir
|
||||
COPY bin/neofs-ir /bin/neofs-ir
|
||||
|
||||
CMD ["frostfs-ir"]
|
||||
CMD ["neofs-ir"]
|
||||
|
|
|
@ -3,6 +3,6 @@ RUN apk add --no-cache bash ca-certificates
|
|||
|
||||
WORKDIR /
|
||||
|
||||
COPY bin/frostfs-node /bin/frostfs-node
|
||||
COPY bin/neofs-node /bin/neofs-node
|
||||
|
||||
CMD ["frostfs-node"]
|
||||
CMD ["neofs-node"]
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
FROM golang:1.22 as builder
|
||||
FROM golang:1.17 as builder
|
||||
ARG BUILD=now
|
||||
ARG VERSION=dev
|
||||
ARG REPO=repository
|
||||
WORKDIR /src
|
||||
COPY . /src
|
||||
|
||||
RUN make bin/frostfs-ir
|
||||
RUN make bin/neofs-ir
|
||||
|
||||
# Executable image
|
||||
FROM alpine AS frostfs-ir
|
||||
FROM alpine AS neofs-ir
|
||||
RUN apk add --no-cache bash
|
||||
|
||||
WORKDIR /
|
||||
|
||||
COPY --from=builder /src/bin/frostfs-ir /bin/frostfs-ir
|
||||
COPY --from=builder /src/bin/neofs-ir /bin/neofs-ir
|
||||
|
||||
CMD ["frostfs-ir"]
|
||||
CMD ["neofs-ir"]
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
FROM golang:1.22 as builder
|
||||
FROM golang:1.17 as builder
|
||||
ARG BUILD=now
|
||||
ARG VERSION=dev
|
||||
ARG REPO=repository
|
||||
WORKDIR /src
|
||||
COPY . /src
|
||||
|
||||
RUN make bin/frostfs-node
|
||||
RUN make bin/neofs-node
|
||||
|
||||
# Executable image
|
||||
FROM alpine AS frostfs-node
|
||||
FROM alpine AS neofs-node
|
||||
RUN apk add --no-cache bash
|
||||
|
||||
WORKDIR /
|
||||
|
||||
COPY --from=builder /src/bin/frostfs-node /bin/frostfs-node
|
||||
COPY --from=builder /src/bin/neofs-node /bin/neofs-node
|
||||
|
||||
CMD ["frostfs-node"]
|
||||
CMD ["neofs-node"]
|
||||
|
|
19
.docker/Dockerfile.storage-testnet
Normal file
19
.docker/Dockerfile.storage-testnet
Normal file
|
@ -0,0 +1,19 @@
|
|||
FROM golang:1.17 as builder
|
||||
ARG BUILD=now
|
||||
ARG VERSION=dev
|
||||
ARG REPO=repository
|
||||
WORKDIR /src
|
||||
COPY . /src
|
||||
|
||||
RUN make bin/neofs-node
|
||||
|
||||
# Executable image
|
||||
FROM alpine AS neofs-node
|
||||
RUN apk add --no-cache bash
|
||||
|
||||
WORKDIR /
|
||||
|
||||
COPY --from=builder /src/bin/neofs-node /bin/neofs-node
|
||||
COPY --from=builder /src/config/testnet/config.yml /config.yml
|
||||
|
||||
CMD ["neofs-node", "--config", "/config.yml"]
|
|
@ -6,4 +6,3 @@ Dockerfile
|
|||
temp
|
||||
.dockerignore
|
||||
docker
|
||||
.cache
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
name: Build
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Components
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
go_versions: [ '1.21', '1.22' ]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
# Allows to fetch all history for all branches and tags.
|
||||
# Need this for proper versioning.
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '${{ matrix.go_versions }}'
|
||||
|
||||
- name: Build CLI
|
||||
run: make bin/frostfs-cli
|
||||
- run: bin/frostfs-cli --version
|
||||
|
||||
- name: Build NODE
|
||||
run: make bin/frostfs-node
|
||||
|
||||
- name: Build IR
|
||||
run: make bin/frostfs-ir
|
||||
|
||||
- name: Build ADM
|
||||
run: make bin/frostfs-adm
|
||||
- run: bin/frostfs-adm --version
|
||||
|
||||
- name: Build LENS
|
||||
run: make bin/frostfs-lens
|
||||
- run: bin/frostfs-lens --version
|
|
@ -1,21 +0,0 @@
|
|||
name: DCO action
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
dco:
|
||||
name: DCO
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.22'
|
||||
|
||||
- name: Run commit format checker
|
||||
uses: https://git.frostfs.info/TrueCloudLab/dco-go@v3
|
||||
with:
|
||||
from: 'origin/${{ github.event.pull_request.base.ref }}'
|
|
@ -1,25 +0,0 @@
|
|||
name: Pre-commit hooks
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
precommit:
|
||||
name: Pre-commit
|
||||
env:
|
||||
# Skip pre-commit hooks which are executed by other actions.
|
||||
SKIP: make-lint,go-staticcheck-repo-mod,go-unit-tests,gofumpt
|
||||
runs-on: ubuntu-22.04
|
||||
# If we use actions/setup-python from either Github or Gitea,
|
||||
# the line above fails with a cryptic error about not being able to found python.
|
||||
# So install everything manually.
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.22
|
||||
- name: Set up Python
|
||||
run: |
|
||||
apt update
|
||||
apt install -y pre-commit
|
||||
- name: Run pre-commit
|
||||
run: pre-commit run --color=always --hook-stage manual --all-files
|
|
@ -1,91 +0,0 @@
|
|||
name: Tests and linters
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.22'
|
||||
cache: true
|
||||
|
||||
- name: Install linters
|
||||
run: make lint-install
|
||||
|
||||
- name: Run linters
|
||||
run: make lint
|
||||
|
||||
tests:
|
||||
name: Tests
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
go_versions: [ '1.21', '1.22' ]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '${{ matrix.go_versions }}'
|
||||
cache: true
|
||||
|
||||
- name: Run tests
|
||||
run: make test
|
||||
|
||||
tests-race:
|
||||
name: Tests with -race
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.21'
|
||||
cache: true
|
||||
|
||||
- name: Run tests
|
||||
run: go test ./... -count=1 -race
|
||||
|
||||
staticcheck:
|
||||
name: Staticcheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.22'
|
||||
cache: true
|
||||
|
||||
- name: Install staticcheck
|
||||
run: make staticcheck-install
|
||||
|
||||
- name: Run staticcheck
|
||||
run: make staticcheck-run
|
||||
|
||||
gopls:
|
||||
name: gopls check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.21'
|
||||
cache: true
|
||||
|
||||
- name: Install gopls
|
||||
run: make gopls-install
|
||||
|
||||
- name: Run gopls
|
||||
run: make gopls-run
|
|
@ -1,22 +0,0 @@
|
|||
name: Vulncheck
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
vulncheck:
|
||||
name: Vulncheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.22'
|
||||
|
||||
- name: Install govulncheck
|
||||
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
|
||||
- name: Run govulncheck
|
||||
run: govulncheck ./...
|
1
.gitattributes
vendored
1
.gitattributes
vendored
|
@ -1,3 +1,2 @@
|
|||
/**/*.pb.go -diff -merge
|
||||
/**/*.pb.go linguist-generated=true
|
||||
/go.sum -diff
|
||||
|
|
9
.github/ISSUE_TEMPLATE/bug_report.md
vendored
9
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -2,7 +2,7 @@
|
|||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: community, triage, bug
|
||||
labels: community, triage
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
@ -18,11 +18,8 @@ assignees: ''
|
|||
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. -->
|
||||
<!-- Not obligatory, but suggest a fix/reason for the bug,
|
||||
or ideas how to implement the addition or change -->
|
||||
|
||||
## Steps to Reproduce (for bugs)
|
||||
<!-- Provide a link to a live example, or an unambiguous set of steps
|
||||
|
|
8
.github/ISSUE_TEMPLATE/feature_request.md
vendored
8
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -18,11 +18,3 @@ assignees: ''
|
|||
|
||||
## Additional context
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
|
||||
## Don't forget to add labels!
|
||||
- component label (`neofs-adm`, `neofs-storage`, ...)
|
||||
- issue type (`enhancement`, `refactor`, ...)
|
||||
- `goodfirstissue`, `helpwanted` if needed
|
||||
- does this issue belong to an epic?
|
||||
- priority (`P0`-`P4`) if already triaged
|
||||
- quarter label (`202XQY`) if possible
|
||||
|
|
187
.github/logo.svg
vendored
187
.github/logo.svg
vendored
|
@ -1,70 +1,129 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Слой_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 184.2 51.8" style="enable-background:new 0 0 184.2 51.8;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{display:none;}
|
||||
.st1{display:inline;}
|
||||
.st2{fill:#01E397;}
|
||||
.st3{display:inline;fill:#010032;}
|
||||
.st4{display:inline;fill:#00E599;}
|
||||
.st5{display:inline;fill:#00AF92;}
|
||||
.st6{fill:#00C3E5;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
<g id="Layer_1-2" class="st0">
|
||||
<g class="st1">
|
||||
<path class="st2" d="M146.6,18.3v7.2h10.9V29h-10.9v10.7h-4V14.8h18v3.5H146.6z"/>
|
||||
<path class="st2" d="M180,15.7c1.7,0.9,3,2.2,4,3.8l-3,2.7c-0.6-1.3-1.5-2.4-2.6-3.3c-1.3-0.7-2.8-1-4.3-1
|
||||
c-1.4-0.1-2.8,0.3-4,1.1c-0.9,0.5-1.5,1.5-1.4,2.6c0,1,0.5,1.9,1.4,2.4c1.5,0.8,3.2,1.3,4.9,1.5c1.9,0.3,3.7,0.8,5.4,1.6
|
||||
c1.2,0.5,2.2,1.3,2.9,2.3c0.6,1,1,2.2,0.9,3.4c0,1.4-0.5,2.7-1.3,3.8c-0.9,1.2-2.1,2.1-3.5,2.6c-1.7,0.6-3.4,0.9-5.2,0.8
|
||||
c-5,0-8.6-1.6-10.7-5l2.9-2.8c0.7,1.4,1.8,2.5,3.1,3.3c1.5,0.7,3.1,1.1,4.7,1c1.5,0.1,2.9-0.2,4.2-0.9c0.9-0.5,1.5-1.5,1.5-2.6
|
||||
c0-0.9-0.5-1.8-1.3-2.2c-1.5-0.7-3.1-1.2-4.8-1.5c-1.9-0.3-3.7-0.8-5.5-1.5c-1.2-0.5-2.2-1.4-3-2.4c-0.6-1-1-2.2-0.9-3.4
|
||||
c0-1.4,0.4-2.7,1.2-3.8c0.8-1.2,2-2.2,3.3-2.8c1.6-0.7,3.4-1.1,5.2-1C176.1,14.3,178.2,14.8,180,15.7z"/>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
sodipodi:docname="logo_fs.svg"
|
||||
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||
id="svg57"
|
||||
version="1.1"
|
||||
viewBox="0 0 105 25"
|
||||
height="25mm"
|
||||
width="105mm">
|
||||
<defs
|
||||
id="defs51">
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath434">
|
||||
<path
|
||||
d="M 0,0 H 1366 V 768 H 0 Z"
|
||||
id="path432" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-x="130"
|
||||
inkscape:window-height="1040"
|
||||
inkscape:window-width="1274"
|
||||
height="50mm"
|
||||
units="mm"
|
||||
showgrid="false"
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:cy="344.49897"
|
||||
inkscape:cx="468.64708"
|
||||
inkscape:zoom="0.7"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
borderopacity="1.0"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff"
|
||||
id="base" />
|
||||
<metadata
|
||||
id="metadata54">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:groupmode="layer"
|
||||
inkscape:label="Layer 1">
|
||||
<g
|
||||
id="g424"
|
||||
transform="matrix(0.35277777,0,0,-0.35277777,63.946468,10.194047)">
|
||||
<path
|
||||
d="m 0,0 v -8.093 h 12.287 v -3.94 H 0 V -24.067 H -4.534 V 3.898 H 15.677 V 0 Z"
|
||||
style="fill:#00e396;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path426" />
|
||||
</g>
|
||||
<path class="st3" d="M73.3,16.3c1.9,1.9,2.9,4.5,2.7,7.1v15.9h-4V24.8c0-2.6-0.5-4.5-1.6-5.7c-1.2-1.2-2.8-1.8-4.5-1.7
|
||||
c-1.3,0-2.5,0.3-3.7,0.8c-1.2,0.7-2.2,1.7-2.9,2.9c-0.8,1.5-1.1,3.2-1.1,4.9v13.3h-4V15.1l3.6,1.5v1.7c0.8-1.5,2.1-2.6,3.6-3.3
|
||||
c1.5-0.8,3.2-1.2,4.9-1.1C68.9,13.8,71.3,14.7,73.3,16.3z"/>
|
||||
<path class="st3" d="M104.4,28.3H85.6c0.1,2.2,1,4.3,2.5,5.9c1.5,1.4,3.5,2.2,5.6,2.1c1.6,0.1,3.2-0.2,4.6-0.9
|
||||
c1.1-0.6,2-1.6,2.5-2.8l3.3,1.8c-0.9,1.7-2.3,3.1-4,4c-2,1-4.2,1.5-6.4,1.4c-3.7,0-6.7-1.1-8.8-3.4s-3.2-5.5-3.2-9.6s1-7.2,3-9.5
|
||||
s5-3.4,8.7-3.4c2.1-0.1,4.2,0.5,6.1,1.5c1.6,1,3,2.5,3.8,4.2c0.9,1.8,1.3,3.9,1.3,5.9C104.6,26.4,104.6,27.4,104.4,28.3z
|
||||
M88.1,19.3c-1.4,1.5-2.2,3.4-2.4,5.5h15.1c-0.2-2-1-3.9-2.3-5.5c-1.4-1.3-3.2-2-5.1-1.9C91.5,17.3,89.6,18,88.1,19.3z"/>
|
||||
<path class="st3" d="M131,17.3c2.2,2.3,3.2,5.5,3.2,9.5s-1,7.3-3.2,9.6s-5.1,3.4-8.8,3.4s-6.7-1.1-8.9-3.4s-3.2-5.5-3.2-9.6
|
||||
s1.1-7.2,3.2-9.5s5.1-3.4,8.9-3.4S128.9,15,131,17.3z M116.2,19.9c-1.5,2-2.2,4.4-2.1,6.9c-0.2,2.5,0.6,5,2.1,7
|
||||
c1.5,1.7,3.7,2.7,6,2.6c2.3,0.1,4.4-0.9,5.9-2.6c1.5-2,2.3-4.5,2.1-7c0.1-2.5-0.6-4.9-2.1-6.9c-1.5-1.7-3.6-2.7-5.9-2.6
|
||||
C119.9,17.2,117.7,18.2,116.2,19.9z"/>
|
||||
<polygon class="st4" points="0,9.1 0,43.7 22.5,51.8 22.5,16.9 46.8,7.9 24.8,0 "/>
|
||||
<polygon class="st5" points="24.3,17.9 24.3,36.8 46.8,44.9 46.8,9.6 "/>
|
||||
<g
|
||||
transform="matrix(0.35277777,0,0,-0.35277777,-315.43002,107.34005)"
|
||||
id="g428">
|
||||
<g
|
||||
id="g430"
|
||||
clip-path="url(#clipPath434)">
|
||||
<g
|
||||
id="g436"
|
||||
transform="translate(1112.874,278.2981)">
|
||||
<path
|
||||
d="M 0,0 C 1.822,-0.932 3.354,-2.359 4.597,-4.28 L 1.165,-7.373 c -0.791,1.695 -1.779,2.924 -2.966,3.686 -1.186,0.763 -2.768,1.145 -4.745,1.145 -1.949,0 -3.461,-0.389 -4.534,-1.166 -1.074,-0.777 -1.61,-1.772 -1.61,-2.987 0,-1.13 0.523,-2.027 1.568,-2.69 1.045,-0.664 2.909,-1.236 5.593,-1.716 2.514,-0.452 4.512,-1.024 5.995,-1.716 1.483,-0.693 2.564,-1.554 3.242,-2.585 0.677,-1.031 1.016,-2.309 1.016,-3.834 0,-1.639 -0.466,-3.079 -1.398,-4.322 -0.932,-1.243 -2.239,-2.197 -3.919,-2.86 -1.681,-0.664 -3.623,-0.996 -5.826,-0.996 -5.678,0 -9.689,1.892 -12.033,5.678 l 3.178,3.178 c 0.903,-1.695 2.068,-2.939 3.495,-3.729 1.426,-0.791 3.199,-1.186 5.318,-1.186 2.005,0 3.58,0.345 4.724,1.038 1.144,0.692 1.716,1.674 1.716,2.945 0,1.017 -0.516,1.835 -1.547,2.457 -1.031,0.621 -2.832,1.172 -5.402,1.653 -2.571,0.479 -4.618,1.073 -6.143,1.779 -1.526,0.706 -2.635,1.582 -3.326,2.627 -0.693,1.045 -1.039,2.316 -1.039,3.813 0,1.582 0.438,3.023 1.314,4.322 0.875,1.299 2.14,2.33 3.792,3.093 1.653,0.763 3.58,1.144 5.783,1.144 C -4.018,1.398 -1.822,0.932 0,0"
|
||||
style="fill:#00e396;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path438" />
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st6" d="M41.6,17.5H28.2v6.9h10.4v3.3H28.2v10.2h-3.9V14.2h17.2V17.5z"/>
|
||||
<path class="st6" d="M45.8,37.9v-18h3.3l0.4,3.2c0.5-1.2,1.2-2.1,2.1-2.7c0.9-0.6,2.1-0.9,3.5-0.9c0.4,0,0.7,0,1.1,0.1
|
||||
c0.4,0.1,0.7,0.2,0.9,0.3l-0.5,3.4c-0.3-0.1-0.6-0.2-0.9-0.2C55.4,23,54.9,23,54.4,23c-0.7,0-1.5,0.2-2.2,0.6
|
||||
c-0.7,0.4-1.3,1-1.8,1.8s-0.7,1.8-0.7,3v9.5H45.8z"/>
|
||||
<path class="st6" d="M68.6,19.6c1.8,0,3.3,0.4,4.6,1.1c1.3,0.7,2.4,1.8,3.1,3.2s1.1,3.1,1.1,5c0,1.9-0.4,3.6-1.1,5
|
||||
c-0.8,1.4-1.8,2.5-3.1,3.2c-1.3,0.7-2.9,1.1-4.6,1.1s-3.3-0.4-4.6-1.1c-1.3-0.7-2.4-1.8-3.2-3.2c-0.8-1.4-1.2-3.1-1.2-5
|
||||
c0-1.9,0.4-3.6,1.2-5s1.8-2.5,3.2-3.2C65.3,19.9,66.8,19.6,68.6,19.6z M68.6,22.6c-1.1,0-2,0.2-2.8,0.7c-0.8,0.5-1.3,1.2-1.7,2.1
|
||||
s-0.6,2.1-0.6,3.5c0,1.3,0.2,2.5,0.6,3.4s1,1.7,1.7,2.2s1.7,0.7,2.8,0.7c1.1,0,2-0.2,2.7-0.7c0.7-0.5,1.3-1.2,1.7-2.2
|
||||
s0.6-2.1,0.6-3.4c0-1.4-0.2-2.5-0.6-3.5s-1-1.6-1.7-2.1C70.6,22.8,69.6,22.6,68.6,22.6z"/>
|
||||
<path class="st6" d="M89.2,38.3c-1.8,0-3.4-0.3-4.9-1c-1.5-0.7-2.7-1.7-3.5-3l2.7-2.3c0.5,1,1.3,1.8,2.3,2.4
|
||||
c1,0.6,2.2,0.9,3.6,0.9c1.1,0,2-0.2,2.6-0.6c0.6-0.4,1-0.9,1-1.6c0-0.5-0.2-0.9-0.5-1.2s-0.9-0.6-1.7-0.8l-3.8-0.8
|
||||
c-1.9-0.4-3.3-1-4.1-1.9c-0.8-0.9-1.2-1.9-1.2-3.3c0-1,0.3-1.9,0.9-2.7c0.6-0.8,1.4-1.5,2.5-2s2.5-0.8,4-0.8c1.8,0,3.3,0.3,4.6,1
|
||||
c1.3,0.6,2.2,1.5,2.9,2.7l-2.7,2.2c-0.5-1-1.1-1.7-2-2.1c-0.9-0.5-1.8-0.7-2.8-0.7c-0.8,0-1.4,0.1-2,0.3c-0.6,0.2-1,0.5-1.3,0.8
|
||||
c-0.3,0.3-0.4,0.7-0.4,1.2c0,0.5,0.2,0.9,0.5,1.3s1,0.6,1.9,0.8l4.1,0.9c1.7,0.3,2.9,0.9,3.7,1.7c0.7,0.8,1.1,1.8,1.1,2.9
|
||||
c0,1.2-0.3,2.2-0.9,3c-0.6,0.9-1.5,1.6-2.6,2C92.1,38.1,90.7,38.3,89.2,38.3z"/>
|
||||
<path class="st6" d="M112.8,19.9v3H99.3v-3H112.8z M106.6,14.6v17.9c0,0.9,0.2,1.5,0.7,1.9c0.5,0.4,1.1,0.6,1.9,0.6
|
||||
c0.6,0,1.2-0.1,1.7-0.3c0.5-0.2,0.9-0.5,1.3-0.8l0.9,2.8c-0.6,0.5-1.2,0.9-2,1.1c-0.8,0.3-1.7,0.4-2.7,0.4c-1,0-2-0.2-2.8-0.5
|
||||
s-1.5-0.9-2-1.6c-0.5-0.8-0.7-1.7-0.8-3V15.7L106.6,14.6z"/>
|
||||
<path d="M137.9,17.5h-13.3v6.9h10.4v3.3h-10.4v10.2h-3.9V14.2h17.2V17.5z"/>
|
||||
<path d="M150.9,13.8c2.1,0,4,0.4,5.5,1.2c1.6,0.8,2.9,2,4,3.5l-2.6,2.5c-0.9-1.4-1.9-2.4-3.1-3c-1.1-0.6-2.5-0.9-4-0.9
|
||||
c-1.2,0-2.1,0.2-2.8,0.5c-0.7,0.3-1.3,0.7-1.6,1.2c-0.3,0.5-0.5,1.1-0.5,1.7c0,0.7,0.3,1.4,0.8,1.9c0.5,0.6,1.5,1,2.9,1.3
|
||||
l4.8,1.1c2.3,0.5,3.9,1.3,4.9,2.3c1,1,1.4,2.3,1.4,3.9c0,1.5-0.4,2.7-1.2,3.8c-0.8,1.1-1.9,1.9-3.3,2.5s-3.1,0.9-5,0.9
|
||||
c-1.7,0-3.2-0.2-4.5-0.6c-1.3-0.4-2.5-1-3.5-1.8c-1-0.7-1.8-1.6-2.5-2.6l2.7-2.7c0.5,0.8,1.1,1.6,1.9,2.2
|
||||
c0.8,0.7,1.7,1.2,2.7,1.5c1,0.4,2.2,0.5,3.4,0.5c1.1,0,2.1-0.1,2.9-0.4c0.8-0.3,1.4-0.7,1.8-1.2c0.4-0.5,0.6-1.1,0.6-1.9
|
||||
c0-0.7-0.2-1.3-0.7-1.8c-0.5-0.5-1.3-0.9-2.6-1.2l-5.2-1.2c-1.4-0.3-2.6-0.8-3.6-1.3c-0.9-0.6-1.6-1.3-2.1-2.1s-0.7-1.8-0.7-2.8
|
||||
c0-1.3,0.4-2.6,1.1-3.7c0.7-1.1,1.8-2,3.2-2.6C147.3,14.1,148.9,13.8,150.9,13.8z"/>
|
||||
<g
|
||||
id="g440"
|
||||
transform="translate(993.0239,277.5454)">
|
||||
<path
|
||||
d="m 0,0 c 2.054,-1.831 3.083,-4.465 3.083,-7.902 v -17.935 h -4.484 v 16.366 c 0,2.914 -0.626,5.024 -1.877,6.332 -1.253,1.308 -2.924,1.962 -5.016,1.962 -1.495,0 -2.896,-0.327 -4.204,-0.981 -1.308,-0.654 -2.381,-1.719 -3.222,-3.194 -0.841,-1.477 -1.261,-3.335 -1.261,-5.576 v -14.909 h -4.484 V 1.328 l 4.086,-1.674 0.118,-1.84 c 0.933,1.681 2.222,2.923 3.867,3.727 1.643,0.803 3.493,1.205 5.548,1.205 C -4.671,2.746 -2.055,1.83 0,0"
|
||||
style="fill:#000033;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path442" />
|
||||
</g>
|
||||
<g
|
||||
id="g444"
|
||||
transform="translate(1027.9968,264.0386)">
|
||||
<path
|
||||
d="m 0,0 h -21.128 c 0.261,-2.84 1.205,-5.044 2.83,-6.613 1.625,-1.57 3.727,-2.355 6.305,-2.355 2.054,0 3.763,0.356 5.128,1.065 1.363,0.71 2.288,1.738 2.774,3.083 l 3.755,-1.961 c -1.121,-1.981 -2.616,-3.495 -4.484,-4.54 -1.868,-1.046 -4.259,-1.569 -7.173,-1.569 -4.223,0 -7.538,1.289 -9.948,3.867 -2.41,2.578 -3.615,6.146 -3.615,10.704 0,4.558 1.149,8.127 3.447,10.705 2.298,2.578 5.557,3.867 9.779,3.867 2.615,0 4.876,-0.58 6.782,-1.738 1.905,-1.158 3.343,-2.728 4.315,-4.707 C -0.262,7.827 0.224,5.605 0.224,3.139 0.224,2.092 0.149,1.046 0,0 m -18.298,10.144 c -1.513,-1.457 -2.438,-3.512 -2.775,-6.165 h 16.982 c -0.3,2.615 -1.159,4.661 -2.578,6.137 -1.42,1.476 -3.307,2.214 -5.661,2.214 -2.466,0 -4.455,-0.728 -5.968,-2.186"
|
||||
style="fill:#000033;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path446" />
|
||||
</g>
|
||||
<g
|
||||
id="g448"
|
||||
transform="translate(1057.8818,276.4246)">
|
||||
<path
|
||||
d="m 0,0 c 2.41,-2.578 3.615,-6.147 3.615,-10.705 0,-4.558 -1.205,-8.126 -3.615,-10.704 -2.41,-2.578 -5.726,-3.867 -9.948,-3.867 -4.222,0 -7.537,1.289 -9.947,3.867 -2.41,2.578 -3.615,6.146 -3.615,10.704 0,4.558 1.205,8.127 3.615,10.705 2.41,2.578 5.725,3.867 9.947,3.867 C -5.726,3.867 -2.41,2.578 0,0 m -16.617,-2.858 c -1.607,-1.906 -2.41,-4.522 -2.41,-7.847 0,-3.326 0.803,-5.94 2.41,-7.846 1.607,-1.905 3.83,-2.858 6.669,-2.858 2.839,0 5.063,0.953 6.67,2.858 1.606,1.906 2.41,4.52 2.41,7.846 0,3.325 -0.804,5.941 -2.41,7.847 C -4.885,-0.953 -7.109,0 -9.948,0 c -2.839,0 -5.062,-0.953 -6.669,-2.858"
|
||||
style="fill:#000033;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path450" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g452"
|
||||
transform="matrix(0.35277777,0,0,-0.35277777,5.8329581,6.5590171)">
|
||||
<path
|
||||
d="m 0,0 0.001,-38.946 25.286,-9.076 V -8.753 L 52.626,1.321 27.815,10.207 Z"
|
||||
style="fill:#00e599;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path454" />
|
||||
</g>
|
||||
<g
|
||||
id="g456"
|
||||
transform="matrix(0.35277777,0,0,-0.35277777,15.479008,10.041927)">
|
||||
<path
|
||||
d="M 0,0 V -21.306 L 25.293,-30.364 25.282,9.347 Z"
|
||||
style="fill:#00b091;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path458" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 6.5 KiB |
21
.github/workflows/dco.yml
vendored
Normal file
21
.github/workflows/dco.yml
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
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 }}
|
57
.github/workflows/go.yml
vendored
Normal file
57
.github/workflows/go.yml
vendored
Normal file
|
@ -0,0 +1,57 @@
|
|||
name: neofs-node tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '*.md'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '*.md'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
go: [ '1.17.x', '1.18.x' ]
|
||||
steps:
|
||||
- name: Setup go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Cache go mod
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ matrix.go }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-${{ matrix.go }}-
|
||||
|
||||
- name: Run go test
|
||||
run: go test -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
|
||||
- name: Codecov
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
run: bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
with:
|
||||
version: v1.45.2
|
||||
args: --timeout=5m
|
||||
only-new-issues: true
|
49
.gitignore
vendored
49
.gitignore
vendored
|
@ -1,51 +1,12 @@
|
|||
# IDE
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
# Vendoring
|
||||
vendor
|
||||
|
||||
# tempfiles
|
||||
.DS_Store
|
||||
*~
|
||||
.cache
|
||||
|
||||
bin
|
||||
release
|
||||
temp
|
||||
tmp
|
||||
|
||||
# binary
|
||||
bin/
|
||||
release/
|
||||
|
||||
# coverage
|
||||
coverage.txt
|
||||
coverage.html
|
||||
|
||||
# testing
|
||||
cmd/test
|
||||
/plugins/
|
||||
testfile
|
||||
/vendor/
|
||||
|
||||
# misc
|
||||
testfile
|
||||
.neofs-cli.yml
|
||||
|
||||
# debhelpers
|
||||
debian/*debhelper*
|
||||
|
||||
# logfiles
|
||||
debian/*.log
|
||||
|
||||
# .substvars
|
||||
debian/*.substvars
|
||||
|
||||
# .bash-completion
|
||||
debian/*.bash-completion
|
||||
|
||||
# Install folders and files
|
||||
debian/frostfs-cli/
|
||||
debian/frostfs-ir/
|
||||
debian/files
|
||||
debian/frostfs-storage/
|
||||
debian/changelog
|
||||
man/
|
||||
debs/
|
||||
.cache
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
# options for analysis running
|
||||
run:
|
||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||
timeout: 20m
|
||||
timeout: 5m
|
||||
|
||||
# include test files or not, default is true
|
||||
tests: false
|
||||
|
@ -24,28 +24,6 @@ linters-settings:
|
|||
govet:
|
||||
# report about shadowed variables
|
||||
check-shadowing: false
|
||||
staticcheck:
|
||||
checks: ["all", "-SA1019"] # TODO Enable SA1019 after deprecated warning are fixed.
|
||||
funlen:
|
||||
lines: 80 # default 60
|
||||
statements: 60 # default 40
|
||||
gocognit:
|
||||
min-complexity: 40 # default 30
|
||||
importas:
|
||||
no-unaliased: true
|
||||
no-extra-aliases: false
|
||||
alias:
|
||||
pkg: git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object
|
||||
alias: objectSDK
|
||||
custom:
|
||||
truecloudlab-linters:
|
||||
path: bin/linters/external_linters.so
|
||||
original-url: git.frostfs.info/TrueCloudLab/linters.git
|
||||
settings:
|
||||
noliteral:
|
||||
target-methods : ["reportFlushError", "reportError"]
|
||||
disable-packages: ["codes", "err", "res","exec"]
|
||||
constants-package: "git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
|
||||
|
||||
linters:
|
||||
enable:
|
||||
|
@ -56,31 +34,16 @@ linters:
|
|||
# some default golangci-lint linters
|
||||
- errcheck
|
||||
- gosimple
|
||||
- godot
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- typecheck
|
||||
- unused
|
||||
|
||||
# extra linters
|
||||
- bidichk
|
||||
- durationcheck
|
||||
- exhaustive
|
||||
- exportloopref
|
||||
- gofmt
|
||||
- goimports
|
||||
- misspell
|
||||
- predeclared
|
||||
- reassign
|
||||
- whitespace
|
||||
- containedctx
|
||||
- funlen
|
||||
- gocognit
|
||||
- contextcheck
|
||||
- importas
|
||||
- truecloudlab-linters
|
||||
- perfsprint
|
||||
- testifylint
|
||||
- protogetter
|
||||
- goimports
|
||||
- unused
|
||||
disable-all: true
|
||||
fast: false
|
||||
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
ci:
|
||||
autofix_prs: false
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: check-added-large-files
|
||||
- id: check-case-conflict
|
||||
- id: check-executables-have-shebangs
|
||||
- id: check-shebang-scripts-are-executable
|
||||
- id: check-merge-conflict
|
||||
- id: check-json
|
||||
- id: check-xml
|
||||
- id: check-yaml
|
||||
- id: trailing-whitespace
|
||||
args: [--markdown-linebreak-ext=md]
|
||||
- id: end-of-file-fixer
|
||||
exclude: "(.key|.svg)$"
|
||||
|
||||
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||
rev: v0.9.0.6
|
||||
hooks:
|
||||
- id: shellcheck
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: make-lint
|
||||
name: Run Make Lint
|
||||
entry: make lint
|
||||
language: system
|
||||
pass_filenames: false
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: go-unit-tests
|
||||
name: go unit tests
|
||||
entry: make test GOFLAGS=''
|
||||
pass_filenames: false
|
||||
types: [go]
|
||||
language: system
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: gofumpt
|
||||
name: gofumpt
|
||||
entry: make fumpt
|
||||
pass_filenames: false
|
||||
types: [go]
|
||||
language: system
|
||||
|
||||
- repo: https://github.com/TekWizely/pre-commit-golang
|
||||
rev: v1.0.0-rc.1
|
||||
hooks:
|
||||
- id: go-staticcheck-repo-mod
|
||||
- id: go-mod-tidy
|
|
@ -1,11 +0,0 @@
|
|||
pipeline:
|
||||
# Kludge for non-root containers under WoodPecker
|
||||
fix-ownership:
|
||||
image: alpine:latest
|
||||
commands: chown -R 1234:1234 .
|
||||
|
||||
pre-commit:
|
||||
image: git.frostfs.info/truecloudlab/frostfs-ci:v0.36
|
||||
commands:
|
||||
- export HOME="$(getent passwd $(id -u) | cut '-d:' -f6)"
|
||||
- pre-commit run --hook-stage manual
|
1189
CHANGELOG.md
1189
CHANGELOG.md
File diff suppressed because it is too large
Load diff
|
@ -3,8 +3,8 @@
|
|||
First, thank you for contributing! We love and encourage pull requests from
|
||||
everyone. Please follow the guidelines:
|
||||
|
||||
- Check the open [issues](https://git.frostfs.info/TrueCloudLab/frostfs-node/issues) and
|
||||
[pull requests](https://git.frostfs.info/TrueCloudLab/frostfs-node/pulls) for existing
|
||||
- Check the open [issues](https://github.com/nspcc-dev/neofs-node/issues) and
|
||||
[pull requests](https://github.com/nspcc-dev/neofs-node/pulls) for existing
|
||||
discussions.
|
||||
|
||||
- Open an issue first, to discuss a new feature or enhancement.
|
||||
|
@ -23,23 +23,23 @@ everyone. Please follow the guidelines:
|
|||
|
||||
## Development Workflow
|
||||
|
||||
Start by forking the `frostfs-node` repository, make changes in a branch and then
|
||||
Start by forking the `neofs-node` repository, make changes in a branch and then
|
||||
send a pull request. We encourage pull requests to discuss code changes. Here
|
||||
are the steps in details:
|
||||
|
||||
### Set up your Forgejo repository
|
||||
Fork [FrostFS node upstream](https://git.frostfs.info/TrueCloudLab/frostfs-node) source
|
||||
### Set up your GitHub Repository
|
||||
Fork [NeoFS node upstream](https://github.com/nspcc-dev/neofs-node/fork) source
|
||||
repository to your own personal repository. Copy the URL of your fork (you will
|
||||
need it for the `git clone` command below).
|
||||
|
||||
```sh
|
||||
$ git clone https://git.frostfs.info/TrueCloudLab/frostfs-node
|
||||
$ git clone https://github.com/nspcc-dev/neofs-node
|
||||
```
|
||||
|
||||
### Set up git remote as ``upstream``
|
||||
```sh
|
||||
$ cd frostfs-node
|
||||
$ git remote add upstream https://git.frostfs.info/TrueCloudLab/frostfs-node
|
||||
$ cd neofs-node
|
||||
$ git remote add upstream https://github.com/nspcc-dev/neofs-node
|
||||
$ git fetch upstream
|
||||
$ git merge upstream/master
|
||||
...
|
||||
|
@ -58,7 +58,7 @@ $ git checkout -b feature/123-something_awesome
|
|||
After your code changes, make sure
|
||||
|
||||
- To add test cases for the new code.
|
||||
- To run `make lint` and `make staticcheck-run`
|
||||
- To run `make lint`
|
||||
- To squash your commits into a single commit or a series of logically separated
|
||||
commits run `git rebase -i`. It's okay to force update your pull request.
|
||||
- To run `make test` and `make all` completes.
|
||||
|
@ -79,7 +79,7 @@ Description
|
|||
```
|
||||
|
||||
```
|
||||
$ git commit -sam '[#123] Add some feature'
|
||||
$ git commit -am '[#123] Add some feature'
|
||||
```
|
||||
|
||||
### Push to the branch
|
||||
|
@ -89,8 +89,8 @@ $ git push origin feature/123-something_awesome
|
|||
```
|
||||
|
||||
### Create a Pull Request
|
||||
Pull requests can be created via Forgejo. Refer to [this
|
||||
document](https://docs.codeberg.org/collaborating/pull-requests-and-git-flow/) for
|
||||
Pull requests can be created via GitHub. Refer to [this
|
||||
document](https://help.github.com/articles/creating-a-pull-request/) for
|
||||
detailed steps on how to create a pull request. After a Pull Request gets peer
|
||||
reviewed and approved, it will be merged.
|
||||
|
||||
|
@ -106,8 +106,7 @@ contributors".
|
|||
To sign your work, just add a line like this at the end of your commit message:
|
||||
|
||||
```
|
||||
Signed-off-by: Samii Sakisaka <samii@ivunojikan.co.jp>
|
||||
|
||||
Signed-off-by: Samii Sakisaka <samii@nspcc.ru>
|
||||
```
|
||||
|
||||
This can easily be done with the `--signoff` option to `git commit`.
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
# Credits
|
||||
|
||||
FrostFS continues the development of NeoFS.
|
||||
|
||||
Initial NeoFS research and development (2018-2020) was done by
|
||||
[NeoSPCC](https://nspcc.ru) team.
|
||||
|
||||
|
|
235
Makefile
Executable file → Normal file
235
Makefile
Executable file → Normal file
|
@ -2,22 +2,14 @@
|
|||
SHELL = bash
|
||||
|
||||
REPO ?= $(shell go list -m)
|
||||
VERSION ?= $(shell git describe --tags --dirty --match "v*" --always --abbrev=8 2>/dev/null || cat VERSION 2>/dev/null || echo "develop")
|
||||
VERSION ?= $(shell git describe --tags --dirty --always)
|
||||
BUILD ?= $(shell date -u --iso=seconds)
|
||||
DEBUG ?= false
|
||||
|
||||
HUB_IMAGE ?= truecloudlab/frostfs
|
||||
HUB_IMAGE ?= nspccdev/neofs
|
||||
HUB_TAG ?= "$(shell echo ${VERSION} | sed 's/^v//')"
|
||||
|
||||
GO_VERSION ?= 1.22
|
||||
LINT_VERSION ?= 1.56.1
|
||||
TRUECLOUDLAB_LINT_VERSION ?= 0.0.5
|
||||
PROTOC_VERSION ?= 25.0
|
||||
PROTOC_GEN_GO_VERSION ?= $(shell go list -f '{{.Version}}' -m google.golang.org/protobuf)
|
||||
PROTOGEN_FROSTFS_VERSION ?= $(shell go list -f '{{.Version}}' -m git.frostfs.info/TrueCloudLab/frostfs-api-go/v2)
|
||||
PROTOC_OS_VERSION=osx-x86_64
|
||||
ifeq ($(shell uname), Linux)
|
||||
PROTOC_OS_VERSION=linux-x86_64
|
||||
endif
|
||||
STATICCHECK_VERSION ?= 2023.1.6
|
||||
GO_VERSION ?= 1.17
|
||||
ARCH = amd64
|
||||
|
||||
BIN = bin
|
||||
|
@ -25,49 +17,24 @@ RELEASE = release
|
|||
DIRS = $(BIN) $(RELEASE)
|
||||
|
||||
# List of binaries to build.
|
||||
CMDS = $(notdir $(basename $(wildcard cmd/frostfs-*)))
|
||||
CMDS = $(notdir $(basename $(wildcard cmd/*)))
|
||||
BINS = $(addprefix $(BIN)/, $(CMDS))
|
||||
|
||||
# .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}
|
||||
|
||||
OUTPUT_LINT_DIR ?= $(abspath $(BIN))/linters
|
||||
LINT_DIR = $(OUTPUT_LINT_DIR)/golangci-lint-$(LINT_VERSION)-v$(TRUECLOUDLAB_LINT_VERSION)
|
||||
TMP_DIR := .cache
|
||||
PROTOBUF_DIR ?= $(abspath $(BIN))/protobuf
|
||||
PROTOC_DIR ?= $(PROTOBUF_DIR)/protoc-v$(PROTOC_VERSION)
|
||||
PROTOC_GEN_GO_DIR ?= $(PROTOBUF_DIR)/protoc-gen-go-$(PROTOC_GEN_GO_VERSION)
|
||||
PROTOGEN_FROSTFS_DIR ?= $(PROTOBUF_DIR)/protogen-$(PROTOGEN_FROSTFS_VERSION)
|
||||
STATICCHECK_DIR ?= $(abspath $(BIN))/staticcheck
|
||||
STATICCHECK_VERSION_DIR ?= $(STATICCHECK_DIR)/$(STATICCHECK_VERSION)
|
||||
|
||||
GOPLS_VERSION ?= v0.15.1
|
||||
GOPLS_DIR ?= $(abspath $(BIN))/gopls
|
||||
GOPLS_VERSION_DIR ?= $(GOPLS_DIR)/$(GOPLS_VERSION)
|
||||
|
||||
FROSTFS_CONTRACTS_PATH=$(abspath ./../frostfs-contract)
|
||||
LOCODE_DB_PATH=$(abspath ./.cache/locode_db)
|
||||
LOCODE_DB_VERSION=v0.4.0
|
||||
|
||||
.PHONY: help all images dep clean fmts fumpt imports test lint docker/lint
|
||||
prepare-release debpackage pre-commit unpre-commit
|
||||
.PHONY: help all images dep clean fmts fmt imports test lint docker/lint prepare-release
|
||||
|
||||
# To build a specific binary, use it's name prefix with bin/ as a target
|
||||
# For example `make bin/frostfs-node` will build only storage node binary
|
||||
# For example `make bin/neofs-node` will build only storage node binary
|
||||
# Just `make` will build all possible binaries
|
||||
all: $(DIRS) $(BINS)
|
||||
|
||||
# help target
|
||||
include help.mk
|
||||
|
||||
$(BINS): $(DIRS) dep
|
||||
@echo "⇒ Build $@"
|
||||
CGO_ENABLED=0 \
|
||||
GO111MODULE=on \
|
||||
go build -v -trimpath \
|
||||
-ldflags "-X $(REPO)/misc.Version=$(VERSION)" \
|
||||
-ldflags "-X $(REPO)/misc.Version=$(VERSION) \
|
||||
-X $(REPO)/misc.Build=$(BUILD) \
|
||||
-X $(REPO)/misc.Debug=$(DEBUG)" \
|
||||
-o $@ ./cmd/$(notdir $@)
|
||||
|
||||
$(DIRS):
|
||||
|
@ -77,7 +44,7 @@ $(DIRS):
|
|||
# Prepare binaries and archives for release
|
||||
.ONESHELL:
|
||||
prepare-release: docker/all
|
||||
@for file in `ls -1 $(BIN)/frostfs-*`; do
|
||||
@for file in `ls -1 $(BIN)/neofs-*`; do
|
||||
cp $$file $(RELEASE)/`basename $$file`-$(ARCH)
|
||||
strip $(RELEASE)/`basename $$file`-$(ARCH)
|
||||
tar -czf $(RELEASE)/`basename $$file`-$(ARCH).tar.gz $(RELEASE)/`basename $$file`-$(ARCH)
|
||||
|
@ -87,50 +54,32 @@ prepare-release: docker/all
|
|||
dep:
|
||||
@printf "⇒ Download requirements: "
|
||||
CGO_ENABLED=0 \
|
||||
GO111MODULE=on \
|
||||
go mod download && echo OK
|
||||
@printf "⇒ Tidy requirements : "
|
||||
CGO_ENABLED=0 \
|
||||
GO111MODULE=on \
|
||||
go mod tidy -v && echo OK
|
||||
|
||||
# Build export-metrics
|
||||
export-metrics: dep
|
||||
@printf "⇒ Build export-metrics\n"
|
||||
CGO_ENABLED=0 \
|
||||
go build -v -trimpath -o bin/export-metrics ./scripts/export-metrics
|
||||
|
||||
# Regenerate proto files:
|
||||
protoc:
|
||||
@if [ ! -d "$(PROTOC_DIR)" ] || [ ! -d "$(PROTOC_GEN_GO_DIR)" ] || [ ! -d "$(PROTOGEN_FROSTFS_DIR)" ]; then \
|
||||
make protoc-install; \
|
||||
fi
|
||||
@for f in `find . -type f -name '*.proto' -not -path './bin/*'`; do \
|
||||
@GOPRIVATE=github.com/nspcc-dev go mod vendor
|
||||
# Install specific version for protobuf lib
|
||||
@go list -f '{{.Path}}/...@{{.Version}}' -m github.com/golang/protobuf | xargs go install -v
|
||||
# Protoc generate
|
||||
@for f in `find . -type f -name '*.proto' -not -path './vendor/*'`; do \
|
||||
echo "⇒ Processing $$f "; \
|
||||
$(PROTOC_DIR)/bin/protoc \
|
||||
--proto_path=.:$(PROTOC_DIR)/include:/usr/local/include \
|
||||
--plugin=protoc-gen-go=$(PROTOC_GEN_GO_DIR)/protoc-gen-go \
|
||||
--plugin=protoc-gen-go-frostfs=$(PROTOGEN_FROSTFS_DIR)/protogen \
|
||||
--go-frostfs_out=. --go-frostfs_opt=paths=source_relative \
|
||||
protoc \
|
||||
--proto_path=.:./vendor:/usr/local/include \
|
||||
--go_out=. --go_opt=paths=source_relative \
|
||||
--go-grpc_opt=require_unimplemented_servers=false \
|
||||
--go-grpc_out=. --go-grpc_opt=paths=source_relative $$f; \
|
||||
done
|
||||
rm -rf vendor
|
||||
|
||||
# Install protoc
|
||||
protoc-install:
|
||||
@rm -rf $(PROTOBUF_DIR)
|
||||
@mkdir $(PROTOBUF_DIR)
|
||||
@echo "⇒ Installing protoc... "
|
||||
@wget -q -O $(PROTOBUF_DIR)/protoc-$(PROTOC_VERSION).zip 'https://github.com/protocolbuffers/protobuf/releases/download/v$(PROTOC_VERSION)/protoc-$(PROTOC_VERSION)-$(PROTOC_OS_VERSION).zip'
|
||||
@unzip -q -o $(PROTOBUF_DIR)/protoc-$(PROTOC_VERSION).zip -d $(PROTOC_DIR)
|
||||
@rm $(PROTOBUF_DIR)/protoc-$(PROTOC_VERSION).zip
|
||||
@echo "⇒ Installing protoc-gen-go..."
|
||||
@GOBIN=$(PROTOC_GEN_GO_DIR) go install -v google.golang.org/protobuf/...@$(PROTOC_GEN_GO_VERSION)
|
||||
@echo "⇒ Instaling protogen FrostFS plugin..."
|
||||
@GOBIN=$(PROTOGEN_FROSTFS_DIR) go install -mod=mod -v git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/util/protogen@$(PROTOGEN_FROSTFS_VERSION)
|
||||
|
||||
# Build FrostFS component's docker image
|
||||
# Build NeoFS component's docker image
|
||||
image-%:
|
||||
@echo "⇒ Build FrostFS $* docker image "
|
||||
@echo "⇒ Build NeoFS $* docker image "
|
||||
@docker build \
|
||||
--build-arg REPO=$(REPO) \
|
||||
--build-arg VERSION=$(VERSION) \
|
||||
|
@ -139,7 +88,7 @@ image-%:
|
|||
-t $(HUB_IMAGE)-$*:$(HUB_TAG) .
|
||||
|
||||
# Build all Docker images
|
||||
images: image-storage image-ir image-cli image-adm
|
||||
images: image-storage image-ir image-cli image-adm image-storage-testnet
|
||||
|
||||
# Build dirty local Docker images
|
||||
dirty-images: image-dirty-storage image-dirty-ir image-dirty-cli image-dirty-adm
|
||||
|
@ -155,74 +104,26 @@ docker/%:
|
|||
|
||||
|
||||
# Run all code formatters
|
||||
fmts: fumpt imports
|
||||
fmts: fmt imports
|
||||
|
||||
# Reformat code
|
||||
fmt:
|
||||
@echo "⇒ Processing gofmt check"
|
||||
@GO111MODULE=on gofmt -s -w cmd/ pkg/ misc/
|
||||
|
||||
# Reformat imports
|
||||
imports:
|
||||
@echo "⇒ Processing goimports check"
|
||||
@goimports -w cmd/ pkg/ misc/
|
||||
|
||||
# Run gofumpt
|
||||
fumpt:
|
||||
@echo "⇒ Processing gofumpt check"
|
||||
@gofumpt -l -w cmd/ pkg/ misc/
|
||||
@GO111MODULE=on goimports -w cmd/ pkg/ misc/
|
||||
|
||||
# Run Unit Test with go test
|
||||
test: GOFLAGS ?= "-count=1"
|
||||
test:
|
||||
@echo "⇒ Running go test"
|
||||
@GOFLAGS="$(GOFLAGS)" go test ./...
|
||||
|
||||
# Run pre-commit
|
||||
pre-commit-run:
|
||||
@pre-commit run -a --hook-stage manual
|
||||
|
||||
# Install linters
|
||||
lint-install:
|
||||
@rm -rf $(OUTPUT_LINT_DIR)
|
||||
@mkdir $(OUTPUT_LINT_DIR)
|
||||
@mkdir -p $(TMP_DIR)
|
||||
@rm -rf $(TMP_DIR)/linters
|
||||
@git -c advice.detachedHead=false clone --branch v$(TRUECLOUDLAB_LINT_VERSION) https://git.frostfs.info/TrueCloudLab/linters.git $(TMP_DIR)/linters
|
||||
@@make -C $(TMP_DIR)/linters lib CGO_ENABLED=1 OUT_DIR=$(OUTPUT_LINT_DIR)
|
||||
@rm -rf $(TMP_DIR)/linters
|
||||
@rmdir $(TMP_DIR) 2>/dev/null || true
|
||||
@CGO_ENABLED=1 GOBIN=$(LINT_DIR) go install github.com/golangci/golangci-lint/cmd/golangci-lint@v$(LINT_VERSION)
|
||||
@GO111MODULE=on go test ./...
|
||||
|
||||
# Run linters
|
||||
lint:
|
||||
@if [ ! -d "$(LINT_DIR)" ]; then \
|
||||
make lint-install; \
|
||||
fi
|
||||
$(LINT_DIR)/golangci-lint run
|
||||
|
||||
# Install staticcheck
|
||||
staticcheck-install:
|
||||
@rm -rf $(STATICCHECK_DIR)
|
||||
@mkdir $(STATICCHECK_DIR)
|
||||
@GOBIN=$(STATICCHECK_VERSION_DIR) go install honnef.co/go/tools/cmd/staticcheck@$(STATICCHECK_VERSION)
|
||||
|
||||
# Run staticcheck
|
||||
staticcheck-run:
|
||||
@if [ ! -d "$(STATICCHECK_VERSION_DIR)" ]; then \
|
||||
make staticcheck-install; \
|
||||
fi
|
||||
@$(STATICCHECK_VERSION_DIR)/staticcheck ./...
|
||||
|
||||
# Install gopls
|
||||
gopls-install:
|
||||
@rm -rf $(GOPLS_DIR)
|
||||
@mkdir $(GOPLS_DIR)
|
||||
@GOBIN=$(GOPLS_VERSION_DIR) go install golang.org/x/tools/gopls@$(GOPLS_VERSION)
|
||||
|
||||
# Run gopls
|
||||
gopls-run:
|
||||
@if [ ! -d "$(GOPLS_VERSION_DIR)" ]; then \
|
||||
make gopls-install; \
|
||||
fi
|
||||
@if [[ $$(find . -type f -name "*.go" -print | xargs $(GOPLS_VERSION_DIR)/gopls check | tee /dev/tty | wc -l) -ne 0 ]]; then \
|
||||
exit 1; \
|
||||
fi
|
||||
@golangci-lint --timeout=5m run
|
||||
|
||||
# Run linters in Docker
|
||||
docker/lint:
|
||||
|
@ -230,65 +131,23 @@ docker/lint:
|
|||
-v `pwd`:/src \
|
||||
-u `stat -c "%u:%g" .` \
|
||||
--env HOME=/src \
|
||||
golangci/golangci-lint:v$(LINT_VERSION) bash -c 'cd /src/ && make lint'
|
||||
|
||||
# Activate pre-commit hooks
|
||||
pre-commit:
|
||||
pre-commit install -t pre-commit -t commit-msg
|
||||
|
||||
# Deactivate pre-commit hooks
|
||||
unpre-commit:
|
||||
pre-commit uninstall -t pre-commit -t commit-msg
|
||||
golangci/golangci-lint:v1.42.1 bash -c 'cd /src/ && make lint'
|
||||
|
||||
# Print version
|
||||
version:
|
||||
@echo $(VERSION)
|
||||
|
||||
# Delete built artifacts
|
||||
# Show this help prompt
|
||||
help:
|
||||
@echo ' Usage:'
|
||||
@echo ''
|
||||
@echo ' make <target>'
|
||||
@echo ''
|
||||
@echo ' Targets:'
|
||||
@echo ''
|
||||
@awk '/^#/{ comment = substr($$0,3) } comment && /^[a-zA-Z][a-zA-Z0-9_-]+ ?:/{ print " ", $$1, comment }' $(MAKEFILE_LIST) | column -t -s ':' | grep -v 'IGNORE' | sort -u
|
||||
|
||||
clean:
|
||||
rm -rf .cache
|
||||
rm -rf vendor
|
||||
rm -rf $(BIN)
|
||||
rm -rf $(RELEASE)
|
||||
|
||||
# Package for Debian
|
||||
debpackage:
|
||||
dch -b --package frostfs-node \
|
||||
--controlmaint \
|
||||
--newversion $(PKG_VERSION) \
|
||||
--distribution $(OS_RELEASE) \
|
||||
"Please see CHANGELOG.md for code changes for $(VERSION)"
|
||||
dpkg-buildpackage --no-sign -b
|
||||
|
||||
# Cleanup deb package build directories
|
||||
debclean:
|
||||
dh clean
|
||||
|
||||
# Download locode database
|
||||
locode-download:
|
||||
mkdir -p $(TMP_DIR)
|
||||
@wget -q -O ./$(TMP_DIR)/locode_db.gz 'https://git.frostfs.info/TrueCloudLab/frostfs-locode-db/releases/download/${LOCODE_DB_VERSION}/locode_db.gz'
|
||||
gzip -dfk ./$(TMP_DIR)/locode_db.gz
|
||||
|
||||
# Start dev environment
|
||||
env-up: all
|
||||
docker compose -f dev/docker-compose.yml up -d
|
||||
@if [ ! -d "$(FROSTFS_CONTRACTS_PATH)" ]; then \
|
||||
echo "Frostfs contracts not found"; exit 1; \
|
||||
fi
|
||||
${BIN}/frostfs-adm --config ./dev/adm/frostfs-adm.yml morph init --contracts ${FROSTFS_CONTRACTS_PATH}
|
||||
${BIN}/frostfs-adm --config ./dev/adm/frostfs-adm.yml morph refill-gas --storage-wallet ./dev/storage/wallet01.json --gas 10.0
|
||||
${BIN}/frostfs-adm --config ./dev/adm/frostfs-adm.yml morph refill-gas --storage-wallet ./dev/storage/wallet02.json --gas 10.0
|
||||
${BIN}/frostfs-adm --config ./dev/adm/frostfs-adm.yml morph refill-gas --storage-wallet ./dev/storage/wallet03.json --gas 10.0
|
||||
${BIN}/frostfs-adm --config ./dev/adm/frostfs-adm.yml morph refill-gas --storage-wallet ./dev/storage/wallet04.json --gas 10.0
|
||||
@if [ ! -f "$(LOCODE_DB_PATH)" ]; then \
|
||||
make locode-download; \
|
||||
fi
|
||||
mkdir -p ./$(TMP_DIR)/state
|
||||
mkdir -p ./$(TMP_DIR)/storage
|
||||
|
||||
# Shutdown dev environment
|
||||
env-down:
|
||||
docker compose -f dev/docker-compose.yml down
|
||||
docker volume rm -f frostfs-node_neo-go
|
||||
rm -rf ./$(TMP_DIR)/state
|
||||
rm -rf ./$(TMP_DIR)/storage
|
||||
|
|
105
README.md
105
README.md
|
@ -1,119 +1,50 @@
|
|||
<p align="center">
|
||||
<img src="./.github/logo.svg" width="500px" alt="FrostFS">
|
||||
<img src="./.github/logo.svg" width="500px" alt="NeoFS">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://frostfs.info">FrostFS</a> is a decentralized distributed object storage integrated with the <a href="https://neo.org">NEO Blockchain</a>.
|
||||
<a href="https://fs.neo.org">NeoFS</a> is a decentralized distributed object storage integrated with the <a href="https://neo.org">NEO Blockchain</a>.
|
||||
</p>
|
||||
|
||||
---
|
||||
[![Report](https://goreportcard.com/badge/github.com/TrueCloudLab/frostfs-node)](https://goreportcard.com/report/github.com/TrueCloudLab/frostfs-node)
|
||||
![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/TrueCloudLab/frostfs-node?sort=semver)
|
||||
![License](https://img.shields.io/github/license/TrueCloudLab/frostfs-node.svg?style=popout)
|
||||
[![Report](https://goreportcard.com/badge/github.com/nspcc-dev/neofs-node)](https://goreportcard.com/report/github.com/nspcc-dev/neofs-node)
|
||||
![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/nspcc-dev/neofs-node?sort=semver)
|
||||
![License](https://img.shields.io/github/license/nspcc-dev/neofs-node.svg?style=popout)
|
||||
|
||||
# Overview
|
||||
|
||||
FrostFS Nodes are organized in a peer-to-peer network that takes care of storing
|
||||
NeoFS Nodes are organized in a peer-to-peer network that takes care of storing
|
||||
and distributing user's data. Any Neo user may participate in the network and
|
||||
get paid for providing storage resources to other users or store their data in
|
||||
FrostFS and pay a competitive price for it.
|
||||
get paid for providing storage resources to other users or store his data in
|
||||
NeoFS and pay a competitive price for it.
|
||||
|
||||
Users can reliably store object data in the FrostFS network and have a transparent
|
||||
Users can reliably store object data in the NeoFS network and have a transparent
|
||||
data placement process due to a decentralized architecture and flexible storage
|
||||
policies. Each node is responsible for executing the storage policies that the
|
||||
users select for geographical location, reliability level, number of nodes, type
|
||||
of disks, capacity, etc. Thus, FrostFS gives full control over data to users.
|
||||
of disks, capacity, etc. Thus, NeoFS gives full control over data to users.
|
||||
|
||||
Deep [Neo Blockchain](https://neo.org) integration allows FrostFS to be used by
|
||||
Deep [Neo Blockchain](https://neo.org) integration allows NeoFS to be used by
|
||||
dApps directly from
|
||||
[NeoVM](https://docs.neo.org/docs/en-us/basic/technology/neovm.html) on the
|
||||
[Smart Contract](https://docs.neo.org/docs/en-us/intro/glossary.html)
|
||||
code level. This way dApps are not limited to on-chain storage and can
|
||||
manipulate large amounts of data without paying a prohibitive price.
|
||||
|
||||
FrostFS has a native [gRPC API](https://git.frostfs.info/TrueCloudLab/frostfs-api) and has
|
||||
NeoFS has a native [gRPC API](https://github.com/nspcc-dev/neofs-api) and has
|
||||
protocol gateways for popular protocols such as [AWS
|
||||
S3](https://github.com/TrueCloudLab/frostfs-s3-gw),
|
||||
[HTTP](https://github.com/TrueCloudLab/frostfs-http-gw),
|
||||
S3](https://github.com/nspcc-dev/neofs-s3-gw),
|
||||
[HTTP](https://github.com/nspcc-dev/neofs-http-gw),
|
||||
[FUSE](https://wikipedia.org/wiki/Filesystem_in_Userspace) and
|
||||
[sFTP](https://en.wikipedia.org/wiki/SSH_File_Transfer_Protocol) allowing
|
||||
developers to integrate applications without rewriting their code.
|
||||
|
||||
# Supported platforms
|
||||
|
||||
Now, we only support GNU/Linux on amd64 CPUs with AVX/AVX2 instructions. More
|
||||
For now we only support GNU/Linux on amd64 CPUs with AVX/AVX2 instructions. More
|
||||
platforms will be officially supported after release `1.0`.
|
||||
|
||||
The latest version of frostfs-node works with frostfs-contract
|
||||
[v0.16.0](https://github.com/TrueCloudLab/frostfs-contract/releases/tag/v0.16.0).
|
||||
|
||||
# Building
|
||||
|
||||
To make all binaries you need Go 1.21+ and `make`:
|
||||
```
|
||||
make all
|
||||
```
|
||||
The resulting binaries will appear in `bin/` folder.
|
||||
|
||||
To make a specific binary use:
|
||||
```
|
||||
make bin/frostfs-<name>
|
||||
```
|
||||
See the list of all available commands in the `cmd` folder.
|
||||
|
||||
## Building with Docker
|
||||
|
||||
Building can also be performed in a container:
|
||||
```
|
||||
make docker/all # build all binaries
|
||||
make docker/bin/frostfs-<name> # build a specific binary
|
||||
```
|
||||
|
||||
## Docker images
|
||||
|
||||
To make docker images suitable for use in [frostfs-dev-env](https://github.com/TrueCloudLab/frostfs-dev-env/) use:
|
||||
```
|
||||
make images
|
||||
```
|
||||
|
||||
# Debugging
|
||||
|
||||
## VSCode
|
||||
|
||||
To run and debug single node cluster with VSCode:
|
||||
|
||||
1. Clone and build [frostfs-contract](https://git.frostfs.info/TrueCloudLab/frostfs-contract) repository to the same directory level as `frostfs-node`. For example:
|
||||
|
||||
```
|
||||
/
|
||||
├── src
|
||||
├── frostfs-node
|
||||
└── frostfs-contract
|
||||
```
|
||||
See `frostfs-contract`'s README.md for build instructions.
|
||||
|
||||
2. Copy `launch.json` and `tasks.json` from `dev/.vscode-example` directory to `.vscode` directory. If you already have such files in `.vscode` directory, then merge them manually.
|
||||
|
||||
3. Go to **Run and Debug** (`Ctrl+Shift+D`) and start `IR+Storage node` configuration.
|
||||
|
||||
4. To create container and put object into it run (container and object IDs will be different):
|
||||
|
||||
```
|
||||
./bin/frostfs-cli container create -r 127.0.0.1:8080 --wallet ./dev/wallet.json --policy "REP 1 IN X CBF 1 SELECT 1 FROM * AS X" --basic-acl public-read-write --await
|
||||
Enter password > <- press ENTER, the is no password for wallet
|
||||
CID: CfPhEuHQ2PRvM4gfBQDC4dWZY3NccovyfcnEdiq2ixju
|
||||
|
||||
./bin/frostfs-cli object put -r 127.0.0.1:8080 --wallet ./dev/wallet.json --file README.md --cid CfPhEuHQ2PRvM4gfBQDC4dWZY3NccovyfcnEdiq2ixju
|
||||
Enter password >
|
||||
4300 / 4300 [===========================================================================================================================================================================================================] 100.00% 0s
|
||||
[README.md] Object successfully stored
|
||||
OID: 78sohnudVMnPsczXqsTUcvezosan2YDNVZwDE8Kq5YwU
|
||||
CID: CfPhEuHQ2PRvM4gfBQDC4dWZY3NccovyfcnEdiq2ixju
|
||||
|
||||
./bin/frostfs-cli object get -r 127.0.0.1:8080 --wallet ./dev/wallet.json --cid CfPhEuHQ2PRvM4gfBQDC4dWZY3NccovyfcnEdiq2ixju --oid 78sohnudVMnPsczXqsTUcvezosan2YDNVZwDE8Kq5YwU
|
||||
...
|
||||
|
||||
```
|
||||
The latest version of neofs-node works with neofs-contract
|
||||
[v0.13.0](https://github.com/nspcc-dev/neofs-contract/releases/tag/v0.13.0).
|
||||
|
||||
# Contributing
|
||||
|
||||
|
@ -125,7 +56,7 @@ the feature/topic you are going to implement.
|
|||
|
||||
# Credits
|
||||
|
||||
FrostFS is maintained by [True Cloud Lab](https://github.com/TrueCloudLab/) with the help and
|
||||
NeoFS is maintained by [NeoSPCC](https://nspcc.ru) with the help and
|
||||
contributions from community members.
|
||||
|
||||
Please see [CREDITS](CREDITS.md) for details.
|
||||
|
|
1
VERSION
1
VERSION
|
@ -1 +0,0 @@
|
|||
v0.38.0
|
|
@ -1,101 +0,0 @@
|
|||
# FrostFS Admin Tool
|
||||
|
||||
## Overview
|
||||
|
||||
Admin tool provides an easier way to deploy and maintain private installation
|
||||
of FrostFS. Private installation provides a set of N3 consensus nodes, FrostFS
|
||||
Alphabet, and Storage nodes. Admin tool generates consensus keys, initializes
|
||||
the sidechain, and provides functions to update the network and register new
|
||||
Storage nodes.
|
||||
|
||||
## Build
|
||||
|
||||
To build binary locally, use `make bin/frostfs-adm` command.
|
||||
|
||||
For clean build inside a docker container, use `make docker/bin/frostfs-adm`.
|
||||
|
||||
Build docker image with `make image-adm`.
|
||||
|
||||
At FrostFS private install deployment, frostfs-adm requires compiled FrostFS
|
||||
contracts. Find them in the latest release of
|
||||
[frostfs-contract repository](https://git.frostfs.info/TrueCloudLab/frostfs-contract/releases).
|
||||
|
||||
## Commands
|
||||
|
||||
### Config
|
||||
|
||||
Config section provides `init` command that creates a configuration file for
|
||||
private installation deployment and updates. Config file is optional, all
|
||||
parameters can be passed by arguments or read from standard input (wallet
|
||||
passwords).
|
||||
|
||||
Config example:
|
||||
```yaml
|
||||
rpc-endpoint: https://address:port # sidechain RPC node endpoint
|
||||
alphabet-wallets: /path # path to consensus node / alphabet wallets storage
|
||||
network:
|
||||
max_object_size: 67108864 # max size of a single FrostFS object, bytes
|
||||
epoch_duration: 240 # duration of a FrostFS epoch in blocks, consider block generation frequency in the sidechain
|
||||
fee:
|
||||
candidate: 0 # inner ring candidate registration fee, for private installation consider 0
|
||||
container: 0 # container creation fee, for private installation consider 0
|
||||
container_alias: 0 # container nice-name registration fee, for private installation consider 0
|
||||
withdraw: 0 # withdraw fee, for private installation consider 0
|
||||
credentials: # passwords for consensus node / alphabet wallets
|
||||
az: password1
|
||||
buky: password2
|
||||
vedi: password3
|
||||
glagoli: password4
|
||||
dobro: password5
|
||||
yest: password6
|
||||
zhivete: password7
|
||||
```
|
||||
|
||||
### Morph
|
||||
|
||||
#### Network deployment
|
||||
|
||||
- `generate-alphabet` generates a set of wallets for consensus and
|
||||
Alphabet nodes.
|
||||
|
||||
- `init` initializes the sidechain by deploying smart contracts and
|
||||
setting provided FrostFS network configuration.
|
||||
|
||||
- `generate-storage-wallet` generates a wallet for the Storage node that
|
||||
is ready for deployment. It also transfers a bit of sidechain GAS, so this
|
||||
wallet can be used for FrostFS bootstrap.
|
||||
|
||||
#### Network maintenance
|
||||
|
||||
- `set-config` add/update configuration values in the Netmap contract.
|
||||
|
||||
- `force-new-epoch` increments FrostFS epoch number and executes new epoch
|
||||
handlers in FrostFS nodes.
|
||||
|
||||
- `refill-gas` transfers sidechain GAS to the specified wallet.
|
||||
|
||||
- `update-contracts` updates contracts to a new version.
|
||||
|
||||
#### Container migration
|
||||
|
||||
If a network has to be redeployed, these commands will migrate all container meta
|
||||
info. These commands **do not migrate actual objects**.
|
||||
|
||||
- `dump-containers` saves all containers and metadata registered in the container
|
||||
contract to a file.
|
||||
|
||||
- `restore-containers` restores previously saved containers by their repeated registration in
|
||||
the container contract.
|
||||
|
||||
- `list-containers` output all containers ids.
|
||||
|
||||
#### Network info
|
||||
|
||||
- `dump-config` prints FrostFS network configuration.
|
||||
|
||||
- `dump-hashes` prints FrostFS contract addresses stored in NNS.
|
||||
|
||||
|
||||
## Private network deployment
|
||||
|
||||
Read step-by-step guide of private storage deployment [in docs](./docs/deploy.md).
|
|
@ -1,42 +0,0 @@
|
|||
package commonflags
|
||||
|
||||
const (
|
||||
ConfigFlag = "config"
|
||||
ConfigFlagShorthand = "c"
|
||||
ConfigFlagUsage = "Config file"
|
||||
|
||||
ConfigDirFlag = "config-dir"
|
||||
ConfigDirFlagUsage = "Config directory"
|
||||
|
||||
Verbose = "verbose"
|
||||
VerboseShorthand = "v"
|
||||
VerboseUsage = "Verbose output"
|
||||
|
||||
EndpointFlag = "rpc-endpoint"
|
||||
EndpointFlagDesc = "N3 RPC node endpoint"
|
||||
EndpointFlagShort = "r"
|
||||
|
||||
AlphabetWalletsFlag = "alphabet-wallets"
|
||||
AlphabetWalletsFlagDesc = "Path to alphabet wallets dir"
|
||||
|
||||
LocalDumpFlag = "local-dump"
|
||||
ContractsInitFlag = "contracts"
|
||||
ContractsInitFlagDesc = "Path to archive with compiled FrostFS contracts (the default is to fetch the latest release from the official repository)"
|
||||
ContractsURLFlag = "contracts-url"
|
||||
ContractsURLFlagDesc = "URL to archive with compiled FrostFS contracts"
|
||||
EpochDurationInitFlag = "network.epoch_duration"
|
||||
MaxObjectSizeInitFlag = "network.max_object_size"
|
||||
MaxECDataCountFlag = "network.max_ec_data_count"
|
||||
MaxECParityCounFlag = "network.max_ec_parity_count"
|
||||
RefillGasAmountFlag = "gas"
|
||||
StorageWalletFlag = "storage-wallet"
|
||||
ContainerFeeInitFlag = "network.fee.container"
|
||||
ContainerAliasFeeInitFlag = "network.fee.container_alias"
|
||||
CandidateFeeInitFlag = "network.fee.candidate"
|
||||
WithdrawFeeInitFlag = "network.fee.withdraw"
|
||||
MaintenanceModeAllowedInitFlag = "network.maintenance_mode_allowed"
|
||||
HomomorphicHashDisabledInitFlag = "network.homomorphic_hash_disabled"
|
||||
CustomZoneFlag = "domain"
|
||||
AlphabetSizeFlag = "size"
|
||||
AllFlag = "all"
|
||||
)
|
|
@ -1,29 +0,0 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const configPathFlag = "path"
|
||||
|
||||
var (
|
||||
// RootCmd is a root command of config section.
|
||||
RootCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Section for frostfs-adm config related commands",
|
||||
}
|
||||
|
||||
initCmd = &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Initialize basic frostfs-adm configuration file",
|
||||
Example: `frostfs-adm config init
|
||||
frostfs-adm config init --path .config/frostfs-adm.yml`,
|
||||
RunE: initConfig,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(initCmd)
|
||||
|
||||
initCmd.Flags().String(configPathFlag, "", "Path to config (default ~/.frostfs/adm/config.yml)")
|
||||
}
|
|
@ -1,268 +0,0 @@
|
|||
package ape
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
parseutil "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
namespaceTarget = "namespace"
|
||||
containerTarget = "container"
|
||||
userTarget = "user"
|
||||
groupTarget = "group"
|
||||
jsonFlag = "json"
|
||||
jsonFlagDesc = "Output rule chains in JSON format"
|
||||
chainIDFlag = "chain-id"
|
||||
chainIDDesc = "Rule chain ID"
|
||||
ruleFlag = "rule"
|
||||
ruleFlagDesc = "Rule chain in text format"
|
||||
pathFlag = "path"
|
||||
pathFlagDesc = "path to encoded chain in JSON or binary format"
|
||||
targetNameFlag = "target-name"
|
||||
targetNameDesc = "Resource name in APE resource name format"
|
||||
targetTypeFlag = "target-type"
|
||||
targetTypeDesc = "Resource type(container/namespace)"
|
||||
addrAdminFlag = "addr"
|
||||
addrAdminDesc = "The address of the admins wallet"
|
||||
chainNameFlag = "chain-name"
|
||||
chainNameFlagDesc = "Chain name(ingress|s3)"
|
||||
)
|
||||
|
||||
var (
|
||||
addRuleChainCmd = &cobra.Command{
|
||||
Use: "add-rule-chain",
|
||||
Short: "Add rule chain",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
},
|
||||
Run: addRuleChain,
|
||||
}
|
||||
|
||||
removeRuleChainCmd = &cobra.Command{
|
||||
Use: "rm-rule-chain",
|
||||
Short: "Remove rule chain",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
},
|
||||
Run: removeRuleChain,
|
||||
}
|
||||
|
||||
listRuleChainsCmd = &cobra.Command{
|
||||
Use: "list-rule-chains",
|
||||
Short: "List rule chains",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: listRuleChains,
|
||||
}
|
||||
|
||||
setAdminCmd = &cobra.Command{
|
||||
Use: "set-admin",
|
||||
Short: "Set admin",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
},
|
||||
Run: setAdmin,
|
||||
}
|
||||
|
||||
getAdminCmd = &cobra.Command{
|
||||
Use: "get-admin",
|
||||
Short: "Get admin",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: getAdmin,
|
||||
}
|
||||
|
||||
listTargetsCmd = &cobra.Command{
|
||||
Use: "list-targets",
|
||||
Short: "List targets",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: listTargets,
|
||||
}
|
||||
)
|
||||
|
||||
func initAddRuleChainCmd() {
|
||||
Cmd.AddCommand(addRuleChainCmd)
|
||||
|
||||
addRuleChainCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
addRuleChainCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
|
||||
addRuleChainCmd.Flags().String(targetTypeFlag, "", targetTypeDesc)
|
||||
_ = addRuleChainCmd.MarkFlagRequired(targetTypeFlag)
|
||||
addRuleChainCmd.Flags().String(targetNameFlag, "", targetNameDesc)
|
||||
_ = addRuleChainCmd.MarkFlagRequired(targetNameFlag)
|
||||
|
||||
addRuleChainCmd.Flags().String(chainIDFlag, "", chainIDDesc)
|
||||
_ = addRuleChainCmd.MarkFlagRequired(chainIDFlag)
|
||||
addRuleChainCmd.Flags().StringArray(ruleFlag, []string{}, ruleFlagDesc)
|
||||
addRuleChainCmd.Flags().String(pathFlag, "", pathFlagDesc)
|
||||
addRuleChainCmd.Flags().String(chainNameFlag, ingress, chainNameFlagDesc)
|
||||
addRuleChainCmd.MarkFlagsMutuallyExclusive(ruleFlag, pathFlag)
|
||||
}
|
||||
|
||||
func initRemoveRuleChainCmd() {
|
||||
Cmd.AddCommand(removeRuleChainCmd)
|
||||
|
||||
removeRuleChainCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
removeRuleChainCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
|
||||
removeRuleChainCmd.Flags().String(targetTypeFlag, "", targetTypeDesc)
|
||||
_ = removeRuleChainCmd.MarkFlagRequired(targetTypeFlag)
|
||||
removeRuleChainCmd.Flags().String(targetNameFlag, "", targetNameDesc)
|
||||
_ = removeRuleChainCmd.MarkFlagRequired(targetNameFlag)
|
||||
removeRuleChainCmd.Flags().String(chainIDFlag, "", chainIDDesc)
|
||||
removeRuleChainCmd.Flags().String(chainNameFlag, ingress, chainNameFlagDesc)
|
||||
removeRuleChainCmd.Flags().Bool(commonflags.AllFlag, false, "Remove all chains for target")
|
||||
removeRuleChainCmd.MarkFlagsMutuallyExclusive(commonflags.AllFlag, chainIDFlag)
|
||||
}
|
||||
|
||||
func initListRuleChainsCmd() {
|
||||
Cmd.AddCommand(listRuleChainsCmd)
|
||||
|
||||
listRuleChainsCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
listRuleChainsCmd.Flags().StringP(targetTypeFlag, "t", "", targetTypeDesc)
|
||||
_ = listRuleChainsCmd.MarkFlagRequired(targetTypeFlag)
|
||||
listRuleChainsCmd.Flags().String(targetNameFlag, "", targetNameDesc)
|
||||
_ = listRuleChainsCmd.MarkFlagRequired(targetNameFlag)
|
||||
listRuleChainsCmd.Flags().Bool(jsonFlag, false, jsonFlagDesc)
|
||||
listRuleChainsCmd.Flags().String(chainNameFlag, ingress, chainNameFlagDesc)
|
||||
}
|
||||
|
||||
func initSetAdminCmd() {
|
||||
Cmd.AddCommand(setAdminCmd)
|
||||
|
||||
setAdminCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
setAdminCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
setAdminCmd.Flags().String(addrAdminFlag, "", addrAdminDesc)
|
||||
_ = setAdminCmd.MarkFlagRequired(addrAdminFlag)
|
||||
}
|
||||
|
||||
func initGetAdminCmd() {
|
||||
Cmd.AddCommand(getAdminCmd)
|
||||
|
||||
getAdminCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
}
|
||||
|
||||
func initListTargetsCmd() {
|
||||
Cmd.AddCommand(listTargetsCmd)
|
||||
|
||||
listTargetsCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
listTargetsCmd.Flags().StringP(targetTypeFlag, "t", "", targetTypeDesc)
|
||||
_ = listTargetsCmd.MarkFlagRequired(targetTypeFlag)
|
||||
}
|
||||
|
||||
func addRuleChain(cmd *cobra.Command, _ []string) {
|
||||
chain := parseChain(cmd)
|
||||
target := parseTarget(cmd)
|
||||
pci, ac := newPolicyContractInterface(cmd)
|
||||
h, vub, err := pci.AddMorphRuleChain(parseChainName(cmd), target, chain)
|
||||
cmd.Println("Waiting for transaction to persist...")
|
||||
_, err = ac.Wait(h, vub, err)
|
||||
commonCmd.ExitOnErr(cmd, "add rule chain error: %w", err)
|
||||
cmd.Println("Rule chain added successfully")
|
||||
}
|
||||
|
||||
func removeRuleChain(cmd *cobra.Command, _ []string) {
|
||||
target := parseTarget(cmd)
|
||||
pci, ac := newPolicyContractInterface(cmd)
|
||||
removeAll, _ := cmd.Flags().GetBool(commonflags.AllFlag)
|
||||
if removeAll {
|
||||
h, vub, err := pci.RemoveMorphRuleChainsByTarget(parseChainName(cmd), target)
|
||||
cmd.Println("Waiting for transaction to persist...")
|
||||
_, err = ac.Wait(h, vub, err)
|
||||
commonCmd.ExitOnErr(cmd, "remove rule chain error: %w", err)
|
||||
cmd.Println("All chains for target removed successfully")
|
||||
} else {
|
||||
chainID := parseChainID(cmd)
|
||||
h, vub, err := pci.RemoveMorphRuleChain(parseChainName(cmd), target, chainID)
|
||||
cmd.Println("Waiting for transaction to persist...")
|
||||
_, err = ac.Wait(h, vub, err)
|
||||
commonCmd.ExitOnErr(cmd, "remove rule chain error: %w", err)
|
||||
cmd.Println("Rule chain removed successfully")
|
||||
}
|
||||
}
|
||||
|
||||
func listRuleChains(cmd *cobra.Command, _ []string) {
|
||||
target := parseTarget(cmd)
|
||||
pci, _ := newPolicyContractReaderInterface(cmd)
|
||||
chains, err := pci.ListMorphRuleChains(parseChainName(cmd), target)
|
||||
commonCmd.ExitOnErr(cmd, "list rule chains error: %w", err)
|
||||
if len(chains) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
toJSON, _ := cmd.Flags().GetBool(jsonFlag)
|
||||
if toJSON {
|
||||
prettyJSONFormat(cmd, chains)
|
||||
} else {
|
||||
for _, c := range chains {
|
||||
parseutil.PrintHumanReadableAPEChain(cmd, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setAdmin(cmd *cobra.Command, _ []string) {
|
||||
s, _ := cmd.Flags().GetString(addrAdminFlag)
|
||||
addr, err := util.Uint160DecodeStringLE(s)
|
||||
commonCmd.ExitOnErr(cmd, "can't decode admin addr: %w", err)
|
||||
pci, ac := newPolicyContractInterface(cmd)
|
||||
h, vub, err := pci.SetAdmin(addr)
|
||||
cmd.Println("Waiting for transaction to persist...")
|
||||
_, err = ac.Wait(h, vub, err)
|
||||
commonCmd.ExitOnErr(cmd, "can't set admin: %w", err)
|
||||
cmd.Println("Admin set successfully")
|
||||
}
|
||||
|
||||
func getAdmin(cmd *cobra.Command, _ []string) {
|
||||
pci, _ := newPolicyContractReaderInterface(cmd)
|
||||
addr, err := pci.GetAdmin()
|
||||
commonCmd.ExitOnErr(cmd, "unable to get admin: %w", err)
|
||||
cmd.Println(addr.StringLE())
|
||||
}
|
||||
|
||||
func listTargets(cmd *cobra.Command, _ []string) {
|
||||
typ, err := parseTargetType(cmd)
|
||||
commonCmd.ExitOnErr(cmd, "parse target type error: %w", err)
|
||||
pci, inv := newPolicyContractReaderInterface(cmd)
|
||||
|
||||
sid, it, err := pci.ListTargetsIterator(typ)
|
||||
commonCmd.ExitOnErr(cmd, "list targets error: %w", err)
|
||||
items, err := inv.TraverseIterator(sid, &it, 0)
|
||||
for err == nil && len(items) != 0 {
|
||||
for _, item := range items {
|
||||
bts, err := item.TryBytes()
|
||||
commonCmd.ExitOnErr(cmd, "list targets error: %w", err)
|
||||
if len(bts) == 0 {
|
||||
cmd.Println("(no name)")
|
||||
} else {
|
||||
cmd.Println(string(bts))
|
||||
}
|
||||
}
|
||||
items, err = inv.TraverseIterator(sid, &it, 0)
|
||||
commonCmd.ExitOnErr(cmd, "unable to list targets: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
func prettyJSONFormat(cmd *cobra.Command, chains []*apechain.Chain) {
|
||||
wr := bytes.NewBufferString("")
|
||||
data, err := json.Marshal(chains)
|
||||
if err == nil {
|
||||
err = json.Indent(wr, data, "", " ")
|
||||
}
|
||||
commonCmd.ExitOnErr(cmd, "print rule chain error: %w", err)
|
||||
cmd.Println(wr)
|
||||
}
|
|
@ -1,133 +0,0 @@
|
|||
package ape
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
parseutil "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
policyengine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
|
||||
morph "git.frostfs.info/TrueCloudLab/policy-engine/pkg/morph/policy"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
ingress = "ingress"
|
||||
s3 = "s3"
|
||||
)
|
||||
|
||||
var mChainName = map[string]apechain.Name{
|
||||
ingress: apechain.Ingress,
|
||||
s3: apechain.S3,
|
||||
}
|
||||
|
||||
var (
|
||||
errUnknownTargetType = errors.New("unknown target type")
|
||||
errChainIDCannotBeEmpty = errors.New("chain id cannot be empty")
|
||||
errRuleIsNotParsed = errors.New("rule is not passed")
|
||||
errUnsupportedChainName = errors.New("unsupported chain name")
|
||||
)
|
||||
|
||||
func parseTarget(cmd *cobra.Command) policyengine.Target {
|
||||
name, _ := cmd.Flags().GetString(targetNameFlag)
|
||||
typ, err := parseTargetType(cmd)
|
||||
commonCmd.ExitOnErr(cmd, "read target type error: %w", err)
|
||||
|
||||
return policyengine.Target{
|
||||
Name: name,
|
||||
Type: typ,
|
||||
}
|
||||
}
|
||||
|
||||
func parseTargetType(cmd *cobra.Command) (policyengine.TargetType, error) {
|
||||
typ, _ := cmd.Flags().GetString(targetTypeFlag)
|
||||
switch typ {
|
||||
case namespaceTarget:
|
||||
return policyengine.Namespace, nil
|
||||
case containerTarget:
|
||||
return policyengine.Container, nil
|
||||
case userTarget:
|
||||
return policyengine.User, nil
|
||||
case groupTarget:
|
||||
return policyengine.Group, nil
|
||||
}
|
||||
return -1, errUnknownTargetType
|
||||
}
|
||||
|
||||
func parseChainID(cmd *cobra.Command) apechain.ID {
|
||||
chainID, _ := cmd.Flags().GetString(chainIDFlag)
|
||||
if chainID == "" {
|
||||
commonCmd.ExitOnErr(cmd, "read chain id error: %w",
|
||||
errChainIDCannotBeEmpty)
|
||||
}
|
||||
return apechain.ID(chainID)
|
||||
}
|
||||
|
||||
func parseChain(cmd *cobra.Command) *apechain.Chain {
|
||||
chain := new(apechain.Chain)
|
||||
|
||||
if rules, _ := cmd.Flags().GetStringArray(ruleFlag); len(rules) > 0 {
|
||||
commonCmd.ExitOnErr(cmd, "parser error: %w", parseutil.ParseAPEChain(chain, rules))
|
||||
} else if encPath, _ := cmd.Flags().GetString(pathFlag); encPath != "" {
|
||||
commonCmd.ExitOnErr(cmd, "decode binary or json error: %w", parseutil.ParseAPEChainBinaryOrJSON(chain, encPath))
|
||||
} else {
|
||||
commonCmd.ExitOnErr(cmd, "parser error: %w", errRuleIsNotParsed)
|
||||
}
|
||||
|
||||
chain.ID = parseChainID(cmd)
|
||||
|
||||
cmd.Println("Parsed chain:")
|
||||
parseutil.PrintHumanReadableAPEChain(cmd, chain)
|
||||
|
||||
return chain
|
||||
}
|
||||
|
||||
func parseChainName(cmd *cobra.Command) apechain.Name {
|
||||
chainName, _ := cmd.Flags().GetString(chainNameFlag)
|
||||
apeChainName, ok := mChainName[strings.ToLower(chainName)]
|
||||
if !ok {
|
||||
commonCmd.ExitOnErr(cmd, "", errUnsupportedChainName)
|
||||
}
|
||||
return apeChainName
|
||||
}
|
||||
|
||||
func newPolicyContractReaderInterface(cmd *cobra.Command) (*morph.ContractStorageReader, *invoker.Invoker) {
|
||||
c, err := helper.GetN3Client(viper.GetViper())
|
||||
commonCmd.ExitOnErr(cmd, "unable to create NEO rpc client: %w", err)
|
||||
|
||||
inv := invoker.New(c, nil)
|
||||
var ch util.Uint160
|
||||
r := management.NewReader(inv)
|
||||
nnsCs, err := r.GetContractByID(1)
|
||||
commonCmd.ExitOnErr(cmd, "can't get NNS contract state: %w", err)
|
||||
|
||||
ch, err = helper.NNSResolveHash(inv, nnsCs.Hash, helper.DomainOf(constants.PolicyContract))
|
||||
commonCmd.ExitOnErr(cmd, "unable to resolve policy contract hash: %w", err)
|
||||
|
||||
return morph.NewContractStorageReader(inv, ch), inv
|
||||
}
|
||||
|
||||
func newPolicyContractInterface(cmd *cobra.Command) (*morph.ContractStorage, *helper.LocalActor) {
|
||||
c, err := helper.GetN3Client(viper.GetViper())
|
||||
commonCmd.ExitOnErr(cmd, "unable to create NEO rpc client: %w", err)
|
||||
|
||||
ac, err := helper.NewLocalActor(cmd, c)
|
||||
commonCmd.ExitOnErr(cmd, "can't create actor: %w", err)
|
||||
|
||||
var ch util.Uint160
|
||||
r := management.NewReader(ac.Invoker)
|
||||
nnsCs, err := r.GetContractByID(1)
|
||||
commonCmd.ExitOnErr(cmd, "can't get NNS contract state: %w", err)
|
||||
|
||||
ch, err = helper.NNSResolveHash(ac.Invoker, nnsCs.Hash, helper.DomainOf(constants.PolicyContract))
|
||||
commonCmd.ExitOnErr(cmd, "unable to resolve policy contract hash: %w", err)
|
||||
|
||||
return morph.NewContractStorage(ac, ch), ac
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package ape
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "ape",
|
||||
Short: "Section for APE configuration commands",
|
||||
}
|
||||
|
||||
func init() {
|
||||
initAddRuleChainCmd()
|
||||
initRemoveRuleChainCmd()
|
||||
initListRuleChainsCmd()
|
||||
initSetAdminCmd()
|
||||
initGetAdminCmd()
|
||||
initListTargetsCmd()
|
||||
}
|
|
@ -1,246 +0,0 @@
|
|||
package balance
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/rolemgmt"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type accBalancePair struct {
|
||||
scriptHash util.Uint160
|
||||
balance *big.Int
|
||||
}
|
||||
|
||||
const (
|
||||
dumpBalancesStorageFlag = "storage"
|
||||
dumpBalancesAlphabetFlag = "alphabet"
|
||||
dumpBalancesProxyFlag = "proxy"
|
||||
dumpBalancesUseScriptHashFlag = "script-hash"
|
||||
)
|
||||
|
||||
func dumpBalances(cmd *cobra.Command, _ []string) error {
|
||||
var (
|
||||
dumpStorage, _ = cmd.Flags().GetBool(dumpBalancesStorageFlag)
|
||||
dumpAlphabet, _ = cmd.Flags().GetBool(dumpBalancesAlphabetFlag)
|
||||
dumpProxy, _ = cmd.Flags().GetBool(dumpBalancesProxyFlag)
|
||||
nnsCs *state.Contract
|
||||
nmHash util.Uint160
|
||||
)
|
||||
|
||||
c, err := helper.GetN3Client(viper.GetViper())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
inv := invoker.New(c, nil)
|
||||
|
||||
if dumpStorage || dumpAlphabet || dumpProxy {
|
||||
r := management.NewReader(inv)
|
||||
nnsCs, err = r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get NNS contract info: %w", err)
|
||||
}
|
||||
|
||||
nmHash, err = helper.NNSResolveHash(inv, nnsCs.Hash, helper.DomainOf(constants.NetmapContract))
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
irList, err := fetchIRNodes(c, rolemgmt.Hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fetchBalances(inv, gas.Hash, irList); err != nil {
|
||||
return err
|
||||
}
|
||||
printBalances(cmd, "Inner ring nodes balances:", irList)
|
||||
|
||||
if dumpStorage {
|
||||
if err := printStorageNodeBalances(cmd, inv, nmHash); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if dumpProxy {
|
||||
if err := printProxyContractBalance(cmd, inv, nnsCs.Hash); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if dumpAlphabet {
|
||||
if err := printAlphabetContractBalances(cmd, c, inv, len(irList), nnsCs.Hash); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func printStorageNodeBalances(cmd *cobra.Command, inv *invoker.Invoker, nmHash util.Uint160) error {
|
||||
arr, err := unwrap.Array(inv.Call(nmHash, "netmap"))
|
||||
if err != nil {
|
||||
return errors.New("can't fetch the list of storage nodes")
|
||||
}
|
||||
|
||||
snList := make([]accBalancePair, len(arr))
|
||||
for i := range arr {
|
||||
node, ok := arr[i].Value().([]stackitem.Item)
|
||||
if !ok || len(node) == 0 {
|
||||
return errors.New("can't parse the list of storage nodes")
|
||||
}
|
||||
bs, err := node[0].TryBytes()
|
||||
if err != nil {
|
||||
return errors.New("can't parse the list of storage nodes")
|
||||
}
|
||||
var ni netmap.NodeInfo
|
||||
if err := ni.Unmarshal(bs); err != nil {
|
||||
return fmt.Errorf("can't parse the list of storage nodes: %w", err)
|
||||
}
|
||||
pub, err := keys.NewPublicKeyFromBytes(ni.PublicKey(), elliptic.P256())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't parse storage node public key: %w", err)
|
||||
}
|
||||
snList[i].scriptHash = pub.GetScriptHash()
|
||||
}
|
||||
|
||||
if err := fetchBalances(inv, gas.Hash, snList); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printBalances(cmd, "\nStorage node balances:", snList)
|
||||
return nil
|
||||
}
|
||||
|
||||
func printProxyContractBalance(cmd *cobra.Command, inv *invoker.Invoker, nnsHash util.Uint160) error {
|
||||
h, err := helper.NNSResolveHash(inv, nnsHash, helper.DomainOf(constants.ProxyContract))
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get hash of the proxy contract: %w", err)
|
||||
}
|
||||
|
||||
proxyList := []accBalancePair{{scriptHash: h}}
|
||||
if err := fetchBalances(inv, gas.Hash, proxyList); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printBalances(cmd, "\nProxy contract balance:", proxyList)
|
||||
return nil
|
||||
}
|
||||
|
||||
func printAlphabetContractBalances(cmd *cobra.Command, c helper.Client, inv *invoker.Invoker, count int, nnsHash util.Uint160) error {
|
||||
alphaList := make([]accBalancePair, count)
|
||||
|
||||
w := io.NewBufBinWriter()
|
||||
for i := range alphaList {
|
||||
emit.AppCall(w.BinWriter, nnsHash, "resolve", callflag.ReadOnly,
|
||||
helper.GetAlphabetNNSDomain(i),
|
||||
int64(nns.TXT))
|
||||
}
|
||||
if w.Err != nil {
|
||||
panic(w.Err)
|
||||
}
|
||||
|
||||
alphaRes, err := c.InvokeScript(w.Bytes(), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch info from NNS: %w", err)
|
||||
}
|
||||
|
||||
for i := range alphaList {
|
||||
h, err := helper.ParseNNSResolveResult(alphaRes.Stack[i])
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch the alphabet contract #%d hash: %w", i, err)
|
||||
}
|
||||
alphaList[i].scriptHash = h
|
||||
}
|
||||
|
||||
if err := fetchBalances(inv, gas.Hash, alphaList); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printBalances(cmd, "\nAlphabet contracts balances:", alphaList)
|
||||
return nil
|
||||
}
|
||||
|
||||
func fetchIRNodes(c helper.Client, desigHash util.Uint160) ([]accBalancePair, error) {
|
||||
inv := invoker.New(c, nil)
|
||||
|
||||
height, err := c.GetBlockCount()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't get block height: %w", err)
|
||||
}
|
||||
|
||||
arr, err := helper.GetDesignatedByRole(inv, desigHash, noderoles.NeoFSAlphabet, height)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't fetch list of IR nodes from the netmap contract")
|
||||
}
|
||||
|
||||
irList := make([]accBalancePair, len(arr))
|
||||
for i := range arr {
|
||||
irList[i].scriptHash = arr[i].GetScriptHash()
|
||||
}
|
||||
return irList, nil
|
||||
}
|
||||
|
||||
func printBalances(cmd *cobra.Command, prefix string, accounts []accBalancePair) {
|
||||
useScriptHash, _ := cmd.Flags().GetBool(dumpBalancesUseScriptHashFlag)
|
||||
|
||||
cmd.Println(prefix)
|
||||
for i := range accounts {
|
||||
var addr string
|
||||
if useScriptHash {
|
||||
addr = accounts[i].scriptHash.StringLE()
|
||||
} else {
|
||||
addr = address.Uint160ToString(accounts[i].scriptHash)
|
||||
}
|
||||
cmd.Printf("%s: %s\n", addr, fixedn.ToString(accounts[i].balance, 8))
|
||||
}
|
||||
}
|
||||
|
||||
func fetchBalances(c *invoker.Invoker, gasHash util.Uint160, accounts []accBalancePair) error {
|
||||
w := io.NewBufBinWriter()
|
||||
for i := range accounts {
|
||||
emit.AppCall(w.BinWriter, gasHash, "balanceOf", callflag.ReadStates, accounts[i].scriptHash)
|
||||
}
|
||||
if w.Err != nil {
|
||||
panic(w.Err)
|
||||
}
|
||||
|
||||
res, err := c.Run(w.Bytes())
|
||||
if err != nil || res.State != vmstate.Halt.String() || len(res.Stack) != len(accounts) {
|
||||
return errors.New("can't fetch account balances")
|
||||
}
|
||||
|
||||
for i := range accounts {
|
||||
bal, err := res.Stack[i].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't parse account balance: %w", err)
|
||||
}
|
||||
accounts[i].balance = bal
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package balance
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var DumpCmd = &cobra.Command{
|
||||
Use: "dump-balances",
|
||||
Short: "Dump GAS balances",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
RunE: dumpBalances,
|
||||
}
|
||||
|
||||
func initDumpBalancesCmd() {
|
||||
DumpCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
DumpCmd.Flags().BoolP(dumpBalancesStorageFlag, "s", false, "Dump balances of storage nodes from the current netmap")
|
||||
DumpCmd.Flags().BoolP(dumpBalancesAlphabetFlag, "a", false, "Dump balances of alphabet contracts")
|
||||
DumpCmd.Flags().BoolP(dumpBalancesProxyFlag, "p", false, "Dump balances of the proxy contract")
|
||||
DumpCmd.Flags().Bool(dumpBalancesUseScriptHashFlag, false, "Use script-hash format for addresses")
|
||||
}
|
||||
|
||||
func init() {
|
||||
initDumpBalancesCmd()
|
||||
}
|
|
@ -1,202 +0,0 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const forceConfigSet = "force"
|
||||
|
||||
func dumpNetworkConfig(cmd *cobra.Command, _ []string) error {
|
||||
c, err := helper.GetN3Client(viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create N3 client: %w", err)
|
||||
}
|
||||
|
||||
inv := invoker.New(c, nil)
|
||||
r := management.NewReader(inv)
|
||||
|
||||
cs, err := r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get NNS contract info: %w", err)
|
||||
}
|
||||
|
||||
nmHash, err := helper.NNSResolveHash(inv, cs.Hash, helper.DomainOf(constants.NetmapContract))
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
||||
}
|
||||
|
||||
arr, err := unwrap.Array(inv.Call(nmHash, "listConfig"))
|
||||
if err != nil {
|
||||
return errors.New("can't fetch list of network config keys from the netmap contract")
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
|
||||
|
||||
m, err := helper.ParseConfigFromNetmapContract(arr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range m {
|
||||
switch k {
|
||||
case netmap.ContainerFeeConfig, netmap.ContainerAliasFeeConfig,
|
||||
netmap.EpochDurationConfig, netmap.IrCandidateFeeConfig,
|
||||
netmap.MaxObjectSizeConfig, netmap.WithdrawFeeConfig,
|
||||
netmap.MaxECDataCountConfig, netmap.MaxECParityCountConfig:
|
||||
nbuf := make([]byte, 8)
|
||||
copy(nbuf[:], v)
|
||||
n := binary.LittleEndian.Uint64(nbuf)
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%d (int)\n", k, n)))
|
||||
case netmap.HomomorphicHashingDisabledKey, netmap.MaintenanceModeAllowedConfig:
|
||||
if len(v) == 0 || len(v) > 1 {
|
||||
return helper.InvalidConfigValueErr(k)
|
||||
}
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%t (bool)\n", k, v[0] == 1)))
|
||||
default:
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%s (hex)\n", k, hex.EncodeToString(v))))
|
||||
}
|
||||
}
|
||||
|
||||
_ = tw.Flush()
|
||||
cmd.Print(buf.String())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetConfigCmd(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return errors.New("empty config pairs")
|
||||
}
|
||||
|
||||
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't initialize context: %w", err)
|
||||
}
|
||||
|
||||
r := management.NewReader(wCtx.ReadOnlyInvoker)
|
||||
cs, err := r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get NNS contract info: %w", err)
|
||||
}
|
||||
|
||||
nmHash, err := helper.NNSResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, helper.DomainOf(constants.NetmapContract))
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
||||
}
|
||||
|
||||
forceFlag, _ := cmd.Flags().GetBool(forceConfigSet)
|
||||
bw := io.NewBufBinWriter()
|
||||
prm := make(map[string]any)
|
||||
for _, arg := range args {
|
||||
k, v, err := parseConfigPair(arg, forceFlag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
prm[k] = v
|
||||
}
|
||||
|
||||
if err := validateConfig(prm, forceFlag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for k, v := range prm {
|
||||
// In NeoFS this is done via Notary contract. Here, however, we can form the
|
||||
// transaction locally. The first `nil` argument is required only for notary
|
||||
// disabled environment which is not supported by that command.
|
||||
emit.AppCall(bw.BinWriter, nmHash, "setConfig", callflag.All, nil, k, v)
|
||||
if bw.Err != nil {
|
||||
return fmt.Errorf("can't form raw transaction: %w", bw.Err)
|
||||
}
|
||||
}
|
||||
|
||||
err = wCtx.SendConsensusTx(bw.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return wCtx.AwaitTx()
|
||||
}
|
||||
|
||||
func validateConfig(args map[string]any, forceFlag bool) error {
|
||||
var sumEC int64
|
||||
_, okData := args[netmap.MaxECDataCountConfig]
|
||||
_, okParity := args[netmap.MaxECParityCountConfig]
|
||||
if okData != okParity {
|
||||
return fmt.Errorf("both %s and %s must be present in the configuration",
|
||||
netmap.MaxECDataCountConfig, netmap.MaxECParityCountConfig)
|
||||
}
|
||||
|
||||
for k, v := range args {
|
||||
value, ok := v.(int64)
|
||||
if !ok || value < 0 {
|
||||
return fmt.Errorf("%s must be >= 0, got %v", k, v)
|
||||
}
|
||||
|
||||
if k == netmap.MaxECDataCountConfig || k == netmap.MaxECParityCountConfig {
|
||||
sumEC += value
|
||||
}
|
||||
}
|
||||
|
||||
if sumEC > 256 && !forceFlag {
|
||||
return fmt.Errorf("the sum of %s and %s must be <= 256, got %d",
|
||||
netmap.MaxECDataCountConfig, netmap.MaxECParityCountConfig, sumEC)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseConfigPair(kvStr string, force bool) (key string, val any, err error) {
|
||||
k, v, found := strings.Cut(kvStr, "=")
|
||||
if !found {
|
||||
return "", nil, fmt.Errorf("invalid parameter format: must be 'key=val', got: %s", kvStr)
|
||||
}
|
||||
|
||||
key = k
|
||||
valRaw := v
|
||||
|
||||
switch key {
|
||||
case netmap.ContainerFeeConfig, netmap.ContainerAliasFeeConfig,
|
||||
netmap.EpochDurationConfig, netmap.IrCandidateFeeConfig,
|
||||
netmap.MaxObjectSizeConfig, netmap.WithdrawFeeConfig,
|
||||
netmap.MaxECDataCountConfig, netmap.MaxECParityCountConfig:
|
||||
val, err = strconv.ParseInt(valRaw, 10, 64)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not parse %s's value '%s' as int: %w", key, valRaw, err)
|
||||
}
|
||||
case netmap.HomomorphicHashingDisabledKey, netmap.MaintenanceModeAllowedConfig:
|
||||
val, err = strconv.ParseBool(valRaw)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not parse %s's value '%s' as bool: %w", key, valRaw, err)
|
||||
}
|
||||
|
||||
default:
|
||||
if !force {
|
||||
return "", nil, fmt.Errorf(
|
||||
"'%s' key is not well-known, use '--%s' flag if want to set it anyway",
|
||||
key, forceConfigSet)
|
||||
}
|
||||
|
||||
val = valRaw
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
SetCmd = &cobra.Command{
|
||||
Use: "set-config key1=val1 [key2=val2 ...]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: "Add/update global config value in the FrostFS network",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: SetConfigCmd,
|
||||
}
|
||||
|
||||
DumpCmd = &cobra.Command{
|
||||
Use: "dump-config",
|
||||
Short: "Dump FrostFS network config",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
RunE: dumpNetworkConfig,
|
||||
}
|
||||
)
|
||||
|
||||
func initSetConfigCmd() {
|
||||
SetCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
SetCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
SetCmd.Flags().Bool(forceConfigSet, false, "Force setting not well-known configuration key")
|
||||
SetCmd.Flags().String(commonflags.LocalDumpFlag, "", "Path to the blocks dump file")
|
||||
}
|
||||
|
||||
func initDumpNetworkConfigCmd() {
|
||||
DumpCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
}
|
||||
|
||||
func init() {
|
||||
initSetConfigCmd()
|
||||
initDumpNetworkConfigCmd()
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
package constants
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
ConsensusAccountName = "consensus"
|
||||
ProtoConfigPath = "protocol"
|
||||
|
||||
// MaxAlphabetNodes is the maximum number of candidates allowed, which is currently limited by the size
|
||||
// of the invocation script.
|
||||
// See: https://github.com/nspcc-dev/neo-go/blob/740488f7f35e367eaa99a71c0a609c315fe2b0fc/pkg/core/transaction/witness.go#L10
|
||||
MaxAlphabetNodes = 22
|
||||
|
||||
SingleAccountName = "single"
|
||||
CommitteeAccountName = "committee"
|
||||
|
||||
NNSContract = "nns"
|
||||
FrostfsContract = "frostfs" // not deployed in side-chain.
|
||||
ProcessingContract = "processing" // not deployed in side-chain.
|
||||
AlphabetContract = "alphabet"
|
||||
BalanceContract = "balance"
|
||||
ContainerContract = "container"
|
||||
FrostfsIDContract = "frostfsid"
|
||||
NetmapContract = "netmap"
|
||||
PolicyContract = "policy"
|
||||
ProxyContract = "proxy"
|
||||
|
||||
ContractWalletFilename = "contract.json"
|
||||
ContractWalletPasswordKey = "contract"
|
||||
|
||||
FrostfsOpsEmail = "ops@frostfs.info"
|
||||
NNSRefreshDefVal = int64(3600)
|
||||
NNSRetryDefVal = int64(600)
|
||||
NNSTtlDefVal = int64(3600)
|
||||
|
||||
DefaultExpirationTime = 10 * 365 * 24 * time.Hour / time.Second
|
||||
|
||||
DeployMethodName = "deploy"
|
||||
UpdateMethodName = "update"
|
||||
|
||||
TestContractPassword = "grouppass"
|
||||
)
|
||||
|
||||
var (
|
||||
ContractList = []string{
|
||||
BalanceContract,
|
||||
ContainerContract,
|
||||
FrostfsIDContract,
|
||||
NetmapContract,
|
||||
PolicyContract,
|
||||
ProxyContract,
|
||||
}
|
||||
|
||||
FullContractList = append([]string{
|
||||
FrostfsContract,
|
||||
ProcessingContract,
|
||||
NNSContract,
|
||||
AlphabetContract,
|
||||
}, ContractList...)
|
||||
)
|
|
@ -1,453 +0,0 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"slices"
|
||||
"sort"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var errInvalidContainerResponse = errors.New("invalid response from container contract")
|
||||
|
||||
func getContainerContractHash(cmd *cobra.Command, inv *invoker.Invoker) (util.Uint160, error) {
|
||||
s, err := cmd.Flags().GetString(containerContractFlag)
|
||||
var ch util.Uint160
|
||||
if err == nil {
|
||||
ch, err = util.Uint160DecodeStringLE(s)
|
||||
}
|
||||
if err != nil {
|
||||
r := management.NewReader(inv)
|
||||
nnsCs, err := r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return util.Uint160{}, fmt.Errorf("can't get NNS contract state: %w", err)
|
||||
}
|
||||
ch, err = helper.NNSResolveHash(inv, nnsCs.Hash, helper.DomainOf(constants.ContainerContract))
|
||||
if err != nil {
|
||||
return util.Uint160{}, err
|
||||
}
|
||||
}
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
func iterateContainerList(inv *invoker.Invoker, ch util.Uint160, f func([]byte) error) error {
|
||||
sid, r, err := unwrap.SessionIterator(inv.Call(ch, "containersOf", ""))
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
|
||||
}
|
||||
// Nothing bad, except live session on the server, do not report to the user.
|
||||
defer func() { _ = inv.TerminateSession(sid) }()
|
||||
|
||||
items, err := inv.TraverseIterator(sid, &r, 0)
|
||||
for err == nil && len(items) != 0 {
|
||||
for j := range items {
|
||||
b, err := items[j].TryBytes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
|
||||
}
|
||||
if err := f(b); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
items, err = inv.TraverseIterator(sid, &r, 0)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func dumpContainers(cmd *cobra.Command, _ []string) error {
|
||||
filename, err := cmd.Flags().GetString(containerDumpFlag)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid filename: %w", err)
|
||||
}
|
||||
|
||||
c, err := helper.GetN3Client(viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create N3 client: %w", err)
|
||||
}
|
||||
|
||||
inv := invoker.New(c, nil)
|
||||
|
||||
ch, err := getContainerContractHash(cmd, inv)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get contaract hash: %w", err)
|
||||
}
|
||||
|
||||
isOK, err := getCIDFilterFunc(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(filename, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0o660)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.Write([]byte{'['})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
written := 0
|
||||
enc := json.NewEncoder(f)
|
||||
bw := io.NewBufBinWriter()
|
||||
iterErr := iterateContainerList(inv, ch, func(id []byte) error {
|
||||
if !isOK(id) {
|
||||
return nil
|
||||
}
|
||||
|
||||
cnt, err := dumpSingleContainer(bw, ch, inv, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Writing directly to the file is ok, because json.Encoder does no internal buffering.
|
||||
if written != 0 {
|
||||
_, err = f.Write([]byte{','})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
written++
|
||||
return enc.Encode(cnt)
|
||||
})
|
||||
if iterErr != nil {
|
||||
return iterErr
|
||||
}
|
||||
|
||||
_, err = f.Write([]byte{']'})
|
||||
return err
|
||||
}
|
||||
|
||||
func dumpSingleContainer(bw *io.BufBinWriter, ch util.Uint160, inv *invoker.Invoker, id []byte) (*Container, error) {
|
||||
bw.Reset()
|
||||
emit.AppCall(bw.BinWriter, ch, "get", callflag.All, id)
|
||||
emit.AppCall(bw.BinWriter, ch, "eACL", callflag.All, id)
|
||||
res, err := inv.Run(bw.Bytes())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't get container info: %w", err)
|
||||
}
|
||||
if len(res.Stack) != 2 {
|
||||
return nil, fmt.Errorf("%w: expected 2 items on stack", errInvalidContainerResponse)
|
||||
}
|
||||
|
||||
cnt := new(Container)
|
||||
err = cnt.FromStackItem(res.Stack[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
|
||||
}
|
||||
|
||||
ea := new(EACL)
|
||||
err = ea.FromStackItem(res.Stack[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
|
||||
}
|
||||
if len(ea.Value) != 0 {
|
||||
cnt.EACL = ea
|
||||
}
|
||||
return cnt, nil
|
||||
}
|
||||
|
||||
func listContainers(cmd *cobra.Command, _ []string) error {
|
||||
c, err := helper.GetN3Client(viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create N3 client: %w", err)
|
||||
}
|
||||
|
||||
inv := invoker.New(c, nil)
|
||||
|
||||
ch, err := getContainerContractHash(cmd, inv)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get contaract hash: %w", err)
|
||||
}
|
||||
|
||||
return iterateContainerList(inv, ch, func(id []byte) error {
|
||||
var idCnr cid.ID
|
||||
err = idCnr.Decode(id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to decode container id: %w", err)
|
||||
}
|
||||
cmd.Println(idCnr)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func restoreContainers(cmd *cobra.Command, _ []string) error {
|
||||
filename, err := cmd.Flags().GetString(containerDumpFlag)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid filename: %w", err)
|
||||
}
|
||||
|
||||
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer wCtx.Close()
|
||||
|
||||
containers, err := parseContainers(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ch, err := fetchContainerContractHash(wCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isOK, err := getCIDFilterFunc(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = restoreOrPutContainers(containers, isOK, cmd, wCtx, ch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return wCtx.AwaitTx()
|
||||
}
|
||||
|
||||
func restoreOrPutContainers(containers []Container, isOK func([]byte) bool, cmd *cobra.Command, wCtx *helper.InitializeContext, ch util.Uint160) error {
|
||||
bw := io.NewBufBinWriter()
|
||||
for _, cnt := range containers {
|
||||
hv := hash.Sha256(cnt.Value)
|
||||
if !isOK(hv[:]) {
|
||||
continue
|
||||
}
|
||||
bw.Reset()
|
||||
restored, err := isContainerRestored(cmd, wCtx, ch, bw, hv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if restored {
|
||||
continue
|
||||
}
|
||||
|
||||
bw.Reset()
|
||||
|
||||
putContainer(bw, ch, cnt)
|
||||
|
||||
if bw.Err != nil {
|
||||
panic(bw.Err)
|
||||
}
|
||||
|
||||
if err := wCtx.SendConsensusTx(bw.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func putContainer(bw *io.BufBinWriter, ch util.Uint160, cnt Container) {
|
||||
emit.AppCall(bw.BinWriter, ch, "put", callflag.All,
|
||||
cnt.Value, cnt.Signature, cnt.PublicKey, cnt.Token)
|
||||
if ea := cnt.EACL; ea != nil {
|
||||
emit.AppCall(bw.BinWriter, ch, "setEACL", callflag.All,
|
||||
ea.Value, ea.Signature, ea.PublicKey, ea.Token)
|
||||
}
|
||||
}
|
||||
|
||||
func isContainerRestored(cmd *cobra.Command, wCtx *helper.InitializeContext, containerHash util.Uint160, bw *io.BufBinWriter, hashValue util.Uint256) (bool, error) {
|
||||
emit.AppCall(bw.BinWriter, containerHash, "get", callflag.All, hashValue.BytesBE())
|
||||
res, err := wCtx.Client.InvokeScript(bw.Bytes(), nil)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("can't check if container is already restored: %w", err)
|
||||
}
|
||||
if len(res.Stack) == 0 {
|
||||
return false, errors.New("empty stack")
|
||||
}
|
||||
|
||||
old := new(Container)
|
||||
if err := old.FromStackItem(res.Stack[0]); err != nil {
|
||||
return false, fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
|
||||
}
|
||||
if len(old.Value) != 0 {
|
||||
var id cid.ID
|
||||
id.SetSHA256(hashValue)
|
||||
cmd.Printf("Container %s is already deployed.\n", id)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func parseContainers(filename string) ([]Container, error) {
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read dump file: %w", err)
|
||||
}
|
||||
|
||||
var containers []Container
|
||||
err = json.Unmarshal(data, &containers)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't parse dump file: %w", err)
|
||||
}
|
||||
return containers, nil
|
||||
}
|
||||
|
||||
func fetchContainerContractHash(wCtx *helper.InitializeContext) (util.Uint160, error) {
|
||||
r := management.NewReader(wCtx.ReadOnlyInvoker)
|
||||
nnsCs, err := r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return util.Uint160{}, fmt.Errorf("can't get NNS contract state: %w", err)
|
||||
}
|
||||
|
||||
ch, err := helper.NNSResolveHash(wCtx.ReadOnlyInvoker, nnsCs.Hash, helper.DomainOf(constants.ContainerContract))
|
||||
if err != nil {
|
||||
return util.Uint160{}, fmt.Errorf("can't fetch container contract hash: %w", err)
|
||||
}
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
// Container represents container struct in contract storage.
|
||||
type Container struct {
|
||||
Value []byte `json:"value"`
|
||||
Signature []byte `json:"signature"`
|
||||
PublicKey []byte `json:"public_key"`
|
||||
Token []byte `json:"token"`
|
||||
EACL *EACL `json:"eacl"`
|
||||
}
|
||||
|
||||
// EACL represents extended ACL struct in contract storage.
|
||||
type EACL struct {
|
||||
Value []byte `json:"value"`
|
||||
Signature []byte `json:"signature"`
|
||||
PublicKey []byte `json:"public_key"`
|
||||
Token []byte `json:"token"`
|
||||
}
|
||||
|
||||
// ToStackItem implements stackitem.Convertible.
|
||||
func (c *Container) ToStackItem() (stackitem.Item, error) {
|
||||
return stackitem.NewStruct([]stackitem.Item{
|
||||
stackitem.NewByteArray(c.Value),
|
||||
stackitem.NewByteArray(c.Signature),
|
||||
stackitem.NewByteArray(c.PublicKey),
|
||||
stackitem.NewByteArray(c.Token),
|
||||
}), nil
|
||||
}
|
||||
|
||||
// FromStackItem implements stackitem.Convertible.
|
||||
func (c *Container) FromStackItem(item stackitem.Item) error {
|
||||
arr, ok := item.Value().([]stackitem.Item)
|
||||
if !ok || len(arr) != 4 {
|
||||
return errors.New("invalid stack item type")
|
||||
}
|
||||
|
||||
value, err := arr[0].TryBytes()
|
||||
if err != nil {
|
||||
return errors.New("invalid container value")
|
||||
}
|
||||
|
||||
sig, err := arr[1].TryBytes()
|
||||
if err != nil {
|
||||
return errors.New("invalid container signature")
|
||||
}
|
||||
|
||||
pub, err := arr[2].TryBytes()
|
||||
if err != nil {
|
||||
return errors.New("invalid container public key")
|
||||
}
|
||||
|
||||
tok, err := arr[3].TryBytes()
|
||||
if err != nil {
|
||||
return errors.New("invalid container token")
|
||||
}
|
||||
|
||||
c.Value = value
|
||||
c.Signature = sig
|
||||
c.PublicKey = pub
|
||||
c.Token = tok
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToStackItem implements stackitem.Convertible.
|
||||
func (c *EACL) ToStackItem() (stackitem.Item, error) {
|
||||
return stackitem.NewStruct([]stackitem.Item{
|
||||
stackitem.NewByteArray(c.Value),
|
||||
stackitem.NewByteArray(c.Signature),
|
||||
stackitem.NewByteArray(c.PublicKey),
|
||||
stackitem.NewByteArray(c.Token),
|
||||
}), nil
|
||||
}
|
||||
|
||||
// FromStackItem implements stackitem.Convertible.
|
||||
func (c *EACL) FromStackItem(item stackitem.Item) error {
|
||||
arr, ok := item.Value().([]stackitem.Item)
|
||||
if !ok || len(arr) != 4 {
|
||||
return errors.New("invalid stack item type")
|
||||
}
|
||||
|
||||
value, err := arr[0].TryBytes()
|
||||
if err != nil {
|
||||
return errors.New("invalid eACL value")
|
||||
}
|
||||
|
||||
sig, err := arr[1].TryBytes()
|
||||
if err != nil {
|
||||
return errors.New("invalid eACL signature")
|
||||
}
|
||||
|
||||
pub, err := arr[2].TryBytes()
|
||||
if err != nil {
|
||||
return errors.New("invalid eACL public key")
|
||||
}
|
||||
|
||||
tok, err := arr[3].TryBytes()
|
||||
if err != nil {
|
||||
return errors.New("invalid eACL token")
|
||||
}
|
||||
|
||||
c.Value = value
|
||||
c.Signature = sig
|
||||
c.PublicKey = pub
|
||||
c.Token = tok
|
||||
return nil
|
||||
}
|
||||
|
||||
// getCIDFilterFunc returns filtering function for container IDs.
|
||||
// Raw byte slices are used because it works with structures returned
|
||||
// from contract.
|
||||
func getCIDFilterFunc(cmd *cobra.Command) (func([]byte) bool, error) {
|
||||
rawIDs, err := cmd.Flags().GetStringSlice(containerIDsFlag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(rawIDs) == 0 {
|
||||
return func([]byte) bool { return true }, nil
|
||||
}
|
||||
|
||||
for i := range rawIDs {
|
||||
err := new(cid.ID).DecodeString(rawIDs[i])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't parse CID %s: %w", rawIDs[i], err)
|
||||
}
|
||||
}
|
||||
sort.Strings(rawIDs)
|
||||
return func(rawID []byte) bool {
|
||||
var v [32]byte
|
||||
copy(v[:], rawID)
|
||||
|
||||
var id cid.ID
|
||||
id.SetSHA256(v)
|
||||
idStr := id.EncodeToString()
|
||||
_, found := slices.BinarySearch(rawIDs, idStr)
|
||||
return found
|
||||
}, nil
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
containerDumpFlag = "dump"
|
||||
containerContractFlag = "container-contract"
|
||||
containerIDsFlag = "cid"
|
||||
)
|
||||
|
||||
var (
|
||||
DumpCmd = &cobra.Command{
|
||||
Use: "dump-containers",
|
||||
Short: "Dump FrostFS containers to file",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
RunE: dumpContainers,
|
||||
}
|
||||
|
||||
RestoreCmd = &cobra.Command{
|
||||
Use: "restore-containers",
|
||||
Short: "Restore FrostFS containers from file",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
RunE: restoreContainers,
|
||||
}
|
||||
|
||||
ListCmd = &cobra.Command{
|
||||
Use: "list-containers",
|
||||
Short: "List FrostFS containers",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
RunE: listContainers,
|
||||
}
|
||||
)
|
||||
|
||||
func initListContainersCmd() {
|
||||
ListCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
ListCmd.Flags().String(containerContractFlag, "", "Container contract hash (for networks without NNS)")
|
||||
}
|
||||
|
||||
func initRestoreContainersCmd() {
|
||||
RestoreCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
RestoreCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
RestoreCmd.Flags().String(containerDumpFlag, "", "File to restore containers from")
|
||||
RestoreCmd.Flags().StringSlice(containerIDsFlag, nil, "Containers to restore")
|
||||
}
|
||||
|
||||
func initDumpContainersCmd() {
|
||||
DumpCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
DumpCmd.Flags().String(containerDumpFlag, "", "File where to save dumped containers")
|
||||
DumpCmd.Flags().String(containerContractFlag, "", "Container contract hash (for networks without NNS)")
|
||||
DumpCmd.Flags().StringSlice(containerIDsFlag, nil, "Containers to dump")
|
||||
}
|
||||
|
||||
func init() {
|
||||
initDumpContainersCmd()
|
||||
initRestoreContainersCmd()
|
||||
initListContainersCmd()
|
||||
}
|
|
@ -1,237 +0,0 @@
|
|||
package contract
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/services/rpcsrv/params"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
contractPathFlag = "contract"
|
||||
updateFlag = "update"
|
||||
)
|
||||
|
||||
var DeployCmd = &cobra.Command{
|
||||
Use: "deploy",
|
||||
Short: "Deploy additional smart-contracts",
|
||||
Long: `Deploy additional smart-contract which are not related to core.
|
||||
All contracts are deployed by the committee, so access to the alphabet wallets is required.
|
||||
Optionally, arguments can be provided to be passed to a contract's _deploy function.
|
||||
The syntax is the same as for 'neo-go contract testinvokefunction' command.
|
||||
Compiled contract file name must contain '_contract.nef' suffix.
|
||||
Contract's manifest file name must be 'config.json'.
|
||||
NNS name is taken by stripping '_contract.nef' from the NEF file (similar to frostfs contracts).`,
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
RunE: deployContractCmd,
|
||||
}
|
||||
|
||||
func init() {
|
||||
ff := DeployCmd.Flags()
|
||||
|
||||
ff.String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
_ = DeployCmd.MarkFlagFilename(commonflags.AlphabetWalletsFlag)
|
||||
|
||||
ff.StringP(commonflags.EndpointFlag, "r", "", commonflags.EndpointFlagDesc)
|
||||
ff.String(contractPathFlag, "", "Path to the contract directory")
|
||||
_ = DeployCmd.MarkFlagFilename(contractPathFlag)
|
||||
|
||||
ff.Bool(updateFlag, false, "Update an existing contract")
|
||||
ff.String(commonflags.CustomZoneFlag, "frostfs", "Custom zone for NNS")
|
||||
}
|
||||
|
||||
func deployContractCmd(cmd *cobra.Command, args []string) error {
|
||||
v := viper.GetViper()
|
||||
c, err := helper.NewInitializeContext(cmd, v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("initialization error: %w", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
ctrPath, _ := cmd.Flags().GetString(contractPathFlag)
|
||||
ctrName, err := probeContractName(ctrPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cs, err := helper.ReadContract(ctrPath, ctrName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r := management.NewReader(c.ReadOnlyInvoker)
|
||||
nnsCs, err := r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch NNS contract state: %w", err)
|
||||
}
|
||||
|
||||
callHash := management.Hash
|
||||
method := constants.DeployMethodName
|
||||
zone, _ := cmd.Flags().GetString(commonflags.CustomZoneFlag)
|
||||
domain := ctrName + "." + zone
|
||||
isUpdate, _ := cmd.Flags().GetBool(updateFlag)
|
||||
if isUpdate {
|
||||
cs.Hash, err = helper.NNSResolveHash(c.ReadOnlyInvoker, nnsCs.Hash, domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch contract hash from NNS: %w", err)
|
||||
}
|
||||
callHash = cs.Hash
|
||||
method = constants.UpdateMethodName
|
||||
} else {
|
||||
cs.Hash = state.CreateContractHash(
|
||||
c.CommitteeAcc.Contract.ScriptHash(),
|
||||
cs.NEF.Checksum,
|
||||
cs.Manifest.Name)
|
||||
}
|
||||
|
||||
writer := io.NewBufBinWriter()
|
||||
if err := emitDeploymentArguments(writer.BinWriter, args); err != nil {
|
||||
return err
|
||||
}
|
||||
emit.Bytes(writer.BinWriter, cs.RawManifest)
|
||||
emit.Bytes(writer.BinWriter, cs.RawNEF)
|
||||
emit.Int(writer.BinWriter, 3)
|
||||
emit.Opcodes(writer.BinWriter, opcode.PACK)
|
||||
emit.AppCallNoArgs(writer.BinWriter, callHash, method, callflag.All)
|
||||
emit.Opcodes(writer.BinWriter, opcode.DROP) // contract state on stack
|
||||
if !isUpdate {
|
||||
err := registerNNS(nnsCs, c, zone, domain, cs, writer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if writer.Err != nil {
|
||||
panic(fmt.Errorf("BUG: can't create deployment script: %w", writer.Err))
|
||||
}
|
||||
|
||||
if err := c.SendCommitteeTx(writer.Bytes(), false); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.AwaitTx()
|
||||
}
|
||||
|
||||
func registerNNS(nnsCs *state.Contract, c *helper.InitializeContext, zone string, domain string, cs *helper.ContractState, writer *io.BufBinWriter) error {
|
||||
bw := io.NewBufBinWriter()
|
||||
emit.Instruction(bw.BinWriter, opcode.INITSSLOT, []byte{1})
|
||||
emit.AppCall(bw.BinWriter, nnsCs.Hash, "getPrice", callflag.All)
|
||||
emit.Opcodes(bw.BinWriter, opcode.STSFLD0)
|
||||
emit.AppCall(bw.BinWriter, nnsCs.Hash, "setPrice", callflag.All, 1)
|
||||
|
||||
start := bw.Len()
|
||||
needRecord := false
|
||||
|
||||
ok, err := c.NNSRootRegistered(nnsCs.Hash, zone)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !ok {
|
||||
needRecord = true
|
||||
|
||||
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
|
||||
zone, c.CommitteeAcc.Contract.ScriptHash(),
|
||||
constants.FrostfsOpsEmail, constants.NNSRefreshDefVal, constants.NNSRetryDefVal,
|
||||
int64(constants.DefaultExpirationTime), constants.NNSTtlDefVal)
|
||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||
|
||||
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
|
||||
domain, c.CommitteeAcc.Contract.ScriptHash(),
|
||||
constants.FrostfsOpsEmail, constants.NNSRefreshDefVal, constants.NNSRetryDefVal,
|
||||
int64(constants.DefaultExpirationTime), constants.NNSTtlDefVal)
|
||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||
} else {
|
||||
s, ok, err := c.NNSRegisterDomainScript(nnsCs.Hash, cs.Hash, domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
needRecord = !ok
|
||||
if len(s) != 0 {
|
||||
bw.WriteBytes(s)
|
||||
}
|
||||
}
|
||||
if needRecord {
|
||||
emit.AppCall(bw.BinWriter, nnsCs.Hash, "deleteRecords", callflag.All, domain, int64(nns.TXT))
|
||||
emit.AppCall(bw.BinWriter, nnsCs.Hash, "addRecord", callflag.All,
|
||||
domain, int64(nns.TXT), address.Uint160ToString(cs.Hash))
|
||||
}
|
||||
|
||||
if bw.Err != nil {
|
||||
panic(fmt.Errorf("BUG: can't create deployment script: %w", writer.Err))
|
||||
} else if bw.Len() != start {
|
||||
writer.WriteBytes(bw.Bytes())
|
||||
emit.Opcodes(writer.BinWriter, opcode.LDSFLD0, opcode.PUSH1, opcode.PACK)
|
||||
emit.AppCallNoArgs(writer.BinWriter, nnsCs.Hash, "setPrice", callflag.All)
|
||||
|
||||
if needRecord {
|
||||
c.Command.Printf("NNS: Set %s -> %s\n", domain, cs.Hash.StringLE())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func emitDeploymentArguments(w *io.BinWriter, args []string) error {
|
||||
_, ps, err := cmdargs.ParseParams(args, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(ps) == 0 {
|
||||
emit.Opcodes(w, opcode.NEWARRAY0)
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(ps) != 1 {
|
||||
return fmt.Errorf("at most one argument is expected for deploy, got %d", len(ps))
|
||||
}
|
||||
|
||||
// We could emit this directly, but round-trip through JSON is more robust.
|
||||
// This a CLI, so optimizing the conversion is not worth the effort.
|
||||
data, err := json.Marshal(ps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var pp params.Params
|
||||
if err := json.Unmarshal(data, &pp); err != nil {
|
||||
return err
|
||||
}
|
||||
return params.ExpandArrayIntoScript(w, pp)
|
||||
}
|
||||
|
||||
func probeContractName(ctrPath string) (string, error) {
|
||||
ds, err := os.ReadDir(ctrPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("can't read directory: %w", err)
|
||||
}
|
||||
|
||||
var ctrName string
|
||||
for i := range ds {
|
||||
if strings.HasSuffix(ds[i].Name(), "_contract.nef") {
|
||||
ctrName = strings.TrimSuffix(ds[i].Name(), "_contract.nef")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ctrName == "" {
|
||||
return "", fmt.Errorf("can't find any NEF files in %s", ctrPath)
|
||||
}
|
||||
return ctrName, nil
|
||||
}
|
|
@ -1,266 +0,0 @@
|
|||
package contract
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const lastGlagoliticLetter = 41
|
||||
|
||||
type contractDumpInfo struct {
|
||||
hash util.Uint160
|
||||
name string
|
||||
version string
|
||||
}
|
||||
|
||||
func dumpContractHashes(cmd *cobra.Command, _ []string) error {
|
||||
c, err := helper.GetN3Client(viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create N3 client: %w", err)
|
||||
}
|
||||
|
||||
r := management.NewReader(invoker.New(c, nil))
|
||||
cs, err := r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
zone, _ := cmd.Flags().GetString(commonflags.CustomZoneFlag)
|
||||
if zone != "" {
|
||||
return dumpCustomZoneHashes(cmd, cs.Hash, zone, c)
|
||||
}
|
||||
|
||||
infos := []contractDumpInfo{{name: constants.NNSContract, hash: cs.Hash}}
|
||||
|
||||
irSize := 0
|
||||
for ; irSize < lastGlagoliticLetter; irSize++ {
|
||||
ok, err := helper.NNSIsAvailable(c, cs.Hash, helper.GetAlphabetNNSDomain(irSize))
|
||||
if err != nil {
|
||||
return err
|
||||
} else if ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
bw := io.NewBufBinWriter()
|
||||
|
||||
if irSize != 0 {
|
||||
bw.Reset()
|
||||
for i := 0; i < irSize; i++ {
|
||||
emit.AppCall(bw.BinWriter, cs.Hash, "resolve", callflag.ReadOnly,
|
||||
helper.GetAlphabetNNSDomain(i),
|
||||
int64(nns.TXT))
|
||||
}
|
||||
|
||||
alphaRes, err := c.InvokeScript(bw.Bytes(), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch info from NNS: %w", err)
|
||||
}
|
||||
|
||||
for i := 0; i < irSize; i++ {
|
||||
info := contractDumpInfo{name: fmt.Sprintf("alphabet %d", i)}
|
||||
if h, err := helper.ParseNNSResolveResult(alphaRes.Stack[i]); err == nil {
|
||||
info.hash = h
|
||||
}
|
||||
infos = append(infos, info)
|
||||
}
|
||||
}
|
||||
|
||||
for _, ctrName := range constants.ContractList {
|
||||
bw.Reset()
|
||||
emit.AppCall(bw.BinWriter, cs.Hash, "resolve", callflag.ReadOnly,
|
||||
helper.DomainOf(ctrName), int64(nns.TXT))
|
||||
|
||||
res, err := c.InvokeScript(bw.Bytes(), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch info from NNS: %w", err)
|
||||
}
|
||||
|
||||
info := contractDumpInfo{name: ctrName}
|
||||
if len(res.Stack) != 0 {
|
||||
if h, err := helper.ParseNNSResolveResult(res.Stack[0]); err == nil {
|
||||
info.hash = h
|
||||
}
|
||||
}
|
||||
infos = append(infos, info)
|
||||
}
|
||||
|
||||
fillContractVersion(cmd, c, infos)
|
||||
printContractInfo(cmd, infos)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func dumpCustomZoneHashes(cmd *cobra.Command, nnsHash util.Uint160, zone string, c helper.Client) error {
|
||||
const nnsMaxTokens = 100
|
||||
|
||||
inv := invoker.New(c, nil)
|
||||
|
||||
if !strings.HasPrefix(zone, ".") {
|
||||
zone = "." + zone
|
||||
}
|
||||
|
||||
var infos []contractDumpInfo
|
||||
processItem := func(item stackitem.Item) {
|
||||
bs, err := item.TryBytes()
|
||||
if err != nil {
|
||||
cmd.PrintErrf("Invalid NNS record: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !bytes.HasSuffix(bs, []byte(zone)) || bytes.HasPrefix(bs, []byte(morphClient.NNSGroupKeyName)) {
|
||||
// Related https://github.com/nspcc-dev/neofs-contract/issues/316.
|
||||
return
|
||||
}
|
||||
|
||||
h, err := helper.NNSResolveHash(inv, nnsHash, string(bs))
|
||||
if err != nil {
|
||||
cmd.PrintErrf("Could not resolve name %s: %v\n", string(bs), err)
|
||||
return
|
||||
}
|
||||
|
||||
infos = append(infos, contractDumpInfo{
|
||||
hash: h,
|
||||
name: strings.TrimSuffix(string(bs), zone),
|
||||
})
|
||||
}
|
||||
|
||||
script, err := smartcontract.CreateCallAndPrefetchIteratorScript(nnsHash, "tokens", nnsMaxTokens)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create prefetch script: %w", err)
|
||||
}
|
||||
|
||||
arr, sessionID, iter, err := unwrap.ArrayAndSessionIterator(inv.Run(script))
|
||||
if err != nil {
|
||||
if errors.Is(err, unwrap.ErrNoSessionID) {
|
||||
items, err := unwrap.Array(inv.CallAndExpandIterator(nnsHash, "tokens", nnsMaxTokens))
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get a list of NNS domains: %w", err)
|
||||
}
|
||||
if len(items) == nnsMaxTokens {
|
||||
cmd.PrintErrln("Provided RPC endpoint doesn't support sessions, some hashes might be lost.")
|
||||
}
|
||||
for i := range items {
|
||||
processItem(items[i])
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
for i := range arr {
|
||||
processItem(arr[i])
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = inv.TerminateSession(sessionID)
|
||||
}()
|
||||
|
||||
items, err := inv.TraverseIterator(sessionID, &iter, 0)
|
||||
for err == nil && len(items) != 0 {
|
||||
for i := range items {
|
||||
processItem(items[i])
|
||||
}
|
||||
items, err = inv.TraverseIterator(sessionID, &iter, 0)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("error during NNS domains iteration: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
fillContractVersion(cmd, c, infos)
|
||||
printContractInfo(cmd, infos)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseContractVersion(item stackitem.Item) string {
|
||||
bi, err := item.TryInteger()
|
||||
if err != nil || bi.Sign() == 0 || !bi.IsInt64() {
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
v := bi.Int64()
|
||||
major := v / 1_000_000
|
||||
minor := (v % 1_000_000) / 1000
|
||||
patch := v % 1_000
|
||||
return fmt.Sprintf("v%d.%d.%d", major, minor, patch)
|
||||
}
|
||||
|
||||
func printContractInfo(cmd *cobra.Command, infos []contractDumpInfo) {
|
||||
if len(infos) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
|
||||
for _, info := range infos {
|
||||
if info.version == "" {
|
||||
info.version = "unknown"
|
||||
}
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("%s\t(%s):\t%s\n",
|
||||
info.name, info.version, info.hash.StringLE())))
|
||||
}
|
||||
_ = tw.Flush()
|
||||
|
||||
cmd.Print(buf.String())
|
||||
}
|
||||
|
||||
func fillContractVersion(cmd *cobra.Command, c helper.Client, infos []contractDumpInfo) {
|
||||
bw := io.NewBufBinWriter()
|
||||
sub := io.NewBufBinWriter()
|
||||
for i := range infos {
|
||||
if infos[i].hash.Equals(util.Uint160{}) {
|
||||
emit.Int(bw.BinWriter, 0)
|
||||
} else {
|
||||
sub.Reset()
|
||||
emit.AppCall(sub.BinWriter, infos[i].hash, "version", callflag.NoneFlag)
|
||||
if sub.Err != nil {
|
||||
panic(fmt.Errorf("BUG: can't create version script: %w", bw.Err))
|
||||
}
|
||||
|
||||
script := sub.Bytes()
|
||||
emit.Instruction(bw.BinWriter, opcode.TRY, []byte{byte(3 + len(script) + 2), 0})
|
||||
bw.BinWriter.WriteBytes(script)
|
||||
emit.Instruction(bw.BinWriter, opcode.ENDTRY, []byte{2 + 1})
|
||||
emit.Opcodes(bw.BinWriter, opcode.PUSH0)
|
||||
}
|
||||
}
|
||||
emit.Opcodes(bw.BinWriter, opcode.NOP) // for the last ENDTRY target
|
||||
if bw.Err != nil {
|
||||
panic(fmt.Errorf("BUG: can't create version script: %w", bw.Err))
|
||||
}
|
||||
|
||||
res, err := c.InvokeScript(bw.Bytes(), nil)
|
||||
if err != nil {
|
||||
cmd.Printf("Can't fetch version from NNS: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if res.State == vmstate.Halt.String() {
|
||||
for i := range res.Stack {
|
||||
infos[i].version = parseContractVersion(res.Stack[i])
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
package contract
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
DumpHashesCmd = &cobra.Command{
|
||||
Use: "dump-hashes",
|
||||
Short: "Dump deployed contract hashes",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
RunE: dumpContractHashes,
|
||||
}
|
||||
UpdateCmd = &cobra.Command{
|
||||
Use: "update-contracts",
|
||||
Short: "Update FrostFS contracts",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
RunE: updateContracts,
|
||||
}
|
||||
)
|
||||
|
||||
func initDumpContractHashesCmd() {
|
||||
DumpHashesCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
DumpHashesCmd.Flags().String(commonflags.CustomZoneFlag, "", "Custom zone to search.")
|
||||
}
|
||||
|
||||
func initUpdateContractsCmd() {
|
||||
UpdateCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
UpdateCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
UpdateCmd.Flags().String(commonflags.ContractsInitFlag, "", commonflags.ContractsInitFlagDesc)
|
||||
UpdateCmd.Flags().String(commonflags.ContractsURLFlag, "", commonflags.ContractsURLFlagDesc)
|
||||
UpdateCmd.MarkFlagsMutuallyExclusive(commonflags.ContractsInitFlag, commonflags.ContractsURLFlag)
|
||||
}
|
||||
|
||||
func init() {
|
||||
initDumpContractHashesCmd()
|
||||
initUpdateContractsCmd()
|
||||
}
|
|
@ -1,197 +0,0 @@
|
|||
package contract
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
io2 "github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
neoUtil "github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var errMissingNNSRecord = errors.New("missing NNS record")
|
||||
|
||||
func updateContracts(cmd *cobra.Command, _ []string) error {
|
||||
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("initialization error: %w", err)
|
||||
}
|
||||
|
||||
if err := helper.DeployNNS(wCtx, constants.UpdateMethodName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return updateContractsInternal(wCtx)
|
||||
}
|
||||
|
||||
func updateContractsInternal(c *helper.InitializeContext) error {
|
||||
alphaCs := c.GetContract(constants.AlphabetContract)
|
||||
|
||||
nnsCs, err := c.NNSContractState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nnsHash := nnsCs.Hash
|
||||
|
||||
w := io2.NewBufBinWriter()
|
||||
|
||||
// Update script size for a single-node committee is close to the maximum allowed size of 65535.
|
||||
// Because of this we want to reuse alphabet contract NEF and manifest for different updates.
|
||||
// The generated script is as following.
|
||||
// 1. Initialize static slot for alphabet NEF.
|
||||
// 2. Store NEF into the static slot.
|
||||
// 3. Push parameters for each alphabet contract on stack.
|
||||
// 4. Add contract group to the manifest.
|
||||
// 5. For each alphabet contract, invoke `update` using parameters on stack and
|
||||
// NEF from step 2 and manifest from step 4.
|
||||
emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1})
|
||||
emit.Bytes(w.BinWriter, alphaCs.RawNEF)
|
||||
emit.Opcodes(w.BinWriter, opcode.STSFLD0)
|
||||
|
||||
keysParam, err := deployAlphabetAccounts(c, nnsHash, w, alphaCs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.Reset()
|
||||
|
||||
if err = deployOrUpdateContracts(c, w, nnsHash, keysParam); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
groupKey := c.ContractWallet.Accounts[0].PrivateKey().PublicKey()
|
||||
_, _, err = c.EmitUpdateNNSGroupScript(w, nnsHash, groupKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Command.Printf("NNS: Set %s -> %s\n", morphClient.NNSGroupKeyName, hex.EncodeToString(groupKey.Bytes()))
|
||||
|
||||
emit.Opcodes(w.BinWriter, opcode.LDSFLD0)
|
||||
emit.Int(w.BinWriter, 1)
|
||||
emit.Opcodes(w.BinWriter, opcode.PACK)
|
||||
emit.AppCallNoArgs(w.BinWriter, nnsHash, "setPrice", callflag.All)
|
||||
|
||||
if err := c.SendCommitteeTx(w.Bytes(), false); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.AwaitTx()
|
||||
}
|
||||
|
||||
func deployAlphabetAccounts(c *helper.InitializeContext, nnsHash neoUtil.Uint160, w *io2.BufBinWriter, alphaCs *helper.ContractState) ([]any, error) {
|
||||
var keysParam []any
|
||||
|
||||
baseGroups := alphaCs.Manifest.Groups
|
||||
|
||||
// alphabet contracts should be deployed by individual nodes to get different hashes.
|
||||
for i, acc := range c.Accounts {
|
||||
ctrHash, err := helper.NNSResolveHash(c.ReadOnlyInvoker, nnsHash, helper.GetAlphabetNNSDomain(i))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't resolve hash for contract update: %w", err)
|
||||
}
|
||||
|
||||
keysParam = append(keysParam, acc.PrivateKey().PublicKey().Bytes())
|
||||
|
||||
params := c.GetAlphabetDeployItems(i, len(c.Wallets))
|
||||
emit.Array(w.BinWriter, params...)
|
||||
|
||||
alphaCs.Manifest.Groups = baseGroups
|
||||
err = helper.AddManifestGroup(c.ContractWallet, ctrHash, alphaCs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't sign manifest group: %v", err)
|
||||
}
|
||||
|
||||
emit.Bytes(w.BinWriter, alphaCs.RawManifest)
|
||||
emit.Opcodes(w.BinWriter, opcode.LDSFLD0)
|
||||
emit.Int(w.BinWriter, 3)
|
||||
emit.Opcodes(w.BinWriter, opcode.PACK)
|
||||
emit.AppCallNoArgs(w.BinWriter, ctrHash, constants.UpdateMethodName, callflag.All)
|
||||
}
|
||||
if err := c.SendCommitteeTx(w.Bytes(), false); err != nil {
|
||||
if !strings.Contains(err.Error(), common.ErrAlreadyUpdated) {
|
||||
return nil, err
|
||||
}
|
||||
c.Command.Println("Alphabet contracts are already updated.")
|
||||
}
|
||||
|
||||
return keysParam, nil
|
||||
}
|
||||
|
||||
func deployOrUpdateContracts(c *helper.InitializeContext, w *io2.BufBinWriter, nnsHash neoUtil.Uint160, keysParam []any) error {
|
||||
emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1})
|
||||
emit.AppCall(w.BinWriter, nnsHash, "getPrice", callflag.All)
|
||||
emit.Opcodes(w.BinWriter, opcode.STSFLD0)
|
||||
emit.AppCall(w.BinWriter, nnsHash, "setPrice", callflag.All, 1)
|
||||
|
||||
for _, ctrName := range constants.ContractList {
|
||||
cs := c.GetContract(ctrName)
|
||||
|
||||
method := constants.UpdateMethodName
|
||||
ctrHash, err := helper.NNSResolveHash(c.ReadOnlyInvoker, nnsHash, helper.DomainOf(ctrName))
|
||||
if err != nil {
|
||||
if errors.Is(err, errMissingNNSRecord) {
|
||||
// if contract not found we deploy it instead of update
|
||||
method = constants.DeployMethodName
|
||||
} else {
|
||||
return fmt.Errorf("can't resolve hash for contract update: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = helper.AddManifestGroup(c.ContractWallet, ctrHash, cs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't sign manifest group: %v", err)
|
||||
}
|
||||
|
||||
invokeHash := management.Hash
|
||||
if method == constants.UpdateMethodName {
|
||||
invokeHash = ctrHash
|
||||
}
|
||||
|
||||
args, err := helper.GetContractDeployData(c, ctrName, keysParam, constants.UpdateMethodName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: getting update params: %v", ctrName, err)
|
||||
}
|
||||
params := helper.GetContractDeployParameters(cs, args)
|
||||
res, err := c.CommitteeAct.MakeCall(invokeHash, method, params...)
|
||||
if err != nil {
|
||||
if method != constants.UpdateMethodName || !strings.Contains(err.Error(), common.ErrAlreadyUpdated) {
|
||||
return fmt.Errorf("deploy contract: %w", err)
|
||||
}
|
||||
c.Command.Printf("%s contract is already updated.\n", ctrName)
|
||||
continue
|
||||
}
|
||||
|
||||
w.WriteBytes(res.Script)
|
||||
|
||||
if method == constants.DeployMethodName {
|
||||
// same actions are done in InitializeContext.setNNS, can be unified
|
||||
domain := ctrName + ".frostfs"
|
||||
script, ok, err := c.NNSRegisterDomainScript(nnsHash, cs.Hash, domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
w.WriteBytes(script)
|
||||
emit.AppCall(w.BinWriter, nnsHash, "deleteRecords", callflag.All, domain, int64(nns.TXT))
|
||||
emit.AppCall(w.BinWriter, nnsHash, "addRecord", callflag.All,
|
||||
domain, int64(nns.TXT), cs.Hash.StringLE())
|
||||
emit.AppCall(w.BinWriter, nnsHash, "addRecord", callflag.All,
|
||||
domain, int64(nns.TXT), address.Uint160ToString(cs.Hash))
|
||||
}
|
||||
c.Command.Printf("NNS: Set %s -> %s\n", domain, cs.Hash.StringLE())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,475 +0,0 @@
|
|||
package frostfsid
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
frostfsidclient "git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
namespaceFlag = "namespace"
|
||||
subjectNameFlag = "subject-name"
|
||||
subjectKeyFlag = "subject-key"
|
||||
subjectAddressFlag = "subject-address"
|
||||
includeNamesFlag = "include-names"
|
||||
groupNameFlag = "group-name"
|
||||
groupIDFlag = "group-id"
|
||||
|
||||
rootNamespacePlaceholder = "<root>"
|
||||
)
|
||||
|
||||
var (
|
||||
Cmd = &cobra.Command{
|
||||
Use: "frostfsid",
|
||||
Short: "Section for frostfsid interactions commands",
|
||||
}
|
||||
|
||||
frostfsidCreateNamespaceCmd = &cobra.Command{
|
||||
Use: "create-namespace",
|
||||
Short: "Create new namespace in frostfsid contract",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: frostfsidCreateNamespace,
|
||||
}
|
||||
|
||||
frostfsidListNamespacesCmd = &cobra.Command{
|
||||
Use: "list-namespaces",
|
||||
Short: "List all namespaces in frostfsid",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: frostfsidListNamespaces,
|
||||
}
|
||||
|
||||
frostfsidCreateSubjectCmd = &cobra.Command{
|
||||
Use: "create-subject",
|
||||
Short: "Create subject in frostfsid contract",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: frostfsidCreateSubject,
|
||||
}
|
||||
|
||||
frostfsidDeleteSubjectCmd = &cobra.Command{
|
||||
Use: "delete-subject",
|
||||
Short: "Delete subject from frostfsid contract",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: frostfsidDeleteSubject,
|
||||
}
|
||||
|
||||
frostfsidListSubjectsCmd = &cobra.Command{
|
||||
Use: "list-subjects",
|
||||
Short: "List subjects in namespace",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: frostfsidListSubjects,
|
||||
}
|
||||
|
||||
frostfsidCreateGroupCmd = &cobra.Command{
|
||||
Use: "create-group",
|
||||
Short: "Create group in frostfsid contract",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: frostfsidCreateGroup,
|
||||
}
|
||||
|
||||
frostfsidDeleteGroupCmd = &cobra.Command{
|
||||
Use: "delete-group",
|
||||
Short: "Delete group from frostfsid contract",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: frostfsidDeleteGroup,
|
||||
}
|
||||
|
||||
frostfsidListGroupsCmd = &cobra.Command{
|
||||
Use: "list-groups",
|
||||
Short: "List groups in namespace",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: frostfsidListGroups,
|
||||
}
|
||||
|
||||
frostfsidAddSubjectToGroupCmd = &cobra.Command{
|
||||
Use: "add-subject-to-group",
|
||||
Short: "Add subject to group",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: frostfsidAddSubjectToGroup,
|
||||
}
|
||||
|
||||
frostfsidRemoveSubjectFromGroupCmd = &cobra.Command{
|
||||
Use: "remove-subject-from-group",
|
||||
Short: "Remove subject from group",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: frostfsidRemoveSubjectFromGroup,
|
||||
}
|
||||
|
||||
frostfsidListGroupSubjectsCmd = &cobra.Command{
|
||||
Use: "list-group-subjects",
|
||||
Short: "List subjects in group",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: frostfsidListGroupSubjects,
|
||||
}
|
||||
)
|
||||
|
||||
func initFrostfsIDCreateNamespaceCmd() {
|
||||
Cmd.AddCommand(frostfsidCreateNamespaceCmd)
|
||||
frostfsidCreateNamespaceCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
frostfsidCreateNamespaceCmd.Flags().String(namespaceFlag, "", "Namespace name to create")
|
||||
frostfsidCreateNamespaceCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
_ = frostfsidCreateNamespaceCmd.MarkFlagRequired(namespaceFlag)
|
||||
}
|
||||
|
||||
func initFrostfsIDListNamespacesCmd() {
|
||||
Cmd.AddCommand(frostfsidListNamespacesCmd)
|
||||
frostfsidListNamespacesCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
frostfsidListNamespacesCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDCreateSubjectCmd() {
|
||||
Cmd.AddCommand(frostfsidCreateSubjectCmd)
|
||||
frostfsidCreateSubjectCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
frostfsidCreateSubjectCmd.Flags().String(namespaceFlag, "", "Namespace where create subject")
|
||||
frostfsidCreateSubjectCmd.Flags().String(subjectNameFlag, "", "Subject name, must be unique in namespace")
|
||||
frostfsidCreateSubjectCmd.Flags().String(subjectKeyFlag, "", "Subject hex-encoded public key")
|
||||
frostfsidCreateSubjectCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDDeleteSubjectCmd() {
|
||||
Cmd.AddCommand(frostfsidDeleteSubjectCmd)
|
||||
frostfsidDeleteSubjectCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
frostfsidDeleteSubjectCmd.Flags().String(subjectAddressFlag, "", "Subject address")
|
||||
frostfsidDeleteSubjectCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDListSubjectsCmd() {
|
||||
Cmd.AddCommand(frostfsidListSubjectsCmd)
|
||||
frostfsidListSubjectsCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
frostfsidListSubjectsCmd.Flags().String(namespaceFlag, "", "Namespace to list subjects")
|
||||
frostfsidListSubjectsCmd.Flags().Bool(includeNamesFlag, false, "Whether include subject name (require additional requests)")
|
||||
frostfsidListSubjectsCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDCreateGroupCmd() {
|
||||
Cmd.AddCommand(frostfsidCreateGroupCmd)
|
||||
frostfsidCreateGroupCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
frostfsidCreateGroupCmd.Flags().String(namespaceFlag, "", "Namespace where create group")
|
||||
frostfsidCreateGroupCmd.Flags().String(groupNameFlag, "", "Group name, must be unique in namespace")
|
||||
frostfsidCreateGroupCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDDeleteGroupCmd() {
|
||||
Cmd.AddCommand(frostfsidDeleteGroupCmd)
|
||||
frostfsidDeleteGroupCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
frostfsidDeleteGroupCmd.Flags().String(namespaceFlag, "", "Namespace to delete group")
|
||||
frostfsidDeleteGroupCmd.Flags().Int64(groupIDFlag, 0, "Group id")
|
||||
frostfsidDeleteGroupCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDListGroupsCmd() {
|
||||
Cmd.AddCommand(frostfsidListGroupsCmd)
|
||||
frostfsidListGroupsCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
frostfsidListGroupsCmd.Flags().String(namespaceFlag, "", "Namespace to list groups")
|
||||
frostfsidListGroupsCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDAddSubjectToGroupCmd() {
|
||||
Cmd.AddCommand(frostfsidAddSubjectToGroupCmd)
|
||||
frostfsidAddSubjectToGroupCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
frostfsidAddSubjectToGroupCmd.Flags().String(subjectAddressFlag, "", "Subject address")
|
||||
frostfsidAddSubjectToGroupCmd.Flags().Int64(groupIDFlag, 0, "Group id")
|
||||
frostfsidAddSubjectToGroupCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDRemoveSubjectFromGroupCmd() {
|
||||
Cmd.AddCommand(frostfsidRemoveSubjectFromGroupCmd)
|
||||
frostfsidRemoveSubjectFromGroupCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
frostfsidRemoveSubjectFromGroupCmd.Flags().String(subjectAddressFlag, "", "Subject address")
|
||||
frostfsidRemoveSubjectFromGroupCmd.Flags().Int64(groupIDFlag, 0, "Group id")
|
||||
frostfsidRemoveSubjectFromGroupCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initFrostfsIDListGroupSubjectsCmd() {
|
||||
Cmd.AddCommand(frostfsidListGroupSubjectsCmd)
|
||||
frostfsidListGroupSubjectsCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
frostfsidListGroupSubjectsCmd.Flags().String(namespaceFlag, "", "Namespace name")
|
||||
frostfsidListGroupSubjectsCmd.Flags().Int64(groupIDFlag, 0, "Group id")
|
||||
frostfsidListGroupSubjectsCmd.Flags().Bool(includeNamesFlag, false, "Whether include subject name (require additional requests)")
|
||||
frostfsidListGroupSubjectsCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func frostfsidCreateNamespace(cmd *cobra.Command, _ []string) {
|
||||
ns := getFrostfsIDNamespace(cmd)
|
||||
|
||||
ffsid, err := newFrostfsIDClient(cmd)
|
||||
commonCmd.ExitOnErr(cmd, "init contract client: %w", err)
|
||||
|
||||
ffsid.addCall(ffsid.roCli.CreateNamespaceCall(ns))
|
||||
|
||||
err = ffsid.sendWait()
|
||||
commonCmd.ExitOnErr(cmd, "create namespace error: %w", err)
|
||||
}
|
||||
|
||||
func frostfsidListNamespaces(cmd *cobra.Command, _ []string) {
|
||||
ffsid, err := newFrostfsIDClient(cmd)
|
||||
commonCmd.ExitOnErr(cmd, "init contract invoker: %w", err)
|
||||
|
||||
namespaces, err := ffsid.roCli.ListNamespaces()
|
||||
commonCmd.ExitOnErr(cmd, "list namespaces: %w", err)
|
||||
|
||||
sort.Slice(namespaces, func(i, j int) bool { return namespaces[i].Name < namespaces[j].Name })
|
||||
|
||||
for _, namespace := range namespaces {
|
||||
if namespace.Name == "" {
|
||||
namespace.Name = rootNamespacePlaceholder
|
||||
}
|
||||
cmd.Printf("%s\n", namespace.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func frostfsidCreateSubject(cmd *cobra.Command, _ []string) {
|
||||
ns := getFrostfsIDNamespace(cmd)
|
||||
subjName := getFrostfsIDSubjectName(cmd)
|
||||
subjKey := getFrostfsIDSubjectKey(cmd)
|
||||
|
||||
ffsid, err := newFrostfsIDClient(cmd)
|
||||
commonCmd.ExitOnErr(cmd, "init contract client: %w", err)
|
||||
|
||||
ffsid.addCall(ffsid.roCli.CreateSubjectCall(ns, subjKey))
|
||||
if subjName != "" {
|
||||
ffsid.addCall(ffsid.roCli.SetSubjectNameCall(subjKey.GetScriptHash(), subjName))
|
||||
}
|
||||
|
||||
err = ffsid.sendWait()
|
||||
commonCmd.ExitOnErr(cmd, "create subject: %w", err)
|
||||
}
|
||||
|
||||
func frostfsidDeleteSubject(cmd *cobra.Command, _ []string) {
|
||||
subjectAddress := getFrostfsIDSubjectAddress(cmd)
|
||||
|
||||
ffsid, err := newFrostfsIDClient(cmd)
|
||||
commonCmd.ExitOnErr(cmd, "init contract client: %w", err)
|
||||
|
||||
ffsid.addCall(ffsid.roCli.DeleteSubjectCall(subjectAddress))
|
||||
|
||||
err = ffsid.sendWait()
|
||||
commonCmd.ExitOnErr(cmd, "delete subject error: %w", err)
|
||||
}
|
||||
|
||||
func frostfsidListSubjects(cmd *cobra.Command, _ []string) {
|
||||
ns := getFrostfsIDNamespace(cmd)
|
||||
includeNames, _ := cmd.Flags().GetBool(includeNamesFlag)
|
||||
|
||||
ffsid, err := newFrostfsIDClient(cmd)
|
||||
commonCmd.ExitOnErr(cmd, "init contract invoker: %w", err)
|
||||
|
||||
subAddresses, err := ffsid.roCli.ListNamespaceSubjects(ns)
|
||||
commonCmd.ExitOnErr(cmd, "list subjects: %w", err)
|
||||
|
||||
sort.Slice(subAddresses, func(i, j int) bool { return subAddresses[i].Less(subAddresses[j]) })
|
||||
|
||||
for _, addr := range subAddresses {
|
||||
if !includeNames {
|
||||
cmd.Println(address.Uint160ToString(addr))
|
||||
continue
|
||||
}
|
||||
|
||||
subj, err := ffsid.roCli.GetSubject(addr)
|
||||
commonCmd.ExitOnErr(cmd, "get subject: %w", err)
|
||||
|
||||
cmd.Printf("%s (%s)\n", address.Uint160ToString(addr), subj.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func frostfsidCreateGroup(cmd *cobra.Command, _ []string) {
|
||||
ns := getFrostfsIDNamespace(cmd)
|
||||
groupName := getFrostfsIDGroupName(cmd)
|
||||
|
||||
ffsid, err := newFrostfsIDClient(cmd)
|
||||
commonCmd.ExitOnErr(cmd, "init contract client: %w", err)
|
||||
|
||||
ffsid.addCall(ffsid.roCli.CreateGroupCall(ns, groupName))
|
||||
|
||||
groupID, err := ffsid.roCli.ParseGroupID(ffsid.sendWaitRes())
|
||||
commonCmd.ExitOnErr(cmd, "create group: %w", err)
|
||||
|
||||
cmd.Printf("group '%s' created with id: %d\n", groupName, groupID)
|
||||
}
|
||||
|
||||
func frostfsidDeleteGroup(cmd *cobra.Command, _ []string) {
|
||||
ns := getFrostfsIDNamespace(cmd)
|
||||
groupID := getFrostfsIDGroupID(cmd)
|
||||
|
||||
ffsid, err := newFrostfsIDClient(cmd)
|
||||
commonCmd.ExitOnErr(cmd, "init contract client: %w", err)
|
||||
|
||||
ffsid.addCall(ffsid.roCli.DeleteGroupCall(ns, groupID))
|
||||
|
||||
err = ffsid.sendWait()
|
||||
commonCmd.ExitOnErr(cmd, "delete group error: %w", err)
|
||||
}
|
||||
|
||||
func frostfsidListGroups(cmd *cobra.Command, _ []string) {
|
||||
ns := getFrostfsIDNamespace(cmd)
|
||||
|
||||
ffsid, err := newFrostfsIDClient(cmd)
|
||||
commonCmd.ExitOnErr(cmd, "init contract invoker: %w", err)
|
||||
|
||||
groups, err := ffsid.roCli.ListGroups(ns)
|
||||
commonCmd.ExitOnErr(cmd, "list groups: %w", err)
|
||||
|
||||
sort.Slice(groups, func(i, j int) bool { return groups[i].Name < groups[j].Name })
|
||||
|
||||
for _, group := range groups {
|
||||
cmd.Printf("%s (%d)\n", group.Name, group.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func frostfsidAddSubjectToGroup(cmd *cobra.Command, _ []string) {
|
||||
subjectAddress := getFrostfsIDSubjectAddress(cmd)
|
||||
groupID := getFrostfsIDGroupID(cmd)
|
||||
|
||||
ffsid, err := newFrostfsIDClient(cmd)
|
||||
commonCmd.ExitOnErr(cmd, "init contract client: %w", err)
|
||||
|
||||
ffsid.addCall(ffsid.roCli.AddSubjectToGroupCall(subjectAddress, groupID))
|
||||
|
||||
err = ffsid.sendWait()
|
||||
commonCmd.ExitOnErr(cmd, "add subject to group error: %w", err)
|
||||
}
|
||||
|
||||
func frostfsidRemoveSubjectFromGroup(cmd *cobra.Command, _ []string) {
|
||||
subjectAddress := getFrostfsIDSubjectAddress(cmd)
|
||||
groupID := getFrostfsIDGroupID(cmd)
|
||||
|
||||
ffsid, err := newFrostfsIDClient(cmd)
|
||||
commonCmd.ExitOnErr(cmd, "init contract client: %w", err)
|
||||
|
||||
ffsid.addCall(ffsid.roCli.RemoveSubjectFromGroupCall(subjectAddress, groupID))
|
||||
|
||||
err = ffsid.sendWait()
|
||||
commonCmd.ExitOnErr(cmd, "remove subject from group error: %w", err)
|
||||
}
|
||||
|
||||
func frostfsidListGroupSubjects(cmd *cobra.Command, _ []string) {
|
||||
ns := getFrostfsIDNamespace(cmd)
|
||||
groupID := getFrostfsIDGroupID(cmd)
|
||||
includeNames, _ := cmd.Flags().GetBool(includeNamesFlag)
|
||||
|
||||
ffsid, err := newFrostfsIDClient(cmd)
|
||||
commonCmd.ExitOnErr(cmd, "init contract client: %w", err)
|
||||
|
||||
subjects, err := ffsid.roCli.ListGroupSubjects(ns, groupID)
|
||||
commonCmd.ExitOnErr(cmd, "list group subjects: %w", err)
|
||||
|
||||
sort.Slice(subjects, func(i, j int) bool { return subjects[i].Less(subjects[j]) })
|
||||
|
||||
for _, subjAddr := range subjects {
|
||||
if !includeNames {
|
||||
cmd.Println(address.Uint160ToString(subjAddr))
|
||||
continue
|
||||
}
|
||||
|
||||
subj, err := ffsid.roCli.GetSubject(subjAddr)
|
||||
commonCmd.ExitOnErr(cmd, "get subject: %w", err)
|
||||
|
||||
cmd.Printf("%s (%s)\n", address.Uint160ToString(subjAddr), subj.Name)
|
||||
}
|
||||
}
|
||||
|
||||
type frostfsidClient struct {
|
||||
bw *io.BufBinWriter
|
||||
contractHash util.Uint160
|
||||
roCli *frostfsidclient.Client // client can be used only for waiting tx, parsing and forming method params
|
||||
wCtx *helper.InitializeContext
|
||||
}
|
||||
|
||||
func newFrostfsIDClient(cmd *cobra.Command) (*frostfsidClient, error) {
|
||||
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't to initialize context: %w", err)
|
||||
}
|
||||
|
||||
r := management.NewReader(wCtx.ReadOnlyInvoker)
|
||||
cs, err := r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't get NNS contract info: %w", err)
|
||||
}
|
||||
|
||||
ffsidHash, err := helper.NNSResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, helper.DomainOf(constants.FrostfsIDContract))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't get proxy contract hash: %w", err)
|
||||
}
|
||||
|
||||
return &frostfsidClient{
|
||||
bw: io.NewBufBinWriter(),
|
||||
contractHash: ffsidHash,
|
||||
roCli: frostfsidclient.NewSimple(wCtx.CommitteeAct, ffsidHash),
|
||||
wCtx: wCtx,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *frostfsidClient) addCall(method string, args []any) {
|
||||
emit.AppCall(f.bw.BinWriter, f.contractHash, method, callflag.All, args...)
|
||||
}
|
||||
|
||||
func (f *frostfsidClient) sendWait() error {
|
||||
if err := f.wCtx.SendConsensusTx(f.bw.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
f.bw.Reset()
|
||||
|
||||
return f.wCtx.AwaitTx()
|
||||
}
|
||||
|
||||
func (f *frostfsidClient) sendWaitRes() (*state.AppExecResult, error) {
|
||||
if err := f.wCtx.SendConsensusTx(f.bw.Bytes()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.bw.Reset()
|
||||
|
||||
if len(f.wCtx.SentTxs) == 0 {
|
||||
return nil, errors.New("no transactions to wait")
|
||||
}
|
||||
|
||||
f.wCtx.Command.Println("Waiting for transactions to persist...")
|
||||
return f.roCli.Wait(f.wCtx.SentTxs[0].Hash, f.wCtx.SentTxs[0].Vub, nil)
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
package frostfsid
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/ape"
|
||||
"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/util"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func getFrostfsIDSubjectKey(cmd *cobra.Command) *keys.PublicKey {
|
||||
subjKeyHex, _ := cmd.Flags().GetString(subjectKeyFlag)
|
||||
subjKey, err := keys.NewPublicKeyFromString(subjKeyHex)
|
||||
commonCmd.ExitOnErr(cmd, "invalid subject key: %w", err)
|
||||
return subjKey
|
||||
}
|
||||
|
||||
func getFrostfsIDSubjectAddress(cmd *cobra.Command) util.Uint160 {
|
||||
subjAddress, _ := cmd.Flags().GetString(subjectAddressFlag)
|
||||
subjAddr, err := address.StringToUint160(subjAddress)
|
||||
commonCmd.ExitOnErr(cmd, "invalid subject address: %w", err)
|
||||
return subjAddr
|
||||
}
|
||||
|
||||
func getFrostfsIDSubjectName(cmd *cobra.Command) string {
|
||||
subjectName, _ := cmd.Flags().GetString(subjectNameFlag)
|
||||
|
||||
if subjectName == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
if !ape.SubjectNameRegexp.MatchString(subjectName) {
|
||||
commonCmd.ExitOnErr(cmd, "invalid subject name: %w",
|
||||
fmt.Errorf("name must match regexp: %s", ape.SubjectNameRegexp.String()))
|
||||
}
|
||||
|
||||
return subjectName
|
||||
}
|
||||
|
||||
func getFrostfsIDGroupName(cmd *cobra.Command) string {
|
||||
groupName, _ := cmd.Flags().GetString(groupNameFlag)
|
||||
|
||||
if !ape.GroupNameRegexp.MatchString(groupName) {
|
||||
commonCmd.ExitOnErr(cmd, "invalid group name: %w",
|
||||
fmt.Errorf("name must match regexp: %s", ape.GroupNameRegexp.String()))
|
||||
}
|
||||
|
||||
return groupName
|
||||
}
|
||||
|
||||
func getFrostfsIDGroupID(cmd *cobra.Command) int64 {
|
||||
groupID, _ := cmd.Flags().GetInt64(groupIDFlag)
|
||||
if groupID <= 0 {
|
||||
commonCmd.ExitOnErr(cmd, "invalid group id: %w",
|
||||
errors.New("group id must be positive integer"))
|
||||
}
|
||||
|
||||
return groupID
|
||||
}
|
||||
|
||||
func getFrostfsIDNamespace(cmd *cobra.Command) string {
|
||||
ns, _ := cmd.Flags().GetString(namespaceFlag)
|
||||
if ns == rootNamespacePlaceholder {
|
||||
ns = ""
|
||||
}
|
||||
|
||||
if !ape.NamespaceNameRegexp.MatchString(ns) {
|
||||
commonCmd.ExitOnErr(cmd, "invalid namespace: %w",
|
||||
fmt.Errorf("name must match regexp: %s", ape.NamespaceNameRegexp.String()))
|
||||
}
|
||||
|
||||
return ns
|
||||
}
|
|
@ -1,174 +0,0 @@
|
|||
package frostfsid
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/ape"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFrostfsIDConfig(t *testing.T) {
|
||||
pks := make([]*keys.PrivateKey, 4)
|
||||
for i := range pks {
|
||||
pk, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
pks[i] = pk
|
||||
}
|
||||
|
||||
fmts := []string{
|
||||
pks[0].GetScriptHash().StringLE(),
|
||||
address.Uint160ToString(pks[1].GetScriptHash()),
|
||||
hex.EncodeToString(pks[2].PublicKey().UncompressedBytes()),
|
||||
hex.EncodeToString(pks[3].PublicKey().Bytes()),
|
||||
}
|
||||
|
||||
for i := range fmts {
|
||||
v := viper.New()
|
||||
v.Set("frostfsid.admin", fmts[i])
|
||||
|
||||
actual, found, err := helper.GetFrostfsIDAdmin(v)
|
||||
require.NoError(t, err)
|
||||
require.True(t, found)
|
||||
require.Equal(t, pks[i].GetScriptHash(), actual)
|
||||
}
|
||||
|
||||
t.Run("bad key", func(t *testing.T) {
|
||||
v := viper.New()
|
||||
v.Set("frostfsid.admin", "abc")
|
||||
|
||||
_, found, err := helper.GetFrostfsIDAdmin(v)
|
||||
require.Error(t, err)
|
||||
require.True(t, found)
|
||||
})
|
||||
t.Run("missing key", func(t *testing.T) {
|
||||
v := viper.New()
|
||||
|
||||
_, found, err := helper.GetFrostfsIDAdmin(v)
|
||||
require.NoError(t, err)
|
||||
require.False(t, found)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNamespaceRegexp(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
namespace string
|
||||
matched bool
|
||||
}{
|
||||
{
|
||||
name: "root empty ns",
|
||||
namespace: "",
|
||||
matched: true,
|
||||
},
|
||||
{
|
||||
name: "simple valid ns",
|
||||
namespace: "my-namespace-123",
|
||||
matched: true,
|
||||
},
|
||||
{
|
||||
name: "root placeholder",
|
||||
namespace: "<root>",
|
||||
matched: false,
|
||||
},
|
||||
{
|
||||
name: "too long",
|
||||
namespace: "abcdefghijklmnopkrstuvwxyzabcdefghijklmnopkrstuvwxyz",
|
||||
matched: false,
|
||||
},
|
||||
{
|
||||
name: "start with hyphen",
|
||||
namespace: "-ns",
|
||||
matched: false,
|
||||
},
|
||||
{
|
||||
name: "end with hyphen",
|
||||
namespace: "ns-",
|
||||
matched: false,
|
||||
},
|
||||
{
|
||||
name: "with spaces",
|
||||
namespace: "ns ns",
|
||||
matched: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require.Equal(t, tc.matched, ape.NamespaceNameRegexp.MatchString(tc.namespace))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubjectNameRegexp(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
subject string
|
||||
matched bool
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
subject: "",
|
||||
matched: false,
|
||||
},
|
||||
{
|
||||
name: "invalid",
|
||||
subject: "invalid{name}",
|
||||
matched: false,
|
||||
},
|
||||
{
|
||||
name: "too long",
|
||||
subject: "abcdefghijklmnopkrstuvwxyzabcdefghijklmnopkrstuvwxyzabcdefghijklmnopkrstuvwxyz",
|
||||
matched: false,
|
||||
},
|
||||
{
|
||||
name: "valid",
|
||||
subject: "valid_name.012345@6789",
|
||||
matched: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require.Equal(t, tc.matched, ape.SubjectNameRegexp.MatchString(tc.subject))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubjectGroupRegexp(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
subject string
|
||||
matched bool
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
subject: "",
|
||||
matched: false,
|
||||
},
|
||||
{
|
||||
name: "invalid",
|
||||
subject: "invalid{name}",
|
||||
matched: false,
|
||||
},
|
||||
{
|
||||
name: "too long",
|
||||
subject: "abcdefghijklmnopkrstuvwxyzabcdefghijklmnopkrstuvwxyzabcdefghijklmnopkrstuvwxyzabcdefghijklmnopkrstuvwxyzabcdefghijklmnopkrstuvwxyz",
|
||||
matched: false,
|
||||
},
|
||||
{
|
||||
name: "long",
|
||||
subject: "abcdefghijklmnopkrstuvwxyzabcdefghijklmnopkrstuvwxyzabcdefghijklmnopkrstuvwxyz",
|
||||
matched: true,
|
||||
},
|
||||
{
|
||||
name: "valid",
|
||||
subject: "valid_name.012345@6789",
|
||||
matched: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require.Equal(t, tc.matched, ape.GroupNameRegexp.MatchString(tc.subject))
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package frostfsid
|
||||
|
||||
func init() {
|
||||
initFrostfsIDCreateNamespaceCmd()
|
||||
initFrostfsIDListNamespacesCmd()
|
||||
initFrostfsIDCreateSubjectCmd()
|
||||
initFrostfsIDDeleteSubjectCmd()
|
||||
initFrostfsIDListSubjectsCmd()
|
||||
initFrostfsIDCreateGroupCmd()
|
||||
initFrostfsIDDeleteGroupCmd()
|
||||
initFrostfsIDListGroupsCmd()
|
||||
initFrostfsIDAddSubjectToGroupCmd()
|
||||
initFrostfsIDRemoveSubjectFromGroupCmd()
|
||||
initFrostfsIDListGroupSubjectsCmd()
|
||||
}
|
|
@ -1,217 +0,0 @@
|
|||
package generate
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
func AlphabetCreds(cmd *cobra.Command, _ []string) error {
|
||||
// alphabet size is not part of the config
|
||||
size, err := cmd.Flags().GetUint(commonflags.AlphabetSizeFlag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if size == 0 {
|
||||
return errors.New("size must be > 0")
|
||||
}
|
||||
if size > constants.MaxAlphabetNodes {
|
||||
return helper.ErrTooManyAlphabetNodes
|
||||
}
|
||||
|
||||
v := viper.GetViper()
|
||||
walletDir := config.ResolveHomePath(viper.GetString(commonflags.AlphabetWalletsFlag))
|
||||
pwds, err := initializeWallets(v, walletDir, int(size))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = helper.InitializeContractWallet(v, walletDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Println("size:", size)
|
||||
cmd.Println("alphabet-wallets:", walletDir)
|
||||
for i := range pwds {
|
||||
cmd.Printf("wallet[%d]: %s\n", i, pwds[i])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func initializeWallets(v *viper.Viper, walletDir string, size int) ([]string, error) {
|
||||
wallets := make([]*wallet.Wallet, size)
|
||||
pubs := make(keys.PublicKeys, size)
|
||||
passwords := make([]string, size)
|
||||
|
||||
for i := range wallets {
|
||||
password, err := config.GetPassword(v, innerring.GlagoliticLetter(i).String())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't fetch password: %w", err)
|
||||
}
|
||||
|
||||
p := filepath.Join(walletDir, innerring.GlagoliticLetter(i).String()+".json")
|
||||
f, err := os.OpenFile(p, os.O_CREATE, 0o644)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't create wallet file: %w", err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
return nil, fmt.Errorf("can't close wallet file: %w", err)
|
||||
}
|
||||
w, err := wallet.NewWallet(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't create wallet: %w", err)
|
||||
}
|
||||
if err := w.CreateAccount(constants.SingleAccountName, password); err != nil {
|
||||
return nil, fmt.Errorf("can't create account: %w", err)
|
||||
}
|
||||
|
||||
passwords[i] = password
|
||||
wallets[i] = w
|
||||
pubs[i] = w.Accounts[0].PrivateKey().PublicKey()
|
||||
}
|
||||
|
||||
var errG errgroup.Group
|
||||
|
||||
// Create committee account with N/2+1 multi-signature.
|
||||
majCount := smartcontract.GetMajorityHonestNodeCount(size)
|
||||
// Create consensus account with 2*N/3+1 multi-signature.
|
||||
bftCount := smartcontract.GetDefaultHonestNodeCount(size)
|
||||
for i := range wallets {
|
||||
i := i
|
||||
ps := pubs.Copy()
|
||||
errG.Go(func() error {
|
||||
if err := addMultisigAccount(wallets[i], majCount, constants.CommitteeAccountName, passwords[i], ps); err != nil {
|
||||
return fmt.Errorf("can't create committee account: %w", err)
|
||||
}
|
||||
if err := addMultisigAccount(wallets[i], bftCount, constants.ConsensusAccountName, passwords[i], ps); err != nil {
|
||||
return fmt.Errorf("can't create consentus account: %w", err)
|
||||
}
|
||||
if err := wallets[i].SavePretty(); err != nil {
|
||||
return fmt.Errorf("can't save wallet: %w", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if err := errG.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return passwords, nil
|
||||
}
|
||||
|
||||
func addMultisigAccount(w *wallet.Wallet, m int, name, password string, pubs keys.PublicKeys) error {
|
||||
acc := wallet.NewAccountFromPrivateKey(w.Accounts[0].PrivateKey())
|
||||
acc.Label = name
|
||||
|
||||
if err := acc.ConvertMultisig(m, pubs); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := acc.Encrypt(password, keys.NEP2ScryptParams()); err != nil {
|
||||
return err
|
||||
}
|
||||
w.AddAccount(acc)
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateStorageCreds(cmd *cobra.Command, _ []string) error {
|
||||
return refillGas(cmd, storageGasConfigFlag, true)
|
||||
}
|
||||
|
||||
func refillGas(cmd *cobra.Command, gasFlag string, createWallet bool) (err error) {
|
||||
// storage wallet path is not part of the config
|
||||
storageWalletPath, _ := cmd.Flags().GetString(commonflags.StorageWalletFlag)
|
||||
// wallet address is not part of the config
|
||||
walletAddress, _ := cmd.Flags().GetString(walletAddressFlag)
|
||||
|
||||
var gasReceiver util.Uint160
|
||||
|
||||
if len(walletAddress) != 0 {
|
||||
gasReceiver, err = address.StringToUint160(walletAddress)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid wallet address %s: %w", walletAddress, err)
|
||||
}
|
||||
} else {
|
||||
if storageWalletPath == "" {
|
||||
return fmt.Errorf("missing wallet path (use '--%s <out.json>')", commonflags.StorageWalletFlag)
|
||||
}
|
||||
|
||||
var w *wallet.Wallet
|
||||
|
||||
if createWallet {
|
||||
w, err = wallet.NewWallet(storageWalletPath)
|
||||
} else {
|
||||
w, err = wallet.NewWalletFromFile(storageWalletPath)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create wallet: %w", err)
|
||||
}
|
||||
|
||||
if createWallet {
|
||||
var password string
|
||||
|
||||
label, _ := cmd.Flags().GetString(storageWalletLabelFlag)
|
||||
password, err := config.GetStoragePassword(viper.GetViper(), label)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch password: %w", err)
|
||||
}
|
||||
|
||||
if label == "" {
|
||||
label = constants.SingleAccountName
|
||||
}
|
||||
|
||||
if err := w.CreateAccount(label, password); err != nil {
|
||||
return fmt.Errorf("can't create account: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
gasReceiver = w.Accounts[0].Contract.ScriptHash()
|
||||
}
|
||||
|
||||
gasStr := viper.GetString(gasFlag)
|
||||
|
||||
gasAmount, err := helper.ParseGASAmount(gasStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bw := io.NewBufBinWriter()
|
||||
emit.AppCall(bw.BinWriter, gas.Hash, "transfer", callflag.All,
|
||||
wCtx.CommitteeAcc.Contract.ScriptHash(), gasReceiver, int64(gasAmount), nil)
|
||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||
if bw.Err != nil {
|
||||
return fmt.Errorf("BUG: invalid transfer arguments: %w", bw.Err)
|
||||
}
|
||||
|
||||
if err := wCtx.SendCommitteeTx(bw.Bytes(), false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return wCtx.AwaitTx()
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
package generate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||
"github.com/nspcc-dev/neo-go/cli/input"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
func TestGenerateAlphabet(t *testing.T) {
|
||||
const size = 4
|
||||
|
||||
walletDir := t.TempDir()
|
||||
buf := setupTestTerminal(t)
|
||||
|
||||
cmd := GenerateAlphabetCmd
|
||||
v := viper.GetViper()
|
||||
|
||||
t.Run("zero size", func(t *testing.T) {
|
||||
buf.Reset()
|
||||
v.Set(commonflags.AlphabetWalletsFlag, walletDir)
|
||||
require.NoError(t, cmd.Flags().Set(commonflags.AlphabetSizeFlag, "0"))
|
||||
buf.WriteString("pass\r")
|
||||
require.Error(t, AlphabetCreds(cmd, nil))
|
||||
})
|
||||
t.Run("no password provided", func(t *testing.T) {
|
||||
buf.Reset()
|
||||
v.Set(commonflags.AlphabetWalletsFlag, walletDir)
|
||||
require.NoError(t, cmd.Flags().Set(commonflags.AlphabetSizeFlag, "1"))
|
||||
require.Error(t, AlphabetCreds(cmd, nil))
|
||||
})
|
||||
t.Run("missing directory", func(t *testing.T) {
|
||||
buf.Reset()
|
||||
dir := filepath.Join(os.TempDir(), "notexist."+strconv.FormatUint(rand.Uint64(), 10))
|
||||
v.Set(commonflags.AlphabetWalletsFlag, dir)
|
||||
require.NoError(t, cmd.Flags().Set(commonflags.AlphabetSizeFlag, "1"))
|
||||
buf.WriteString("pass\r")
|
||||
require.Error(t, AlphabetCreds(cmd, nil))
|
||||
})
|
||||
t.Run("no password for contract group wallet", func(t *testing.T) {
|
||||
buf.Reset()
|
||||
v.Set(commonflags.AlphabetWalletsFlag, walletDir)
|
||||
require.NoError(t, cmd.Flags().Set(commonflags.AlphabetSizeFlag, strconv.FormatUint(size, 10)))
|
||||
for i := uint64(0); i < size; i++ {
|
||||
buf.WriteString(strconv.FormatUint(i, 10) + "\r")
|
||||
}
|
||||
require.Error(t, AlphabetCreds(cmd, nil))
|
||||
})
|
||||
|
||||
buf.Reset()
|
||||
v.Set(commonflags.AlphabetWalletsFlag, walletDir)
|
||||
require.NoError(t, GenerateAlphabetCmd.Flags().Set(commonflags.AlphabetSizeFlag, strconv.FormatUint(size, 10)))
|
||||
for i := uint64(0); i < size; i++ {
|
||||
buf.WriteString(strconv.FormatUint(i, 10) + "\r")
|
||||
}
|
||||
|
||||
buf.WriteString(constants.TestContractPassword + "\r")
|
||||
require.NoError(t, AlphabetCreds(GenerateAlphabetCmd, nil))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := uint64(0); i < size; i++ {
|
||||
i := i
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
p := filepath.Join(walletDir, innerring.GlagoliticLetter(i).String()+".json")
|
||||
w, err := wallet.NewWalletFromFile(p)
|
||||
require.NoError(t, err, "wallet doesn't exist")
|
||||
require.Equal(t, 3, len(w.Accounts), "not all accounts were created")
|
||||
|
||||
for _, a := range w.Accounts {
|
||||
err := a.Decrypt(strconv.FormatUint(i, 10), keys.NEP2ScryptParams())
|
||||
require.NoError(t, err, "can't decrypt account")
|
||||
switch a.Label {
|
||||
case constants.ConsensusAccountName:
|
||||
require.Equal(t, smartcontract.GetDefaultHonestNodeCount(size), len(a.Contract.Parameters))
|
||||
case constants.CommitteeAccountName:
|
||||
require.Equal(t, smartcontract.GetMajorityHonestNodeCount(size), len(a.Contract.Parameters))
|
||||
default:
|
||||
require.Equal(t, constants.SingleAccountName, a.Label)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
t.Run("check contract group wallet", func(t *testing.T) {
|
||||
p := filepath.Join(walletDir, constants.ContractWalletFilename)
|
||||
w, err := wallet.NewWalletFromFile(p)
|
||||
require.NoError(t, err, "contract wallet doesn't exist")
|
||||
require.Equal(t, 1, len(w.Accounts), "contract wallet must have 1 accout")
|
||||
require.NoError(t, w.Accounts[0].Decrypt(constants.TestContractPassword, keys.NEP2ScryptParams()))
|
||||
})
|
||||
}
|
||||
|
||||
func setupTestTerminal(t *testing.T) *bytes.Buffer {
|
||||
in := bytes.NewBuffer(nil)
|
||||
input.Terminal = term.NewTerminal(input.ReadWriter{
|
||||
Reader: in,
|
||||
Writer: io.Discard,
|
||||
}, "")
|
||||
|
||||
t.Cleanup(func() { input.Terminal = nil })
|
||||
|
||||
return in
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
package generate
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
storageWalletLabelFlag = "label"
|
||||
storageGasCLIFlag = "initial-gas"
|
||||
storageGasConfigFlag = "storage.initial_gas"
|
||||
walletAddressFlag = "wallet-address"
|
||||
)
|
||||
|
||||
var (
|
||||
GenerateStorageCmd = &cobra.Command{
|
||||
Use: "generate-storage-wallet",
|
||||
Short: "Generate storage node wallet for the morph network",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
_ = viper.BindPFlag(storageGasConfigFlag, cmd.Flags().Lookup(storageGasCLIFlag))
|
||||
},
|
||||
RunE: generateStorageCreds,
|
||||
}
|
||||
RefillGasCmd = &cobra.Command{
|
||||
Use: "refill-gas",
|
||||
Short: "Refill GAS of storage node's wallet in the morph network",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.RefillGasAmountFlag, cmd.Flags().Lookup(commonflags.RefillGasAmountFlag))
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
return refillGas(cmd, commonflags.RefillGasAmountFlag, false)
|
||||
},
|
||||
}
|
||||
GenerateAlphabetCmd = &cobra.Command{
|
||||
Use: "generate-alphabet",
|
||||
Short: "Generate alphabet wallets for consensus nodes of the morph network",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
// PreRun fixes https://github.com/spf13/viper/issues/233
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
},
|
||||
RunE: AlphabetCreds,
|
||||
}
|
||||
)
|
||||
|
||||
func initRefillGasCmd() {
|
||||
RefillGasCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
RefillGasCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
RefillGasCmd.Flags().String(commonflags.StorageWalletFlag, "", "Path to storage node wallet")
|
||||
RefillGasCmd.Flags().String(walletAddressFlag, "", "Address of wallet")
|
||||
RefillGasCmd.Flags().String(commonflags.RefillGasAmountFlag, "", "Additional amount of GAS to transfer")
|
||||
RefillGasCmd.MarkFlagsMutuallyExclusive(walletAddressFlag, commonflags.StorageWalletFlag)
|
||||
}
|
||||
|
||||
func initGenerateStorageCmd() {
|
||||
GenerateStorageCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
GenerateStorageCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
GenerateStorageCmd.Flags().String(commonflags.StorageWalletFlag, "", "Path to new storage node wallet")
|
||||
GenerateStorageCmd.Flags().String(storageGasCLIFlag, "", "Initial amount of GAS to transfer")
|
||||
GenerateStorageCmd.Flags().StringP(storageWalletLabelFlag, "l", "", "Wallet label")
|
||||
}
|
||||
|
||||
func initGenerateAlphabetCmd() {
|
||||
GenerateAlphabetCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
GenerateAlphabetCmd.Flags().Uint(commonflags.AlphabetSizeFlag, 7, "Amount of alphabet wallets to generate")
|
||||
}
|
||||
|
||||
func init() {
|
||||
initRefillGasCmd()
|
||||
initGenerateStorageCmd()
|
||||
initGenerateAlphabetCmd()
|
||||
}
|
|
@ -1,169 +0,0 @@
|
|||
package helper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/google/uuid"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/context"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// LocalActor is a kludge, do not use it outside of the morph commands.
|
||||
type LocalActor struct {
|
||||
neoActor *actor.Actor
|
||||
accounts []*wallet.Account
|
||||
Invoker *invoker.Invoker
|
||||
}
|
||||
|
||||
// NewLocalActor create LocalActor with accounts form provided wallets.
|
||||
// In case of empty wallets provided created actor with dummy account only for read operation.
|
||||
func NewLocalActor(cmd *cobra.Command, c actor.RPCActor) (*LocalActor, error) {
|
||||
walletDir := config.ResolveHomePath(viper.GetString(commonflags.AlphabetWalletsFlag))
|
||||
var act *actor.Actor
|
||||
var accounts []*wallet.Account
|
||||
if walletDir == "" {
|
||||
account, err := wallet.NewAccount()
|
||||
commonCmd.ExitOnErr(cmd, "unable to create dummy account: %w", err)
|
||||
act, err = actor.New(c, []actor.SignerAccount{{
|
||||
Signer: transaction.Signer{
|
||||
Account: account.Contract.ScriptHash(),
|
||||
Scopes: transaction.Global,
|
||||
},
|
||||
Account: account,
|
||||
}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
wallets, err := GetAlphabetWallets(viper.GetViper(), walletDir)
|
||||
commonCmd.ExitOnErr(cmd, "unable to get alphabet wallets: %w", err)
|
||||
|
||||
for _, w := range wallets {
|
||||
acc, err := GetWalletAccount(w, constants.CommitteeAccountName)
|
||||
commonCmd.ExitOnErr(cmd, "can't find committee account: %w", err)
|
||||
accounts = append(accounts, acc)
|
||||
}
|
||||
act, err = actor.New(c, []actor.SignerAccount{{
|
||||
Signer: transaction.Signer{
|
||||
Account: accounts[0].Contract.ScriptHash(),
|
||||
Scopes: transaction.Global,
|
||||
},
|
||||
Account: accounts[0],
|
||||
}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &LocalActor{
|
||||
neoActor: act,
|
||||
accounts: accounts,
|
||||
Invoker: &act.Invoker,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *LocalActor) SendCall(contract util.Uint160, method string, params ...any) (util.Uint256, uint32, error) {
|
||||
tx, err := a.neoActor.MakeCall(contract, method, params...)
|
||||
if err != nil {
|
||||
return util.Uint256{}, 0, err
|
||||
}
|
||||
err = a.resign(tx)
|
||||
if err != nil {
|
||||
return util.Uint256{}, 0, err
|
||||
}
|
||||
return a.neoActor.Send(tx)
|
||||
}
|
||||
|
||||
func (a *LocalActor) SendRun(script []byte) (util.Uint256, uint32, error) {
|
||||
tx, err := a.neoActor.MakeRun(script)
|
||||
if err != nil {
|
||||
return util.Uint256{}, 0, err
|
||||
}
|
||||
err = a.resign(tx)
|
||||
if err != nil {
|
||||
return util.Uint256{}, 0, err
|
||||
}
|
||||
return a.neoActor.Send(tx)
|
||||
}
|
||||
|
||||
// resign is used to sign tx with committee accounts.
|
||||
// Inside the methods `MakeCall` and `SendRun` of the NeoGO's actor transaction is signing by committee account,
|
||||
// because actor uses committee wallet.
|
||||
// But it is not enough, need to sign with another committee accounts.
|
||||
func (a *LocalActor) resign(tx *transaction.Transaction) error {
|
||||
if len(a.accounts[0].Contract.Parameters) > 1 {
|
||||
// Use parameter context to avoid dealing with signature order.
|
||||
network := a.neoActor.GetNetwork()
|
||||
pc := context.NewParameterContext("", network, tx)
|
||||
h := a.accounts[0].Contract.ScriptHash()
|
||||
for _, acc := range a.accounts {
|
||||
priv := acc.PrivateKey()
|
||||
sign := priv.SignHashable(uint32(network), tx)
|
||||
if err := pc.AddSignature(h, acc.Contract, priv.PublicKey(), sign); err != nil {
|
||||
return fmt.Errorf("can't add signature: %w", err)
|
||||
}
|
||||
if len(pc.Items[h].Signatures) == len(acc.Contract.Parameters) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
w, err := pc.GetWitness(h)
|
||||
if err != nil {
|
||||
return fmt.Errorf("incomplete signature: %w", err)
|
||||
}
|
||||
tx.Scripts[0] = *w
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *LocalActor) Wait(h util.Uint256, vub uint32, err error) (*state.AppExecResult, error) {
|
||||
return a.neoActor.Wait(h, vub, err)
|
||||
}
|
||||
|
||||
func (a *LocalActor) Sender() util.Uint160 {
|
||||
return a.neoActor.Sender()
|
||||
}
|
||||
|
||||
func (a *LocalActor) Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error) {
|
||||
return a.neoActor.Call(contract, operation, params...)
|
||||
}
|
||||
|
||||
func (a *LocalActor) CallAndExpandIterator(_ util.Uint160, _ string, _ int, _ ...any) (*result.Invoke, error) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (a *LocalActor) TerminateSession(_ uuid.UUID) error {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (a *LocalActor) TraverseIterator(sessionID uuid.UUID, iterator *result.Iterator, num int) ([]stackitem.Item, error) {
|
||||
return a.neoActor.TraverseIterator(sessionID, iterator, num)
|
||||
}
|
||||
|
||||
func (a *LocalActor) MakeRun(_ []byte) (*transaction.Transaction, error) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (a *LocalActor) MakeUnsignedCall(_ util.Uint160, _ string, _ []transaction.Attribute, _ ...any) (*transaction.Transaction, error) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (a *LocalActor) MakeUnsignedRun(_ []byte, _ []transaction.Attribute) (*transaction.Transaction, error) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (a *LocalActor) MakeCall(_ util.Uint160, _ string, _ ...any) (*transaction.Transaction, error) {
|
||||
panic("unimplemented")
|
||||
}
|
|
@ -1,170 +0,0 @@
|
|||
package helper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func getFrostfsIDAdminFromContract(roInvoker *invoker.Invoker) (util.Uint160, bool, error) {
|
||||
r := management.NewReader(roInvoker)
|
||||
cs, err := r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return util.Uint160{}, false, fmt.Errorf("get nns contract: %w", err)
|
||||
}
|
||||
fidHash, err := NNSResolveHash(roInvoker, cs.Hash, DomainOf(constants.FrostfsIDContract))
|
||||
if err != nil {
|
||||
return util.Uint160{}, false, fmt.Errorf("resolve frostfsid contract hash: %w", err)
|
||||
}
|
||||
item, err := unwrap.Item(roInvoker.Call(fidHash, "getAdmin"))
|
||||
if err != nil {
|
||||
return util.Uint160{}, false, fmt.Errorf("getAdmin: %w", err)
|
||||
}
|
||||
if _, ok := item.(stackitem.Null); ok {
|
||||
return util.Uint160{}, false, nil
|
||||
}
|
||||
|
||||
bs, err := item.TryBytes()
|
||||
if err != nil {
|
||||
return util.Uint160{}, true, fmt.Errorf("getAdmin: decode result: %w", err)
|
||||
}
|
||||
h, err := util.Uint160DecodeBytesBE(bs)
|
||||
if err != nil {
|
||||
return util.Uint160{}, true, fmt.Errorf("getAdmin: decode result: %w", err)
|
||||
}
|
||||
return h, true, nil
|
||||
}
|
||||
|
||||
func GetContractDeployData(c *InitializeContext, ctrName string, keysParam []any, method string) ([]any, error) {
|
||||
items := make([]any, 0, 6)
|
||||
|
||||
switch ctrName {
|
||||
case constants.FrostfsContract:
|
||||
items = append(items,
|
||||
c.Contracts[constants.ProcessingContract].Hash,
|
||||
keysParam,
|
||||
smartcontract.Parameter{})
|
||||
case constants.ProcessingContract:
|
||||
items = append(items, c.Contracts[constants.FrostfsContract].Hash)
|
||||
return items[1:], nil // no notary info
|
||||
case constants.BalanceContract:
|
||||
items = append(items,
|
||||
c.Contracts[constants.NetmapContract].Hash,
|
||||
c.Contracts[constants.ContainerContract].Hash)
|
||||
case constants.ContainerContract:
|
||||
// In case if NNS is updated multiple times, we can't calculate
|
||||
// it's actual hash based on local data, thus query chain.
|
||||
r := management.NewReader(c.ReadOnlyInvoker)
|
||||
nnsCs, err := r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get nns contract: %w", err)
|
||||
}
|
||||
items = append(items,
|
||||
c.Contracts[constants.NetmapContract].Hash,
|
||||
c.Contracts[constants.BalanceContract].Hash,
|
||||
c.Contracts[constants.FrostfsIDContract].Hash,
|
||||
nnsCs.Hash,
|
||||
"container")
|
||||
case constants.FrostfsIDContract:
|
||||
var (
|
||||
h util.Uint160
|
||||
found bool
|
||||
err error
|
||||
)
|
||||
if method == constants.UpdateMethodName {
|
||||
h, found, err = getFrostfsIDAdminFromContract(c.ReadOnlyInvoker)
|
||||
}
|
||||
if method != constants.UpdateMethodName || err == nil && !found {
|
||||
h, found, err = GetFrostfsIDAdmin(viper.GetViper())
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if found {
|
||||
items = append(items, h)
|
||||
} else {
|
||||
items = append(items, c.Contracts[constants.ProxyContract].Hash)
|
||||
}
|
||||
case constants.NetmapContract:
|
||||
md := GetDefaultNetmapContractConfigMap()
|
||||
if method == constants.UpdateMethodName {
|
||||
if err := MergeNetmapConfig(c.ReadOnlyInvoker, md); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var configParam []any
|
||||
for k, v := range md {
|
||||
configParam = append(configParam, k, v)
|
||||
}
|
||||
|
||||
items = append(items,
|
||||
c.Contracts[constants.BalanceContract].Hash,
|
||||
c.Contracts[constants.ContainerContract].Hash,
|
||||
keysParam,
|
||||
configParam)
|
||||
case constants.ProxyContract:
|
||||
items = nil
|
||||
case constants.PolicyContract:
|
||||
items = append(items, c.Contracts[constants.ProxyContract].Hash)
|
||||
default:
|
||||
panic("invalid contract name: " + ctrName)
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func GetContractDeployParameters(cs *ContractState, deployData []any) []any {
|
||||
return []any{cs.RawNEF, cs.RawManifest, deployData}
|
||||
}
|
||||
|
||||
func DeployNNS(c *InitializeContext, method string) error {
|
||||
cs := c.GetContract(constants.NNSContract)
|
||||
h := cs.Hash
|
||||
|
||||
nnsCs, err := c.NNSContractState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if nnsCs != nil {
|
||||
if nnsCs.NEF.Checksum == cs.NEF.Checksum {
|
||||
if method == constants.DeployMethodName {
|
||||
c.Command.Println("NNS contract is already deployed.")
|
||||
} else {
|
||||
c.Command.Println("NNS contract is already updated.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
h = nnsCs.Hash
|
||||
}
|
||||
|
||||
err = AddManifestGroup(c.ContractWallet, h, cs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't sign manifest group: %v", err)
|
||||
}
|
||||
|
||||
params := GetContractDeployParameters(cs, nil)
|
||||
|
||||
invokeHash := management.Hash
|
||||
if method == constants.UpdateMethodName {
|
||||
invokeHash = nnsCs.Hash
|
||||
}
|
||||
|
||||
tx, err := c.CommitteeAct.MakeCall(invokeHash, method, params...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create deploy tx for %s: %w", constants.NNSContract, err)
|
||||
}
|
||||
|
||||
if err := c.MultiSignAndSend(tx, constants.CommitteeAccountName); err != nil {
|
||||
return fmt.Errorf("can't send deploy transaction: %w", err)
|
||||
}
|
||||
|
||||
return c.AwaitTx()
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
package helper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var errNoReleasesFound = errors.New("attempt to fetch contracts archive from the offitial repository failed: no releases found")
|
||||
|
||||
func downloadContracts(cmd *cobra.Command, url string) (io.ReadCloser, error) {
|
||||
cmd.Printf("Downloading contracts archive from '%s'\n", url)
|
||||
|
||||
// HTTP client with connect timeout
|
||||
client := http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 10 * time.Second,
|
||||
}).DialContext,
|
||||
},
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(cmd.Context(), 60*time.Second)
|
||||
defer cancel()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't create request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't fetch contracts archive: %w", err)
|
||||
}
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
func downloadContractsFromRepository(cmd *cobra.Command) (io.ReadCloser, error) {
|
||||
client, err := gitea.NewClient("https://git.frostfs.info")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't initialize repository client: %w", err)
|
||||
}
|
||||
|
||||
releases, _, err := client.ListReleases("TrueCloudLab", "frostfs-contract", gitea.ListReleasesOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't fetch release information: %w", err)
|
||||
}
|
||||
|
||||
var latestRelease *gitea.Release
|
||||
for _, r := range releases {
|
||||
if !r.IsDraft && !r.IsPrerelease {
|
||||
latestRelease = r
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if latestRelease == nil {
|
||||
return nil, errNoReleasesFound
|
||||
}
|
||||
|
||||
cmd.Printf("Found release %s (%s)\n", latestRelease.TagName, latestRelease.Title)
|
||||
|
||||
var url string
|
||||
for _, a := range latestRelease.Attachments {
|
||||
if strings.HasPrefix(a.Name, "frostfs-contract") {
|
||||
url = a.DownloadURL
|
||||
break
|
||||
}
|
||||
}
|
||||
if url == "" {
|
||||
return nil, errors.New("can't find contracts archive in the latest release")
|
||||
}
|
||||
|
||||
return downloadContracts(cmd, url)
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
package helper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const frostfsIDAdminConfigKey = "frostfsid.admin"
|
||||
|
||||
func GetFrostfsIDAdmin(v *viper.Viper) (util.Uint160, bool, error) {
|
||||
admin := v.GetString(frostfsIDAdminConfigKey)
|
||||
if admin == "" {
|
||||
return util.Uint160{}, false, nil
|
||||
}
|
||||
|
||||
h, err := address.StringToUint160(admin)
|
||||
if err == nil {
|
||||
return h, true, nil
|
||||
}
|
||||
|
||||
h, err = util.Uint160DecodeStringLE(admin)
|
||||
if err == nil {
|
||||
return h, true, nil
|
||||
}
|
||||
|
||||
pk, err := keys.NewPublicKeyFromString(admin)
|
||||
if err == nil {
|
||||
return pk.GetScriptHash(), true, nil
|
||||
}
|
||||
return util.Uint160{}, true, fmt.Errorf("frostfsid: admin is invalid: '%s'", admin)
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package helper
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
)
|
||||
|
||||
func AddManifestGroup(cw *wallet.Wallet, h util.Uint160, cs *ContractState) error {
|
||||
priv := cw.Accounts[0].PrivateKey()
|
||||
pub := priv.PublicKey()
|
||||
|
||||
sig := priv.Sign(h.BytesBE())
|
||||
found := false
|
||||
|
||||
for i := range cs.Manifest.Groups {
|
||||
if cs.Manifest.Groups[i].PublicKey.Equal(pub) {
|
||||
cs.Manifest.Groups[i].Signature = sig
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
cs.Manifest.Groups = append(cs.Manifest.Groups, manifest.Group{
|
||||
PublicKey: pub,
|
||||
Signature: sig,
|
||||
})
|
||||
}
|
||||
|
||||
data, err := json.Marshal(cs.Manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cs.RawManifest = data
|
||||
return nil
|
||||
}
|
|
@ -1,223 +0,0 @@
|
|||
package helper
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
nns2 "github.com/nspcc-dev/neo-go/pkg/rpcclient/nns"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||
"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/vmstate"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var ErrTooManyAlphabetNodes = fmt.Errorf("too many alphabet nodes (maximum allowed is %d)", constants.MaxAlphabetNodes)
|
||||
|
||||
func AwaitTx(cmd *cobra.Command, c Client, txs []HashVUBPair) error {
|
||||
cmd.Println("Waiting for transactions to persist...")
|
||||
|
||||
at := trigger.Application
|
||||
|
||||
var retErr error
|
||||
|
||||
loop:
|
||||
for i := range txs {
|
||||
var it int
|
||||
var pollInterval time.Duration
|
||||
var pollIntervalChanged bool
|
||||
for {
|
||||
// We must fetch current height before application log, to avoid race condition.
|
||||
currBlock, err := c.GetBlockCount()
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch current block height: %w", err)
|
||||
}
|
||||
res, err := c.GetApplicationLog(txs[i].Hash, &at)
|
||||
if err == nil {
|
||||
if retErr == nil && len(res.Executions) > 0 && res.Executions[0].VMState != vmstate.Halt {
|
||||
retErr = fmt.Errorf("tx %d persisted in %s state: %s",
|
||||
i, res.Executions[0].VMState, res.Executions[0].FaultException)
|
||||
}
|
||||
continue loop
|
||||
}
|
||||
if txs[i].Vub < currBlock {
|
||||
return fmt.Errorf("tx was not persisted: Vub=%d, height=%d", txs[i].Vub, currBlock)
|
||||
}
|
||||
|
||||
pollInterval, pollIntervalChanged = NextPollInterval(it, pollInterval)
|
||||
if pollIntervalChanged && viper.GetBool(commonflags.Verbose) {
|
||||
cmd.Printf("Pool interval to check transaction persistence changed: %s\n", pollInterval.String())
|
||||
}
|
||||
|
||||
timer := time.NewTimer(pollInterval)
|
||||
select {
|
||||
case <-cmd.Context().Done():
|
||||
return cmd.Context().Err()
|
||||
case <-timer.C:
|
||||
}
|
||||
|
||||
it++
|
||||
}
|
||||
}
|
||||
|
||||
return retErr
|
||||
}
|
||||
|
||||
func NextPollInterval(it int, previous time.Duration) (time.Duration, bool) {
|
||||
const minPollInterval = 1 * time.Second
|
||||
const maxPollInterval = 16 * time.Second
|
||||
const changeAfter = 5
|
||||
if it == 0 {
|
||||
return minPollInterval, true
|
||||
}
|
||||
if it%changeAfter != 0 {
|
||||
return previous, false
|
||||
}
|
||||
nextInterval := previous * 2
|
||||
if nextInterval > maxPollInterval {
|
||||
return maxPollInterval, previous != maxPollInterval
|
||||
}
|
||||
return nextInterval, true
|
||||
}
|
||||
|
||||
func GetWalletAccount(w *wallet.Wallet, typ string) (*wallet.Account, error) {
|
||||
for i := range w.Accounts {
|
||||
if w.Accounts[i].Label == typ {
|
||||
return w.Accounts[i], nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("account for '%s' not found", typ)
|
||||
}
|
||||
|
||||
func GetComitteAcc(cmd *cobra.Command, v *viper.Viper) *wallet.Account {
|
||||
walletDir := config.ResolveHomePath(viper.GetString(commonflags.AlphabetWalletsFlag))
|
||||
wallets, err := GetAlphabetWallets(v, walletDir)
|
||||
commonCmd.ExitOnErr(cmd, "unable to get alphabet wallets: %w", err)
|
||||
|
||||
committeeAcc, err := GetWalletAccount(wallets[0], constants.CommitteeAccountName)
|
||||
commonCmd.ExitOnErr(cmd, "can't find committee account: %w", err)
|
||||
return committeeAcc
|
||||
}
|
||||
|
||||
func NNSResolve(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (stackitem.Item, error) {
|
||||
return unwrap.Item(inv.Call(nnsHash, "resolve", domain, int64(nns.TXT)))
|
||||
}
|
||||
|
||||
// ParseNNSResolveResult parses the result of resolving NNS record.
|
||||
// It works with multiple formats (corresponding to multiple NNS versions).
|
||||
// If array of hashes is provided, it returns only the first one.
|
||||
func ParseNNSResolveResult(res stackitem.Item) (util.Uint160, error) {
|
||||
arr, ok := res.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
arr = []stackitem.Item{res}
|
||||
}
|
||||
if _, ok := res.Value().(stackitem.Null); ok || len(arr) == 0 {
|
||||
return util.Uint160{}, errors.New("NNS record is missing")
|
||||
}
|
||||
for i := range arr {
|
||||
bs, err := arr[i].TryBytes()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// We support several formats for hash encoding, this logic should be maintained in sync
|
||||
// with NNSResolve from pkg/morph/client/nns.go
|
||||
h, err := util.Uint160DecodeStringLE(string(bs))
|
||||
if err == nil {
|
||||
return h, nil
|
||||
}
|
||||
|
||||
h, err = address.StringToUint160(string(bs))
|
||||
if err == nil {
|
||||
return h, nil
|
||||
}
|
||||
}
|
||||
return util.Uint160{}, errors.New("no valid hashes are found")
|
||||
}
|
||||
|
||||
// NNSResolveHash Returns errMissingNNSRecord if invocation fault exception contains "token not found".
|
||||
func NNSResolveHash(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (util.Uint160, error) {
|
||||
item, err := NNSResolve(inv, nnsHash, domain)
|
||||
if err != nil {
|
||||
return util.Uint160{}, err
|
||||
}
|
||||
return ParseNNSResolveResult(item)
|
||||
}
|
||||
|
||||
func DomainOf(contract string) string {
|
||||
return contract + ".frostfs"
|
||||
}
|
||||
|
||||
func NNSResolveKey(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (*keys.PublicKey, error) {
|
||||
res, err := NNSResolve(inv, nnsHash, domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := res.Value().(stackitem.Null); ok {
|
||||
return nil, errors.New("NNS record is missing")
|
||||
}
|
||||
arr, ok := res.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
return nil, errors.New("API of the NNS contract method `resolve` has changed")
|
||||
}
|
||||
for i := range arr {
|
||||
var bs []byte
|
||||
bs, err = arr[i].TryBytes()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
return keys.NewPublicKeyFromString(string(bs))
|
||||
}
|
||||
return nil, errors.New("no valid keys are found")
|
||||
}
|
||||
|
||||
func NNSIsAvailable(c Client, nnsHash util.Uint160, name string) (bool, error) {
|
||||
switch c.(type) {
|
||||
case *rpcclient.Client:
|
||||
inv := invoker.New(c, nil)
|
||||
reader := nns2.NewReader(inv, nnsHash)
|
||||
return reader.IsAvailable(name)
|
||||
default:
|
||||
b, err := unwrap.Bool(InvokeFunction(c, nnsHash, "isAvailable", []any{name}, nil))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("`isAvailable`: invalid response: %w", err)
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
}
|
||||
|
||||
func CheckNotaryEnabled(c Client) error {
|
||||
ns, err := c.GetNativeContracts()
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get native contract hashes: %w", err)
|
||||
}
|
||||
|
||||
notaryEnabled := false
|
||||
nativeHashes := make(map[string]util.Uint160, len(ns))
|
||||
for i := range ns {
|
||||
if ns[i].Manifest.Name == nativenames.Notary {
|
||||
notaryEnabled = true
|
||||
}
|
||||
nativeHashes[ns[i].Manifest.Name] = ns[i].Hash
|
||||
}
|
||||
if !notaryEnabled {
|
||||
return errors.New("notary contract must be enabled")
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,553 +0,0 @@
|
|||
package helper
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
io2 "io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/context"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
errNegativeDuration = errors.New("epoch duration must be positive")
|
||||
errNegativeSize = errors.New("max object size must be positive")
|
||||
)
|
||||
|
||||
type ContractState struct {
|
||||
NEF *nef.File
|
||||
RawNEF []byte
|
||||
Manifest *manifest.Manifest
|
||||
RawManifest []byte
|
||||
Hash util.Uint160
|
||||
}
|
||||
|
||||
type Cache struct {
|
||||
NNSCs *state.Contract
|
||||
GroupKey *keys.PublicKey
|
||||
}
|
||||
|
||||
type InitializeContext struct {
|
||||
ClientContext
|
||||
Cache
|
||||
// CommitteeAcc is used for retrieving the committee address and the verification script.
|
||||
CommitteeAcc *wallet.Account
|
||||
// ConsensusAcc is used for retrieving the committee address and the verification script.
|
||||
ConsensusAcc *wallet.Account
|
||||
Wallets []*wallet.Wallet
|
||||
// ContractWallet is a wallet for providing the contract group signature.
|
||||
ContractWallet *wallet.Wallet
|
||||
// Accounts contains simple signature accounts in the same order as in Wallets.
|
||||
Accounts []*wallet.Account
|
||||
Contracts map[string]*ContractState
|
||||
Command *cobra.Command
|
||||
ContractPath string
|
||||
ContractURL string
|
||||
}
|
||||
|
||||
func (cs *ContractState) Parse() error {
|
||||
nf, err := nef.FileFromBytes(cs.RawNEF)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't parse NEF file: %w", err)
|
||||
}
|
||||
|
||||
m := new(manifest.Manifest)
|
||||
if err := json.Unmarshal(cs.RawManifest, m); err != nil {
|
||||
return fmt.Errorf("can't parse manifest file: %w", err)
|
||||
}
|
||||
|
||||
cs.NEF = &nf
|
||||
cs.Manifest = m
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewInitializeContext(cmd *cobra.Command, v *viper.Viper) (*InitializeContext, error) {
|
||||
walletDir := config.ResolveHomePath(viper.GetString(commonflags.AlphabetWalletsFlag))
|
||||
wallets, err := GetAlphabetWallets(v, walletDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
needContracts := cmd.Name() == "update-contracts" || cmd.Name() == "init"
|
||||
|
||||
var w *wallet.Wallet
|
||||
w, err = getWallet(cmd, v, needContracts, walletDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := createClient(cmd, v, wallets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
committeeAcc, err := GetWalletAccount(wallets[0], constants.CommitteeAccountName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't find committee account: %w", err)
|
||||
}
|
||||
|
||||
consensusAcc, err := GetWalletAccount(wallets[0], constants.ConsensusAccountName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't find consensus account: %w", err)
|
||||
}
|
||||
|
||||
if err := validateInit(cmd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctrPath, err := getContractsPath(cmd, needContracts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ctrURL string
|
||||
if needContracts {
|
||||
ctrURL, _ = cmd.Flags().GetString(commonflags.ContractsURLFlag)
|
||||
}
|
||||
|
||||
if err := CheckNotaryEnabled(c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accounts, err := createWalletAccounts(wallets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cliCtx, err := DefaultClientContext(c, committeeAcc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("client context: %w", err)
|
||||
}
|
||||
|
||||
initCtx := &InitializeContext{
|
||||
ClientContext: *cliCtx,
|
||||
ConsensusAcc: consensusAcc,
|
||||
CommitteeAcc: committeeAcc,
|
||||
ContractWallet: w,
|
||||
Wallets: wallets,
|
||||
Accounts: accounts,
|
||||
Command: cmd,
|
||||
Contracts: make(map[string]*ContractState),
|
||||
ContractPath: ctrPath,
|
||||
ContractURL: ctrURL,
|
||||
}
|
||||
|
||||
if needContracts {
|
||||
err := readContracts(initCtx, constants.FullContractList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return initCtx, nil
|
||||
}
|
||||
|
||||
func validateInit(cmd *cobra.Command) error {
|
||||
if cmd.Name() != "init" {
|
||||
return nil
|
||||
}
|
||||
if viper.GetInt64(commonflags.EpochDurationInitFlag) <= 0 {
|
||||
return errNegativeDuration
|
||||
}
|
||||
|
||||
if viper.GetInt64(commonflags.MaxObjectSizeInitFlag) <= 0 {
|
||||
return errNegativeSize
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet) (Client, error) {
|
||||
var c Client
|
||||
var err error
|
||||
if ldf := cmd.Flags().Lookup(commonflags.LocalDumpFlag); ldf != nil && ldf.Changed {
|
||||
if cmd.Flags().Changed(commonflags.EndpointFlag) {
|
||||
return nil, fmt.Errorf("`%s` and `%s` flags are mutually exclusive", commonflags.EndpointFlag, commonflags.LocalDumpFlag)
|
||||
}
|
||||
c, err = NewLocalClient(cmd, v, wallets, ldf.Value.String())
|
||||
} else {
|
||||
c, err = GetN3Client(v)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't create N3 client: %w", err)
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func getContractsPath(cmd *cobra.Command, needContracts bool) (string, error) {
|
||||
if !needContracts {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
ctrPath, err := cmd.Flags().GetString(commonflags.ContractsInitFlag)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid contracts path: %w", err)
|
||||
}
|
||||
return ctrPath, nil
|
||||
}
|
||||
|
||||
func createWalletAccounts(wallets []*wallet.Wallet) ([]*wallet.Account, error) {
|
||||
accounts := make([]*wallet.Account, len(wallets))
|
||||
for i, w := range wallets {
|
||||
acc, err := GetWalletAccount(w, constants.SingleAccountName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wallet %s is invalid (no single account): %w", w.Path(), err)
|
||||
}
|
||||
accounts[i] = acc
|
||||
}
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
func readContracts(c *InitializeContext, names []string) error {
|
||||
var (
|
||||
fi os.FileInfo
|
||||
err error
|
||||
)
|
||||
if c.ContractPath != "" {
|
||||
fi, err = os.Stat(c.ContractPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid contracts path: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if c.ContractPath != "" && fi.IsDir() {
|
||||
for _, ctrName := range names {
|
||||
cs, err := ReadContract(filepath.Join(c.ContractPath, ctrName), ctrName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Contracts[ctrName] = cs
|
||||
}
|
||||
} else {
|
||||
var r io2.ReadCloser
|
||||
if c.ContractPath != "" {
|
||||
r, err = os.Open(c.ContractPath)
|
||||
} else if c.ContractURL != "" {
|
||||
r, err = downloadContracts(c.Command, c.ContractURL)
|
||||
} else {
|
||||
r, err = downloadContractsFromRepository(c.Command)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't open contracts archive: %w", err)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
m, err := readContractsFromArchive(r, names)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, name := range names {
|
||||
if err := m[name].Parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Contracts[name] = m[name]
|
||||
}
|
||||
}
|
||||
|
||||
for _, ctrName := range names {
|
||||
if ctrName != constants.AlphabetContract {
|
||||
cs := c.Contracts[ctrName]
|
||||
cs.Hash = state.CreateContractHash(c.CommitteeAcc.Contract.ScriptHash(),
|
||||
cs.NEF.Checksum, cs.Manifest.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *InitializeContext) Close() {
|
||||
if local, ok := c.Client.(*LocalClient); ok {
|
||||
err := local.Dump()
|
||||
if err != nil {
|
||||
c.Command.PrintErrf("Can't write dump: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *InitializeContext) AwaitTx() error {
|
||||
return c.ClientContext.AwaitTx(c.Command)
|
||||
}
|
||||
|
||||
func (c *InitializeContext) NNSContractState() (*state.Contract, error) {
|
||||
if c.NNSCs != nil {
|
||||
return c.NNSCs, nil
|
||||
}
|
||||
|
||||
r := management.NewReader(c.ReadOnlyInvoker)
|
||||
cs, err := r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.NNSCs = cs
|
||||
return cs, nil
|
||||
}
|
||||
|
||||
func (c *InitializeContext) GetSigner(tryGroup bool, acc *wallet.Account) transaction.Signer {
|
||||
if tryGroup && c.GroupKey != nil {
|
||||
return transaction.Signer{
|
||||
Account: acc.Contract.ScriptHash(),
|
||||
Scopes: transaction.CustomGroups,
|
||||
AllowedGroups: keys.PublicKeys{c.GroupKey},
|
||||
}
|
||||
}
|
||||
|
||||
signer := transaction.Signer{
|
||||
Account: acc.Contract.ScriptHash(),
|
||||
Scopes: transaction.Global, // Scope is important, as we have nested call to container contract.
|
||||
}
|
||||
|
||||
if !tryGroup {
|
||||
return signer
|
||||
}
|
||||
|
||||
nnsCs, err := c.NNSContractState()
|
||||
if err != nil {
|
||||
return signer
|
||||
}
|
||||
|
||||
groupKey, err := NNSResolveKey(c.ReadOnlyInvoker, nnsCs.Hash, client.NNSGroupKeyName)
|
||||
if err == nil {
|
||||
c.GroupKey = groupKey
|
||||
|
||||
signer.Scopes = transaction.CustomGroups
|
||||
signer.AllowedGroups = keys.PublicKeys{groupKey}
|
||||
}
|
||||
return signer
|
||||
}
|
||||
|
||||
// SendCommitteeTx creates transaction from script, signs it by committee nodes and sends it to RPC.
|
||||
// If tryGroup is false, global scope is used for the signer (useful when
|
||||
// working with native contracts).
|
||||
func (c *InitializeContext) SendCommitteeTx(script []byte, tryGroup bool) error {
|
||||
return c.sendMultiTx(script, tryGroup, false)
|
||||
}
|
||||
|
||||
// SendConsensusTx creates transaction from script, signs it by alphabet nodes and sends it to RPC.
|
||||
// Not that because this is used only after the contracts were initialized and deployed,
|
||||
// we always try to have a group scope.
|
||||
func (c *InitializeContext) SendConsensusTx(script []byte) error {
|
||||
return c.sendMultiTx(script, true, true)
|
||||
}
|
||||
|
||||
func (c *InitializeContext) sendMultiTx(script []byte, tryGroup bool, withConsensus bool) error {
|
||||
var act *actor.Actor
|
||||
var err error
|
||||
|
||||
withConsensus = withConsensus && !c.ConsensusAcc.Contract.ScriptHash().Equals(c.CommitteeAcc.ScriptHash())
|
||||
if tryGroup {
|
||||
// Even for consensus signatures we need the committee to pay.
|
||||
signers := make([]actor.SignerAccount, 1, 2)
|
||||
signers[0] = actor.SignerAccount{
|
||||
Signer: c.GetSigner(tryGroup, c.CommitteeAcc),
|
||||
Account: c.CommitteeAcc,
|
||||
}
|
||||
if withConsensus {
|
||||
signers = append(signers, actor.SignerAccount{
|
||||
Signer: c.GetSigner(tryGroup, c.ConsensusAcc),
|
||||
Account: c.ConsensusAcc,
|
||||
})
|
||||
}
|
||||
act, err = actor.New(c.Client, signers)
|
||||
} else {
|
||||
if withConsensus {
|
||||
panic("BUG: should never happen")
|
||||
}
|
||||
act, err = c.CommitteeAct, nil
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create actor: %w", err)
|
||||
}
|
||||
|
||||
tx, err := act.MakeUnsignedRun(script, []transaction.Attribute{{Type: transaction.HighPriority}})
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not perform test invocation: %w", err)
|
||||
}
|
||||
|
||||
if err := c.MultiSign(tx, constants.CommitteeAccountName); err != nil {
|
||||
return err
|
||||
}
|
||||
if withConsensus {
|
||||
if err := c.MultiSign(tx, constants.ConsensusAccountName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return c.SendTx(tx, c.Command, false)
|
||||
}
|
||||
|
||||
func (c *InitializeContext) MultiSignAndSend(tx *transaction.Transaction, accType string) error {
|
||||
if err := c.MultiSign(tx, accType); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.SendTx(tx, c.Command, false)
|
||||
}
|
||||
|
||||
func (c *InitializeContext) MultiSign(tx *transaction.Transaction, accType string) error {
|
||||
version, err := c.Client.GetVersion()
|
||||
if err != nil {
|
||||
// error appears only if client
|
||||
// has not been initialized
|
||||
panic(err)
|
||||
}
|
||||
network := version.Protocol.Network
|
||||
|
||||
// Use parameter context to avoid dealing with signature order.
|
||||
pc := context.NewParameterContext("", network, tx)
|
||||
h := c.CommitteeAcc.Contract.ScriptHash()
|
||||
if accType == constants.ConsensusAccountName {
|
||||
h = c.ConsensusAcc.Contract.ScriptHash()
|
||||
}
|
||||
for _, w := range c.Wallets {
|
||||
acc, err := GetWalletAccount(w, accType)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't find %s wallet account: %w", accType, err)
|
||||
}
|
||||
|
||||
priv := acc.PrivateKey()
|
||||
sign := priv.SignHashable(uint32(network), tx)
|
||||
if err := pc.AddSignature(h, acc.Contract, priv.PublicKey(), sign); err != nil {
|
||||
return fmt.Errorf("can't add signature: %w", err)
|
||||
}
|
||||
if len(pc.Items[h].Signatures) == len(acc.Contract.Parameters) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
w, err := pc.GetWitness(h)
|
||||
if err != nil {
|
||||
return fmt.Errorf("incomplete signature: %w", err)
|
||||
}
|
||||
|
||||
for i := range tx.Signers {
|
||||
if tx.Signers[i].Account == h {
|
||||
if i < len(tx.Scripts) {
|
||||
tx.Scripts[i] = *w
|
||||
} else if i == len(tx.Scripts) {
|
||||
tx.Scripts = append(tx.Scripts, *w)
|
||||
} else {
|
||||
panic("BUG: invalid signing order")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("%s account was not found among transaction signers", accType)
|
||||
}
|
||||
|
||||
// EmitUpdateNNSGroupScript emits script for updating group key stored in NNS.
|
||||
// First return value is true iff the key is already there and nothing should be done.
|
||||
// Second return value is true iff a domain registration code was emitted.
|
||||
func (c *InitializeContext) EmitUpdateNNSGroupScript(bw *io.BufBinWriter, nnsHash util.Uint160, pub *keys.PublicKey) (bool, bool, error) {
|
||||
isAvail, err := NNSIsAvailable(c.Client, nnsHash, client.NNSGroupKeyName)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
if !isAvail {
|
||||
currentPub, err := NNSResolveKey(c.ReadOnlyInvoker, nnsHash, client.NNSGroupKeyName)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
if pub.Equal(currentPub) {
|
||||
return true, false, nil
|
||||
}
|
||||
}
|
||||
|
||||
if isAvail {
|
||||
emit.AppCall(bw.BinWriter, nnsHash, "register", callflag.All,
|
||||
client.NNSGroupKeyName, c.CommitteeAcc.Contract.ScriptHash(),
|
||||
constants.FrostfsOpsEmail, constants.NNSRefreshDefVal, constants.NNSRetryDefVal,
|
||||
int64(constants.DefaultExpirationTime), constants.NNSTtlDefVal)
|
||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||
}
|
||||
|
||||
emit.AppCall(bw.BinWriter, nnsHash, "deleteRecords", callflag.All, "group.frostfs", int64(nns.TXT))
|
||||
emit.AppCall(bw.BinWriter, nnsHash, "addRecord", callflag.All,
|
||||
"group.frostfs", int64(nns.TXT), hex.EncodeToString(pub.Bytes()))
|
||||
|
||||
return false, isAvail, nil
|
||||
}
|
||||
|
||||
func (c *InitializeContext) NNSRegisterDomainScript(nnsHash, expectedHash util.Uint160, domain string) ([]byte, bool, error) {
|
||||
ok, err := NNSIsAvailable(c.Client, nnsHash, domain)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if ok {
|
||||
bw := io.NewBufBinWriter()
|
||||
emit.AppCall(bw.BinWriter, nnsHash, "register", callflag.All,
|
||||
domain, c.CommitteeAcc.Contract.ScriptHash(),
|
||||
constants.FrostfsOpsEmail, constants.NNSRefreshDefVal, constants.NNSRetryDefVal,
|
||||
int64(constants.DefaultExpirationTime), constants.NNSTtlDefVal)
|
||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||
|
||||
if bw.Err != nil {
|
||||
panic(bw.Err)
|
||||
}
|
||||
return bw.Bytes(), false, nil
|
||||
}
|
||||
|
||||
s, err := NNSResolveHash(c.ReadOnlyInvoker, nnsHash, domain)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return nil, s == expectedHash, nil
|
||||
}
|
||||
|
||||
func (c *InitializeContext) NNSRootRegistered(nnsHash util.Uint160, zone string) (bool, error) {
|
||||
res, err := c.CommitteeAct.Call(nnsHash, "isAvailable", "name."+zone)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return res.State == vmstate.Halt.String(), nil
|
||||
}
|
||||
|
||||
func (c *InitializeContext) IsUpdated(ctrHash util.Uint160, cs *ContractState) bool {
|
||||
r := management.NewReader(c.ReadOnlyInvoker)
|
||||
realCs, err := r.GetContract(ctrHash)
|
||||
return err == nil && realCs != nil && realCs.NEF.Checksum == cs.NEF.Checksum
|
||||
}
|
||||
|
||||
func (c *InitializeContext) GetContract(ctrName string) *ContractState {
|
||||
return c.Contracts[ctrName]
|
||||
}
|
||||
|
||||
func (c *InitializeContext) GetAlphabetDeployItems(i, n int) []any {
|
||||
items := make([]any, 5)
|
||||
items[0] = c.Contracts[constants.NetmapContract].Hash
|
||||
items[1] = c.Contracts[constants.ProxyContract].Hash
|
||||
items[2] = innerring.GlagoliticLetter(i).String()
|
||||
items[3] = int64(i)
|
||||
items[4] = int64(n)
|
||||
return items
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package helper
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNextPollInterval(t *testing.T) {
|
||||
var pollInterval time.Duration
|
||||
var iteration int
|
||||
|
||||
pollInterval, hasChanged := NextPollInterval(iteration, pollInterval)
|
||||
require.True(t, hasChanged)
|
||||
require.Equal(t, time.Second, pollInterval)
|
||||
|
||||
iteration = 4
|
||||
pollInterval, hasChanged = NextPollInterval(iteration, pollInterval)
|
||||
require.False(t, hasChanged)
|
||||
require.Equal(t, time.Second, pollInterval)
|
||||
|
||||
iteration = 5
|
||||
pollInterval, hasChanged = NextPollInterval(iteration, pollInterval)
|
||||
require.True(t, hasChanged)
|
||||
require.Equal(t, 2*time.Second, pollInterval)
|
||||
|
||||
iteration = 10
|
||||
pollInterval, hasChanged = NextPollInterval(iteration, pollInterval)
|
||||
require.True(t, hasChanged)
|
||||
require.Equal(t, 4*time.Second, pollInterval)
|
||||
|
||||
iteration = 20
|
||||
pollInterval = 32 * time.Second
|
||||
pollInterval, hasChanged = NextPollInterval(iteration, pollInterval)
|
||||
require.True(t, hasChanged) // from 32s to 16s
|
||||
require.Equal(t, 16*time.Second, pollInterval)
|
||||
|
||||
pollInterval = 16 * time.Second
|
||||
pollInterval, hasChanged = NextPollInterval(iteration, pollInterval)
|
||||
require.False(t, hasChanged)
|
||||
require.Equal(t, 16*time.Second, pollInterval)
|
||||
}
|
|
@ -1,402 +0,0 @@
|
|||
package helper
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"github.com/google/uuid"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/chaindump"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type LocalClient struct {
|
||||
bc *core.Blockchain
|
||||
transactions []*transaction.Transaction
|
||||
dumpPath string
|
||||
accounts []*wallet.Account
|
||||
}
|
||||
|
||||
func NewLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet, dumpPath string) (*LocalClient, error) {
|
||||
cfg, err := config.LoadFile(v.GetString(constants.ProtoConfigPath))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bc, err := core.NewBlockchain(storage.NewMemoryStore(), cfg.Blockchain(), zap.NewNop())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m := smartcontract.GetDefaultHonestNodeCount(int(cfg.ProtocolConfiguration.ValidatorsCount))
|
||||
accounts := make([]*wallet.Account, len(wallets))
|
||||
for i := range accounts {
|
||||
accounts[i], err = GetWalletAccount(wallets[i], constants.ConsensusAccountName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
indexMap := make(map[string]int)
|
||||
for i, pub := range cfg.ProtocolConfiguration.StandbyCommittee {
|
||||
indexMap[pub] = i
|
||||
}
|
||||
|
||||
sort.Slice(accounts, func(i, j int) bool {
|
||||
pi := accounts[i].PrivateKey().PublicKey().Bytes()
|
||||
pj := accounts[j].PrivateKey().PublicKey().Bytes()
|
||||
return indexMap[string(pi)] < indexMap[string(pj)]
|
||||
})
|
||||
sort.Slice(accounts[:cfg.ProtocolConfiguration.ValidatorsCount], func(i, j int) bool {
|
||||
return accounts[i].PublicKey().Cmp(accounts[j].PublicKey()) == -1
|
||||
})
|
||||
|
||||
go bc.Run()
|
||||
|
||||
if cmd.Name() != "init" {
|
||||
f, err := os.OpenFile(dumpPath, os.O_RDONLY, 0o600)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't open local dump: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
r := io.NewBinReaderFromIO(f)
|
||||
|
||||
var skip uint32
|
||||
if bc.BlockHeight() != 0 {
|
||||
skip = bc.BlockHeight() + 1
|
||||
}
|
||||
|
||||
count := r.ReadU32LE() - skip
|
||||
if err := chaindump.Restore(bc, r, skip, count, nil); err != nil {
|
||||
return nil, fmt.Errorf("can't restore local dump: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &LocalClient{
|
||||
bc: bc,
|
||||
dumpPath: dumpPath,
|
||||
accounts: accounts[:m],
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *LocalClient) GetBlockCount() (uint32, error) {
|
||||
return l.bc.BlockHeight(), nil
|
||||
}
|
||||
|
||||
func (l *LocalClient) GetNativeContracts() ([]state.NativeContract, error) {
|
||||
return l.bc.GetNatives(), nil
|
||||
}
|
||||
|
||||
func (l *LocalClient) GetApplicationLog(h util.Uint256, t *trigger.Type) (*result.ApplicationLog, error) {
|
||||
aer, err := l.bc.GetAppExecResults(h, *t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
a := result.NewApplicationLog(h, aer, *t)
|
||||
return &a, nil
|
||||
}
|
||||
|
||||
func (l *LocalClient) GetCommittee() (keys.PublicKeys, error) {
|
||||
// not used by `morph init` command
|
||||
panic("unexpected call")
|
||||
}
|
||||
|
||||
// InvokeFunction is implemented via `InvokeScript`.
|
||||
func (l *LocalClient) InvokeFunction(h util.Uint160, method string, sPrm []smartcontract.Parameter, ss []transaction.Signer) (*result.Invoke, error) {
|
||||
var err error
|
||||
|
||||
pp := make([]any, len(sPrm))
|
||||
for i, p := range sPrm {
|
||||
pp[i], err = smartcontract.ExpandParameterToEmitable(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("incorrect parameter type %s: %w", p.Type, err)
|
||||
}
|
||||
}
|
||||
|
||||
return InvokeFunction(l, h, method, pp, ss)
|
||||
}
|
||||
|
||||
func (l *LocalClient) TerminateSession(_ uuid.UUID) (bool, error) {
|
||||
// not used by `morph init` command
|
||||
panic("unexpected call")
|
||||
}
|
||||
|
||||
func (l *LocalClient) TraverseIterator(_, _ uuid.UUID, _ int) ([]stackitem.Item, error) {
|
||||
// not used by `morph init` command
|
||||
panic("unexpected call")
|
||||
}
|
||||
|
||||
// GetVersion return default version.
|
||||
func (l *LocalClient) GetVersion() (*result.Version, error) {
|
||||
c := l.bc.GetConfig()
|
||||
return &result.Version{
|
||||
Protocol: result.Protocol{
|
||||
AddressVersion: address.NEO3Prefix,
|
||||
Network: c.Magic,
|
||||
MillisecondsPerBlock: int(c.TimePerBlock / time.Millisecond),
|
||||
MaxTraceableBlocks: c.MaxTraceableBlocks,
|
||||
MaxValidUntilBlockIncrement: c.MaxValidUntilBlockIncrement,
|
||||
MaxTransactionsPerBlock: c.MaxTransactionsPerBlock,
|
||||
MemoryPoolMaxTransactions: c.MemPoolSize,
|
||||
ValidatorsCount: byte(c.ValidatorsCount),
|
||||
InitialGasDistribution: c.InitialGASSupply,
|
||||
CommitteeHistory: c.CommitteeHistory,
|
||||
P2PSigExtensions: c.P2PSigExtensions,
|
||||
StateRootInHeader: c.StateRootInHeader,
|
||||
ValidatorsHistory: c.ValidatorsHistory,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *LocalClient) InvokeContractVerify(util.Uint160, []smartcontract.Parameter, []transaction.Signer, ...transaction.Witness) (*result.Invoke, error) {
|
||||
// not used by `morph init` command
|
||||
panic("unexpected call")
|
||||
}
|
||||
|
||||
// CalculateNetworkFee calculates network fee for the given transaction.
|
||||
// Copied from neo-go with minor corrections (no need to support non-notary mode):
|
||||
// https://github.com/nspcc-dev/neo-go/blob/v0.103.0/pkg/services/rpcsrv/server.go#L911
|
||||
func (l *LocalClient) CalculateNetworkFee(tx *transaction.Transaction) (int64, error) {
|
||||
// Avoid setting hash for this tx: server code doesn't touch client transaction.
|
||||
data := tx.Bytes()
|
||||
tx, err := transaction.NewTransactionFromBytes(data)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
hashablePart, err := tx.EncodeHashableFields()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
size := len(hashablePart) + io.GetVarSize(len(tx.Signers))
|
||||
var (
|
||||
netFee int64
|
||||
// Verification GAS cost can't exceed this policy.
|
||||
gasLimit = l.bc.GetMaxVerificationGAS()
|
||||
)
|
||||
for i, signer := range tx.Signers {
|
||||
w := tx.Scripts[i]
|
||||
if len(w.InvocationScript) == 0 { // No invocation provided, try to infer one.
|
||||
var paramz []manifest.Parameter
|
||||
if len(w.VerificationScript) == 0 { // Contract-based verification
|
||||
cs := l.bc.GetContractState(signer.Account)
|
||||
if cs == nil {
|
||||
return 0, fmt.Errorf("signer %d has no verification script and no deployed contract", i)
|
||||
}
|
||||
md := cs.Manifest.ABI.GetMethod(manifest.MethodVerify, -1)
|
||||
if md == nil || md.ReturnType != smartcontract.BoolType {
|
||||
return 0, fmt.Errorf("signer %d has no verify method in deployed contract", i)
|
||||
}
|
||||
paramz = md.Parameters // Might as well have none params and it's OK.
|
||||
} else { // Regular signature verification.
|
||||
if vm.IsSignatureContract(w.VerificationScript) {
|
||||
paramz = []manifest.Parameter{{Type: smartcontract.SignatureType}}
|
||||
} else if nSigs, _, ok := vm.ParseMultiSigContract(w.VerificationScript); ok {
|
||||
paramz = make([]manifest.Parameter, nSigs)
|
||||
for j := 0; j < nSigs; j++ {
|
||||
paramz[j] = manifest.Parameter{Type: smartcontract.SignatureType}
|
||||
}
|
||||
}
|
||||
}
|
||||
inv := io.NewBufBinWriter()
|
||||
for _, p := range paramz {
|
||||
p.Type.EncodeDefaultValue(inv.BinWriter)
|
||||
}
|
||||
if inv.Err != nil {
|
||||
return 0, fmt.Errorf("failed to create dummy invocation script (signer %d): %s", i, inv.Err.Error())
|
||||
}
|
||||
w.InvocationScript = inv.Bytes()
|
||||
}
|
||||
gasConsumed, err := l.bc.VerifyWitness(signer.Account, tx, &w, gasLimit)
|
||||
if err != nil && !errors.Is(err, core.ErrInvalidSignature) {
|
||||
return 0, err
|
||||
}
|
||||
gasLimit -= gasConsumed
|
||||
netFee += gasConsumed
|
||||
size += io.GetVarSize(w.VerificationScript) + io.GetVarSize(w.InvocationScript)
|
||||
}
|
||||
if l.bc.P2PSigExtensionsEnabled() {
|
||||
attrs := tx.GetAttributes(transaction.NotaryAssistedT)
|
||||
if len(attrs) != 0 {
|
||||
na := attrs[0].Value.(*transaction.NotaryAssisted)
|
||||
netFee += (int64(na.NKeys) + 1) * l.bc.GetNotaryServiceFeePerKey()
|
||||
}
|
||||
}
|
||||
fee := l.bc.FeePerByte()
|
||||
netFee += int64(size) * fee
|
||||
return netFee, nil
|
||||
}
|
||||
|
||||
func (l *LocalClient) InvokeScript(script []byte, signers []transaction.Signer) (*result.Invoke, error) {
|
||||
lastBlock, err := l.bc.GetBlock(l.bc.CurrentBlockHash())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tx := transaction.New(script, 0)
|
||||
tx.Signers = signers
|
||||
tx.ValidUntilBlock = l.bc.BlockHeight() + 2
|
||||
|
||||
ic, err := l.bc.GetTestVM(trigger.Application, tx, &block.Block{
|
||||
Header: block.Header{
|
||||
Index: lastBlock.Index + 1,
|
||||
Timestamp: lastBlock.Timestamp + 1,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get test VM: %w", err)
|
||||
}
|
||||
|
||||
ic.VM.GasLimit = 100_0000_0000
|
||||
ic.VM.LoadScriptWithFlags(script, callflag.All)
|
||||
|
||||
var errStr string
|
||||
if err := ic.VM.Run(); err != nil {
|
||||
errStr = err.Error()
|
||||
}
|
||||
return &result.Invoke{
|
||||
State: ic.VM.State().String(),
|
||||
GasConsumed: ic.VM.GasConsumed(),
|
||||
Script: script,
|
||||
Stack: ic.VM.Estack().ToArray(),
|
||||
FaultException: errStr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *LocalClient) SendRawTransaction(tx *transaction.Transaction) (util.Uint256, error) {
|
||||
// We need to test that transaction was formed correctly to catch as many errors as we can.
|
||||
bs := tx.Bytes()
|
||||
_, err := transaction.NewTransactionFromBytes(bs)
|
||||
if err != nil {
|
||||
return tx.Hash(), fmt.Errorf("invalid transaction: %w", err)
|
||||
}
|
||||
|
||||
l.transactions = append(l.transactions, tx)
|
||||
return tx.Hash(), nil
|
||||
}
|
||||
|
||||
func (l *LocalClient) putTransactions() error {
|
||||
// 1. Prepare new block.
|
||||
lastBlock, err := l.bc.GetBlock(l.bc.CurrentBlockHash())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer func() { l.transactions = l.transactions[:0] }()
|
||||
|
||||
b := &block.Block{
|
||||
Header: block.Header{
|
||||
NextConsensus: l.accounts[0].Contract.ScriptHash(),
|
||||
Script: transaction.Witness{
|
||||
VerificationScript: l.accounts[0].Contract.Script,
|
||||
},
|
||||
Timestamp: lastBlock.Timestamp + 1,
|
||||
},
|
||||
Transactions: l.transactions,
|
||||
}
|
||||
|
||||
if l.bc.GetConfig().StateRootInHeader {
|
||||
b.StateRootEnabled = true
|
||||
b.PrevStateRoot = l.bc.GetStateModule().CurrentLocalStateRoot()
|
||||
}
|
||||
b.PrevHash = lastBlock.Hash()
|
||||
b.Index = lastBlock.Index + 1
|
||||
b.RebuildMerkleRoot()
|
||||
|
||||
// 2. Sign prepared block.
|
||||
var invocationScript []byte
|
||||
|
||||
magic := l.bc.GetConfig().Magic
|
||||
for _, acc := range l.accounts {
|
||||
sign := acc.PrivateKey().SignHashable(uint32(magic), b)
|
||||
invocationScript = append(invocationScript, byte(opcode.PUSHDATA1), 64)
|
||||
invocationScript = append(invocationScript, sign...)
|
||||
}
|
||||
b.Script.InvocationScript = invocationScript
|
||||
|
||||
// 3. Persist block.
|
||||
return l.bc.AddBlock(b)
|
||||
}
|
||||
|
||||
func InvokeFunction(c Client, h util.Uint160, method string, parameters []any, signers []transaction.Signer) (*result.Invoke, error) {
|
||||
w := io.NewBufBinWriter()
|
||||
emit.Array(w.BinWriter, parameters...)
|
||||
emit.AppCallNoArgs(w.BinWriter, h, method, callflag.All)
|
||||
if w.Err != nil {
|
||||
panic(fmt.Sprintf("BUG: invalid parameters for '%s': %v", method, w.Err))
|
||||
}
|
||||
return c.InvokeScript(w.Bytes(), signers)
|
||||
}
|
||||
|
||||
var errGetDesignatedByRoleResponse = errors.New("`getDesignatedByRole`: invalid response")
|
||||
|
||||
func GetDesignatedByRole(inv *invoker.Invoker, h util.Uint160, role noderoles.Role, u uint32) (keys.PublicKeys, error) {
|
||||
arr, err := unwrap.Array(inv.Call(h, "getDesignatedByRole", int64(role), int64(u)))
|
||||
if err != nil {
|
||||
return nil, errGetDesignatedByRoleResponse
|
||||
}
|
||||
|
||||
pubs := make(keys.PublicKeys, len(arr))
|
||||
for i := range arr {
|
||||
bs, err := arr[i].TryBytes()
|
||||
if err != nil {
|
||||
return nil, errGetDesignatedByRoleResponse
|
||||
}
|
||||
pubs[i], err = keys.NewPublicKeyFromBytes(bs, elliptic.P256())
|
||||
if err != nil {
|
||||
return nil, errGetDesignatedByRoleResponse
|
||||
}
|
||||
}
|
||||
|
||||
return pubs, nil
|
||||
}
|
||||
|
||||
func (l *LocalClient) Dump() (err error) {
|
||||
defer l.bc.Close()
|
||||
|
||||
f, err := os.Create(l.dumpPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
closeErr := f.Close()
|
||||
if err == nil && closeErr != nil {
|
||||
err = closeErr
|
||||
}
|
||||
}()
|
||||
|
||||
w := io.NewBinWriterFromIO(f)
|
||||
w.WriteU32LE(l.bc.BlockHeight() + 1)
|
||||
err = chaindump.Dump(l.bc, w, 0, l.bc.BlockHeight()+1)
|
||||
return
|
||||
}
|
|
@ -1,122 +0,0 @@
|
|||
package helper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Client represents N3 client interface capable of test-invoking scripts
|
||||
// and sending signed transactions to chain.
|
||||
type Client interface {
|
||||
invoker.RPCInvoke
|
||||
|
||||
GetBlockCount() (uint32, error)
|
||||
GetNativeContracts() ([]state.NativeContract, error)
|
||||
GetApplicationLog(util.Uint256, *trigger.Type) (*result.ApplicationLog, error)
|
||||
GetVersion() (*result.Version, error)
|
||||
SendRawTransaction(*transaction.Transaction) (util.Uint256, error)
|
||||
GetCommittee() (keys.PublicKeys, error)
|
||||
CalculateNetworkFee(tx *transaction.Transaction) (int64, error)
|
||||
}
|
||||
|
||||
type HashVUBPair struct {
|
||||
Hash util.Uint256
|
||||
Vub uint32
|
||||
}
|
||||
|
||||
type ClientContext struct {
|
||||
Client Client // a raw neo-go client OR a local chain implementation
|
||||
CommitteeAct *actor.Actor // committee actor with the Global witness scope
|
||||
ReadOnlyInvoker *invoker.Invoker // R/O contract invoker, does not contain any signer
|
||||
SentTxs []HashVUBPair
|
||||
}
|
||||
|
||||
func GetN3Client(v *viper.Viper) (Client, error) {
|
||||
// number of opened connections
|
||||
// by neo-go client per one host
|
||||
const (
|
||||
maxConnsPerHost = 10
|
||||
requestTimeout = time.Second * 10
|
||||
)
|
||||
|
||||
ctx := context.Background()
|
||||
endpoint := v.GetString(commonflags.EndpointFlag)
|
||||
if endpoint == "" {
|
||||
return nil, errors.New("missing endpoint")
|
||||
}
|
||||
c, err := rpcclient.New(ctx, endpoint, rpcclient.Options{
|
||||
MaxConnsPerHost: maxConnsPerHost,
|
||||
RequestTimeout: requestTimeout,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.Init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func DefaultClientContext(c Client, committeeAcc *wallet.Account) (*ClientContext, error) {
|
||||
commAct, err := NewActor(c, committeeAcc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ClientContext{
|
||||
Client: c,
|
||||
CommitteeAct: commAct,
|
||||
ReadOnlyInvoker: invoker.New(c, nil),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *ClientContext) SendTx(tx *transaction.Transaction, cmd *cobra.Command, await bool) error {
|
||||
h, err := c.Client.SendRawTransaction(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if h != tx.Hash() {
|
||||
return fmt.Errorf("sent and actual tx hashes mismatch:\n\tsent: %v\n\tactual: %v", tx.Hash().StringLE(), h.StringLE())
|
||||
}
|
||||
|
||||
c.SentTxs = append(c.SentTxs, HashVUBPair{Hash: h, Vub: tx.ValidUntilBlock})
|
||||
|
||||
if await {
|
||||
return c.AwaitTx(cmd)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClientContext) AwaitTx(cmd *cobra.Command) error {
|
||||
if len(c.SentTxs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if local, ok := c.Client.(*LocalClient); ok {
|
||||
if err := local.putTransactions(); err != nil {
|
||||
return fmt.Errorf("can't persist transactions: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
err := AwaitTx(cmd, c.Client, c.SentTxs)
|
||||
c.SentTxs = c.SentTxs[:0]
|
||||
|
||||
return err
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
package helper
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var NetmapConfigKeys = []string{
|
||||
netmap.EpochDurationConfig,
|
||||
netmap.MaxObjectSizeConfig,
|
||||
netmap.ContainerFeeConfig,
|
||||
netmap.ContainerAliasFeeConfig,
|
||||
netmap.IrCandidateFeeConfig,
|
||||
netmap.WithdrawFeeConfig,
|
||||
netmap.HomomorphicHashingDisabledKey,
|
||||
netmap.MaintenanceModeAllowedConfig,
|
||||
}
|
||||
|
||||
var errFailedToFetchListOfNetworkKeys = errors.New("can't fetch list of network config keys from the netmap contract")
|
||||
|
||||
func GetDefaultNetmapContractConfigMap() map[string]any {
|
||||
m := make(map[string]any)
|
||||
m[netmap.EpochDurationConfig] = viper.GetInt64(commonflags.EpochDurationInitFlag)
|
||||
m[netmap.MaxObjectSizeConfig] = viper.GetInt64(commonflags.MaxObjectSizeInitFlag)
|
||||
m[netmap.MaxECDataCountConfig] = viper.GetInt64(commonflags.MaxECDataCountFlag)
|
||||
m[netmap.MaxECParityCountConfig] = viper.GetInt64(commonflags.MaxECParityCounFlag)
|
||||
m[netmap.ContainerFeeConfig] = viper.GetInt64(commonflags.ContainerFeeInitFlag)
|
||||
m[netmap.ContainerAliasFeeConfig] = viper.GetInt64(commonflags.ContainerAliasFeeInitFlag)
|
||||
m[netmap.IrCandidateFeeConfig] = viper.GetInt64(commonflags.CandidateFeeInitFlag)
|
||||
m[netmap.WithdrawFeeConfig] = viper.GetInt64(commonflags.WithdrawFeeInitFlag)
|
||||
m[netmap.HomomorphicHashingDisabledKey] = viper.GetBool(commonflags.HomomorphicHashDisabledInitFlag)
|
||||
m[netmap.MaintenanceModeAllowedConfig] = viper.GetBool(commonflags.MaintenanceModeAllowedInitFlag)
|
||||
return m
|
||||
}
|
||||
|
||||
func ParseConfigFromNetmapContract(arr []stackitem.Item) (map[string][]byte, error) {
|
||||
m := make(map[string][]byte, len(arr))
|
||||
for _, param := range arr {
|
||||
tuple, ok := param.Value().([]stackitem.Item)
|
||||
if !ok || len(tuple) != 2 {
|
||||
return nil, errors.New("invalid ListConfig response from netmap contract")
|
||||
}
|
||||
|
||||
k, err := tuple[0].TryBytes()
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid config key from netmap contract")
|
||||
}
|
||||
|
||||
v, err := tuple[1].TryBytes()
|
||||
if err != nil {
|
||||
return nil, InvalidConfigValueErr(string(k))
|
||||
}
|
||||
m[string(k)] = v
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func InvalidConfigValueErr(key string) error {
|
||||
return fmt.Errorf("invalid %s config value from netmap contract", key)
|
||||
}
|
||||
|
||||
func EmitNewEpochCall(bw *io.BufBinWriter, wCtx *InitializeContext, nmHash util.Uint160) error {
|
||||
curr, err := unwrap.Int64(wCtx.ReadOnlyInvoker.Call(nmHash, "epoch"))
|
||||
if err != nil {
|
||||
return errors.New("can't fetch current epoch from the netmap contract")
|
||||
}
|
||||
|
||||
newEpoch := curr + 1
|
||||
wCtx.Command.Printf("Current epoch: %d, increase to %d.\n", curr, newEpoch)
|
||||
|
||||
// In NeoFS this is done via Notary contract. Here, however, we can form the
|
||||
// transaction locally.
|
||||
emit.AppCall(bw.BinWriter, nmHash, "newEpoch", callflag.All, newEpoch)
|
||||
return bw.Err
|
||||
}
|
||||
|
||||
func GetNetConfigFromNetmapContract(roInvoker *invoker.Invoker) ([]stackitem.Item, error) {
|
||||
r := management.NewReader(roInvoker)
|
||||
cs, err := r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get nns contract: %w", err)
|
||||
}
|
||||
nmHash, err := NNSResolveHash(roInvoker, cs.Hash, DomainOf(constants.NetmapContract))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't get netmap contract hash: %w", err)
|
||||
}
|
||||
arr, err := unwrap.Array(roInvoker.Call(nmHash, "listConfig"))
|
||||
if err != nil {
|
||||
return nil, errFailedToFetchListOfNetworkKeys
|
||||
}
|
||||
return arr, err
|
||||
}
|
||||
|
||||
func MergeNetmapConfig(roInvoker *invoker.Invoker, md map[string]any) error {
|
||||
arr, err := GetNetConfigFromNetmapContract(roInvoker)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m, err := ParseConfigFromNetmapContract(arr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range m {
|
||||
for _, key := range NetmapConfigKeys {
|
||||
if k == key {
|
||||
md[k] = v
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,177 +0,0 @@
|
|||
package helper
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func GetAlphabetWallets(v *viper.Viper, walletDir string) ([]*wallet.Wallet, error) {
|
||||
wallets, err := openAlphabetWallets(v, walletDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(wallets) > constants.MaxAlphabetNodes {
|
||||
return nil, ErrTooManyAlphabetNodes
|
||||
}
|
||||
return wallets, nil
|
||||
}
|
||||
|
||||
func openAlphabetWallets(v *viper.Viper, walletDir string) ([]*wallet.Wallet, error) {
|
||||
walletFiles, err := os.ReadDir(walletDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read alphabet wallets dir: %w", err)
|
||||
}
|
||||
|
||||
var size int
|
||||
loop:
|
||||
for i := 0; i < len(walletFiles); i++ {
|
||||
name := innerring.GlagoliticLetter(i).String() + ".json"
|
||||
for j := range walletFiles {
|
||||
if walletFiles[j].Name() == name {
|
||||
size++
|
||||
continue loop
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
if size == 0 {
|
||||
return nil, errors.New("alphabet wallets dir is empty (run `generate-alphabet` command first)")
|
||||
}
|
||||
|
||||
wallets := make([]*wallet.Wallet, size)
|
||||
for i := 0; i < size; i++ {
|
||||
letter := innerring.GlagoliticLetter(i).String()
|
||||
p := filepath.Join(walletDir, letter+".json")
|
||||
w, err := wallet.NewWalletFromFile(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't open wallet: %w", err)
|
||||
}
|
||||
|
||||
password, err := config.GetPassword(v, letter)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't fetch password: %w", err)
|
||||
}
|
||||
|
||||
for i := range w.Accounts {
|
||||
if err := w.Accounts[i].Decrypt(password, keys.NEP2ScryptParams()); err != nil {
|
||||
return nil, fmt.Errorf("can't unlock wallet: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
wallets[i] = w
|
||||
}
|
||||
|
||||
return wallets, nil
|
||||
}
|
||||
|
||||
func NewActor(c actor.RPCActor, committeeAcc *wallet.Account) (*actor.Actor, error) {
|
||||
return actor.New(c, []actor.SignerAccount{{
|
||||
Signer: transaction.Signer{
|
||||
Account: committeeAcc.Contract.ScriptHash(),
|
||||
Scopes: transaction.Global,
|
||||
},
|
||||
Account: committeeAcc,
|
||||
}})
|
||||
}
|
||||
|
||||
func ReadContract(ctrPath, ctrName string) (*ContractState, error) {
|
||||
rawNef, err := os.ReadFile(filepath.Join(ctrPath, ctrName+"_contract.nef"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read NEF file for %s contract: %w", ctrName, err)
|
||||
}
|
||||
rawManif, err := os.ReadFile(filepath.Join(ctrPath, "config.json"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read manifest file for %s contract: %w", ctrName, err)
|
||||
}
|
||||
|
||||
cs := &ContractState{
|
||||
RawNEF: rawNef,
|
||||
RawManifest: rawManif,
|
||||
}
|
||||
|
||||
return cs, cs.Parse()
|
||||
}
|
||||
|
||||
func readContractsFromArchive(file io.Reader, names []string) (map[string]*ContractState, error) {
|
||||
m := make(map[string]*ContractState, len(names))
|
||||
for i := range names {
|
||||
m[names[i]] = new(ContractState)
|
||||
}
|
||||
|
||||
gr, err := gzip.NewReader(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("contracts file must be tar.gz archive: %w", err)
|
||||
}
|
||||
|
||||
r := tar.NewReader(gr)
|
||||
for h, err := r.Next(); ; h, err = r.Next() {
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
dir, _ := filepath.Split(h.Name)
|
||||
ctrName := filepath.Base(dir)
|
||||
|
||||
cs, ok := m[ctrName]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.HasSuffix(h.Name, filepath.Join(ctrName, ctrName+"_contract.nef")):
|
||||
cs.RawNEF, err = io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read NEF file for %s contract: %w", ctrName, err)
|
||||
}
|
||||
case strings.HasSuffix(h.Name, "config.json"):
|
||||
cs.RawManifest, err = io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read manifest file for %s contract: %w", ctrName, err)
|
||||
}
|
||||
}
|
||||
m[ctrName] = cs
|
||||
}
|
||||
|
||||
for ctrName, cs := range m {
|
||||
if cs.RawNEF == nil {
|
||||
return nil, fmt.Errorf("NEF for %s contract wasn't found", ctrName)
|
||||
}
|
||||
if cs.RawManifest == nil {
|
||||
return nil, fmt.Errorf("manifest for %s contract wasn't found", ctrName)
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func GetAlphabetNNSDomain(i int) string {
|
||||
return constants.AlphabetContract + strconv.FormatUint(uint64(i), 10) + ".frostfs"
|
||||
}
|
||||
|
||||
func ParseGASAmount(s string) (fixedn.Fixed8, error) {
|
||||
gasAmount, err := fixedn.Fixed8FromString(s)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid GAS amount %s: %w", s, err)
|
||||
}
|
||||
if gasAmount <= 0 {
|
||||
return 0, fmt.Errorf("GAS amount must be positive (got %d)", gasAmount)
|
||||
}
|
||||
return gasAmount, nil
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
package helper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func InitializeContractWallet(v *viper.Viper, walletDir string) (*wallet.Wallet, error) {
|
||||
password, err := config.GetPassword(v, constants.ContractWalletPasswordKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w, err := wallet.NewWallet(filepath.Join(walletDir, constants.ContractWalletFilename))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
acc, err := wallet.NewAccount()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = acc.Encrypt(password, keys.NEP2ScryptParams())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w.AddAccount(acc)
|
||||
if err := w.SavePretty(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func openContractWallet(v *viper.Viper, cmd *cobra.Command, walletDir string) (*wallet.Wallet, error) {
|
||||
p := filepath.Join(walletDir, constants.ContractWalletFilename)
|
||||
w, err := wallet.NewWalletFromFile(p)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("can't open wallet: %w", err)
|
||||
}
|
||||
|
||||
cmd.Printf("Contract group wallet is missing, initialize at %s\n", p)
|
||||
return InitializeContractWallet(v, walletDir)
|
||||
}
|
||||
|
||||
password, err := config.GetPassword(v, constants.ContractWalletPasswordKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range w.Accounts {
|
||||
if err := w.Accounts[i].Decrypt(password, keys.NEP2ScryptParams()); err != nil {
|
||||
return nil, fmt.Errorf("can't unlock wallet: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func getWallet(cmd *cobra.Command, v *viper.Viper, needContracts bool, walletDir string) (*wallet.Wallet, error) {
|
||||
if !needContracts {
|
||||
return nil, nil
|
||||
}
|
||||
return openContractWallet(v, cmd, walletDir)
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
package initialize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func initializeSideChainCmd(cmd *cobra.Command, _ []string) error {
|
||||
initCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("initialization error: %w", err)
|
||||
}
|
||||
defer initCtx.Close()
|
||||
|
||||
// 1. Transfer funds to committee accounts.
|
||||
cmd.Println("Stage 1: transfer GAS to alphabet nodes.")
|
||||
if err := transferFunds(initCtx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Println("Stage 2: set notary and alphabet nodes in designate contract.")
|
||||
if err := setNotaryAndAlphabetNodes(initCtx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. Deploy NNS contract.
|
||||
cmd.Println("Stage 3: deploy NNS contract.")
|
||||
if err := helper.DeployNNS(initCtx, constants.DeployMethodName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 4. Deploy NeoFS contracts.
|
||||
cmd.Println("Stage 4: deploy NeoFS contracts.")
|
||||
if err := deployContracts(initCtx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Println("Stage 4.1: Transfer GAS to proxy contract.")
|
||||
if err := transferGASToProxy(initCtx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Println("Stage 5: register candidates.")
|
||||
if err := registerCandidates(initCtx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Println("Stage 6: transfer NEO to alphabet contracts.")
|
||||
if err := transferNEOToAlphabetContracts(initCtx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Println("Stage 7: set addresses in NNS.")
|
||||
return setNNS(initCtx)
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
package initialize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
)
|
||||
|
||||
func deployContracts(c *helper.InitializeContext) error {
|
||||
alphaCs := c.GetContract(constants.AlphabetContract)
|
||||
|
||||
var keysParam []any
|
||||
|
||||
baseGroups := alphaCs.Manifest.Groups
|
||||
|
||||
// alphabet contracts should be deployed by individual nodes to get different hashes.
|
||||
for i, acc := range c.Accounts {
|
||||
ctrHash := state.CreateContractHash(acc.Contract.ScriptHash(), alphaCs.NEF.Checksum, alphaCs.Manifest.Name)
|
||||
if c.IsUpdated(ctrHash, alphaCs) {
|
||||
c.Command.Printf("Alphabet contract #%d is already deployed.\n", i)
|
||||
continue
|
||||
}
|
||||
|
||||
alphaCs.Manifest.Groups = baseGroups
|
||||
err := helper.AddManifestGroup(c.ContractWallet, ctrHash, alphaCs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't sign manifest group: %v", err)
|
||||
}
|
||||
|
||||
keysParam = append(keysParam, acc.PrivateKey().PublicKey().Bytes())
|
||||
params := helper.GetContractDeployParameters(alphaCs, c.GetAlphabetDeployItems(i, len(c.Wallets)))
|
||||
|
||||
act, err := actor.NewSimple(c.Client, acc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create actor: %w", err)
|
||||
}
|
||||
|
||||
txHash, vub, err := act.SendCall(management.Hash, constants.DeployMethodName, params...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't deploy alphabet #%d contract: %w", i, err)
|
||||
}
|
||||
|
||||
c.SentTxs = append(c.SentTxs, helper.HashVUBPair{Hash: txHash, Vub: vub})
|
||||
}
|
||||
|
||||
for _, ctrName := range constants.ContractList {
|
||||
cs := c.GetContract(ctrName)
|
||||
|
||||
ctrHash := cs.Hash
|
||||
if c.IsUpdated(ctrHash, cs) {
|
||||
c.Command.Printf("%s contract is already deployed.\n", ctrName)
|
||||
continue
|
||||
}
|
||||
|
||||
err := helper.AddManifestGroup(c.ContractWallet, ctrHash, cs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't sign manifest group: %v", err)
|
||||
}
|
||||
|
||||
args, err := helper.GetContractDeployData(c, ctrName, keysParam, constants.DeployMethodName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: getting deploy params: %v", ctrName, err)
|
||||
}
|
||||
params := helper.GetContractDeployParameters(cs, args)
|
||||
res, err := c.CommitteeAct.MakeCall(management.Hash, constants.DeployMethodName, params...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't deploy %s contract: %w", ctrName, err)
|
||||
}
|
||||
|
||||
if err := c.SendCommitteeTx(res.Script, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return c.AwaitTx()
|
||||
}
|
|
@ -1,135 +0,0 @@
|
|||
package initialize
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
)
|
||||
|
||||
func setNNS(c *helper.InitializeContext) error {
|
||||
r := management.NewReader(c.ReadOnlyInvoker)
|
||||
nnsCs, err := r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ok, err := c.NNSRootRegistered(nnsCs.Hash, "frostfs")
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !ok {
|
||||
bw := io.NewBufBinWriter()
|
||||
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
|
||||
"frostfs", c.CommitteeAcc.Contract.ScriptHash(),
|
||||
constants.FrostfsOpsEmail, constants.NNSRefreshDefVal, constants.NNSRetryDefVal,
|
||||
int64(constants.DefaultExpirationTime), constants.NNSTtlDefVal)
|
||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||
if err := c.SendCommitteeTx(bw.Bytes(), true); err != nil {
|
||||
return fmt.Errorf("can't add domain root to NNS: %w", err)
|
||||
}
|
||||
if err := c.AwaitTx(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
alphaCs := c.GetContract(constants.AlphabetContract)
|
||||
for i, acc := range c.Accounts {
|
||||
alphaCs.Hash = state.CreateContractHash(acc.Contract.ScriptHash(), alphaCs.NEF.Checksum, alphaCs.Manifest.Name)
|
||||
|
||||
domain := helper.GetAlphabetNNSDomain(i)
|
||||
if err := nnsRegisterDomain(c, nnsCs.Hash, alphaCs.Hash, domain); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Command.Printf("NNS: Set %s -> %s\n", domain, alphaCs.Hash.StringLE())
|
||||
}
|
||||
|
||||
for _, ctrName := range constants.ContractList {
|
||||
cs := c.GetContract(ctrName)
|
||||
|
||||
domain := ctrName + ".frostfs"
|
||||
if err := nnsRegisterDomain(c, nnsCs.Hash, cs.Hash, domain); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Command.Printf("NNS: Set %s -> %s\n", domain, cs.Hash.StringLE())
|
||||
}
|
||||
|
||||
groupKey := c.ContractWallet.Accounts[0].PrivateKey().PublicKey()
|
||||
err = updateNNSGroup(c, nnsCs.Hash, groupKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Command.Printf("NNS: Set %s -> %s\n", morphClient.NNSGroupKeyName, hex.EncodeToString(groupKey.Bytes()))
|
||||
|
||||
return c.AwaitTx()
|
||||
}
|
||||
|
||||
func updateNNSGroup(c *helper.InitializeContext, nnsHash util.Uint160, pub *keys.PublicKey) error {
|
||||
bw := io.NewBufBinWriter()
|
||||
keyAlreadyAdded, domainRegCodeEmitted, err := c.EmitUpdateNNSGroupScript(bw, nnsHash, pub)
|
||||
if keyAlreadyAdded || err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
script := bw.Bytes()
|
||||
if domainRegCodeEmitted {
|
||||
w := io.NewBufBinWriter()
|
||||
emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1})
|
||||
wrapRegisterScriptWithPrice(w, nnsHash, script)
|
||||
script = w.Bytes()
|
||||
}
|
||||
|
||||
return c.SendCommitteeTx(script, true)
|
||||
}
|
||||
|
||||
// wrapRegisterScriptWithPrice wraps a given script with `getPrice`/`setPrice` calls for NNS.
|
||||
// It is intended to be used for a single transaction, and not as a part of other scripts.
|
||||
// It is assumed that script already contains static slot initialization code, the first one
|
||||
// (with index 0) is used to store the price.
|
||||
func wrapRegisterScriptWithPrice(w *io.BufBinWriter, nnsHash util.Uint160, s []byte) {
|
||||
if len(s) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
emit.AppCall(w.BinWriter, nnsHash, "getPrice", callflag.All)
|
||||
emit.Opcodes(w.BinWriter, opcode.STSFLD0)
|
||||
emit.AppCall(w.BinWriter, nnsHash, "setPrice", callflag.All, 1)
|
||||
|
||||
w.WriteBytes(s)
|
||||
|
||||
emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.PUSH1, opcode.PACK)
|
||||
emit.AppCallNoArgs(w.BinWriter, nnsHash, "setPrice", callflag.All)
|
||||
|
||||
if w.Err != nil {
|
||||
panic(fmt.Errorf("BUG: can't wrap register script: %w", w.Err))
|
||||
}
|
||||
}
|
||||
|
||||
func nnsRegisterDomain(c *helper.InitializeContext, nnsHash, expectedHash util.Uint160, domain string) error {
|
||||
script, ok, err := c.NNSRegisterDomainScript(nnsHash, expectedHash, domain)
|
||||
if ok || err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w := io.NewBufBinWriter()
|
||||
emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1})
|
||||
wrapRegisterScriptWithPrice(w, nnsHash, script)
|
||||
|
||||
emit.AppCall(w.BinWriter, nnsHash, "deleteRecords", callflag.All, domain, int64(nns.TXT))
|
||||
emit.AppCall(w.BinWriter, nnsHash, "addRecord", callflag.All,
|
||||
domain, int64(nns.TXT), expectedHash.StringLE())
|
||||
emit.AppCall(w.BinWriter, nnsHash, "addRecord", callflag.All,
|
||||
domain, int64(nns.TXT), address.Uint160ToString(expectedHash))
|
||||
return c.SendCommitteeTx(w.Bytes(), true)
|
||||
}
|
|
@ -1,171 +0,0 @@
|
|||
package initialize
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
)
|
||||
|
||||
// initialAlphabetNEOAmount represents the total amount of GAS distributed between alphabet nodes.
|
||||
const (
|
||||
initialAlphabetNEOAmount = native.NEOTotalSupply
|
||||
registerBatchSize = transaction.MaxAttributes - 1
|
||||
)
|
||||
|
||||
func registerCandidateRange(c *helper.InitializeContext, start, end int) error {
|
||||
regPrice, err := getCandidateRegisterPrice(c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch registration price: %w", err)
|
||||
}
|
||||
|
||||
w := io.NewBufBinWriter()
|
||||
emit.AppCall(w.BinWriter, neo.Hash, "setRegisterPrice", callflag.States, 1)
|
||||
for _, acc := range c.Accounts[start:end] {
|
||||
emit.AppCall(w.BinWriter, neo.Hash, "registerCandidate", callflag.States, acc.PrivateKey().PublicKey().Bytes())
|
||||
emit.Opcodes(w.BinWriter, opcode.ASSERT)
|
||||
}
|
||||
emit.AppCall(w.BinWriter, neo.Hash, "setRegisterPrice", callflag.States, regPrice)
|
||||
if w.Err != nil {
|
||||
panic(fmt.Sprintf("BUG: %v", w.Err))
|
||||
}
|
||||
|
||||
signers := []actor.SignerAccount{{
|
||||
Signer: c.GetSigner(false, c.CommitteeAcc),
|
||||
Account: c.CommitteeAcc,
|
||||
}}
|
||||
for _, acc := range c.Accounts[start:end] {
|
||||
signers = append(signers, actor.SignerAccount{
|
||||
Signer: transaction.Signer{
|
||||
Account: acc.Contract.ScriptHash(),
|
||||
Scopes: transaction.CustomContracts,
|
||||
AllowedContracts: []util.Uint160{neo.Hash},
|
||||
},
|
||||
Account: acc,
|
||||
})
|
||||
}
|
||||
|
||||
act, err := actor.New(c.Client, signers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create actor: %w", err)
|
||||
}
|
||||
tx, err := act.MakeRun(w.Bytes())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create tx: %w", err)
|
||||
}
|
||||
if err := c.MultiSign(tx, constants.CommitteeAccountName); err != nil {
|
||||
return fmt.Errorf("can't sign a transaction: %w", err)
|
||||
}
|
||||
|
||||
network := c.CommitteeAct.GetNetwork()
|
||||
for _, acc := range c.Accounts[start:end] {
|
||||
if err := acc.SignTx(network, tx); err != nil {
|
||||
return fmt.Errorf("can't sign a transaction: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return c.SendTx(tx, c.Command, true)
|
||||
}
|
||||
|
||||
func registerCandidates(c *helper.InitializeContext) error {
|
||||
cc, err := unwrap.Array(c.ReadOnlyInvoker.Call(neo.Hash, "getCandidates"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("`getCandidates`: %w", err)
|
||||
}
|
||||
|
||||
need := len(c.Accounts)
|
||||
have := len(cc)
|
||||
|
||||
if need == have {
|
||||
c.Command.Println("Candidates are already registered.")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Register candidates in batches in order to overcome the signers amount limit.
|
||||
// See: https://github.com/nspcc-dev/neo-go/blob/master/pkg/core/transaction/transaction.go#L27
|
||||
for i := 0; i < need; i += registerBatchSize {
|
||||
start, end := i, min(i+registerBatchSize, need)
|
||||
// This check is sound because transactions are accepted/rejected atomically.
|
||||
if have >= end {
|
||||
continue
|
||||
}
|
||||
if err := registerCandidateRange(c, start, end); err != nil {
|
||||
return fmt.Errorf("registering candidates %d..%d: %q", start, end-1, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func transferNEOToAlphabetContracts(c *helper.InitializeContext) error {
|
||||
neoHash := neo.Hash
|
||||
|
||||
ok, err := transferNEOFinished(c, neoHash)
|
||||
if ok || err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cs := c.GetContract(constants.AlphabetContract)
|
||||
amount := initialAlphabetNEOAmount / len(c.Wallets)
|
||||
|
||||
bw := io.NewBufBinWriter()
|
||||
for _, acc := range c.Accounts {
|
||||
h := state.CreateContractHash(acc.Contract.ScriptHash(), cs.NEF.Checksum, cs.Manifest.Name)
|
||||
emit.AppCall(bw.BinWriter, neoHash, "transfer", callflag.All,
|
||||
c.CommitteeAcc.Contract.ScriptHash(), h, int64(amount), nil)
|
||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||
}
|
||||
|
||||
if err := c.SendCommitteeTx(bw.Bytes(), false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.AwaitTx()
|
||||
}
|
||||
|
||||
func transferNEOFinished(c *helper.InitializeContext, neoHash util.Uint160) (bool, error) {
|
||||
r := nep17.NewReader(c.ReadOnlyInvoker, neoHash)
|
||||
bal, err := r.BalanceOf(c.CommitteeAcc.Contract.ScriptHash())
|
||||
return bal.Cmp(big.NewInt(native.NEOTotalSupply)) == -1, err
|
||||
}
|
||||
|
||||
var errGetPriceInvalid = errors.New("`getRegisterPrice`: invalid response")
|
||||
|
||||
func getCandidateRegisterPrice(c *helper.InitializeContext) (int64, error) {
|
||||
switch c.Client.(type) {
|
||||
case *rpcclient.Client:
|
||||
inv := invoker.New(c.Client, nil)
|
||||
reader := neo.NewReader(inv)
|
||||
return reader.GetRegisterPrice()
|
||||
default:
|
||||
neoHash := neo.Hash
|
||||
res, err := helper.InvokeFunction(c.Client, neoHash, "getRegisterPrice", nil, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(res.Stack) == 0 {
|
||||
return 0, errGetPriceInvalid
|
||||
}
|
||||
bi, err := res.Stack[0].TryInteger()
|
||||
if err != nil || !bi.IsInt64() {
|
||||
return 0, errGetPriceInvalid
|
||||
}
|
||||
return bi.Int64(), nil
|
||||
}
|
||||
}
|
|
@ -1,155 +0,0 @@
|
|||
package initialize
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
cmdConfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/generate"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/netmap"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/node"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/policy"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
contractsPath = "../../../../../../contract/frostfs-contract-v0.18.0.tar.gz"
|
||||
protoFileName = "proto.yml"
|
||||
)
|
||||
|
||||
func TestInitialize(t *testing.T) {
|
||||
// This test needs frostfs-contract tarball, so it is skipped by default.
|
||||
// It is here for performing local testing after the changes.
|
||||
t.Skip()
|
||||
|
||||
t.Run("1 nodes", func(t *testing.T) {
|
||||
testInitialize(t, 1)
|
||||
})
|
||||
t.Run("4 nodes", func(t *testing.T) {
|
||||
testInitialize(t, 4)
|
||||
})
|
||||
t.Run("7 nodes", func(t *testing.T) {
|
||||
testInitialize(t, 7)
|
||||
})
|
||||
t.Run("16 nodes", func(t *testing.T) {
|
||||
testInitialize(t, 16)
|
||||
})
|
||||
t.Run("max nodes", func(t *testing.T) {
|
||||
testInitialize(t, constants.MaxAlphabetNodes)
|
||||
})
|
||||
t.Run("too many nodes", func(t *testing.T) {
|
||||
require.ErrorIs(t, generateTestData(t.TempDir(), constants.MaxAlphabetNodes+1), helper.ErrTooManyAlphabetNodes)
|
||||
})
|
||||
}
|
||||
|
||||
func testInitialize(t *testing.T, committeeSize int) {
|
||||
testdataDir := t.TempDir()
|
||||
v := viper.GetViper()
|
||||
|
||||
require.NoError(t, generateTestData(testdataDir, committeeSize))
|
||||
v.Set(constants.ProtoConfigPath, filepath.Join(testdataDir, protoFileName))
|
||||
|
||||
// Set to the path or remove the next statement to download from the network.
|
||||
require.NoError(t, Cmd.Flags().Set(commonflags.ContractsInitFlag, contractsPath))
|
||||
|
||||
dumpPath := filepath.Join(testdataDir, "out")
|
||||
require.NoError(t, Cmd.Flags().Set(commonflags.LocalDumpFlag, dumpPath))
|
||||
v.Set(commonflags.AlphabetWalletsFlag, testdataDir)
|
||||
v.Set(commonflags.EpochDurationInitFlag, 1)
|
||||
v.Set(commonflags.MaxObjectSizeInitFlag, 1024)
|
||||
|
||||
setTestCredentials(v, committeeSize)
|
||||
require.NoError(t, initializeSideChainCmd(Cmd, nil))
|
||||
|
||||
t.Run("force-new-epoch", func(t *testing.T) {
|
||||
require.NoError(t, netmap.ForceNewEpoch.Flags().Set(commonflags.LocalDumpFlag, dumpPath))
|
||||
require.NoError(t, netmap.ForceNewEpochCmd(netmap.ForceNewEpoch, nil))
|
||||
})
|
||||
t.Run("set-config", func(t *testing.T) {
|
||||
require.NoError(t, cmdConfig.SetCmd.Flags().Set(commonflags.LocalDumpFlag, dumpPath))
|
||||
require.NoError(t, cmdConfig.SetConfigCmd(cmdConfig.SetCmd, []string{"MaintenanceModeAllowed=true"}))
|
||||
})
|
||||
t.Run("set-policy", func(t *testing.T) {
|
||||
require.NoError(t, policy.Set.Flags().Set(commonflags.LocalDumpFlag, dumpPath))
|
||||
require.NoError(t, policy.SetPolicyCmd(policy.Set, []string{"ExecFeeFactor=1"}))
|
||||
})
|
||||
t.Run("remove-node", func(t *testing.T) {
|
||||
pk, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
pub := hex.EncodeToString(pk.PublicKey().Bytes())
|
||||
require.NoError(t, node.RemoveCmd.Flags().Set(commonflags.LocalDumpFlag, dumpPath))
|
||||
require.NoError(t, node.RemoveNodesCmd(node.RemoveCmd, []string{pub}))
|
||||
})
|
||||
}
|
||||
|
||||
func generateTestData(dir string, size int) error {
|
||||
v := viper.GetViper()
|
||||
v.Set(commonflags.AlphabetWalletsFlag, dir)
|
||||
|
||||
sizeStr := strconv.FormatUint(uint64(size), 10)
|
||||
if err := generate.GenerateAlphabetCmd.Flags().Set(commonflags.AlphabetSizeFlag, sizeStr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
setTestCredentials(v, size)
|
||||
if err := generate.AlphabetCreds(generate.GenerateAlphabetCmd, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var pubs []string
|
||||
for i := 0; i < size; i++ {
|
||||
p := filepath.Join(dir, innerring.GlagoliticLetter(i).String()+".json")
|
||||
w, err := wallet.NewWalletFromFile(p)
|
||||
if err != nil {
|
||||
return fmt.Errorf("wallet doesn't exist: %w", err)
|
||||
}
|
||||
for _, acc := range w.Accounts {
|
||||
if acc.Label == constants.SingleAccountName {
|
||||
pub, ok := vm.ParseSignatureContract(acc.Contract.Script)
|
||||
if !ok {
|
||||
return fmt.Errorf("could not parse signature script for %s", acc.Address)
|
||||
}
|
||||
pubs = append(pubs, hex.EncodeToString(pub))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg := config.Config{}
|
||||
cfg.ProtocolConfiguration.Magic = 12345
|
||||
cfg.ProtocolConfiguration.ValidatorsCount = uint32(size)
|
||||
cfg.ProtocolConfiguration.TimePerBlock = time.Second
|
||||
cfg.ProtocolConfiguration.StandbyCommittee = pubs // sorted by glagolic letters
|
||||
cfg.ProtocolConfiguration.P2PSigExtensions = true
|
||||
cfg.ProtocolConfiguration.VerifyTransactions = true
|
||||
data, err := yaml.Marshal(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
protoPath := filepath.Join(dir, protoFileName)
|
||||
return os.WriteFile(protoPath, data, os.ModePerm)
|
||||
}
|
||||
|
||||
func setTestCredentials(v *viper.Viper, size int) {
|
||||
for i := 0; i < size; i++ {
|
||||
v.Set("credentials."+innerring.GlagoliticLetter(i).String(), strconv.FormatUint(uint64(i), 10))
|
||||
}
|
||||
v.Set("credentials.contract", constants.TestContractPassword)
|
||||
}
|
|
@ -1,144 +0,0 @@
|
|||
package initialize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
)
|
||||
|
||||
const (
|
||||
gasInitialTotalSupply = 30000000 * native.GASFactor
|
||||
// initialAlphabetGASAmount represents the amount of GAS given to each alphabet node.
|
||||
initialAlphabetGASAmount = 10_000 * native.GASFactor
|
||||
// initialProxyGASAmount represents the amount of GAS given to a proxy contract.
|
||||
initialProxyGASAmount = 50_000 * native.GASFactor
|
||||
)
|
||||
|
||||
func transferFunds(c *helper.InitializeContext) error {
|
||||
ok, err := transferFundsFinished(c)
|
||||
if ok || err != nil {
|
||||
if err == nil {
|
||||
c.Command.Println("Stage 1: already performed.")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var transfers []transferTarget
|
||||
for _, acc := range c.Accounts {
|
||||
to := acc.Contract.ScriptHash()
|
||||
transfers = append(transfers,
|
||||
transferTarget{
|
||||
Token: gas.Hash,
|
||||
Address: to,
|
||||
Amount: initialAlphabetGASAmount,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// It is convenient to have all funds at the committee account.
|
||||
transfers = append(transfers,
|
||||
transferTarget{
|
||||
Token: gas.Hash,
|
||||
Address: c.CommitteeAcc.Contract.ScriptHash(),
|
||||
Amount: (gasInitialTotalSupply - initialAlphabetGASAmount*int64(len(c.Wallets))) / 2,
|
||||
},
|
||||
transferTarget{
|
||||
Token: neo.Hash,
|
||||
Address: c.CommitteeAcc.Contract.ScriptHash(),
|
||||
Amount: native.NEOTotalSupply,
|
||||
},
|
||||
)
|
||||
|
||||
tx, err := createNEP17MultiTransferTx(c.Client, c.ConsensusAcc, transfers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create transfer transaction: %w", err)
|
||||
}
|
||||
|
||||
if err := c.MultiSignAndSend(tx, constants.ConsensusAccountName); err != nil {
|
||||
return fmt.Errorf("can't send transfer transaction: %w", err)
|
||||
}
|
||||
|
||||
return c.AwaitTx()
|
||||
}
|
||||
|
||||
func transferFundsFinished(c *helper.InitializeContext) (bool, error) {
|
||||
acc := c.Accounts[0]
|
||||
|
||||
r := nep17.NewReader(c.ReadOnlyInvoker, gas.Hash)
|
||||
res, err := r.BalanceOf(acc.Contract.ScriptHash())
|
||||
return res.Cmp(big.NewInt(initialAlphabetGASAmount/2)) == 1, err
|
||||
}
|
||||
|
||||
func transferGASToProxy(c *helper.InitializeContext) error {
|
||||
proxyCs := c.GetContract(constants.ProxyContract)
|
||||
|
||||
r := nep17.NewReader(c.ReadOnlyInvoker, gas.Hash)
|
||||
bal, err := r.BalanceOf(proxyCs.Hash)
|
||||
if err != nil || bal.Sign() > 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
tx, err := createNEP17MultiTransferTx(c.Client, c.CommitteeAcc, []transferTarget{{
|
||||
Token: gas.Hash,
|
||||
Address: proxyCs.Hash,
|
||||
Amount: initialProxyGASAmount,
|
||||
}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.MultiSignAndSend(tx, constants.CommitteeAccountName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.AwaitTx()
|
||||
}
|
||||
|
||||
type transferTarget struct {
|
||||
Token util.Uint160
|
||||
Address util.Uint160
|
||||
Amount int64
|
||||
Data any
|
||||
}
|
||||
|
||||
func createNEP17MultiTransferTx(c helper.Client, acc *wallet.Account, recipients []transferTarget) (*transaction.Transaction, error) {
|
||||
from := acc.Contract.ScriptHash()
|
||||
|
||||
w := io.NewBufBinWriter()
|
||||
for i := range recipients {
|
||||
emit.AppCall(w.BinWriter, recipients[i].Token, "transfer", callflag.All,
|
||||
from, recipients[i].Address, recipients[i].Amount, recipients[i].Data)
|
||||
emit.Opcodes(w.BinWriter, opcode.ASSERT)
|
||||
}
|
||||
if w.Err != nil {
|
||||
return nil, fmt.Errorf("failed to create transfer script: %w", w.Err)
|
||||
}
|
||||
|
||||
signers := []actor.SignerAccount{{
|
||||
Signer: transaction.Signer{
|
||||
Account: acc.Contract.ScriptHash(),
|
||||
Scopes: transaction.CalledByEntry,
|
||||
},
|
||||
Account: acc,
|
||||
}}
|
||||
|
||||
act, err := actor.New(c, signers)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't create actor: %w", err)
|
||||
}
|
||||
return act.MakeRun(w.Bytes())
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
package initialize
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
maxObjectSizeCLIFlag = "max-object-size"
|
||||
epochDurationCLIFlag = "epoch-duration"
|
||||
containerFeeCLIFlag = "container-fee"
|
||||
containerAliasFeeCLIFlag = "container-alias-fee"
|
||||
candidateFeeCLIFlag = "candidate-fee"
|
||||
homomorphicHashDisabledCLIFlag = "homomorphic-disabled"
|
||||
withdrawFeeCLIFlag = "withdraw-fee"
|
||||
)
|
||||
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Initialize side chain network with smart-contracts and network settings",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.EpochDurationInitFlag, cmd.Flags().Lookup(epochDurationCLIFlag))
|
||||
_ = viper.BindPFlag(commonflags.MaxObjectSizeInitFlag, cmd.Flags().Lookup(maxObjectSizeCLIFlag))
|
||||
_ = viper.BindPFlag(commonflags.MaxECDataCountFlag, cmd.Flags().Lookup(commonflags.MaxECDataCountFlag))
|
||||
_ = viper.BindPFlag(commonflags.MaxECParityCounFlag, cmd.Flags().Lookup(commonflags.MaxECParityCounFlag))
|
||||
_ = viper.BindPFlag(commonflags.HomomorphicHashDisabledInitFlag, cmd.Flags().Lookup(homomorphicHashDisabledCLIFlag))
|
||||
_ = viper.BindPFlag(commonflags.CandidateFeeInitFlag, cmd.Flags().Lookup(candidateFeeCLIFlag))
|
||||
_ = viper.BindPFlag(commonflags.ContainerFeeInitFlag, cmd.Flags().Lookup(containerFeeCLIFlag))
|
||||
_ = viper.BindPFlag(commonflags.ContainerAliasFeeInitFlag, cmd.Flags().Lookup(containerAliasFeeCLIFlag))
|
||||
_ = viper.BindPFlag(commonflags.WithdrawFeeInitFlag, cmd.Flags().Lookup(withdrawFeeCLIFlag))
|
||||
_ = viper.BindPFlag(constants.ProtoConfigPath, cmd.Flags().Lookup(constants.ProtoConfigPath))
|
||||
},
|
||||
RunE: initializeSideChainCmd,
|
||||
}
|
||||
|
||||
func initInitCmd() {
|
||||
Cmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
Cmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
Cmd.Flags().String(commonflags.ContractsInitFlag, "", commonflags.ContractsInitFlagDesc)
|
||||
Cmd.Flags().String(commonflags.ContractsURLFlag, "", commonflags.ContractsURLFlagDesc)
|
||||
Cmd.Flags().Uint(epochDurationCLIFlag, 240, "Amount of side chain blocks in one FrostFS epoch")
|
||||
Cmd.Flags().Uint(maxObjectSizeCLIFlag, 67108864, "Max single object size in bytes")
|
||||
Cmd.Flags().Bool(homomorphicHashDisabledCLIFlag, false, "Disable object homomorphic hashing")
|
||||
// Defaults are taken from neo-preodolenie.
|
||||
Cmd.Flags().Uint64(containerFeeCLIFlag, 1000, "Container registration fee")
|
||||
Cmd.Flags().Uint64(containerAliasFeeCLIFlag, 500, "Container alias fee")
|
||||
Cmd.Flags().String(constants.ProtoConfigPath, "", "Path to the consensus node configuration")
|
||||
Cmd.Flags().String(commonflags.LocalDumpFlag, "", "Path to the blocks dump file")
|
||||
Cmd.MarkFlagsMutuallyExclusive(commonflags.ContractsInitFlag, commonflags.ContractsURLFlag)
|
||||
}
|
||||
|
||||
func init() {
|
||||
initInitCmd()
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
package netmap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func ForceNewEpochCmd(cmd *cobra.Command, _ []string) error {
|
||||
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't to initialize context: %w", err)
|
||||
}
|
||||
|
||||
r := management.NewReader(wCtx.ReadOnlyInvoker)
|
||||
cs, err := r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get NNS contract info: %w", err)
|
||||
}
|
||||
|
||||
nmHash, err := helper.NNSResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, helper.DomainOf(constants.NetmapContract))
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
||||
}
|
||||
|
||||
bw := io.NewBufBinWriter()
|
||||
if err := helper.EmitNewEpochCall(bw, wCtx, nmHash); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = wCtx.SendConsensusTx(bw.Bytes()); err == nil {
|
||||
err = wCtx.AwaitTx()
|
||||
}
|
||||
if err != nil && strings.Contains(err.Error(), "invalid epoch") {
|
||||
cmd.Println("Epoch has already ticked.")
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
package netmap
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func listNetmapCandidatesNodes(cmd *cobra.Command, _ []string) {
|
||||
c, err := helper.GetN3Client(viper.GetViper())
|
||||
commonCmd.ExitOnErr(cmd, "can't create N3 client: %w", err)
|
||||
|
||||
inv := invoker.New(c, nil)
|
||||
r := management.NewReader(inv)
|
||||
|
||||
cs, err := r.GetContractByID(1)
|
||||
commonCmd.ExitOnErr(cmd, "can't get NNS contract info: %w", err)
|
||||
|
||||
nmHash, err := helper.NNSResolveHash(inv, cs.Hash, helper.DomainOf(constants.NetmapContract))
|
||||
commonCmd.ExitOnErr(cmd, "can't get netmap contract hash: %w", err)
|
||||
|
||||
res, err := inv.Call(nmHash, "netmapCandidates")
|
||||
commonCmd.ExitOnErr(cmd, "can't fetch list of network config keys from the netmap contract", err)
|
||||
nm, err := netmap.DecodeNetMap(res.Stack)
|
||||
commonCmd.ExitOnErr(cmd, "unable to decode netmap: %w", err)
|
||||
commonCmd.PrettyPrintNetMap(cmd, *nm, !viper.GetBool(commonflags.Verbose))
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package netmap
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
CandidatesCmd = &cobra.Command{
|
||||
Use: "netmap-candidates",
|
||||
Short: "List netmap candidates nodes",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
},
|
||||
Run: listNetmapCandidatesNodes,
|
||||
}
|
||||
ForceNewEpoch = &cobra.Command{
|
||||
Use: "force-new-epoch",
|
||||
Short: "Create new FrostFS epoch event in the side chain",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
RunE: ForceNewEpochCmd,
|
||||
}
|
||||
)
|
||||
|
||||
func initNetmapCandidatesCmd() {
|
||||
CandidatesCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
}
|
||||
|
||||
func initForceNewEpochCmd() {
|
||||
ForceNewEpoch.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
ForceNewEpoch.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
ForceNewEpoch.Flags().String(commonflags.LocalDumpFlag, "", "Path to the blocks dump file")
|
||||
}
|
||||
|
||||
func init() {
|
||||
initNetmapCandidatesCmd()
|
||||
initForceNewEpochCmd()
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package nns
|
||||
|
||||
import (
|
||||
client "git.frostfs.info/TrueCloudLab/frostfs-contract/rpcclient/nns"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func getRPCClient(cmd *cobra.Command) (*client.Contract, *helper.LocalActor, util.Uint160) {
|
||||
v := viper.GetViper()
|
||||
c, err := helper.GetN3Client(v)
|
||||
commonCmd.ExitOnErr(cmd, "unable to create NEO rpc client: %w", err)
|
||||
|
||||
ac, err := helper.NewLocalActor(cmd, c)
|
||||
commonCmd.ExitOnErr(cmd, "can't create actor: %w", err)
|
||||
|
||||
r := management.NewReader(ac.Invoker)
|
||||
nnsCs, err := r.GetContractByID(1)
|
||||
commonCmd.ExitOnErr(cmd, "can't get NNS contract state: %w", err)
|
||||
return client.New(ac, nnsCs.Hash), ac, nnsCs.Hash
|
||||
}
|
|
@ -1,148 +0,0 @@
|
|||
package nns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func initAddRecordCmd() {
|
||||
Cmd.AddCommand(addRecordCmd)
|
||||
addRecordCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
addRecordCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
addRecordCmd.Flags().String(nnsNameFlag, "", nnsNameFlagDesc)
|
||||
addRecordCmd.Flags().String(nnsRecordTypeFlag, "", nnsRecordTypeFlagDesc)
|
||||
addRecordCmd.Flags().String(nnsRecordDataFlag, "", nnsRecordDataFlagDesc)
|
||||
|
||||
_ = cobra.MarkFlagRequired(addRecordCmd.Flags(), nnsNameFlag)
|
||||
_ = cobra.MarkFlagRequired(addRecordCmd.Flags(), nnsRecordTypeFlag)
|
||||
_ = cobra.MarkFlagRequired(addRecordCmd.Flags(), nnsRecordDataFlag)
|
||||
}
|
||||
|
||||
func initGetRecordsCmd() {
|
||||
Cmd.AddCommand(getRecordsCmd)
|
||||
getRecordsCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
getRecordsCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
getRecordsCmd.Flags().String(nnsNameFlag, "", nnsNameFlagDesc)
|
||||
getRecordsCmd.Flags().String(nnsRecordTypeFlag, "", nnsRecordTypeFlagDesc)
|
||||
|
||||
_ = cobra.MarkFlagRequired(getRecordsCmd.Flags(), nnsNameFlag)
|
||||
}
|
||||
|
||||
func initDelRecordsCmd() {
|
||||
Cmd.AddCommand(delRecordsCmd)
|
||||
delRecordsCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
delRecordsCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
delRecordsCmd.Flags().String(nnsNameFlag, "", nnsNameFlagDesc)
|
||||
delRecordsCmd.Flags().String(nnsRecordTypeFlag, "", nnsRecordTypeFlagDesc)
|
||||
|
||||
_ = cobra.MarkFlagRequired(delRecordsCmd.Flags(), nnsNameFlag)
|
||||
_ = cobra.MarkFlagRequired(delRecordsCmd.Flags(), nnsRecordTypeFlag)
|
||||
}
|
||||
|
||||
func addRecord(cmd *cobra.Command, _ []string) {
|
||||
c, actor, _ := getRPCClient(cmd)
|
||||
name, _ := cmd.Flags().GetString(nnsNameFlag)
|
||||
data, _ := cmd.Flags().GetString(nnsRecordDataFlag)
|
||||
recordType, _ := cmd.Flags().GetString(nnsRecordTypeFlag)
|
||||
typ, err := getRecordType(recordType)
|
||||
commonCmd.ExitOnErr(cmd, "unable to parse record type: %w", err)
|
||||
h, vub, err := c.AddRecord(name, typ, data)
|
||||
commonCmd.ExitOnErr(cmd, "unable to add record: %w", err)
|
||||
|
||||
cmd.Println("Waiting for transaction to persist...")
|
||||
_, err = actor.Wait(h, vub, err)
|
||||
commonCmd.ExitOnErr(cmd, "renew domain error: %w", err)
|
||||
cmd.Println("Record added successfully")
|
||||
}
|
||||
|
||||
func getRecords(cmd *cobra.Command, _ []string) {
|
||||
c, act, hash := getRPCClient(cmd)
|
||||
name, _ := cmd.Flags().GetString(nnsNameFlag)
|
||||
recordType, _ := cmd.Flags().GetString(nnsRecordTypeFlag)
|
||||
if recordType == "" {
|
||||
sid, r, err := unwrap.SessionIterator(act.Invoker.Call(hash, "getAllRecords", name))
|
||||
commonCmd.ExitOnErr(cmd, "unable to get records: %w", err)
|
||||
defer func() {
|
||||
_ = act.Invoker.TerminateSession(sid)
|
||||
}()
|
||||
items, err := act.Invoker.TraverseIterator(sid, &r, 0)
|
||||
commonCmd.ExitOnErr(cmd, "unable to get records: %w", err)
|
||||
for len(items) != 0 {
|
||||
for j := range items {
|
||||
rs := items[j].Value().([]stackitem.Item)
|
||||
bs, err := rs[2].TryBytes()
|
||||
commonCmd.ExitOnErr(cmd, "unable to parse record state: %w", err)
|
||||
cmd.Printf("%s %s\n",
|
||||
recordTypeToString(nns.RecordType(rs[1].Value().(*big.Int).Int64())),
|
||||
string(bs))
|
||||
}
|
||||
items, err = act.Invoker.TraverseIterator(sid, &r, 0)
|
||||
commonCmd.ExitOnErr(cmd, "unable to get records: %w", err)
|
||||
}
|
||||
} else {
|
||||
typ, err := getRecordType(recordType)
|
||||
commonCmd.ExitOnErr(cmd, "unable to parse record type: %w", err)
|
||||
items, err := c.GetRecords(name, typ)
|
||||
commonCmd.ExitOnErr(cmd, "unable to get records: %w", err)
|
||||
for _, item := range items {
|
||||
record, err := item.TryBytes()
|
||||
commonCmd.ExitOnErr(cmd, "unable to parse response: %w", err)
|
||||
cmd.Println(string(record))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func delRecords(cmd *cobra.Command, _ []string) {
|
||||
c, actor, _ := getRPCClient(cmd)
|
||||
name, _ := cmd.Flags().GetString(nnsNameFlag)
|
||||
recordType, _ := cmd.Flags().GetString(nnsRecordTypeFlag)
|
||||
typ, err := getRecordType(recordType)
|
||||
commonCmd.ExitOnErr(cmd, "unable to parse record type: %w", err)
|
||||
h, vub, err := c.DeleteRecords(name, typ)
|
||||
commonCmd.ExitOnErr(cmd, "unable to delete records: %w", err)
|
||||
|
||||
cmd.Println("Waiting for transaction to persist...")
|
||||
_, err = actor.Wait(h, vub, err)
|
||||
commonCmd.ExitOnErr(cmd, "delete records error: %w", err)
|
||||
cmd.Println("Records removed successfully")
|
||||
}
|
||||
|
||||
func getRecordType(recordType string) (*big.Int, error) {
|
||||
switch strings.ToUpper(recordType) {
|
||||
case "A":
|
||||
return big.NewInt(int64(nns.A)), nil
|
||||
case "CNAME":
|
||||
return big.NewInt(int64(nns.CNAME)), nil
|
||||
case "SOA":
|
||||
return big.NewInt(int64(nns.SOA)), nil
|
||||
case "TXT":
|
||||
return big.NewInt(int64(nns.TXT)), nil
|
||||
case "AAAA":
|
||||
return big.NewInt(int64(nns.AAAA)), nil
|
||||
}
|
||||
return nil, errors.New("unsupported record type")
|
||||
}
|
||||
|
||||
func recordTypeToString(rt nns.RecordType) string {
|
||||
switch rt {
|
||||
case nns.A:
|
||||
return "A"
|
||||
case nns.CNAME:
|
||||
return "CNAME"
|
||||
case nns.SOA:
|
||||
return "SOA"
|
||||
case nns.TXT:
|
||||
return "TXT"
|
||||
case nns.AAAA:
|
||||
return "AAAA"
|
||||
}
|
||||
return ""
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package nns
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func initRegisterCmd() {
|
||||
Cmd.AddCommand(registerCmd)
|
||||
registerCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
registerCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
registerCmd.Flags().String(nnsNameFlag, "", nnsNameFlagDesc)
|
||||
registerCmd.Flags().String(nnsEmailFlag, constants.FrostfsOpsEmail, "Domain owner email")
|
||||
registerCmd.Flags().Int64(nnsRefreshFlag, constants.NNSRefreshDefVal, "SOA record REFRESH parameter")
|
||||
registerCmd.Flags().Int64(nnsRetryFlag, constants.NNSRetryDefVal, "SOA record RETRY parameter")
|
||||
registerCmd.Flags().Int64(nnsExpireFlag, int64(constants.DefaultExpirationTime), "SOA record EXPIRE parameter")
|
||||
registerCmd.Flags().Int64(nnsTTLFlag, constants.NNSTtlDefVal, "SOA record TTL parameter")
|
||||
|
||||
_ = cobra.MarkFlagRequired(registerCmd.Flags(), nnsNameFlag)
|
||||
}
|
||||
|
||||
func registerDomain(cmd *cobra.Command, _ []string) {
|
||||
c, actor, _ := getRPCClient(cmd)
|
||||
|
||||
name, _ := cmd.Flags().GetString(nnsNameFlag)
|
||||
email, _ := cmd.Flags().GetString(nnsEmailFlag)
|
||||
refresh, _ := cmd.Flags().GetInt64(nnsRefreshFlag)
|
||||
retry, _ := cmd.Flags().GetInt64(nnsRetryFlag)
|
||||
expire, _ := cmd.Flags().GetInt64(nnsExpireFlag)
|
||||
ttl, _ := cmd.Flags().GetInt64(nnsTTLFlag)
|
||||
|
||||
h, vub, err := c.Register(name, actor.Sender(), email, big.NewInt(refresh),
|
||||
big.NewInt(retry), big.NewInt(expire), big.NewInt(ttl))
|
||||
commonCmd.ExitOnErr(cmd, "unable to register domain: %w", err)
|
||||
|
||||
cmd.Println("Waiting for transaction to persist...")
|
||||
_, err = actor.Wait(h, vub, err)
|
||||
commonCmd.ExitOnErr(cmd, "register domain error: %w", err)
|
||||
cmd.Println("Domain registered successfully")
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package nns
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func initRenewCmd() {
|
||||
Cmd.AddCommand(renewCmd)
|
||||
renewCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
renewCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
renewCmd.Flags().String(nnsNameFlag, "", nnsNameFlagDesc)
|
||||
}
|
||||
|
||||
func renewDomain(cmd *cobra.Command, _ []string) {
|
||||
c, actor, _ := getRPCClient(cmd)
|
||||
name, _ := cmd.Flags().GetString(nnsNameFlag)
|
||||
h, vub, err := c.Renew(name)
|
||||
commonCmd.ExitOnErr(cmd, "unable to renew domain: %w", err)
|
||||
|
||||
cmd.Println("Waiting for transaction to persist...")
|
||||
_, err = actor.Wait(h, vub, err)
|
||||
commonCmd.ExitOnErr(cmd, "renew domain error: %w", err)
|
||||
cmd.Println("Domain renewed successfully")
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
package nns
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
nnsNameFlag = "name"
|
||||
nnsNameFlagDesc = "Domain name"
|
||||
nnsEmailFlag = "email"
|
||||
nnsRefreshFlag = "refresh"
|
||||
nnsRetryFlag = "retry"
|
||||
nnsExpireFlag = "expire"
|
||||
nnsTTLFlag = "ttl"
|
||||
nnsRecordTypeFlag = "type"
|
||||
nnsRecordTypeFlagDesc = "Domain name service record type(A|CNAME|SOA|TXT)"
|
||||
nnsRecordDataFlag = "data"
|
||||
nnsRecordDataFlagDesc = "Domain name service record data"
|
||||
)
|
||||
|
||||
var (
|
||||
Cmd = &cobra.Command{
|
||||
Use: "nns",
|
||||
Short: "Section for Neo Name Service (NNS)",
|
||||
}
|
||||
tokensCmd = &cobra.Command{
|
||||
Use: "tokens",
|
||||
Short: "List all registered domain names",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: listTokens,
|
||||
}
|
||||
registerCmd = &cobra.Command{
|
||||
Use: "register",
|
||||
Short: "Registers a new domain",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
},
|
||||
Run: registerDomain,
|
||||
}
|
||||
renewCmd = &cobra.Command{
|
||||
Use: "renew",
|
||||
Short: "Increases domain expiration date",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
},
|
||||
Run: renewDomain,
|
||||
}
|
||||
updateCmd = &cobra.Command{
|
||||
Use: "update",
|
||||
Short: "Updates soa record",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
},
|
||||
Run: updateSOA,
|
||||
}
|
||||
addRecordCmd = &cobra.Command{
|
||||
Use: "add-record",
|
||||
Short: "Adds a new record of the specified type to the provided domain",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
},
|
||||
Run: addRecord,
|
||||
}
|
||||
getRecordsCmd = &cobra.Command{
|
||||
Use: "get-records",
|
||||
Short: "Returns domain record of the specified type",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: getRecords,
|
||||
}
|
||||
delRecordsCmd = &cobra.Command{
|
||||
Use: "delete-records",
|
||||
Short: "Removes domain records with the specified type",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
},
|
||||
Run: delRecords,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
initTokensCmd()
|
||||
initRegisterCmd()
|
||||
initRenewCmd()
|
||||
initUpdateCmd()
|
||||
initAddRecordCmd()
|
||||
initGetRecordsCmd()
|
||||
initDelRecordsCmd()
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package nns
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func initTokensCmd() {
|
||||
Cmd.AddCommand(tokensCmd)
|
||||
tokensCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
tokensCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func listTokens(cmd *cobra.Command, _ []string) {
|
||||
c, _, _ := getRPCClient(cmd)
|
||||
it, err := c.Tokens()
|
||||
commonCmd.ExitOnErr(cmd, "unable to get tokens: %w", err)
|
||||
for toks, err := it.Next(10); err == nil && len(toks) > 0; toks, err = it.Next(10) {
|
||||
for _, token := range toks {
|
||||
cmd.Println(string(token))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
package nns
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func initUpdateCmd() {
|
||||
Cmd.AddCommand(updateCmd)
|
||||
updateCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
updateCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
updateCmd.Flags().String(nnsNameFlag, "", nnsNameFlagDesc)
|
||||
updateCmd.Flags().String(nnsEmailFlag, constants.FrostfsOpsEmail, "Domain owner email")
|
||||
updateCmd.Flags().Int64(nnsRefreshFlag, constants.NNSRefreshDefVal,
|
||||
"The number of seconds between update requests from secondary and slave name servers")
|
||||
updateCmd.Flags().Int64(nnsRetryFlag, constants.NNSRetryDefVal,
|
||||
"The number of seconds the secondary or slave will wait before retrying when the last attempt has failed")
|
||||
updateCmd.Flags().Int64(nnsExpireFlag, int64(constants.DefaultExpirationTime),
|
||||
"The number of seconds a master or slave will wait before considering the data stale "+
|
||||
"if it cannot reach the primary name server")
|
||||
updateCmd.Flags().Int64(nnsTTLFlag, constants.NNSTtlDefVal,
|
||||
"The number of seconds a domain name is cached locally before expiration and return to authoritative "+
|
||||
"nameservers for updated information")
|
||||
|
||||
_ = cobra.MarkFlagRequired(updateCmd.Flags(), nnsNameFlag)
|
||||
}
|
||||
|
||||
func updateSOA(cmd *cobra.Command, _ []string) {
|
||||
c, actor, _ := getRPCClient(cmd)
|
||||
|
||||
name, _ := cmd.Flags().GetString(nnsNameFlag)
|
||||
email, _ := cmd.Flags().GetString(nnsEmailFlag)
|
||||
refresh, _ := cmd.Flags().GetInt64(nnsRefreshFlag)
|
||||
retry, _ := cmd.Flags().GetInt64(nnsRetryFlag)
|
||||
expire, _ := cmd.Flags().GetInt64(nnsExpireFlag)
|
||||
ttl, _ := cmd.Flags().GetInt64(nnsTTLFlag)
|
||||
|
||||
h, vub, err := c.UpdateSOA(name, email, big.NewInt(refresh),
|
||||
big.NewInt(retry), big.NewInt(expire), big.NewInt(ttl))
|
||||
commonCmd.ExitOnErr(cmd, "unable to send transaction: %w", err)
|
||||
|
||||
cmd.Println("Waiting for transaction to persist...")
|
||||
_, err = actor.Wait(h, vub, err)
|
||||
commonCmd.ExitOnErr(cmd, "register domain error: %w", err)
|
||||
cmd.Println("SOA records updated successfully")
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
package node
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
netmapcontract "git.frostfs.info/TrueCloudLab/frostfs-contract/netmap"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func RemoveNodesCmd(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return errors.New("at least one node key must be provided")
|
||||
}
|
||||
|
||||
nodeKeys := make(keys.PublicKeys, len(args))
|
||||
for i := range args {
|
||||
var err error
|
||||
nodeKeys[i], err = keys.NewPublicKeyFromString(args[i])
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't parse node public key: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't initialize context: %w", err)
|
||||
}
|
||||
defer wCtx.Close()
|
||||
|
||||
r := management.NewReader(wCtx.ReadOnlyInvoker)
|
||||
cs, err := r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get NNS contract info: %w", err)
|
||||
}
|
||||
|
||||
nmHash, err := helper.NNSResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, helper.DomainOf(constants.NetmapContract))
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
||||
}
|
||||
|
||||
bw := io.NewBufBinWriter()
|
||||
for i := range nodeKeys {
|
||||
emit.AppCall(bw.BinWriter, nmHash, "updateStateIR", callflag.All,
|
||||
int64(netmapcontract.NodeStateOffline), nodeKeys[i].Bytes())
|
||||
}
|
||||
|
||||
if err := helper.EmitNewEpochCall(bw, wCtx, nmHash); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := wCtx.SendConsensusTx(bw.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return wCtx.AwaitTx()
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package node
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var RemoveCmd = &cobra.Command{
|
||||
Use: "remove-nodes key1 [key2 [...]]",
|
||||
Short: "Remove storage nodes from the netmap",
|
||||
Long: `Move nodes to the Offline state in the candidates list and tick an epoch to update the netmap`,
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
RunE: RemoveNodesCmd,
|
||||
}
|
||||
|
||||
func initRemoveNodesCmd() {
|
||||
RemoveCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
RemoveCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
RemoveCmd.Flags().String(commonflags.LocalDumpFlag, "", "Path to the blocks dump file")
|
||||
}
|
||||
|
||||
func init() {
|
||||
initRemoveNodesCmd()
|
||||
}
|
|
@ -1,145 +0,0 @@
|
|||
package notary
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"github.com/nspcc-dev/neo-go/cli/input"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/notary"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
// defaultNotaryDepositLifetime is an amount of blocks notary deposit stays valid.
|
||||
// https://github.com/nspcc-dev/neo-go/blob/master/pkg/core/native/notary.go#L48
|
||||
defaultNotaryDepositLifetime = 5760
|
||||
|
||||
walletAccountFlag = "account"
|
||||
notaryDepositTillFlag = "till"
|
||||
)
|
||||
|
||||
var errInvalidNotaryDepositLifetime = errors.New("notary deposit lifetime must be a positive integer")
|
||||
|
||||
func depositNotary(cmd *cobra.Command, _ []string) error {
|
||||
w, err := openWallet(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
accHash := w.GetChangeAddress()
|
||||
if addr, err := cmd.Flags().GetString(walletAccountFlag); err == nil {
|
||||
accHash, err = address.StringToUint160(addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid address: %s", addr)
|
||||
}
|
||||
}
|
||||
|
||||
acc := w.GetAccount(accHash)
|
||||
if acc == nil {
|
||||
return fmt.Errorf("can't find account for %s", accHash)
|
||||
}
|
||||
|
||||
prompt := fmt.Sprintf("Enter password for %s >", address.Uint160ToString(accHash))
|
||||
pass, err := input.ReadPassword(prompt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get password: %v", err)
|
||||
}
|
||||
|
||||
err = acc.Decrypt(pass, keys.NEP2ScryptParams())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't unlock account: %v", err)
|
||||
}
|
||||
|
||||
gasStr, err := cmd.Flags().GetString(commonflags.RefillGasAmountFlag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gasAmount, err := helper.ParseGASAmount(gasStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
till := int64(defaultNotaryDepositLifetime)
|
||||
tillStr, err := cmd.Flags().GetString(notaryDepositTillFlag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tillStr != "" {
|
||||
till, err = strconv.ParseInt(tillStr, 10, 64)
|
||||
if err != nil || till <= 0 {
|
||||
return errInvalidNotaryDepositLifetime
|
||||
}
|
||||
}
|
||||
|
||||
return transferGas(cmd, acc, accHash, gasAmount, till)
|
||||
}
|
||||
|
||||
func transferGas(cmd *cobra.Command, acc *wallet.Account, accHash util.Uint160, gasAmount fixedn.Fixed8, till int64) error {
|
||||
c, err := helper.GetN3Client(viper.GetViper())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := helper.CheckNotaryEnabled(c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
height, err := c.GetBlockCount()
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get current height: %v", err)
|
||||
}
|
||||
|
||||
act, err := actor.New(c, []actor.SignerAccount{{
|
||||
Signer: transaction.Signer{
|
||||
Account: acc.Contract.ScriptHash(),
|
||||
Scopes: transaction.Global,
|
||||
},
|
||||
Account: acc,
|
||||
}})
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create actor: %w", err)
|
||||
}
|
||||
|
||||
gasActor := nep17.New(act, gas.Hash)
|
||||
|
||||
txHash, vub, err := gasActor.Transfer(
|
||||
accHash,
|
||||
notary.Hash,
|
||||
big.NewInt(int64(gasAmount)),
|
||||
[]any{nil, int64(height) + till},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not send tx: %w", err)
|
||||
}
|
||||
|
||||
return helper.AwaitTx(cmd, c, []helper.HashVUBPair{{Hash: txHash, Vub: vub}})
|
||||
}
|
||||
|
||||
func openWallet(cmd *cobra.Command) (*wallet.Wallet, error) {
|
||||
p, err := cmd.Flags().GetString(commonflags.StorageWalletFlag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if p == "" {
|
||||
return nil, fmt.Errorf("missing wallet path (use '--%s <out.json>')", commonflags.StorageWalletFlag)
|
||||
}
|
||||
|
||||
w, err := wallet.NewWalletFromFile(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't open wallet: %v", err)
|
||||
}
|
||||
return w, nil
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package notary
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var DepositCmd = &cobra.Command{
|
||||
Use: "deposit-notary",
|
||||
Short: "Deposit GAS for notary service",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
RunE: depositNotary,
|
||||
}
|
||||
|
||||
func initDepositoryNotaryCmd() {
|
||||
DepositCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
DepositCmd.Flags().String(commonflags.StorageWalletFlag, "", "Path to storage node wallet")
|
||||
DepositCmd.Flags().String(walletAccountFlag, "", "Wallet account address")
|
||||
DepositCmd.Flags().String(commonflags.RefillGasAmountFlag, "", "Amount of GAS to deposit")
|
||||
DepositCmd.Flags().String(notaryDepositTillFlag, "", "Notary deposit duration in blocks")
|
||||
}
|
||||
|
||||
func init() {
|
||||
initDepositoryNotaryCmd()
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
package policy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/policy"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
execFeeParam = "ExecFeeFactor"
|
||||
storagePriceParam = "StoragePrice"
|
||||
setFeeParam = "FeePerByte"
|
||||
)
|
||||
|
||||
var errInvalidParameterFormat = errors.New("invalid parameter format, must be Parameter=Value")
|
||||
|
||||
func SetPolicyCmd(cmd *cobra.Command, args []string) error {
|
||||
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't to initialize context: %w", err)
|
||||
}
|
||||
|
||||
bw := io.NewBufBinWriter()
|
||||
for i := range args {
|
||||
k, v, found := strings.Cut(args[i], "=")
|
||||
if !found {
|
||||
return errInvalidParameterFormat
|
||||
}
|
||||
|
||||
switch k {
|
||||
case execFeeParam, storagePriceParam, setFeeParam:
|
||||
default:
|
||||
return fmt.Errorf("parameter must be one of %s, %s and %s", execFeeParam, storagePriceParam, setFeeParam)
|
||||
}
|
||||
|
||||
value, err := strconv.ParseUint(v, 10, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't parse parameter value '%s': %w", args[1], err)
|
||||
}
|
||||
|
||||
emit.AppCall(bw.BinWriter, policy.Hash, "set"+k, callflag.All, int64(value))
|
||||
}
|
||||
|
||||
if err := wCtx.SendCommitteeTx(bw.Bytes(), false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return wCtx.AwaitTx()
|
||||
}
|
||||
|
||||
func dumpPolicyCmd(cmd *cobra.Command, _ []string) error {
|
||||
c, err := helper.GetN3Client(viper.GetViper())
|
||||
commonCmd.ExitOnErr(cmd, "can't create N3 client:", err)
|
||||
|
||||
inv := invoker.New(c, nil)
|
||||
policyContract := policy.NewReader(inv)
|
||||
|
||||
execFee, err := policyContract.GetExecFeeFactor()
|
||||
commonCmd.ExitOnErr(cmd, "can't get execution fee factor:", err)
|
||||
|
||||
feePerByte, err := policyContract.GetFeePerByte()
|
||||
commonCmd.ExitOnErr(cmd, "can't get fee per byte:", err)
|
||||
|
||||
storagePrice, err := policyContract.GetStoragePrice()
|
||||
commonCmd.ExitOnErr(cmd, "can't get storage price:", err)
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
|
||||
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("Execution Fee Factor:\t%d (int)\n", execFee)))
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("Fee Per Byte:\t%d (int)\n", feePerByte)))
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("Storage Price:\t%d (int)\n", storagePrice)))
|
||||
|
||||
_ = tw.Flush()
|
||||
cmd.Print(buf.String())
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
package policy
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
Set = &cobra.Command{
|
||||
Use: "set-policy [ExecFeeFactor=<n1>] [StoragePrice=<n2>] [FeePerByte=<n3>]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: "Set global policy values",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
RunE: SetPolicyCmd,
|
||||
ValidArgsFunction: func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
return []string{"ExecFeeFactor=", "StoragePrice=", "FeePerByte="}, cobra.ShellCompDirectiveNoSpace
|
||||
},
|
||||
}
|
||||
|
||||
Dump = &cobra.Command{
|
||||
Use: "dump-policy",
|
||||
Short: "Dump FrostFS policy",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
RunE: dumpPolicyCmd,
|
||||
}
|
||||
)
|
||||
|
||||
func initSetPolicyCmd() {
|
||||
Set.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
Set.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
Set.Flags().String(commonflags.LocalDumpFlag, "", "Path to the blocks dump file")
|
||||
}
|
||||
|
||||
func initDumpPolicyCmd() {
|
||||
Dump.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
}
|
||||
|
||||
func init() {
|
||||
initSetPolicyCmd()
|
||||
initDumpPolicyCmd()
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
package proxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
accountAddressFlag = "account"
|
||||
)
|
||||
|
||||
func addProxyAccount(cmd *cobra.Command, _ []string) {
|
||||
acc, _ := cmd.Flags().GetString(accountAddressFlag)
|
||||
addr, err := address.StringToUint160(acc)
|
||||
commonCmd.ExitOnErr(cmd, "invalid account: %w", err)
|
||||
err = processAccount(cmd, addr, "addAccount")
|
||||
commonCmd.ExitOnErr(cmd, "processing error: %w", err)
|
||||
}
|
||||
|
||||
func removeProxyAccount(cmd *cobra.Command, _ []string) {
|
||||
acc, _ := cmd.Flags().GetString(accountAddressFlag)
|
||||
addr, err := address.StringToUint160(acc)
|
||||
commonCmd.ExitOnErr(cmd, "invalid account: %w", err)
|
||||
err = processAccount(cmd, addr, "removeAccount")
|
||||
commonCmd.ExitOnErr(cmd, "processing error: %w", err)
|
||||
}
|
||||
|
||||
func processAccount(cmd *cobra.Command, addr util.Uint160, method string) error {
|
||||
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't to initialize context: %w", err)
|
||||
}
|
||||
|
||||
r := management.NewReader(wCtx.ReadOnlyInvoker)
|
||||
cs, err := r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get NNS contract info: %w", err)
|
||||
}
|
||||
|
||||
proxyHash, err := helper.NNSResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, helper.DomainOf(constants.ProxyContract))
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get proxy contract hash: %w", err)
|
||||
}
|
||||
|
||||
bw := io.NewBufBinWriter()
|
||||
emit.AppCall(bw.BinWriter, proxyHash, method, callflag.All, addr)
|
||||
|
||||
if err := wCtx.SendConsensusTx(bw.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = wCtx.AwaitTx(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Println("Proxy contract has been updated")
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package proxy
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
AddAccountCmd = &cobra.Command{
|
||||
Use: "proxy-add-account",
|
||||
Short: "Adds account to proxy contract",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: addProxyAccount,
|
||||
}
|
||||
RemoveAccountCmd = &cobra.Command{
|
||||
Use: "proxy-remove-account",
|
||||
Short: "Remove from proxy contract",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: removeProxyAccount,
|
||||
}
|
||||
)
|
||||
|
||||
func initProxyAddAccount() {
|
||||
AddAccountCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
AddAccountCmd.Flags().String(accountAddressFlag, "", "Wallet address string")
|
||||
}
|
||||
|
||||
func initProxyRemoveAccount() {
|
||||
RemoveAccountCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
RemoveAccountCmd.Flags().String(accountAddressFlag, "", "Wallet address string")
|
||||
}
|
||||
|
||||
func init() {
|
||||
initProxyAddAccount()
|
||||
initProxyRemoveAccount()
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
package morph
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/ape"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/balance"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/container"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/contract"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/frostfsid"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/generate"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/initialize"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/netmap"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/nns"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/node"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/notary"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/policy"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/proxy"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// RootCmd is a root command of config section.
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "morph",
|
||||
Short: "Section for morph network configuration commands",
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(generate.RefillGasCmd)
|
||||
RootCmd.AddCommand(initialize.Cmd)
|
||||
RootCmd.AddCommand(contract.DeployCmd)
|
||||
RootCmd.AddCommand(generate.GenerateStorageCmd)
|
||||
RootCmd.AddCommand(netmap.ForceNewEpoch)
|
||||
RootCmd.AddCommand(node.RemoveCmd)
|
||||
RootCmd.AddCommand(policy.Set)
|
||||
RootCmd.AddCommand(policy.Dump)
|
||||
RootCmd.AddCommand(contract.DumpHashesCmd)
|
||||
RootCmd.AddCommand(config.SetCmd)
|
||||
RootCmd.AddCommand(config.DumpCmd)
|
||||
RootCmd.AddCommand(balance.DumpCmd)
|
||||
RootCmd.AddCommand(contract.UpdateCmd)
|
||||
RootCmd.AddCommand(container.ListCmd)
|
||||
RootCmd.AddCommand(container.RestoreCmd)
|
||||
RootCmd.AddCommand(container.DumpCmd)
|
||||
RootCmd.AddCommand(generate.GenerateAlphabetCmd)
|
||||
RootCmd.AddCommand(notary.DepositCmd)
|
||||
RootCmd.AddCommand(netmap.CandidatesCmd)
|
||||
|
||||
RootCmd.AddCommand(ape.Cmd)
|
||||
RootCmd.AddCommand(proxy.AddAccountCmd)
|
||||
RootCmd.AddCommand(proxy.RemoveAccountCmd)
|
||||
|
||||
RootCmd.AddCommand(frostfsid.Cmd)
|
||||
RootCmd.AddCommand(nns.Cmd)
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
package modules
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/storagecfg"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/misc"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/autocomplete"
|
||||
utilConfig "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/gendoc"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "frostfs-adm",
|
||||
Short: "FrostFS Administrative Tool",
|
||||
Long: `FrostFS Administrative Tool provides functions to setup and
|
||||
manage FrostFS network deployment.`,
|
||||
RunE: entryPoint,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(func() { initConfig(rootCmd) })
|
||||
// we need to init viper config to bind viper and cobra configurations for
|
||||
// rpc endpoint, alphabet wallet dir, key credentials, etc.
|
||||
|
||||
// use stdout as default output for cmd.Print()
|
||||
rootCmd.SetOut(os.Stdout)
|
||||
|
||||
rootCmd.PersistentFlags().StringP(commonflags.ConfigFlag, commonflags.ConfigFlagShorthand, "", commonflags.ConfigFlagUsage)
|
||||
rootCmd.PersistentFlags().String(commonflags.ConfigDirFlag, "", commonflags.ConfigDirFlagUsage)
|
||||
rootCmd.PersistentFlags().BoolP(commonflags.Verbose, commonflags.VerboseShorthand, false, commonflags.VerboseUsage)
|
||||
_ = viper.BindPFlag(commonflags.Verbose, rootCmd.PersistentFlags().Lookup(commonflags.Verbose))
|
||||
rootCmd.Flags().Bool("version", false, "Application version")
|
||||
|
||||
rootCmd.AddCommand(config.RootCmd)
|
||||
rootCmd.AddCommand(morph.RootCmd)
|
||||
rootCmd.AddCommand(storagecfg.RootCmd)
|
||||
|
||||
rootCmd.AddCommand(autocomplete.Command("frostfs-adm"))
|
||||
rootCmd.AddCommand(gendoc.Command(rootCmd, gendoc.Options{}))
|
||||
}
|
||||
|
||||
func Execute() error {
|
||||
return rootCmd.Execute()
|
||||
}
|
||||
|
||||
func entryPoint(cmd *cobra.Command, _ []string) error {
|
||||
printVersion, _ := cmd.Flags().GetBool("version")
|
||||
if printVersion {
|
||||
cmd.Print(misc.BuildInfo("FrostFS Adm"))
|
||||
return nil
|
||||
}
|
||||
|
||||
return cmd.Usage()
|
||||
}
|
||||
|
||||
func initConfig(cmd *cobra.Command) {
|
||||
configFile, err := cmd.Flags().GetString(commonflags.ConfigFlag)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if configFile != "" {
|
||||
viper.SetConfigType("yml")
|
||||
viper.SetConfigFile(configFile)
|
||||
_ = viper.ReadInConfig() // if config file is set but unavailable, ignore it
|
||||
}
|
||||
|
||||
configDir, err := cmd.Flags().GetString(commonflags.ConfigDirFlag)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if configDir != "" {
|
||||
_ = utilConfig.ReadConfigDir(viper.GetViper(), configDir) // if config files cannot be read, ignore it
|
||||
}
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
# How manage local Access Policy Engine (APE) override of the node
|
||||
|
||||
## Overview
|
||||
APE is a replacement for eACL. Each rule can restrict somehow access to the object/container or list of them.
|
||||
Here is a simple representation for the rule:
|
||||
`<status>[:status_detail] <action>... <condition>... <resource>...`
|
||||
|
||||
Rule start with `status`(with or without details), contains list of actions(which this rule regulate) or conditions
|
||||
(which can be under resource or request) and ends with list of resources.
|
||||
|
||||
Resource is the combination of namespace, identificator of the FrostFS container/object and wildcard `*`.
|
||||
|
||||
For object it can be represented as:
|
||||
- `namespace/cid/oid` object in the container of the namespace
|
||||
- `namespace/cid/*` all objects in the container of the namespace
|
||||
- `namespace/*` all objects in the namespace
|
||||
- `*` all objects
|
||||
- `/*` all object in the `root` namespace
|
||||
- `/cid/*` all objects in the container of the `root` namespace
|
||||
- `/cid/oid` object in the container of the `root` namespace
|
||||
|
||||
For container it can be represented as:
|
||||
- `namespace/cid` container in the namespace
|
||||
- `namespace/*` all containers in the namespace
|
||||
- `*` all containers
|
||||
- `/cid` container in the `root` namespace
|
||||
- `/*` all containers in the `root` namespace
|
||||
|
||||
Actions is a regular operations upon FrostFS containers/objects. Like `Object.Put`, `Container.Get` etc.
|
||||
You can use `Object.*`, `Container.*` that implies all actions.
|
||||
|
||||
In status section it is possible to use `allow`, `deny` or `deny:QuotaLimitReached` actions.
|
||||
|
||||
If a statement does not contain lexeme `any`, field `Any` is set to `false` by default. Otherwise, it is set
|
||||
to `true`. Optionally, `all` can be used - it also sets `Any=false`.
|
||||
|
||||
It is prohibited to mix operation under FrostFS container and object in one rule.
|
||||
The same statement is equal for conditions and resources - one rule is for one type of items.
|
||||
|
||||
## Add rule
|
||||
Local rule can be added with the command `frostfs-cli control add-rule`:
|
||||
```shell
|
||||
@:~$ frostfs-cli control add-rule --endpoint s04.frostfs.devenv:8081 -c cnt_create_cfg.yml \
|
||||
--address NbUgTSFvPmsRxmGeWpuuGeJUoRoi6PErcM --cid SeHNpifDH2Fc4scNBphrbmrKi96QXj2HzYJkhSGuytH \
|
||||
--chain-id TestPolicy \
|
||||
--rule "allow Object.Get Object.Head /*" --rule "deny Container.Put *"
|
||||
Parsed chain:
|
||||
Chain ID: TestPolicy
|
||||
HEX: 54657374506f6c696379
|
||||
Rules:
|
||||
|
||||
Status: Allowed
|
||||
Any: false
|
||||
Conditions:
|
||||
Actions: Inverted:false
|
||||
GetObject
|
||||
HeadObject
|
||||
Resources: Inverted:false
|
||||
native:object//*
|
||||
|
||||
Status: Access denied
|
||||
Any: false
|
||||
Conditions:
|
||||
Actions: Inverted:false
|
||||
PutContainer
|
||||
Resources: Inverted:false
|
||||
native:container/*
|
||||
|
||||
Rule has been added.
|
||||
@:~$
|
||||
```
|
||||
## List rules
|
||||
Local rules can be listed with command `frostfs-cli control list-rules`:
|
||||
```shell
|
||||
@:~$ frostfs-cli control list-rules --endpoint s04.frostfs.devenv:8081 --address NbUgTSFvPmsRxmGeWpuuGeJUoRoi6PErcM \
|
||||
--cid SeHNpifDH2Fc4scNBphrbmrKi96QXj2HzYJkhSGuytH -w wallets/wallet.json
|
||||
Enter password >
|
||||
Chain ID: TestPolicy
|
||||
HEX: 54657374506f6c696379
|
||||
Rules:
|
||||
|
||||
Status: Allowed
|
||||
Any: false
|
||||
...
|
||||
@:~$
|
||||
```
|
||||
|
||||
## Get rule
|
||||
Rules can be retrieved with `frostfs-cli control get-rule`:
|
||||
```shell
|
||||
@:~$ frostfs-cli control get-rule --endpoint s04.frostfs.devenv:8081 -c cnt_create_cfg.yml \
|
||||
--address NbUgTSFvPmsRxmGeWpuuGeJUoRoi6PErcM --cid SeHNpifDH2Fc4scNBphrbmrKi96QXj2HzYJkhSGuytH \
|
||||
--chain-id TestPolicy
|
||||
Parsed chain (chain id hex: '54657374506f6c696379'):
|
||||
Chain ID: TestPolicy
|
||||
HEX: 54657374506f6c696379
|
||||
Rules:
|
||||
|
||||
Status: Allowed
|
||||
Any: false
|
||||
...
|
||||
@:~$
|
||||
```
|
||||
|
||||
## Remove rule
|
||||
To remove rule need to use command `frostfs-cli control remove-rule`:
|
||||
```shell
|
||||
@:~$ frostfs-cli control remove-rule --endpoint s04.frostfs.devenv:8081 -c cnt_create_cfg.yml \
|
||||
--address NbUgTSFvPmsRxmGeWpuuGeJUoRoi6PErcM --cid SeHNpifDH2Fc4scNBphrbmrKi96QXj2HzYJkhSGuytH --chain-id TestPolicy
|
||||
Rule has been removed.
|
||||
@:~$ frostfs-cli control get-rule --endpoint s04.frostfs.devenv:8081 -c cnt_create_cfg.yml \
|
||||
--address NbUgTSFvPmsRxmGeWpuuGeJUoRoi6PErcM --cid SeHNpifDH2Fc4scNBphrbmrKi96QXj2HzYJkhSGuytH --chain-id TestPolicy
|
||||
rpc error: rpc error: code = NotFound desc = chain not found
|
||||
@:~$ frostfs-cli control list-rules --endpoint s04.frostfs.devenv:8081 \
|
||||
--address NbUgTSFvPmsRxmGeWpuuGeJUoRoi6PErcM --cid SeHNpifDH2Fc4scNBphrbmrKi96QXj2HzYJkhSGuytH -w wallets/wallet.json
|
||||
Enter password >
|
||||
Local overrides are not defined for the container.
|
||||
@:~$
|
||||
```
|
|
@ -1,75 +0,0 @@
|
|||
# How FrostFS CLI uses session mechanism of the FrostFS
|
||||
|
||||
## Overview
|
||||
|
||||
FrostFS sessions implement a mechanism for issuing a power of attorney by one
|
||||
party to another. A trusted party can provide a so-called session token as
|
||||
proof of the right to act on behalf of another member of the network. The
|
||||
client of operations carried out with such a token will be the user who opened
|
||||
the session. The token contains information which limits power of attorney like
|
||||
action context or lifetime.
|
||||
|
||||
The client confirms trust in a third party by signing its public (session) key
|
||||
with his private key. Any operation signed using private session key with
|
||||
attached session token is treated as performed by the original client.
|
||||
|
||||
## Types
|
||||
|
||||
FrostFS CLI supports two ways to execute operation within a session depending on
|
||||
whether the user of the command application is an original user (1) or a trusted
|
||||
one (2).
|
||||
|
||||
### Dynamic
|
||||
|
||||
For case (1) CLI user can only open dynamic sessions. Protocol call
|
||||
`SessionService.Create` is used for this purpose. As a result of the call, a
|
||||
private session key will be generated on the server, thus making the remote
|
||||
server trusted. This type of session is useful when the client needs to
|
||||
transfer part of the responsibility for the formation of strict system elements
|
||||
to the trusted server. At the moment, the approach is applicable only to
|
||||
creating objects.
|
||||
|
||||
```shell
|
||||
$ frostfs-cli session create --rpc-endpoint <server_ip> --out ./blank_token
|
||||
```
|
||||
After this example command remote node holds session private key while its
|
||||
public part is written into the session token encoded into the output file.
|
||||
Later this token can be attached to the operations which support dynamic
|
||||
sessions. Then the token will be finally formed and signed by CLI itself.
|
||||
|
||||
### Static
|
||||
|
||||
For case (2) CLI user can act on behalf of the person who issued the session
|
||||
token to him. Unlike (1) the token must be fully prepared on the side of the
|
||||
original client, and the CLI uses it only for reading. Ready token MUST have:
|
||||
- correct context (object, container, etc.)
|
||||
- valid lifetime
|
||||
- public session key corresponding to the CLI key
|
||||
- valid client signature
|
||||
|
||||
To sign the session token, exec:
|
||||
```shell
|
||||
$ frostfs-cli --wallet <client_wallet> util sign session-token --from ./blank_token --to ./token
|
||||
```
|
||||
Once the token is signed, it MUST NOT be modified.
|
||||
|
||||
## Commands
|
||||
|
||||
### Object
|
||||
|
||||
Here are sub-commands of `object` command which support only dynamic sessions (1):
|
||||
- `put`
|
||||
- `delete`
|
||||
- `lock`
|
||||
|
||||
These commands accept blank token of the dynamically opened session or open
|
||||
session internally if it has not been opened yet.
|
||||
|
||||
All other `object` sub-commands support only static sessions (2).
|
||||
|
||||
### Container
|
||||
|
||||
List of commands supporting sessions (static only):
|
||||
- `create`
|
||||
- `delete`
|
||||
- `set-eacl`
|
|
@ -1,33 +0,0 @@
|
|||
# Extended headers
|
||||
|
||||
## Overview
|
||||
|
||||
Extended headers are used for request/response. They may contain any
|
||||
user-defined headers to be interpreted on application level. Key name must be a
|
||||
unique valid UTF-8 string. Value can't be empty. Requests or Responses with
|
||||
duplicated header names or headers with empty values are considered invalid.
|
||||
|
||||
## Existing headers
|
||||
|
||||
There are some "well-known" headers starting with `__SYSTEM__` prefix that
|
||||
affect system behaviour. For backward compatibility, the same set of
|
||||
"well-known" headers may also use `__NEOFS__` prefix:
|
||||
|
||||
* `__SYSTEM__NETMAP_EPOCH` - netmap epoch to use for object placement calculation. The `value` is string
|
||||
encoded `uint64` in decimal presentation. If set to '0' or omitted, the
|
||||
current epoch only will be used.
|
||||
* `__SYSTEM__NETMAP_LOOKUP_DEPTH` - if object can't be found using current epoch's netmap, this header limits
|
||||
how many past epochs the node can look up through. Depth is applied to a current epoch or the value
|
||||
of `__SYSTEM__NETMAP_EPOCH` attribute. The `value` is string encoded `uint64` in decimal presentation.
|
||||
If set to '0' or not set, only the current epoch is used.
|
||||
|
||||
## `frostfs-cli` commands with `--xhdr`
|
||||
|
||||
List of commands with support of extended headers:
|
||||
* `container list-objects`
|
||||
* `object delete/get/hash/head/lock/put/range/search`
|
||||
|
||||
Example:
|
||||
```shell
|
||||
$ frostfs-cli object put -r s01.frostfs.devenv:8080 -w wallet.json --cid CID --file FILE --xhdr "__SYSTEM__NETMAP_EPOCH=777"
|
||||
```
|
|
@ -1,894 +0,0 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/accounting"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||
containerSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/version"
|
||||
)
|
||||
|
||||
var errMissingHeaderInResponse = errors.New("missing header in response")
|
||||
|
||||
// BalanceOfPrm groups parameters of BalanceOf operation.
|
||||
type BalanceOfPrm struct {
|
||||
commonPrm
|
||||
client.PrmBalanceGet
|
||||
}
|
||||
|
||||
// BalanceOfRes groups the resulting values of BalanceOf operation.
|
||||
type BalanceOfRes struct {
|
||||
cliRes *client.ResBalanceGet
|
||||
}
|
||||
|
||||
// Balance returns the current balance.
|
||||
func (x BalanceOfRes) Balance() accounting.Decimal {
|
||||
return x.cliRes.Amount()
|
||||
}
|
||||
|
||||
// BalanceOf requests the current balance of a FrostFS user.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func BalanceOf(ctx context.Context, prm BalanceOfPrm) (res BalanceOfRes, err error) {
|
||||
res.cliRes, err = prm.cli.BalanceGet(ctx, prm.PrmBalanceGet)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ListContainersPrm groups parameters of ListContainers operation.
|
||||
type ListContainersPrm struct {
|
||||
commonPrm
|
||||
client.PrmContainerList
|
||||
}
|
||||
|
||||
// ListContainersRes groups the resulting values of ListContainers operation.
|
||||
type ListContainersRes struct {
|
||||
cliRes *client.ResContainerList
|
||||
}
|
||||
|
||||
// IDList returns list of identifiers of user's containers.
|
||||
func (x ListContainersRes) IDList() []cid.ID {
|
||||
return x.cliRes.Containers()
|
||||
}
|
||||
|
||||
// ListContainers requests a list of FrostFS user's containers.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func ListContainers(ctx context.Context, prm ListContainersPrm) (res ListContainersRes, err error) {
|
||||
res.cliRes, err = prm.cli.ContainerList(ctx, prm.PrmContainerList)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SortedIDList returns sorted list of identifiers of user's containers.
|
||||
func (x ListContainersRes) SortedIDList() []cid.ID {
|
||||
list := x.cliRes.Containers()
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
lhs, rhs := list[i].EncodeToString(), list[j].EncodeToString()
|
||||
return strings.Compare(lhs, rhs) < 0
|
||||
})
|
||||
return list
|
||||
}
|
||||
|
||||
// PutContainerPrm groups parameters of PutContainer operation.
|
||||
type PutContainerPrm struct {
|
||||
Client *client.Client
|
||||
ClientParams client.PrmContainerPut
|
||||
}
|
||||
|
||||
// PutContainerRes groups the resulting values of PutContainer operation.
|
||||
type PutContainerRes struct {
|
||||
cnr cid.ID
|
||||
}
|
||||
|
||||
// ID returns identifier of the created container.
|
||||
func (x PutContainerRes) ID() cid.ID {
|
||||
return x.cnr
|
||||
}
|
||||
|
||||
// PutContainer sends a request to save the container in FrostFS.
|
||||
//
|
||||
// Operation is asynchronous and not guaranteed even in the absence of errors.
|
||||
// The required time is also not predictable.
|
||||
//
|
||||
// Success can be verified by reading by identifier.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func PutContainer(ctx context.Context, prm PutContainerPrm) (res PutContainerRes, err error) {
|
||||
cliRes, err := prm.Client.ContainerPut(ctx, prm.ClientParams)
|
||||
if err == nil {
|
||||
res.cnr = cliRes.ID()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetContainerPrm groups parameters of GetContainer operation.
|
||||
type GetContainerPrm struct {
|
||||
Client *client.Client
|
||||
ClientParams client.PrmContainerGet
|
||||
}
|
||||
|
||||
// SetContainer sets identifier of the container to be read.
|
||||
//
|
||||
// Deprecated: Use GetContainerPrm.ClientParams.ContainerID instead.
|
||||
func (x *GetContainerPrm) SetContainer(id cid.ID) {
|
||||
x.ClientParams.ContainerID = &id
|
||||
}
|
||||
|
||||
// GetContainerRes groups the resulting values of GetContainer operation.
|
||||
type GetContainerRes struct {
|
||||
cliRes *client.ResContainerGet
|
||||
}
|
||||
|
||||
// Container returns structured of the requested container.
|
||||
func (x GetContainerRes) Container() containerSDK.Container {
|
||||
return x.cliRes.Container()
|
||||
}
|
||||
|
||||
// GetContainer reads a container from FrostFS by ID.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func GetContainer(ctx context.Context, prm GetContainerPrm) (res GetContainerRes, err error) {
|
||||
res.cliRes, err = prm.Client.ContainerGet(ctx, prm.ClientParams)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// IsACLExtendable checks if ACL of the container referenced by the given identifier
|
||||
// can be extended. Client connection MUST BE correctly established in advance.
|
||||
func IsACLExtendable(ctx context.Context, c *client.Client, cnr cid.ID) (bool, error) {
|
||||
prm := GetContainerPrm{
|
||||
Client: c,
|
||||
ClientParams: client.PrmContainerGet{
|
||||
ContainerID: &cnr,
|
||||
},
|
||||
}
|
||||
|
||||
res, err := GetContainer(ctx, prm)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("get container from the FrostFS: %w", err)
|
||||
}
|
||||
|
||||
return res.Container().BasicACL().Extendable(), nil
|
||||
}
|
||||
|
||||
// DeleteContainerPrm groups parameters of DeleteContainerPrm operation.
|
||||
type DeleteContainerPrm struct {
|
||||
Client *client.Client
|
||||
ClientParams client.PrmContainerDelete
|
||||
}
|
||||
|
||||
// DeleteContainerRes groups the resulting values of DeleteContainer operation.
|
||||
type DeleteContainerRes struct{}
|
||||
|
||||
// DeleteContainer sends a request to remove a container from FrostFS by ID.
|
||||
//
|
||||
// Operation is asynchronous and not guaranteed even in the absence of errors.
|
||||
// The required time is also not predictable.
|
||||
//
|
||||
// Success can be verified by reading by identifier.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func DeleteContainer(ctx context.Context, prm DeleteContainerPrm) (res DeleteContainerRes, err error) {
|
||||
_, err = prm.Client.ContainerDelete(ctx, prm.ClientParams)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// EACLPrm groups parameters of EACL operation.
|
||||
type EACLPrm struct {
|
||||
Client *client.Client
|
||||
ClientParams client.PrmContainerEACL
|
||||
}
|
||||
|
||||
// EACLRes groups the resulting values of EACL operation.
|
||||
type EACLRes struct {
|
||||
cliRes *client.ResContainerEACL
|
||||
}
|
||||
|
||||
// EACL returns requested eACL table.
|
||||
func (x EACLRes) EACL() eacl.Table {
|
||||
return x.cliRes.Table()
|
||||
}
|
||||
|
||||
// EACL reads eACL table from FrostFS by container ID.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func EACL(ctx context.Context, prm EACLPrm) (res EACLRes, err error) {
|
||||
res.cliRes, err = prm.Client.ContainerEACL(ctx, prm.ClientParams)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SetEACLPrm groups parameters of SetEACL operation.
|
||||
type SetEACLPrm struct {
|
||||
Client *client.Client
|
||||
ClientParams client.PrmContainerSetEACL
|
||||
}
|
||||
|
||||
// SetEACLRes groups the resulting values of SetEACL operation.
|
||||
type SetEACLRes struct{}
|
||||
|
||||
// SetEACL requests to save an eACL table in FrostFS.
|
||||
//
|
||||
// Operation is asynchronous and no guaranteed even in the absence of errors.
|
||||
// The required time is also not predictable.
|
||||
//
|
||||
// Success can be verified by reading by container identifier.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func SetEACL(ctx context.Context, prm SetEACLPrm) (res SetEACLRes, err error) {
|
||||
_, err = prm.Client.ContainerSetEACL(ctx, prm.ClientParams)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// NetworkInfoPrm groups parameters of NetworkInfo operation.
|
||||
type NetworkInfoPrm struct {
|
||||
Client *client.Client
|
||||
ClientParams client.PrmNetworkInfo
|
||||
}
|
||||
|
||||
// NetworkInfoRes groups the resulting values of NetworkInfo operation.
|
||||
type NetworkInfoRes struct {
|
||||
cliRes *client.ResNetworkInfo
|
||||
}
|
||||
|
||||
// NetworkInfo returns structured information about the FrostFS network.
|
||||
func (x NetworkInfoRes) NetworkInfo() netmap.NetworkInfo {
|
||||
return x.cliRes.Info()
|
||||
}
|
||||
|
||||
// NetworkInfo reads information about the FrostFS network.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func NetworkInfo(ctx context.Context, prm NetworkInfoPrm) (res NetworkInfoRes, err error) {
|
||||
res.cliRes, err = prm.Client.NetworkInfo(ctx, prm.ClientParams)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// NodeInfoPrm groups parameters of NodeInfo operation.
|
||||
type NodeInfoPrm struct {
|
||||
Client *client.Client
|
||||
ClientParams client.PrmEndpointInfo
|
||||
}
|
||||
|
||||
// NodeInfoRes groups the resulting values of NodeInfo operation.
|
||||
type NodeInfoRes struct {
|
||||
cliRes *client.ResEndpointInfo
|
||||
}
|
||||
|
||||
// NodeInfo returns information about the node from netmap.
|
||||
func (x NodeInfoRes) NodeInfo() netmap.NodeInfo {
|
||||
return x.cliRes.NodeInfo()
|
||||
}
|
||||
|
||||
// LatestVersion returns the latest FrostFS API version in use.
|
||||
func (x NodeInfoRes) LatestVersion() version.Version {
|
||||
return x.cliRes.LatestVersion()
|
||||
}
|
||||
|
||||
// NodeInfo requests information about the remote server from FrostFS netmap.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func NodeInfo(ctx context.Context, prm NodeInfoPrm) (res NodeInfoRes, err error) {
|
||||
res.cliRes, err = prm.Client.EndpointInfo(ctx, prm.ClientParams)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// NetMapSnapshotPrm groups parameters of NetMapSnapshot operation.
|
||||
type NetMapSnapshotPrm struct {
|
||||
commonPrm
|
||||
}
|
||||
|
||||
// NetMapSnapshotRes groups the resulting values of NetMapSnapshot operation.
|
||||
type NetMapSnapshotRes struct {
|
||||
cliRes *client.ResNetMapSnapshot
|
||||
}
|
||||
|
||||
// NetMap returns current local snapshot of the FrostFS network map.
|
||||
func (x NetMapSnapshotRes) NetMap() netmap.NetMap {
|
||||
return x.cliRes.NetMap()
|
||||
}
|
||||
|
||||
// NetMapSnapshot requests current network view of the remote server.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func NetMapSnapshot(ctx context.Context, prm NetMapSnapshotPrm) (res NetMapSnapshotRes, err error) {
|
||||
res.cliRes, err = prm.cli.NetMapSnapshot(ctx, client.PrmNetMapSnapshot{})
|
||||
return
|
||||
}
|
||||
|
||||
// CreateSessionPrm groups parameters of CreateSession operation.
|
||||
type CreateSessionPrm struct {
|
||||
commonPrm
|
||||
client.PrmSessionCreate
|
||||
}
|
||||
|
||||
// CreateSessionRes groups the resulting values of CreateSession operation.
|
||||
type CreateSessionRes struct {
|
||||
cliRes *client.ResSessionCreate
|
||||
}
|
||||
|
||||
// ID returns session identifier.
|
||||
func (x CreateSessionRes) ID() []byte {
|
||||
return x.cliRes.ID()
|
||||
}
|
||||
|
||||
// SessionKey returns public session key in a binary format.
|
||||
func (x CreateSessionRes) SessionKey() []byte {
|
||||
return x.cliRes.PublicKey()
|
||||
}
|
||||
|
||||
// CreateSession opens a new unlimited session with the remote node.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func CreateSession(ctx context.Context, prm CreateSessionPrm) (res CreateSessionRes, err error) {
|
||||
res.cliRes, err = prm.cli.SessionCreate(ctx, prm.PrmSessionCreate)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// PutObjectPrm groups parameters of PutObject operation.
|
||||
type PutObjectPrm struct {
|
||||
commonObjectPrm
|
||||
|
||||
copyNum []uint32
|
||||
|
||||
hdr *objectSDK.Object
|
||||
|
||||
rdr io.Reader
|
||||
|
||||
headerCallback func()
|
||||
|
||||
prepareLocally bool
|
||||
}
|
||||
|
||||
// SetHeader sets object header.
|
||||
func (x *PutObjectPrm) SetHeader(hdr *objectSDK.Object) {
|
||||
x.hdr = hdr
|
||||
}
|
||||
|
||||
// SetPayloadReader sets reader of the object payload.
|
||||
func (x *PutObjectPrm) SetPayloadReader(rdr io.Reader) {
|
||||
x.rdr = rdr
|
||||
}
|
||||
|
||||
// SetHeaderCallback sets callback which is called on the object after the header is received
|
||||
// but before the payload is written.
|
||||
func (x *PutObjectPrm) SetHeaderCallback(f func()) {
|
||||
x.headerCallback = f
|
||||
}
|
||||
|
||||
// SetCopiesNumberByVectors sets ordered list of minimal required object copies numbers
|
||||
// per placement vector.
|
||||
func (x *PutObjectPrm) SetCopiesNumberByVectors(copiesNumbers []uint32) {
|
||||
x.copyNum = copiesNumbers
|
||||
}
|
||||
|
||||
// PrepareLocally generate object header on the client side.
|
||||
// For big object - split locally too.
|
||||
func (x *PutObjectPrm) PrepareLocally() {
|
||||
x.prepareLocally = true
|
||||
}
|
||||
|
||||
func (x *PutObjectPrm) convertToSDKPrm(ctx context.Context) (client.PrmObjectPutInit, error) {
|
||||
putPrm := client.PrmObjectPutInit{
|
||||
XHeaders: x.xHeaders,
|
||||
BearerToken: x.bearerToken,
|
||||
Local: x.local,
|
||||
CopiesNumber: x.copyNum,
|
||||
}
|
||||
|
||||
if x.prepareLocally {
|
||||
res, err := x.cli.NetworkInfo(ctx, client.PrmNetworkInfo{})
|
||||
if err != nil {
|
||||
return client.PrmObjectPutInit{}, err
|
||||
}
|
||||
putPrm.MaxSize = res.Info().MaxObjectSize()
|
||||
putPrm.EpochSource = epochSource(res.Info().CurrentEpoch())
|
||||
putPrm.WithoutHomomorphHash = res.Info().HomomorphicHashingDisabled()
|
||||
} else {
|
||||
putPrm.Session = x.sessionToken
|
||||
}
|
||||
return putPrm, nil
|
||||
}
|
||||
|
||||
// PutObjectRes groups the resulting values of PutObject operation.
|
||||
type PutObjectRes struct {
|
||||
id oid.ID
|
||||
}
|
||||
|
||||
// ID returns identifier of the created object.
|
||||
func (x PutObjectRes) ID() oid.ID {
|
||||
return x.id
|
||||
}
|
||||
|
||||
type epochSource uint64
|
||||
|
||||
func (s epochSource) CurrentEpoch() uint64 {
|
||||
return uint64(s)
|
||||
}
|
||||
|
||||
// PutObject saves the object in FrostFS network.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func PutObject(ctx context.Context, prm PutObjectPrm) (*PutObjectRes, error) {
|
||||
sdkPrm, err := prm.convertToSDKPrm(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create parameters of object put operation: %w", err)
|
||||
}
|
||||
wrt, err := prm.cli.ObjectPutInit(ctx, sdkPrm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("init object writing: %w", err)
|
||||
}
|
||||
|
||||
if wrt.WriteHeader(ctx, *prm.hdr) {
|
||||
if prm.headerCallback != nil {
|
||||
prm.headerCallback()
|
||||
}
|
||||
|
||||
sz := prm.hdr.PayloadSize()
|
||||
|
||||
if data := prm.hdr.Payload(); len(data) > 0 {
|
||||
if prm.rdr != nil {
|
||||
prm.rdr = io.MultiReader(bytes.NewReader(data), prm.rdr)
|
||||
} else {
|
||||
prm.rdr = bytes.NewReader(data)
|
||||
sz = uint64(len(data))
|
||||
}
|
||||
}
|
||||
|
||||
if prm.rdr != nil {
|
||||
const defaultBufferSizePut = 3 << 20 // Maximum chunk size is 3 MiB in the SDK.
|
||||
|
||||
if sz == 0 || sz > defaultBufferSizePut {
|
||||
sz = defaultBufferSizePut
|
||||
}
|
||||
|
||||
buf := make([]byte, sz)
|
||||
|
||||
var n int
|
||||
|
||||
for {
|
||||
n, err = prm.rdr.Read(buf)
|
||||
if n > 0 {
|
||||
if !wrt.WritePayloadChunk(ctx, buf[:n]) {
|
||||
break
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("read payload: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cliRes, err := wrt.Close(ctx)
|
||||
if err != nil { // here err already carries both status and client errors
|
||||
return nil, fmt.Errorf("client failure: %w", err)
|
||||
}
|
||||
|
||||
return &PutObjectRes{
|
||||
id: cliRes.StoredObjectID(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteObjectPrm groups parameters of DeleteObject operation.
|
||||
type DeleteObjectPrm struct {
|
||||
commonObjectPrm
|
||||
objectAddressPrm
|
||||
}
|
||||
|
||||
// DeleteObjectRes groups the resulting values of DeleteObject operation.
|
||||
type DeleteObjectRes struct {
|
||||
tomb oid.ID
|
||||
}
|
||||
|
||||
// Tombstone returns the ID of the created object with tombstone.
|
||||
func (x DeleteObjectRes) Tombstone() oid.ID {
|
||||
return x.tomb
|
||||
}
|
||||
|
||||
// DeleteObject marks an object to be removed from FrostFS through tombstone placement.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func DeleteObject(ctx context.Context, prm DeleteObjectPrm) (*DeleteObjectRes, error) {
|
||||
cnr := prm.objAddr.Container()
|
||||
obj := prm.objAddr.Object()
|
||||
|
||||
delPrm := client.PrmObjectDelete{
|
||||
XHeaders: prm.xHeaders,
|
||||
ContainerID: &cnr,
|
||||
ObjectID: &obj,
|
||||
Session: prm.sessionToken,
|
||||
BearerToken: prm.bearerToken,
|
||||
}
|
||||
|
||||
cliRes, err := prm.cli.ObjectDelete(ctx, delPrm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("remove object via client: %w", err)
|
||||
}
|
||||
|
||||
return &DeleteObjectRes{
|
||||
tomb: cliRes.Tombstone(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetObjectPrm groups parameters of GetObject operation.
|
||||
type GetObjectPrm struct {
|
||||
commonObjectPrm
|
||||
objectAddressPrm
|
||||
rawPrm
|
||||
payloadWriterPrm
|
||||
headerCallback func(*objectSDK.Object)
|
||||
}
|
||||
|
||||
// SetHeaderCallback sets callback which is called on the object after the header is received
|
||||
// but before the payload is written.
|
||||
func (p *GetObjectPrm) SetHeaderCallback(f func(*objectSDK.Object)) {
|
||||
p.headerCallback = f
|
||||
}
|
||||
|
||||
// GetObjectRes groups the resulting values of GetObject operation.
|
||||
type GetObjectRes struct {
|
||||
hdr *objectSDK.Object
|
||||
}
|
||||
|
||||
// Header returns the header of the request object.
|
||||
func (x GetObjectRes) Header() *objectSDK.Object {
|
||||
return x.hdr
|
||||
}
|
||||
|
||||
// GetObject reads an object by address.
|
||||
//
|
||||
// Interrupts on any writer error. If successful, payload is written to the writer.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
// For raw reading, returns *object.SplitInfoError error if object is virtual.
|
||||
func GetObject(ctx context.Context, prm GetObjectPrm) (*GetObjectRes, error) {
|
||||
cnr := prm.objAddr.Container()
|
||||
obj := prm.objAddr.Object()
|
||||
|
||||
getPrm := client.PrmObjectGet{
|
||||
XHeaders: prm.xHeaders,
|
||||
BearerToken: prm.bearerToken,
|
||||
Session: prm.sessionToken,
|
||||
Raw: prm.raw,
|
||||
Local: prm.local,
|
||||
ContainerID: &cnr,
|
||||
ObjectID: &obj,
|
||||
}
|
||||
|
||||
rdr, err := prm.cli.ObjectGetInit(ctx, getPrm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("init object reading on client: %w", err)
|
||||
}
|
||||
|
||||
var hdr objectSDK.Object
|
||||
|
||||
if !rdr.ReadHeader(&hdr) {
|
||||
_, err = rdr.Close()
|
||||
return nil, fmt.Errorf("read object header: %w", err)
|
||||
}
|
||||
if prm.headerCallback != nil {
|
||||
prm.headerCallback(&hdr)
|
||||
}
|
||||
|
||||
_, err = io.Copy(prm.wrt, rdr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("copy payload: %w", err)
|
||||
}
|
||||
|
||||
return &GetObjectRes{
|
||||
hdr: &hdr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// HeadObjectPrm groups parameters of HeadObject operation.
|
||||
type HeadObjectPrm struct {
|
||||
commonObjectPrm
|
||||
objectAddressPrm
|
||||
rawPrm
|
||||
|
||||
mainOnly bool
|
||||
}
|
||||
|
||||
// SetMainOnlyFlag sets flag to get only main fields of an object header in terms of FrostFS API.
|
||||
func (x *HeadObjectPrm) SetMainOnlyFlag(v bool) {
|
||||
x.mainOnly = v
|
||||
}
|
||||
|
||||
// HeadObjectRes groups the resulting values of HeadObject operation.
|
||||
type HeadObjectRes struct {
|
||||
hdr *objectSDK.Object
|
||||
}
|
||||
|
||||
// Header returns the requested object header.
|
||||
func (x HeadObjectRes) Header() *objectSDK.Object {
|
||||
return x.hdr
|
||||
}
|
||||
|
||||
// HeadObject reads an object header by address.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
// For raw reading, returns *object.SplitInfoError error if object is virtual.
|
||||
func HeadObject(ctx context.Context, prm HeadObjectPrm) (*HeadObjectRes, error) {
|
||||
cnr := prm.objAddr.Container()
|
||||
obj := prm.objAddr.Object()
|
||||
|
||||
headPrm := client.PrmObjectHead{
|
||||
XHeaders: prm.xHeaders,
|
||||
BearerToken: prm.bearerToken,
|
||||
Session: prm.sessionToken,
|
||||
Raw: prm.raw,
|
||||
Local: prm.local,
|
||||
ContainerID: &cnr,
|
||||
ObjectID: &obj,
|
||||
}
|
||||
|
||||
res, err := prm.cli.ObjectHead(ctx, headPrm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read object header via client: %w", err)
|
||||
}
|
||||
|
||||
var hdr objectSDK.Object
|
||||
|
||||
if !res.ReadHeader(&hdr) {
|
||||
return nil, errMissingHeaderInResponse
|
||||
}
|
||||
|
||||
return &HeadObjectRes{
|
||||
hdr: &hdr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SearchObjectsPrm groups parameters of SearchObjects operation.
|
||||
type SearchObjectsPrm struct {
|
||||
commonObjectPrm
|
||||
containerIDPrm
|
||||
|
||||
filters objectSDK.SearchFilters
|
||||
}
|
||||
|
||||
// SetFilters sets search filters.
|
||||
func (x *SearchObjectsPrm) SetFilters(filters objectSDK.SearchFilters) {
|
||||
x.filters = filters
|
||||
}
|
||||
|
||||
// SearchObjectsRes groups the resulting values of SearchObjects operation.
|
||||
type SearchObjectsRes struct {
|
||||
ids []oid.ID
|
||||
}
|
||||
|
||||
// IDList returns identifiers of the matched objects.
|
||||
func (x SearchObjectsRes) IDList() []oid.ID {
|
||||
return x.ids
|
||||
}
|
||||
|
||||
// SearchObjects selects objects from the container which match the filters.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func SearchObjects(ctx context.Context, prm SearchObjectsPrm) (*SearchObjectsRes, error) {
|
||||
cliPrm := client.PrmObjectSearch{
|
||||
XHeaders: prm.xHeaders,
|
||||
Local: prm.local,
|
||||
BearerToken: prm.bearerToken,
|
||||
Session: prm.sessionToken,
|
||||
ContainerID: &prm.cnrID,
|
||||
Filters: prm.filters,
|
||||
}
|
||||
|
||||
rdr, err := prm.cli.ObjectSearchInit(ctx, cliPrm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("init object search: %w", err)
|
||||
}
|
||||
|
||||
buf := make([]oid.ID, 10)
|
||||
var list []oid.ID
|
||||
var n int
|
||||
var ok bool
|
||||
|
||||
for {
|
||||
n, ok = rdr.Read(buf)
|
||||
for i := 0; i < n; i++ {
|
||||
list = append(list, buf[i])
|
||||
}
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
_, err = rdr.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read object list: %w", err)
|
||||
}
|
||||
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
lhs, rhs := list[i].EncodeToString(), list[j].EncodeToString()
|
||||
return strings.Compare(lhs, rhs) < 0
|
||||
})
|
||||
|
||||
return &SearchObjectsRes{
|
||||
ids: list,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// HashPayloadRangesPrm groups parameters of HashPayloadRanges operation.
|
||||
type HashPayloadRangesPrm struct {
|
||||
commonObjectPrm
|
||||
objectAddressPrm
|
||||
|
||||
tz bool
|
||||
|
||||
rngs []objectSDK.Range
|
||||
|
||||
salt []byte
|
||||
}
|
||||
|
||||
// TZ sets flag to request Tillich-Zemor hashes.
|
||||
func (x *HashPayloadRangesPrm) TZ() {
|
||||
x.tz = true
|
||||
}
|
||||
|
||||
// SetRanges sets a list of payload ranges to hash.
|
||||
func (x *HashPayloadRangesPrm) SetRanges(rngs []objectSDK.Range) {
|
||||
x.rngs = rngs
|
||||
}
|
||||
|
||||
// SetSalt sets data for each range to be XOR'ed with.
|
||||
func (x *HashPayloadRangesPrm) SetSalt(salt []byte) {
|
||||
x.salt = salt
|
||||
}
|
||||
|
||||
// HashPayloadRangesRes groups the resulting values of HashPayloadRanges operation.
|
||||
type HashPayloadRangesRes struct {
|
||||
cliRes *client.ResObjectHash
|
||||
}
|
||||
|
||||
// HashList returns a list of hashes of the payload ranges keeping order.
|
||||
func (x HashPayloadRangesRes) HashList() [][]byte {
|
||||
return x.cliRes.Checksums()
|
||||
}
|
||||
|
||||
// HashPayloadRanges requests hashes (by default SHA256) of the object payload ranges.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
// Returns an error if number of received hashes differs with the number of requested ranges.
|
||||
func HashPayloadRanges(ctx context.Context, prm HashPayloadRangesPrm) (*HashPayloadRangesRes, error) {
|
||||
cs := checksum.SHA256
|
||||
if prm.tz {
|
||||
cs = checksum.TZ
|
||||
}
|
||||
|
||||
cnr := prm.objAddr.Container()
|
||||
obj := prm.objAddr.Object()
|
||||
cliPrm := client.PrmObjectHash{
|
||||
ContainerID: &cnr,
|
||||
ObjectID: &obj,
|
||||
Local: prm.local,
|
||||
Salt: prm.salt,
|
||||
Ranges: prm.rngs,
|
||||
ChecksumType: cs,
|
||||
Session: prm.sessionToken,
|
||||
BearerToken: prm.bearerToken,
|
||||
XHeaders: prm.xHeaders,
|
||||
}
|
||||
|
||||
res, err := prm.cli.ObjectHash(ctx, cliPrm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read payload hashes via client: %w", err)
|
||||
}
|
||||
|
||||
return &HashPayloadRangesRes{
|
||||
cliRes: res,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PayloadRangePrm groups parameters of PayloadRange operation.
|
||||
type PayloadRangePrm struct {
|
||||
commonObjectPrm
|
||||
objectAddressPrm
|
||||
rawPrm
|
||||
payloadWriterPrm
|
||||
|
||||
rng *objectSDK.Range
|
||||
}
|
||||
|
||||
// SetRange sets payload range to read.
|
||||
func (x *PayloadRangePrm) SetRange(rng *objectSDK.Range) {
|
||||
x.rng = rng
|
||||
}
|
||||
|
||||
// PayloadRangeRes groups the resulting values of PayloadRange operation.
|
||||
type PayloadRangeRes struct{}
|
||||
|
||||
// PayloadRange reads object payload range from FrostFS and writes it to the specified writer.
|
||||
//
|
||||
// Interrupts on any writer error.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
// For raw reading, returns *object.SplitInfoError error if object is virtual.
|
||||
func PayloadRange(ctx context.Context, prm PayloadRangePrm) (*PayloadRangeRes, error) {
|
||||
cnr := prm.objAddr.Container()
|
||||
obj := prm.objAddr.Object()
|
||||
|
||||
rangePrm := client.PrmObjectRange{
|
||||
XHeaders: prm.xHeaders,
|
||||
BearerToken: prm.bearerToken,
|
||||
Session: prm.sessionToken,
|
||||
Raw: prm.raw,
|
||||
Local: prm.local,
|
||||
ContainerID: &cnr,
|
||||
ObjectID: &obj,
|
||||
Offset: prm.rng.GetOffset(),
|
||||
Length: prm.rng.GetLength(),
|
||||
}
|
||||
|
||||
rdr, err := prm.cli.ObjectRangeInit(ctx, rangePrm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("init payload reading: %w", err)
|
||||
}
|
||||
|
||||
_, err = io.Copy(prm.wrt, rdr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("copy payload: %w", err)
|
||||
}
|
||||
|
||||
return new(PayloadRangeRes), nil
|
||||
}
|
||||
|
||||
// SyncContainerPrm groups parameters of SyncContainerSettings operation.
|
||||
type SyncContainerPrm struct {
|
||||
commonPrm
|
||||
c *containerSDK.Container
|
||||
}
|
||||
|
||||
// SetContainer sets a container that is required to be synced.
|
||||
func (s *SyncContainerPrm) SetContainer(c *containerSDK.Container) {
|
||||
s.c = c
|
||||
}
|
||||
|
||||
// SyncContainerRes groups resulting values of SyncContainerSettings
|
||||
// operation.
|
||||
type SyncContainerRes struct{}
|
||||
|
||||
// SyncContainerSettings reads global network config from FrostFS and
|
||||
// syncs container settings with it.
|
||||
//
|
||||
// Interrupts on any writer error.
|
||||
//
|
||||
// Panics if a container passed as a parameter is nil.
|
||||
func SyncContainerSettings(ctx context.Context, prm SyncContainerPrm) (*SyncContainerRes, error) {
|
||||
if prm.c == nil {
|
||||
panic("sync container settings with the network: nil container")
|
||||
}
|
||||
|
||||
err := client.SyncContainerWithNetwork(ctx, prm.c, prm.cli)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return new(SyncContainerRes), nil
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue