Compare commits

..

1 commit

Author SHA1 Message Date
55f9181c68 [#375] Add log metrics
All checks were successful
ci/woodpecker/pr/pre-commit Pipeline was successful
Signed-off-by: Alejandro Lopez <a.lopez@yadro.com>
2023-06-09 12:59:28 +03:00
1177 changed files with 28425 additions and 63567 deletions

View file

@ -1,4 +1,4 @@
FROM golang:1.23 AS builder FROM golang:1.20 as builder
ARG BUILD=now ARG BUILD=now
ARG VERSION=dev ARG VERSION=dev
ARG REPO=repository ARG REPO=repository

View file

@ -1,4 +1,4 @@
FROM golang:1.23 FROM golang:1.20
WORKDIR /tmp WORKDIR /tmp

View file

@ -1,4 +1,4 @@
FROM golang:1.23 AS builder FROM golang:1.20 as builder
ARG BUILD=now ARG BUILD=now
ARG VERSION=dev ARG VERSION=dev
ARG REPO=repository ARG REPO=repository

View file

@ -1,4 +1,4 @@
FROM golang:1.23 AS builder FROM golang:1.20 as builder
ARG BUILD=now ARG BUILD=now
ARG VERSION=dev ARG VERSION=dev
ARG REPO=repository ARG REPO=repository

View file

@ -1,4 +1,4 @@
FROM golang:1.23 AS builder FROM golang:1.20 as builder
ARG BUILD=now ARG BUILD=now
ARG VERSION=dev ARG VERSION=dev
ARG REPO=repository ARG REPO=repository

View file

@ -0,0 +1,19 @@
FROM golang:1.20 as builder
ARG BUILD=now
ARG VERSION=dev
ARG REPO=repository
WORKDIR /src
COPY . /src
RUN make bin/frostfs-node
# Executable image
FROM alpine AS frostfs-node
RUN apk add --no-cache bash
WORKDIR /
COPY --from=builder /src/bin/frostfs-node /bin/frostfs-node
COPY --from=builder /src/config/testnet/config.yml /config.yml
CMD ["frostfs-node", "--config", "/config.yml"]

View file

@ -1,45 +0,0 @@
name: Build
on:
pull_request:
push:
branches:
- master
jobs:
build:
name: Build Components
runs-on: ubuntu-latest
strategy:
matrix:
go_versions: [ '1.22', '1.23' ]
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

View file

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

View file

@ -1,30 +0,0 @@
name: Pre-commit hooks
on:
pull_request:
push:
branches:
- master
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 find python.
# So install everything manually.
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.23
- 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

View file

@ -1,116 +0,0 @@
name: Tests and linters
on:
pull_request:
push:
branches:
- master
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.23'
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.22', '1.23' ]
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.22'
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.23'
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.22'
cache: true
- name: Install gopls
run: make gopls-install
- name: Run gopls
run: make gopls-run
fumpt:
name: Run gofumpt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: '1.23'
cache: true
- name: Install gofumpt
run: make fumpt-install
- name: Run gofumpt
run: |
make fumpt
git diff --exit-code --quiet

View file

@ -1,27 +0,0 @@
name: Vulncheck
on:
pull_request:
push:
branches:
- master
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.23'
- name: Install govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest
- name: Run govulncheck
run: govulncheck ./...

View file

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

11
.gitlint Normal file
View file

@ -0,0 +1,11 @@
[general]
fail-without-commits=True
regex-style-search=True
contrib=CC1
[title-match-regex]
regex=^\[\#[0-9Xx]+\]\s
[ignore-by-title]
regex=^Release(.*)
ignore=title-match-regex

View file

@ -4,7 +4,7 @@
# options for analysis running # options for analysis running
run: run:
# timeout for analysis, e.g. 30s, 5m, default is 1m # timeout for analysis, e.g. 30s, 5m, default is 1m
timeout: 20m timeout: 10m
# include test files or not, default is true # include test files or not, default is true
tests: false tests: false
@ -12,8 +12,7 @@ run:
# output configuration options # output configuration options
output: output:
# colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number" # colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number"
formats: format: tab
- format: tab
# all available settings of specific linters # all available settings of specific linters
linters-settings: linters-settings:
@ -32,25 +31,6 @@ linters-settings:
statements: 60 # default 40 statements: 60 # default 40
gocognit: gocognit:
min-complexity: 40 # default 30 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
unused:
field-writes-are-uses: false
exported-fields-are-used: false
local-variables-are-used: false
custom:
truecloudlab-linters:
path: bin/linters/external_linters.so
original-url: git.frostfs.info/TrueCloudLab/linters.git
settings:
noliteral:
target-methods : ["reportFlushError", "reportError"]
disable-packages: ["codes", "err", "res","exec"]
constants-package: "git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
linters: linters:
enable: enable:
@ -71,7 +51,7 @@ linters:
- bidichk - bidichk
- durationcheck - durationcheck
- exhaustive - exhaustive
- copyloopvar - exportloopref
- gofmt - gofmt
- goimports - goimports
- misspell - misspell
@ -82,12 +62,5 @@ linters:
- funlen - funlen
- gocognit - gocognit
- contextcheck - contextcheck
- importas
- truecloudlab-linters
- perfsprint
- testifylint
- protogetter
- intrange
- tenv
disable-all: true disable-all: true
fast: false fast: false

View file

@ -2,8 +2,15 @@ ci:
autofix_prs: false autofix_prs: false
repos: repos:
- repo: https://github.com/jorisroovers/gitlint
rev: v0.19.1
hooks:
- id: gitlint
stages: [commit-msg]
- id: gitlint-ci
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0 rev: v4.4.0
hooks: hooks:
- id: check-added-large-files - id: check-added-large-files
- id: check-case-conflict - id: check-case-conflict
@ -16,35 +23,23 @@ repos:
- id: trailing-whitespace - id: trailing-whitespace
args: [--markdown-linebreak-ext=md] args: [--markdown-linebreak-ext=md]
- id: end-of-file-fixer - id: end-of-file-fixer
exclude: "(.key|.svg)$" exclude: ".key$"
- repo: https://github.com/shellcheck-py/shellcheck-py - repo: https://github.com/shellcheck-py/shellcheck-py
rev: v0.9.0.6 rev: v0.9.0.2
hooks: hooks:
- id: shellcheck - id: shellcheck
- repo: local - repo: https://github.com/golangci/golangci-lint
rev: v1.52.2
hooks: hooks:
- id: make-lint - id: golangci-lint
name: Run Make Lint
entry: make lint
language: system
pass_filenames: false
- repo: local - repo: local
hooks: hooks:
- id: go-unit-tests - id: go-unit-tests
name: go unit tests name: go unit tests
entry: make test GOFLAGS='' entry: make test
pass_filenames: false
types: [go]
language: system
- repo: local
hooks:
- id: gofumpt
name: gofumpt
entry: make fumpt
pass_filenames: false pass_filenames: false
types: [go] types: [go]
language: system language: system

View file

@ -0,0 +1,11 @@
pipeline:
# Kludge for non-root containers under WoodPecker
fix-ownership:
image: alpine:latest
commands: chown -R 1234:1234 .
pre-commit:
image: git.frostfs.info/truecloudlab/frostfs-ci:v0.36
commands:
- export HOME="$(getent passwd $(id -u) | cut '-d:' -f6)"
- pre-commit run --hook-stage manual

View file

@ -3,105 +3,6 @@ Changelog for FrostFS Node
## [Unreleased] ## [Unreleased]
### Added
### Changed
### Fixed
### Removed
### Updated
## [v0.42.0]
### Added
- Add audit logs for gRPC requests (#1184)
- Add CLI command to convert eACL to APE (#1189)
- Add `--await` flag to `control set-status` (#60)
- `app_info` metric for binary version (#1154)
- `--quiet` flag for healthcheck command (#1209)
### Changed
- Deprecate Container.SetEACL RPC (#1219)
### Fixed
- Take groups into account during APE processing (#1190)
- Handle double SIGHUP correctly (#1145)
- Handle empty filenames in tree listing (#1074)
- Handle duplicate tree nodes in the split-brain scenario (#1234, #1251)
- Remove APE pre-check in Object.GET/HEAD/RANGE RPC (#1249)
- Delete EC gc marks and split info (#1257)
- Do not search for non-existent objects on deletion (#1261)
### Updated
- Make putting EC chunks more robust (#1233)
## [v0.41.0]
### Added
- Support mTLS for morph client (#1170)
### Fixed
- Update shard state metric during shard init (#1174)
- Handle ENOSPC in blobovnicza (#1166)
- Handle multiple split-infos for EC objects (#1163)
- Set `Disabled` mode as the default for components (#1168)
## [v0.40.0]
### Added
- Support EC chunk reconstruction in policer (#1129)
- Support LOCK, DELETE and SEARCH methods on EC objects (#1147, 1144)
- apemanager service to manage APE chains (#1105)
### Fixed
- Properly verify GetRangeHash response (#1083)
- Send `MONOTONIC_USEC` in sdnotify on reload (#1135)
### Updated
- neo-go to `v0.106.0`
## [v0.39.0]
### Added
- Preliminary erasure coding support (#1065, #1112, #1103, #1120)
- TTL cache for blobovnicza tree (#1004)
- Cache for frostfsid and policy contracts (#1117)
- Writecache path to metric labels (#966)
- Documentation for authentication mechanisms (#1097, #1104)
- Metrics for metabase resync status (#1029)
### Changed
- Speed up metabase resync (#1024)
### Fixed
- Possible panic in GET_RANGE (#1077)
### Updated
- Minimum required Go version to 1.21
## [v0.38.0]
### Added
- Add `trace_id` to logs in `frostfs-node` (#146)
- Allow to forcefully remove container from IR (#733)
- LOKI support (#740)
- Allow sealing writecache (#569)
- Support tree service in data evacuation (#947)
- Use new policy engine mechanism for access control (#770, #804)
- Log about active notary deposit waiting (#963)
### Changed
- Sort output in `frostfs-cli` subcommands (#333)
- Send bootstrap query at each epoch tick (#721)
- Do not retain garbage in fstree on systems supporting O_TMPFILE (#970)
### Fixed
- Handle synchronization failures better in tree service (#741)
- Fix invalid batch size for iterator traversal in morph (#1000)
### Updated
- `neo-go` to `v0.105.0`
## [v0.37.0]
### Added ### Added
- Support impersonate bearer token (#229) - Support impersonate bearer token (#229)
- Change log level on SIGHUP for ir (#125) - Change log level on SIGHUP for ir (#125)
@ -135,13 +36,12 @@ Changelog for FrostFS Node
- `github.com/multiformats/go-multiaddr` to `v0.9.0` - `github.com/multiformats/go-multiaddr` to `v0.9.0`
- `github.com/hashicorp/golang-lru/v2` to `v2.0.2` - `github.com/hashicorp/golang-lru/v2` to `v2.0.2`
- `go.uber.org/atomic` to `v1.11.0` - `go.uber.org/atomic` to `v1.11.0`
- Minimum go version to v1.20 - Minimum go version to v1.19
- `github.com/prometheus/client_golang` to `v1.15.1` - `github.com/prometheus/client_golang` to `v1.15.1`
- `github.com/prometheus/client_model` to `v0.4.0` - `github.com/prometheus/client_model` to `v0.4.0`
- `go.opentelemetry.io/otel` to `v1.15.1` - `go.opentelemetry.io/otel` to `v1.15.1`
- `go.opentelemetry.io/otel/trace` to `v1.15.1` - `go.opentelemetry.io/otel/trace` to `v1.15.1`
- `github.com/spf13/cast` to `v1.5.1` - `github.com/spf13/cast` to `v1.5.1`
- `git.frostfs.info/TrueCloudLab/hrw` to `v1.2.1`
### Updating from v0.36.0 ### Updating from v0.36.0

View file

@ -3,8 +3,8 @@
First, thank you for contributing! We love and encourage pull requests from First, thank you for contributing! We love and encourage pull requests from
everyone. Please follow the guidelines: everyone. Please follow the guidelines:
- Check the open [issues](https://git.frostfs.info/TrueCloudLab/frostfs-node/issues) and - Check the open [issues](https://github.com/TrueCloudLab/frostfs-node/issues) and
[pull requests](https://git.frostfs.info/TrueCloudLab/frostfs-node/pulls) for existing [pull requests](https://github.com/TrueCloudLab/frostfs-node/pulls) for existing
discussions. discussions.
- Open an issue first, to discuss a new feature or enhancement. - Open an issue first, to discuss a new feature or enhancement.
@ -27,19 +27,19 @@ Start by forking the `frostfs-node` repository, make changes in a branch and the
send a pull request. We encourage pull requests to discuss code changes. Here send a pull request. We encourage pull requests to discuss code changes. Here
are the steps in details: are the steps in details:
### Set up your Forgejo repository ### Set up your GitHub Repository
Fork [FrostFS node upstream](https://git.frostfs.info/TrueCloudLab/frostfs-node) source Fork [FrostFS node upstream](https://github.com/TrueCloudLab/frostfs-node/fork) source
repository to your own personal repository. Copy the URL of your fork (you will repository to your own personal repository. Copy the URL of your fork (you will
need it for the `git clone` command below). need it for the `git clone` command below).
```sh ```sh
$ git clone https://git.frostfs.info/TrueCloudLab/frostfs-node $ git clone https://github.com/TrueCloudLab/frostfs-node
``` ```
### Set up git remote as ``upstream`` ### Set up git remote as ``upstream``
```sh ```sh
$ cd frostfs-node $ cd frostfs-node
$ git remote add upstream https://git.frostfs.info/TrueCloudLab/frostfs-node $ git remote add upstream https://github.com/TrueCloudLab/frostfs-node
$ git fetch upstream $ git fetch upstream
$ git merge upstream/master $ git merge upstream/master
... ...
@ -58,7 +58,7 @@ $ git checkout -b feature/123-something_awesome
After your code changes, make sure After your code changes, make sure
- To add test cases for the new code. - To add test cases for the new code.
- To run `make lint` and `make staticcheck-run` - To run `make lint`
- To squash your commits into a single commit or a series of logically separated - To squash your commits into a single commit or a series of logically separated
commits run `git rebase -i`. It's okay to force update your pull request. commits run `git rebase -i`. It's okay to force update your pull request.
- To run `make test` and `make all` completes. - To run `make test` and `make all` completes.
@ -89,8 +89,8 @@ $ git push origin feature/123-something_awesome
``` ```
### Create a Pull Request ### Create a Pull Request
Pull requests can be created via Forgejo. Refer to [this Pull requests can be created via GitHub. Refer to [this
document](https://docs.codeberg.org/collaborating/pull-requests-and-git-flow/) for 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 detailed steps on how to create a pull request. After a Pull Request gets peer
reviewed and approved, it will be merged. reviewed and approved, it will be merged.

194
Makefile
View file

@ -4,19 +4,11 @@ SHELL = bash
REPO ?= $(shell go list -m) REPO ?= $(shell go list -m)
VERSION ?= $(shell git describe --tags --dirty --match "v*" --always --abbrev=8 2>/dev/null || cat VERSION 2>/dev/null || echo "develop") VERSION ?= $(shell git describe --tags --dirty --match "v*" --always --abbrev=8 2>/dev/null || cat VERSION 2>/dev/null || echo "develop")
HUB_IMAGE ?= git.frostfs.info/truecloudlab/frostfs HUB_IMAGE ?= truecloudlab/frostfs
HUB_TAG ?= "$(shell echo ${VERSION} | sed 's/^v//')" HUB_TAG ?= "$(shell echo ${VERSION} | sed 's/^v//')"
GO_VERSION ?= 1.22 GO_VERSION ?= 1.19
LINT_VERSION ?= 1.62.0 LINT_VERSION ?= 1.52.2
TRUECLOUDLAB_LINT_VERSION ?= 0.0.8
PROTOC_VERSION ?= 25.0
PROTOGEN_FROSTFS_VERSION ?= $(shell go list -f '{{.Version}}' -m git.frostfs.info/TrueCloudLab/frostfs-sdk-go)
PROTOC_OS_VERSION=osx-x86_64
ifeq ($(shell uname), Linux)
PROTOC_OS_VERSION=linux-x86_64
endif
STATICCHECK_VERSION ?= 2024.1.1
ARCH = amd64 ARCH = amd64
BIN = bin BIN = bin
@ -27,32 +19,14 @@ DIRS = $(BIN) $(RELEASE)
CMDS = $(notdir $(basename $(wildcard cmd/frostfs-*))) CMDS = $(notdir $(basename $(wildcard cmd/frostfs-*)))
BINS = $(addprefix $(BIN)/, $(CMDS)) BINS = $(addprefix $(BIN)/, $(CMDS))
OUTPUT_LINT_DIR ?= $(abspath $(BIN))/linters # .deb package versioning
LINT_DIR = $(OUTPUT_LINT_DIR)/golangci-lint-$(LINT_VERSION)-v$(TRUECLOUDLAB_LINT_VERSION) OS_RELEASE = $(shell lsb_release -cs)
TMP_DIR := .cache PKG_VERSION ?= $(shell echo $(VERSION) | sed "s/^v//" | \
PROTOBUF_DIR ?= $(abspath $(BIN))/protobuf sed -E "s/(.*)-(g[a-fA-F0-9]{6,8})(.*)/\1\3~\2/" | \
PROTOC_DIR ?= $(PROTOBUF_DIR)/protoc-v$(PROTOC_VERSION) sed "s/-/~/")-${OS_RELEASE}
PROTOGEN_FROSTFS_DIR ?= $(PROTOBUF_DIR)/protogen-$(PROTOGEN_FROSTFS_VERSION)
STATICCHECK_DIR ?= $(abspath $(BIN))/staticcheck
STATICCHECK_VERSION_DIR ?= $(STATICCHECK_DIR)/$(STATICCHECK_VERSION)
SOURCES = $(shell find . -type f -name "*.go" -print) .PHONY: help all images dep clean fmts fmt imports test lint docker/lint
prepare-release debpackage pre-commit unpre-commit
GOFUMPT_VERSION ?= v0.7.0
GOFUMPT_DIR ?= $(abspath $(BIN))/gofumpt
GOFUMPT_VERSION_DIR ?= $(GOFUMPT_DIR)/$(GOFUMPT_VERSION)
GOPLS_VERSION ?= v0.15.1
GOPLS_DIR ?= $(abspath $(BIN))/gopls
GOPLS_VERSION_DIR ?= $(GOPLS_DIR)/$(GOPLS_VERSION)
GOPLS_TEMP_FILE := $(shell mktemp)
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 pre-commit unpre-commit
# To build a specific binary, use it's name prefix with bin/ as a target # To build a specific binary, use it's name prefix with bin/ as a target
# For example `make bin/frostfs-node` will build only storage node binary # For example `make bin/frostfs-node` will build only storage node binary
@ -91,37 +65,24 @@ dep:
CGO_ENABLED=0 \ CGO_ENABLED=0 \
go mod tidy -v && echo OK go mod tidy -v && echo OK
# Build export-metrics
export-metrics: dep
@printf "⇒ Build export-metrics\n"
CGO_ENABLED=0 \
go build -v -trimpath -o bin/export-metrics ./scripts/export-metrics
# Regenerate proto files: # Regenerate proto files:
protoc: protoc:
@if [ ! -d "$(PROTOC_DIR)" ] || [ ! -d "$(PROTOGEN_FROSTFS_DIR)" ]; then \ @GOPRIVATE=github.com/TrueCloudLab go mod vendor
make protoc-install; \ # Install specific version for protobuf lib
fi @go list -f '{{.Path}}/...@{{.Version}}' -m github.com/golang/protobuf | xargs go install -v
@for f in `find . -type f -name '*.proto' -not -path './bin/*'`; do \ @GOBIN=$(abspath $(BIN)) go install -mod=mod -v git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/util/protogen
# Protoc generate
@for f in `find . -type f -name '*.proto' -not -path './vendor/*'`; do \
echo "⇒ Processing $$f "; \ echo "⇒ Processing $$f "; \
$(PROTOC_DIR)/bin/protoc \ protoc \
--proto_path=.:$(PROTOC_DIR)/include:/usr/local/include \ --proto_path=.:./vendor:/usr/local/include \
--plugin=protoc-gen-go-frostfs=$(PROTOGEN_FROSTFS_DIR)/protogen \ --plugin=protoc-gen-go-frostfs=$(BIN)/protogen \
--go-frostfs_out=. --go-frostfs_opt=paths=source_relative \ --go-frostfs_out=. --go-frostfs_opt=paths=source_relative \
--go_out=. --go_opt=paths=source_relative \
--go-grpc_opt=require_unimplemented_servers=false \ --go-grpc_opt=require_unimplemented_servers=false \
--go-grpc_out=. --go-grpc_opt=paths=source_relative $$f; \ --go-grpc_out=. --go-grpc_opt=paths=source_relative $$f; \
done done
rm -rf vendor
# 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 "⇒ Instaling protogen FrostFS plugin..."
@GOBIN=$(PROTOGEN_FROSTFS_DIR) go install -mod=mod -v git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/util/protogen@$(PROTOGEN_FROSTFS_VERSION)
# Build FrostFS component's docker image # Build FrostFS component's docker image
image-%: image-%:
@ -134,7 +95,7 @@ image-%:
-t $(HUB_IMAGE)-$*:$(HUB_TAG) . -t $(HUB_IMAGE)-$*:$(HUB_TAG) .
# Build all Docker images # Build all Docker images
images: image-storage image-ir image-cli image-adm images: image-storage image-ir image-cli image-adm image-storage-testnet
# Build dirty local Docker images # Build dirty local Docker images
dirty-images: image-dirty-storage image-dirty-ir image-dirty-cli image-dirty-adm dirty-images: image-dirty-storage image-dirty-ir image-dirty-cli image-dirty-adm
@ -150,86 +111,33 @@ docker/%:
# Run all code formatters # Run all code formatters
fmts: fumpt imports fmts: fmt imports
# Reformat code
fmt:
@echo "⇒ Processing gofmt check"
@gofmt -s -w cmd/ pkg/ misc/
# Reformat imports # Reformat imports
imports: imports:
@echo "⇒ Processing goimports check" @echo "⇒ Processing goimports check"
@goimports -w cmd/ pkg/ misc/ @goimports -w cmd/ pkg/ misc/
# Install gofumpt
fumpt-install:
@rm -rf $(GOFUMPT_DIR)
@mkdir $(GOFUMPT_DIR)
@GOBIN=$(GOFUMPT_VERSION_DIR) go install mvdan.cc/gofumpt@$(GOFUMPT_VERSION)
# Run gofumpt
fumpt:
@if [ ! -d "$(GOFUMPT_VERSION_DIR)" ]; then \
make fumpt-install; \
fi
@echo "⇒ Processing gofumpt check"
$(GOFUMPT_VERSION_DIR)/gofumpt -l -w cmd/ pkg/ misc/
# Run Unit Test with go test # Run Unit Test with go test
test: GOFLAGS ?= "-count=1"
test: test:
@echo "⇒ Running go test" @echo "⇒ Running go test"
@GOFLAGS="$(GOFLAGS)" go test ./... @go test ./... -count=1
# Run pre-commit
pre-commit-run: pre-commit-run:
@pre-commit run -a --hook-stage manual @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 -trimpath github.com/golangci/golangci-lint/cmd/golangci-lint@v$(LINT_VERSION)
# Run linters # Run linters
lint: lint:
@if [ ! -d "$(LINT_DIR)" ]; then \ @golangci-lint --timeout=5m run
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 # Run staticcheck
staticcheck-run: staticcheck:
@if [ ! -d "$(STATICCHECK_VERSION_DIR)" ]; then \ @staticcheck ./...
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
$(GOPLS_VERSION_DIR)/gopls check $(SOURCES) 2>&1 >$(GOPLS_TEMP_FILE)
@if [[ $$(wc -l < $(GOPLS_TEMP_FILE)) -ne 0 ]]; then \
cat $(GOPLS_TEMP_FILE); \
exit 1; \
fi
rm $(GOPLS_TEMP_FILE)
# Run linters in Docker # Run linters in Docker
docker/lint: docker/lint:
@ -253,35 +161,19 @@ version:
# Delete built artifacts # Delete built artifacts
clean: clean:
rm -rf vendor
rm -rf .cache rm -rf .cache
rm -rf $(BIN) rm -rf $(BIN)
rm -rf $(RELEASE) rm -rf $(RELEASE)
# Download locode database # Package for Debian
locode-download: debpackage:
mkdir -p $(TMP_DIR) dch -b --package frostfs-node \
@wget -q -O ./$(TMP_DIR)/locode_db.gz 'https://git.frostfs.info/TrueCloudLab/frostfs-locode-db/releases/download/${LOCODE_DB_VERSION}/locode_db.gz' --controlmaint \
gzip -dfk ./$(TMP_DIR)/locode_db.gz --newversion $(PKG_VERSION) \
--distribution $(OS_RELEASE) \
"Please see CHANGELOG.md for code changes for $(VERSION)"
dpkg-buildpackage --no-sign -b
# Start dev environment debclean:
env-up: all dh clean
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 -v
rm -rf ./$(TMP_DIR)/state
rm -rf ./$(TMP_DIR)/storage

View file

@ -1,5 +1,5 @@
<p align="center"> <p align="center">
<img src="./.forgejo/logo.svg" width="500px" alt="FrostFS"> <img src="./.github/logo.svg" width="500px" alt="FrostFS">
</p> </p>
<p align="center"> <p align="center">
@ -7,8 +7,9 @@
</p> </p>
--- ---
[![Report](https://goreportcard.com/badge/git.frostfs.info/TrueCloudLab/frostfs-node)](https://goreportcard.com/report/git.frostfs.info/TrueCloudLab/frostfs-node) [![Report](https://goreportcard.com/badge/github.com/TrueCloudLab/frostfs-node)](https://goreportcard.com/report/github.com/TrueCloudLab/frostfs-node)
![Release (latest)](https://git.frostfs.info/TrueCloudLab/frostfs-node/badges/release.svg) ![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/TrueCloudLab/frostfs-node?sort=semver)
![License](https://img.shields.io/github/license/TrueCloudLab/frostfs-node.svg?style=popout)
# Overview # Overview
@ -32,8 +33,8 @@ 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 FrostFS has a native [gRPC API](https://git.frostfs.info/TrueCloudLab/frostfs-api) and has
protocol gateways for popular protocols such as [AWS protocol gateways for popular protocols such as [AWS
S3](https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw), S3](https://github.com/TrueCloudLab/frostfs-s3-gw),
[HTTP](https://git.frostfs.info/TrueCloudLab/frostfs-http-gw), [HTTP](https://github.com/TrueCloudLab/frostfs-http-gw),
[FUSE](https://wikipedia.org/wiki/Filesystem_in_Userspace) and [FUSE](https://wikipedia.org/wiki/Filesystem_in_Userspace) and
[sFTP](https://en.wikipedia.org/wiki/SSH_File_Transfer_Protocol) allowing [sFTP](https://en.wikipedia.org/wiki/SSH_File_Transfer_Protocol) allowing
developers to integrate applications without rewriting their code. developers to integrate applications without rewriting their code.
@ -44,11 +45,11 @@ Now, we only support GNU/Linux on amd64 CPUs with AVX/AVX2 instructions. More
platforms will be officially supported after release `1.0`. platforms will be officially supported after release `1.0`.
The latest version of frostfs-node works with frostfs-contract The latest version of frostfs-node works with frostfs-contract
[v0.19.2](https://git.frostfs.info/TrueCloudLab/frostfs-contract/releases/tag/v0.19.2). [v0.16.0](https://github.com/TrueCloudLab/frostfs-contract/releases/tag/v0.16.0).
# Building # Building
To make all binaries you need Go 1.22+ and `make`: To make all binaries you need Go 1.19+ and `make`:
``` ```
make all make all
``` ```
@ -70,50 +71,11 @@ make docker/bin/frostfs-<name> # build a specific binary
## Docker images ## Docker images
To make docker images suitable for use in [frostfs-dev-env](https://git.frostfs.info/TrueCloudLab/frostfs-dev-env/) use: To make docker images suitable for use in [frostfs-dev-env](https://github.com/TrueCloudLab/frostfs-dev-env/) use:
``` ```
make images make images
``` ```
# 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" --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
...
```
# Contributing # Contributing
Feel free to contribute to this project after reading the [contributing Feel free to contribute to this project after reading the [contributing
@ -124,7 +86,7 @@ the feature/topic you are going to implement.
# Credits # Credits
FrostFS is maintained by [True Cloud Lab](https://git.frostfs.info/TrueCloudLab/) with the help and FrostFS is maintained by [True Cloud Lab](https://github.com/TrueCloudLab/) with the help and
contributions from community members. contributions from community members.
Please see [CREDITS](CREDITS.md) for details. Please see [CREDITS](CREDITS.md) for details.

View file

@ -1 +1 @@
v0.42.0 v0.36.0

View file

@ -36,7 +36,9 @@ alphabet-wallets: /path # path to consensus node / alphabet wallets s
network: network:
max_object_size: 67108864 # max size of a single FrostFS object, bytes 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 epoch_duration: 240 # duration of a FrostFS epoch in blocks, consider block generation frequency in the sidechain
basic_income_rate: 0 # basic income rate, for private consider 0
fee: fee:
audit: 0 # network audit fee, for private installation consider 0
candidate: 0 # inner ring candidate registration fee, for private installation consider 0 candidate: 0 # inner ring candidate registration fee, for private installation consider 0
container: 0 # container creation fee, for private installation consider 0 container: 0 # container creation fee, for private installation consider 0
container_alias: 0 # container nice-name registration fee, for private installation consider 0 container_alias: 0 # container nice-name registration fee, for private installation consider 0
@ -56,8 +58,7 @@ credentials: # passwords for consensus node / alphabet wallets
#### Network deployment #### Network deployment
- `generate-alphabet` generates a set of wallets for consensus and - `generate-alphabet` generates a set of wallets for consensus and
Alphabet nodes. The list of the name for alphabet wallets(no gaps between names allowed, order is important): Alphabet nodes.
- az, buky, vedi, glagoli, dobro, yest, zhivete, dzelo, zemlja, izhe, izhei, gerv, kako, ljudi, mislete, nash, on, pokoj, rtsi, slovo, tverdo, uk
- `init` initializes the sidechain by deploying smart contracts and - `init` initializes the sidechain by deploying smart contracts and
setting provided FrostFS network configuration. setting provided FrostFS network configuration.

View file

@ -9,8 +9,8 @@ related configuration details.
To follow this guide you need: To follow this guide you need:
- latest released version of [neo-go](https://github.com/nspcc-dev/neo-go/releases) (v0.97.2 at the moment), - latest released version of [neo-go](https://github.com/nspcc-dev/neo-go/releases) (v0.97.2 at the moment),
- latest released version of [frostfs-adm](https://git.frostfs.info/TrueCloudLab/frostfs-node/releases) utility (v0.42.9 at the moment), - latest released version of [frostfs-adm](https://github.com/TrueCloudLab/frostfs-node/releases) utility (v0.25.1 at the moment),
- latest released version of compiled [frostfs-contract](https://git.frostfs.info/TrueCloudLab/frostfs-contract/releases) (v0.19.2 at the moment). - latest released version of compiled [frostfs-contract](https://github.com/TrueCloudLab/frostfs-contract/releases) (v0.11.0 at the moment).
## Step 1: Prepare network configuration ## Step 1: Prepare network configuration
@ -34,9 +34,9 @@ alphabet-wallets: /home/user/deploy/alphabet-wallets
network: network:
max_object_size: 67108864 max_object_size: 67108864
epoch_duration: 240 epoch_duration: 240
max_ec_data_count: 12 basic_income_rate: 0
max_ec_parity_count: 4
fee: fee:
audit: 0
candidate: 0 candidate: 0
container: 0 container: 0
withdraw: 0 withdraw: 0
@ -64,11 +64,6 @@ alphabet-wallets: /home/user/deploy/alphabet-wallets
wallet[0]: hunter2 wallet[0]: hunter2
``` ```
This command generates wallets with the following names:
- az, buky, vedi, glagoli, dobro, yest, zhivete, dzelo, zemlja, izhe, izhei, gerv, kako, ljudi, mislete, nash, on, pokoj, rtsi, slovo, tverdo, uk
No gaps between names allowed, order is important.
Do not lose wallet files and network config. Store it in an encrypted backed up Do not lose wallet files and network config. Store it in an encrypted backed up
storage. storage.
@ -147,6 +142,7 @@ Waiting for transactions to persist...
Stage 7: set addresses in NNS. Stage 7: set addresses in NNS.
Waiting for transactions to persist... Waiting for transactions to persist...
NNS: Set alphabet0.frostfs -> f692dfb4d43a15b464eb51a7041160fb29c44b6a NNS: Set alphabet0.frostfs -> f692dfb4d43a15b464eb51a7041160fb29c44b6a
NNS: Set audit.frostfs -> 7df847b993affb3852074345a7c2bd622171ee0d
NNS: Set balance.frostfs -> 103519b3067a66307080a66570c0491ee8f68879 NNS: Set balance.frostfs -> 103519b3067a66307080a66570c0491ee8f68879
NNS: Set container.frostfs -> cae60bdd689d185901e495352d0247752ce50846 NNS: Set container.frostfs -> cae60bdd689d185901e495352d0247752ce50846
NNS: Set frostfsid.frostfs -> c421fb60a3895865a8f24d197d6a80ef686041d2 NNS: Set frostfsid.frostfs -> c421fb60a3895865a8f24d197d6a80ef686041d2

View file

@ -11,33 +11,4 @@ const (
Verbose = "verbose" Verbose = "verbose"
VerboseShorthand = "v" VerboseShorthand = "v"
VerboseUsage = "Verbose output" 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"
ProtoConfigPath = "protocol"
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"
) )

View file

@ -18,11 +18,11 @@ type configTemplate struct {
AlphabetDir string AlphabetDir string
MaxObjectSize int MaxObjectSize int
EpochDuration int EpochDuration int
BasicIncomeRate int
AuditFee int
CandidateFee int CandidateFee int
ContainerFee int ContainerFee int
ContainerAliasFee int ContainerAliasFee int
MaxECDataCount int
MaxECParityCount int
WithdrawFee int WithdrawFee int
Glagolitics []string Glagolitics []string
HomomorphicHashDisabled bool HomomorphicHashDisabled bool
@ -33,10 +33,10 @@ alphabet-wallets: {{ .AlphabetDir}}
network: network:
max_object_size: {{ .MaxObjectSize}} max_object_size: {{ .MaxObjectSize}}
epoch_duration: {{ .EpochDuration}} epoch_duration: {{ .EpochDuration}}
max_ec_data_count: {{ .MaxECDataCount}} basic_income_rate: {{ .BasicIncomeRate}}
max_ec_parity_count: {{ .MaxECParityCount}}
homomorphic_hash_disabled: {{ .HomomorphicHashDisabled}} homomorphic_hash_disabled: {{ .HomomorphicHashDisabled}}
fee: fee:
audit: {{ .AuditFee}}
candidate: {{ .CandidateFee}} candidate: {{ .CandidateFee}}
container: {{ .ContainerFee}} container: {{ .ContainerFee}}
container_alias: {{ .ContainerAliasFee }} container_alias: {{ .ContainerAliasFee }}
@ -54,12 +54,12 @@ func initConfig(cmd *cobra.Command, _ []string) error {
} }
pathDir := filepath.Dir(configPath) pathDir := filepath.Dir(configPath)
err = os.MkdirAll(pathDir, 0o700) err = os.MkdirAll(pathDir, 0700)
if err != nil { if err != nil {
return fmt.Errorf("create dir %s: %w", pathDir, err) return fmt.Errorf("create dir %s: %w", pathDir, err)
} }
f, err := os.OpenFile(configPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_SYNC, 0o600) f, err := os.OpenFile(configPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_SYNC, 0600)
if err != nil { if err != nil {
return fmt.Errorf("open %s: %w", configPath, err) return fmt.Errorf("open %s: %w", configPath, err)
} }
@ -110,10 +110,10 @@ func generateConfigExample(appDir string, credSize int) (string, error) {
tmpl := configTemplate{ tmpl := configTemplate{
Endpoint: "https://neo.rpc.node:30333", Endpoint: "https://neo.rpc.node:30333",
MaxObjectSize: 67108864, // 64 MiB MaxObjectSize: 67108864, // 64 MiB
MaxECDataCount: 12, // Tested with 16-node networks, assuming 12 data + 4 parity nodes.
MaxECParityCount: 4, // Maximum 4 parity chunks, typically <= 3 for most policies.
EpochDuration: 240, // 1 hour with 15s per block EpochDuration: 240, // 1 hour with 15s per block
BasicIncomeRate: 1_0000_0000, // 0.0001 GAS per GiB (Fixed12)
HomomorphicHashDisabled: false, // object homomorphic hash is enabled HomomorphicHashDisabled: false, // object homomorphic hash is enabled
AuditFee: 1_0000, // 0.00000001 GAS per audit (Fixed12)
CandidateFee: 100_0000_0000, // 100.0 GAS (Fixed8) CandidateFee: 100_0000_0000, // 100.0 GAS (Fixed8)
ContainerFee: 1000, // 0.000000001 * 7 GAS per container (Fixed12) ContainerFee: 1000, // 0.000000001 * 7 GAS per container (Fixed12)
ContainerAliasFee: 500, // ContainerFee / 2 ContainerAliasFee: 500, // ContainerFee / 2
@ -128,7 +128,7 @@ func generateConfigExample(appDir string, credSize int) (string, error) {
tmpl.AlphabetDir = filepath.Join(appDir, "alphabet-wallets") tmpl.AlphabetDir = filepath.Join(appDir, "alphabet-wallets")
var i innerring.GlagoliticLetter var i innerring.GlagoliticLetter
for i = range innerring.GlagoliticLetter(credSize) { for i = 0; i < innerring.GlagoliticLetter(credSize); i++ {
tmpl.Glagolitics = append(tmpl.Glagolitics, i.String()) tmpl.Glagolitics = append(tmpl.Glagolitics, i.String())
} }

View file

@ -27,9 +27,9 @@ func TestGenerateConfigExample(t *testing.T) {
require.Equal(t, "https://neo.rpc.node:30333", v.GetString("rpc-endpoint")) require.Equal(t, "https://neo.rpc.node:30333", v.GetString("rpc-endpoint"))
require.Equal(t, filepath.Join(appDir, "alphabet-wallets"), v.GetString("alphabet-wallets")) require.Equal(t, filepath.Join(appDir, "alphabet-wallets"), v.GetString("alphabet-wallets"))
require.Equal(t, 67108864, v.GetInt("network.max_object_size")) require.Equal(t, 67108864, v.GetInt("network.max_object_size"))
require.Equal(t, 12, v.GetInt("network.max_ec_data_count"))
require.Equal(t, 4, v.GetInt("network.max_ec_parity_count"))
require.Equal(t, 240, v.GetInt("network.epoch_duration")) require.Equal(t, 240, v.GetInt("network.epoch_duration"))
require.Equal(t, 100000000, v.GetInt("network.basic_income_rate"))
require.Equal(t, 10000, v.GetInt("network.fee.audit"))
require.Equal(t, 10000000000, v.GetInt("network.fee.candidate")) require.Equal(t, 10000000000, v.GetInt("network.fee.candidate"))
require.Equal(t, 1000, v.GetInt("network.fee.container")) require.Equal(t, 1000, v.GetInt("network.fee.container"))
require.Equal(t, 100000000, v.GetInt("network.fee.withdraw")) require.Equal(t, 100000000, v.GetInt("network.fee.withdraw"))

View file

@ -1,15 +0,0 @@
package metabase
import "github.com/spf13/cobra"
// RootCmd is a root command of config section.
var RootCmd = &cobra.Command{
Use: "metabase",
Short: "Section for metabase commands",
}
func init() {
RootCmd.AddCommand(UpgradeCmd)
initUpgradeCommand()
}

View file

@ -1,150 +0,0 @@
package metabase
import (
"context"
"errors"
"fmt"
"sync"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config"
engineconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/engine"
shardconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/engine/shard"
morphconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/morph"
nodeconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/node"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
meta "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/metabase"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
morphcontainer "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/container"
"github.com/spf13/cobra"
"golang.org/x/sync/errgroup"
)
const (
noCompactFlag = "no-compact"
)
var (
errNoPathsFound = errors.New("no metabase paths found")
errNoMorphEndpointsFound = errors.New("no morph endpoints found")
)
var UpgradeCmd = &cobra.Command{
Use: "upgrade",
Short: "Upgrade metabase to latest version",
RunE: upgrade,
}
func upgrade(cmd *cobra.Command, _ []string) error {
configFile, err := cmd.Flags().GetString(commonflags.ConfigFlag)
if err != nil {
return err
}
configDir, err := cmd.Flags().GetString(commonflags.ConfigDirFlag)
if err != nil {
return err
}
appCfg := config.New(configFile, configDir, config.EnvPrefix)
paths, err := getMetabasePaths(appCfg)
if err != nil {
return err
}
if len(paths) == 0 {
return errNoPathsFound
}
cmd.Println("found", len(paths), "metabases:")
for i, path := range paths {
cmd.Println(i+1, ":", path)
}
mc, err := createMorphClient(cmd.Context(), appCfg)
if err != nil {
return err
}
defer mc.Close()
civ, err := createContainerInfoProvider(mc)
if err != nil {
return err
}
noCompact, _ := cmd.Flags().GetBool(noCompactFlag)
result := make(map[string]bool)
var resultGuard sync.Mutex
eg, ctx := errgroup.WithContext(cmd.Context())
for _, path := range paths {
eg.Go(func() error {
var success bool
cmd.Println("upgrading metabase", path, "...")
if err := meta.Upgrade(ctx, path, !noCompact, civ, func(a ...any) {
cmd.Println(append([]any{time.Now().Format(time.RFC3339), ":", path, ":"}, a...)...)
}); err != nil {
cmd.Println("error: failed to upgrade metabase", path, ":", err)
} else {
success = true
cmd.Println("metabase", path, "upgraded successfully")
}
resultGuard.Lock()
result[path] = success
resultGuard.Unlock()
return nil
})
}
if err := eg.Wait(); err != nil {
return err
}
for mb, ok := range result {
if ok {
cmd.Println(mb, ": success")
} else {
cmd.Println(mb, ": failed")
}
}
return nil
}
func getMetabasePaths(appCfg *config.Config) ([]string, error) {
var paths []string
if err := engineconfig.IterateShards(appCfg, false, func(sc *shardconfig.Config) error {
paths = append(paths, sc.Metabase().Path())
return nil
}); err != nil {
return nil, fmt.Errorf("get metabase paths: %w", err)
}
return paths, nil
}
func createMorphClient(ctx context.Context, appCfg *config.Config) (*client.Client, error) {
addresses := morphconfig.RPCEndpoint(appCfg)
if len(addresses) == 0 {
return nil, errNoMorphEndpointsFound
}
key := nodeconfig.Key(appCfg)
cli, err := client.New(ctx,
key,
client.WithDialTimeout(morphconfig.DialTimeout(appCfg)),
client.WithEndpoints(addresses...),
client.WithSwitchInterval(morphconfig.SwitchInterval(appCfg)),
)
if err != nil {
return nil, fmt.Errorf("create morph client:%w", err)
}
return cli, nil
}
func createContainerInfoProvider(cli *client.Client) (container.InfoProvider, error) {
sh, err := cli.NNSContractAddress(client.NNSContainerContractName)
if err != nil {
return nil, fmt.Errorf("resolve container contract hash: %w", err)
}
cc, err := morphcontainer.NewFromMorph(cli, sh, 0, morphcontainer.TryNotary())
if err != nil {
return nil, fmt.Errorf("create morph container client: %w", err)
}
return container.NewInfoProvider(func() (container.Source, error) {
return morphcontainer.AsContainerSource(cc), nil
}), nil
}
func initUpgradeCommand() {
flags := UpgradeCmd.Flags()
flags.Bool(noCompactFlag, false, "Do not compact upgraded metabase file")
}

View file

@ -1,250 +0,0 @@
package ape
import (
"bytes"
"encoding/json"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
apeCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/ape"
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 (
jsonFlag = "json"
jsonFlagDesc = "Output rule chains in JSON format"
addrAdminFlag = "addr"
addrAdminDesc = "The address of the admins wallet"
)
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(apeCmd.TargetTypeFlag, "", apeCmd.TargetTypeFlagDesc)
_ = addRuleChainCmd.MarkFlagRequired(apeCmd.TargetTypeFlag)
addRuleChainCmd.Flags().String(apeCmd.TargetNameFlag, "", apeCmd.TargetTypeFlagDesc)
_ = addRuleChainCmd.MarkFlagRequired(apeCmd.TargetNameFlag)
addRuleChainCmd.Flags().String(apeCmd.ChainIDFlag, "", apeCmd.ChainIDFlagDesc)
_ = addRuleChainCmd.MarkFlagRequired(apeCmd.ChainIDFlag)
addRuleChainCmd.Flags().StringArray(apeCmd.RuleFlag, []string{}, apeCmd.RuleFlagDesc)
addRuleChainCmd.Flags().String(apeCmd.PathFlag, "", apeCmd.PathFlagDesc)
addRuleChainCmd.Flags().String(apeCmd.ChainNameFlag, apeCmd.Ingress, apeCmd.ChainNameFlagDesc)
addRuleChainCmd.MarkFlagsMutuallyExclusive(apeCmd.RuleFlag, apeCmd.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(apeCmd.TargetTypeFlag, "", apeCmd.TargetTypeFlagDesc)
_ = removeRuleChainCmd.MarkFlagRequired(apeCmd.TargetTypeFlag)
removeRuleChainCmd.Flags().String(apeCmd.TargetNameFlag, "", apeCmd.TargetNameFlagDesc)
_ = removeRuleChainCmd.MarkFlagRequired(apeCmd.TargetNameFlag)
removeRuleChainCmd.Flags().String(apeCmd.ChainIDFlag, "", apeCmd.ChainIDFlagDesc)
removeRuleChainCmd.Flags().String(apeCmd.ChainNameFlag, apeCmd.Ingress, apeCmd.ChainNameFlagDesc)
removeRuleChainCmd.Flags().Bool(commonflags.AllFlag, false, "Remove all chains for target")
removeRuleChainCmd.MarkFlagsMutuallyExclusive(commonflags.AllFlag, apeCmd.ChainIDFlag)
}
func initListRuleChainsCmd() {
Cmd.AddCommand(listRuleChainsCmd)
listRuleChainsCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
listRuleChainsCmd.Flags().StringP(apeCmd.TargetTypeFlag, "t", "", apeCmd.TargetTypeFlagDesc)
_ = listRuleChainsCmd.MarkFlagRequired(apeCmd.TargetTypeFlag)
listRuleChainsCmd.Flags().String(apeCmd.TargetNameFlag, "", apeCmd.TargetNameFlagDesc)
listRuleChainsCmd.Flags().Bool(jsonFlag, false, jsonFlagDesc)
listRuleChainsCmd.Flags().String(apeCmd.ChainNameFlag, apeCmd.Ingress, apeCmd.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(apeCmd.TargetTypeFlag, "t", "", apeCmd.TargetTypeFlagDesc)
_ = listTargetsCmd.MarkFlagRequired(apeCmd.TargetTypeFlag)
}
func addRuleChain(cmd *cobra.Command, _ []string) {
chain := apeCmd.ParseChain(cmd)
target := parseTarget(cmd)
pci, ac := newPolicyContractInterface(cmd)
h, vub, err := pci.AddMorphRuleChain(apeCmd.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(apeCmd.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 := apeCmd.ParseChainID(cmd)
h, vub, err := pci.RemoveMorphRuleChain(apeCmd.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(apeCmd.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 {
apeCmd.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 := apeCmd.ParseTargetType(cmd)
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)
}

View file

@ -1,92 +0,0 @@
package ape
import (
"errors"
"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"
apeCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/ape"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
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"
)
var errUnknownTargetType = errors.New("unknown target type")
func parseTarget(cmd *cobra.Command) policyengine.Target {
typ := apeCmd.ParseTargetType(cmd)
name, _ := cmd.Flags().GetString(apeCmd.TargetNameFlag)
switch typ {
case policyengine.Namespace:
if name == "root" {
name = ""
}
return policyengine.NamespaceTarget(name)
case policyengine.Container:
var cnr cid.ID
commonCmd.ExitOnErr(cmd, "can't decode container ID: %w", cnr.DecodeString(name))
return policyengine.ContainerTarget(name)
case policyengine.User:
return policyengine.UserTarget(name)
case policyengine.Group:
return policyengine.GroupTarget(name)
default:
commonCmd.ExitOnErr(cmd, "read target type error: %w", errUnknownTargetType)
}
panic("unreachable")
}
// invokerAdapter adapats invoker.Invoker to ContractStorageInvoker interface.
type invokerAdapter struct {
*invoker.Invoker
rpcActor invoker.RPCInvoke
}
func (n *invokerAdapter) GetRPCInvoker() invoker.RPCInvoke {
return n.rpcActor
}
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 := helper.GetContractByID(r, 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)
invokerAdapter := &invokerAdapter{
Invoker: inv,
rpcActor: c,
}
return morph.NewContractStorageReader(invokerAdapter, 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, constants.ConsensusAccountName)
commonCmd.ExitOnErr(cmd, "can't create actor: %w", err)
var ch util.Uint160
r := management.NewReader(ac.Invoker)
nnsCs, err := helper.GetContractByID(r, 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
}

View file

@ -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()
}

View file

@ -1,4 +1,4 @@
package balance package morph
import ( import (
"crypto/elliptic" "crypto/elliptic"
@ -7,8 +7,6 @@ import (
"math/big" "math/big"
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns" "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" "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/native/noderoles"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
@ -18,7 +16,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas" "github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/rolemgmt" "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/rpcclient/unwrap"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
@ -51,7 +48,7 @@ func dumpBalances(cmd *cobra.Command, _ []string) error {
nmHash util.Uint160 nmHash util.Uint160
) )
c, err := helper.GetN3Client(viper.GetViper()) c, err := getN3Client(viper.GetViper())
if err != nil { if err != nil {
return err return err
} }
@ -59,13 +56,12 @@ func dumpBalances(cmd *cobra.Command, _ []string) error {
inv := invoker.New(c, nil) inv := invoker.New(c, nil)
if dumpStorage || dumpAlphabet || dumpProxy { if dumpStorage || dumpAlphabet || dumpProxy {
r := management.NewReader(inv) nnsCs, err = c.GetContractStateByID(1)
nnsCs, err = helper.GetContractByID(r, 1)
if err != nil { if err != nil {
return fmt.Errorf("can't get NNS contract info: %w", err) return fmt.Errorf("can't get NNS contract info: %w", err)
} }
nmHash, err = helper.NNSResolveHash(inv, nnsCs.Hash, helper.DomainOf(constants.NetmapContract)) nmHash, err = nnsResolveHash(inv, nnsCs.Hash, netmapContract+".frostfs")
if err != nil { if err != nil {
return fmt.Errorf("can't get netmap contract hash: %w", err) return fmt.Errorf("can't get netmap contract hash: %w", err)
} }
@ -138,7 +134,7 @@ func printStorageNodeBalances(cmd *cobra.Command, inv *invoker.Invoker, nmHash u
} }
func printProxyContractBalance(cmd *cobra.Command, inv *invoker.Invoker, nnsHash util.Uint160) error { func printProxyContractBalance(cmd *cobra.Command, inv *invoker.Invoker, nnsHash util.Uint160) error {
h, err := helper.NNSResolveHash(inv, nnsHash, helper.DomainOf(constants.ProxyContract)) h, err := nnsResolveHash(inv, nnsHash, proxyContract+".frostfs")
if err != nil { if err != nil {
return fmt.Errorf("can't get hash of the proxy contract: %w", err) return fmt.Errorf("can't get hash of the proxy contract: %w", err)
} }
@ -152,13 +148,13 @@ func printProxyContractBalance(cmd *cobra.Command, inv *invoker.Invoker, nnsHash
return nil return nil
} }
func printAlphabetContractBalances(cmd *cobra.Command, c helper.Client, inv *invoker.Invoker, count int, nnsHash util.Uint160) error { func printAlphabetContractBalances(cmd *cobra.Command, c Client, inv *invoker.Invoker, count int, nnsHash util.Uint160) error {
alphaList := make([]accBalancePair, count) alphaList := make([]accBalancePair, count)
w := io.NewBufBinWriter() w := io.NewBufBinWriter()
for i := range alphaList { for i := range alphaList {
emit.AppCall(w.BinWriter, nnsHash, "resolve", callflag.ReadOnly, emit.AppCall(w.BinWriter, nnsHash, "resolve", callflag.ReadOnly,
helper.GetAlphabetNNSDomain(i), getAlphabetNNSDomain(i),
int64(nns.TXT)) int64(nns.TXT))
} }
if w.Err != nil { if w.Err != nil {
@ -171,7 +167,7 @@ func printAlphabetContractBalances(cmd *cobra.Command, c helper.Client, inv *inv
} }
for i := range alphaList { for i := range alphaList {
h, err := helper.ParseNNSResolveResult(alphaRes.Stack[i]) h, err := parseNNSResolveResult(alphaRes.Stack[i])
if err != nil { if err != nil {
return fmt.Errorf("can't fetch the alphabet contract #%d hash: %w", i, err) return fmt.Errorf("can't fetch the alphabet contract #%d hash: %w", i, err)
} }
@ -186,7 +182,7 @@ func printAlphabetContractBalances(cmd *cobra.Command, c helper.Client, inv *inv
return nil return nil
} }
func fetchIRNodes(c helper.Client, desigHash util.Uint160) ([]accBalancePair, error) { func fetchIRNodes(c Client, desigHash util.Uint160) ([]accBalancePair, error) {
inv := invoker.New(c, nil) inv := invoker.New(c, nil)
height, err := c.GetBlockCount() height, err := c.GetBlockCount()
@ -194,7 +190,7 @@ func fetchIRNodes(c helper.Client, desigHash util.Uint160) ([]accBalancePair, er
return nil, fmt.Errorf("can't get block height: %w", err) return nil, fmt.Errorf("can't get block height: %w", err)
} }
arr, err := helper.GetDesignatedByRole(inv, desigHash, noderoles.NeoFSAlphabet, height) arr, err := getDesignatedByRole(inv, desigHash, noderoles.NeoFSAlphabet, height)
if err != nil { if err != nil {
return nil, errors.New("can't fetch list of IR nodes from the netmap contract") return nil, errors.New("can't fetch list of IR nodes from the netmap contract")
} }

View file

@ -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()
}

View file

@ -1,4 +1,4 @@
package config package morph
import ( import (
"bytes" "bytes"
@ -10,12 +10,9 @@ import (
"strings" "strings"
"text/tabwriter" "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" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/emit"
@ -26,20 +23,19 @@ import (
const forceConfigSet = "force" const forceConfigSet = "force"
func dumpNetworkConfig(cmd *cobra.Command, _ []string) error { func dumpNetworkConfig(cmd *cobra.Command, _ []string) error {
c, err := helper.GetN3Client(viper.GetViper()) c, err := getN3Client(viper.GetViper())
if err != nil { if err != nil {
return fmt.Errorf("can't create N3 client: %w", err) return fmt.Errorf("can't create N3 client: %w", err)
} }
inv := invoker.New(c, nil) inv := invoker.New(c, nil)
r := management.NewReader(inv)
cs, err := helper.GetContractByID(r, 1) cs, err := c.GetContractStateByID(1)
if err != nil { if err != nil {
return fmt.Errorf("can't get NNS contract info: %w", err) return fmt.Errorf("can't get NNS contract info: %w", err)
} }
nmHash, err := helper.NNSResolveHash(inv, cs.Hash, helper.DomainOf(constants.NetmapContract)) nmHash, err := nnsResolveHash(inv, cs.Hash, netmapContract+".frostfs")
if err != nil { if err != nil {
return fmt.Errorf("can't get netmap contract hash: %w", err) return fmt.Errorf("can't get netmap contract hash: %w", err)
} }
@ -52,23 +48,23 @@ func dumpNetworkConfig(cmd *cobra.Command, _ []string) error {
buf := bytes.NewBuffer(nil) buf := bytes.NewBuffer(nil)
tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0) tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
m, err := helper.ParseConfigFromNetmapContract(arr) m, err := parseConfigFromNetmapContract(arr)
if err != nil { if err != nil {
return err return err
} }
for k, v := range m { for k, v := range m {
switch k { switch k {
case netmap.ContainerFeeConfig, netmap.ContainerAliasFeeConfig, case netmap.AuditFeeConfig, netmap.BasicIncomeRateConfig,
netmap.ContainerFeeConfig, netmap.ContainerAliasFeeConfig,
netmap.EpochDurationConfig, netmap.IrCandidateFeeConfig, netmap.EpochDurationConfig, netmap.IrCandidateFeeConfig,
netmap.MaxObjectSizeConfig, netmap.WithdrawFeeConfig, netmap.MaxObjectSizeConfig, netmap.WithdrawFeeConfig:
netmap.MaxECDataCountConfig, netmap.MaxECParityCountConfig:
nbuf := make([]byte, 8) nbuf := make([]byte, 8)
copy(nbuf[:], v) copy(nbuf[:], v)
n := binary.LittleEndian.Uint64(nbuf) n := binary.LittleEndian.Uint64(nbuf)
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%d (int)\n", k, n))) _, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%d (int)\n", k, n)))
case netmap.HomomorphicHashingDisabledKey, netmap.MaintenanceModeAllowedConfig: case netmap.HomomorphicHashingDisabledKey, netmap.MaintenanceModeAllowedConfig:
if len(v) == 0 || len(v) > 1 { if len(v) == 0 || len(v) > 1 {
return helper.InvalidConfigValueErr(k) return invalidConfigValueErr(k)
} }
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%t (bool)\n", k, v[0] == 1))) _, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%t (bool)\n", k, v[0] == 1)))
default: default:
@ -82,44 +78,35 @@ func dumpNetworkConfig(cmd *cobra.Command, _ []string) error {
return nil return nil
} }
func SetConfigCmd(cmd *cobra.Command, args []string) error { func setConfigCmd(cmd *cobra.Command, args []string) error {
if len(args) == 0 { if len(args) == 0 {
return errors.New("empty config pairs") return errors.New("empty config pairs")
} }
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper()) wCtx, err := newInitializeContext(cmd, viper.GetViper())
if err != nil { if err != nil {
return fmt.Errorf("can't initialize context: %w", err) return fmt.Errorf("can't initialize context: %w", err)
} }
r := management.NewReader(wCtx.ReadOnlyInvoker) cs, err := wCtx.Client.GetContractStateByID(1)
cs, err := helper.GetContractByID(r, 1)
if err != nil { if err != nil {
return fmt.Errorf("can't get NNS contract info: %w", err) return fmt.Errorf("can't get NNS contract info: %w", err)
} }
nmHash, err := helper.NNSResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, helper.DomainOf(constants.NetmapContract)) nmHash, err := nnsResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, netmapContract+".frostfs")
if err != nil { if err != nil {
return fmt.Errorf("can't get netmap contract hash: %w", err) return fmt.Errorf("can't get netmap contract hash: %w", err)
} }
forceFlag, _ := cmd.Flags().GetBool(forceConfigSet) forceFlag, _ := cmd.Flags().GetBool(forceConfigSet)
bw := io.NewBufBinWriter() bw := io.NewBufBinWriter()
prm := make(map[string]any)
for _, arg := range args { for _, arg := range args {
k, v, err := parseConfigPair(arg, forceFlag) k, v, err := parseConfigPair(arg, forceFlag)
if err != nil { if err != nil {
return err 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 // 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 // transaction locally. The first `nil` argument is required only for notary
// disabled environment which is not supported by that command. // disabled environment which is not supported by that command.
@ -129,56 +116,12 @@ func SetConfigCmd(cmd *cobra.Command, args []string) error {
} }
} }
err = wCtx.SendConsensusTx(bw.Bytes()) err = wCtx.sendConsensusTx(bw.Bytes())
if err != nil { if err != nil {
return err return err
} }
return wCtx.AwaitTx() return wCtx.awaitTx()
}
const maxECSum = 256
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 {
switch k {
case netmap.ContainerFeeConfig, netmap.ContainerAliasFeeConfig,
netmap.EpochDurationConfig, netmap.IrCandidateFeeConfig,
netmap.MaxObjectSizeConfig, netmap.WithdrawFeeConfig,
netmap.MaxECDataCountConfig, netmap.MaxECParityCountConfig:
value, ok := v.(int64)
if !ok {
return fmt.Errorf("%s has an invalid type. Expected type: int", k)
}
if value < 0 {
return fmt.Errorf("%s must be >= 0, got %v", k, v)
}
if k == netmap.MaxECDataCountConfig || k == netmap.MaxECParityCountConfig {
sumEC += value
}
case netmap.HomomorphicHashingDisabledKey, netmap.MaintenanceModeAllowedConfig:
_, ok := v.(bool)
if !ok {
return fmt.Errorf("%s has an invalid type. Expected type: bool", k)
}
}
}
if sumEC > maxECSum && !forceFlag {
return fmt.Errorf("the sum of %s and %s must be <= %d, got %d",
netmap.MaxECDataCountConfig, netmap.MaxECParityCountConfig, maxECSum, sumEC)
}
return nil
} }
func parseConfigPair(kvStr string, force bool) (key string, val any, err error) { func parseConfigPair(kvStr string, force bool) (key string, val any, err error) {
@ -191,10 +134,10 @@ func parseConfigPair(kvStr string, force bool) (key string, val any, err error)
valRaw := v valRaw := v
switch key { switch key {
case netmap.ContainerFeeConfig, netmap.ContainerAliasFeeConfig, case netmap.AuditFeeConfig, netmap.BasicIncomeRateConfig,
netmap.ContainerFeeConfig, netmap.ContainerAliasFeeConfig,
netmap.EpochDurationConfig, netmap.IrCandidateFeeConfig, netmap.EpochDurationConfig, netmap.IrCandidateFeeConfig,
netmap.MaxObjectSizeConfig, netmap.WithdrawFeeConfig, netmap.MaxObjectSizeConfig, netmap.WithdrawFeeConfig:
netmap.MaxECDataCountConfig, netmap.MaxECParityCountConfig:
val, err = strconv.ParseInt(valRaw, 10, 64) val, err = strconv.ParseInt(valRaw, 10, 64)
if err != nil { if err != nil {
err = fmt.Errorf("could not parse %s's value '%s' as int: %w", key, valRaw, err) err = fmt.Errorf("could not parse %s's value '%s' as int: %w", key, valRaw, err)
@ -217,3 +160,7 @@ func parseConfigPair(kvStr string, force bool) (key string, val any, err error)
return return
} }
func invalidConfigValueErr(key string) error {
return fmt.Errorf("invalid %s config value from netmap contract", key)
}

View file

@ -1,34 +0,0 @@
package config
import (
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
"github.com/stretchr/testify/require"
)
func Test_ValidateConfig(t *testing.T) {
testArgs := make(map[string]any)
testArgs[netmap.MaxECDataCountConfig] = int64(11)
require.Error(t, validateConfig(testArgs, false))
testArgs[netmap.MaxECParityCountConfig] = int64(256)
require.Error(t, validateConfig(testArgs, false))
require.NoError(t, validateConfig(testArgs, true))
testArgs[netmap.MaxECParityCountConfig] = int64(-1)
require.Error(t, validateConfig(testArgs, false))
testArgs[netmap.MaxECParityCountConfig] = int64(55)
require.NoError(t, validateConfig(testArgs, false))
testArgs[netmap.HomomorphicHashingDisabledKey] = "1"
require.Error(t, validateConfig(testArgs, false))
testArgs[netmap.HomomorphicHashingDisabledKey] = true
require.NoError(t, validateConfig(testArgs, false))
testArgs["not-well-known-configuration-key"] = "key"
require.NoError(t, validateConfig(testArgs, false))
}

View file

@ -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()
}

View file

@ -1,59 +0,0 @@
package constants
import "time"
const (
ConsensusAccountName = "consensus"
// 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...)
)

View file

@ -1,20 +1,16 @@
package container package morph
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"os" "os"
"slices"
"sort" "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" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
@ -26,19 +22,18 @@ import (
var errInvalidContainerResponse = errors.New("invalid response from container contract") var errInvalidContainerResponse = errors.New("invalid response from container contract")
func getContainerContractHash(cmd *cobra.Command, inv *invoker.Invoker) (util.Uint160, error) { func getContainerContractHash(cmd *cobra.Command, inv *invoker.Invoker, c Client) (util.Uint160, error) {
s, err := cmd.Flags().GetString(containerContractFlag) s, err := cmd.Flags().GetString(containerContractFlag)
var ch util.Uint160 var ch util.Uint160
if err == nil { if err == nil {
ch, err = util.Uint160DecodeStringLE(s) ch, err = util.Uint160DecodeStringLE(s)
} }
if err != nil { if err != nil {
r := management.NewReader(inv) nnsCs, err := c.GetContractStateByID(1)
nnsCs, err := helper.GetContractByID(r, 1)
if err != nil { if err != nil {
return util.Uint160{}, fmt.Errorf("can't get NNS contract state: %w", err) return util.Uint160{}, fmt.Errorf("can't get NNS contract state: %w", err)
} }
ch, err = helper.NNSResolveHash(inv, nnsCs.Hash, helper.DomainOf(constants.ContainerContract)) ch, err = nnsResolveHash(inv, nnsCs.Hash, containerContract+".frostfs")
if err != nil { if err != nil {
return util.Uint160{}, err return util.Uint160{}, err
} }
@ -76,14 +71,14 @@ func dumpContainers(cmd *cobra.Command, _ []string) error {
return fmt.Errorf("invalid filename: %w", err) return fmt.Errorf("invalid filename: %w", err)
} }
c, err := helper.GetN3Client(viper.GetViper()) c, err := getN3Client(viper.GetViper())
if err != nil { if err != nil {
return fmt.Errorf("can't create N3 client: %w", err) return fmt.Errorf("can't create N3 client: %w", err)
} }
inv := invoker.New(c, nil) inv := invoker.New(c, nil)
ch, err := getContainerContractHash(cmd, inv) ch, err := getContainerContractHash(cmd, inv, c)
if err != nil { if err != nil {
return fmt.Errorf("unable to get contaract hash: %w", err) return fmt.Errorf("unable to get contaract hash: %w", err)
} }
@ -139,12 +134,13 @@ func dumpContainers(cmd *cobra.Command, _ []string) error {
func dumpSingleContainer(bw *io.BufBinWriter, ch util.Uint160, inv *invoker.Invoker, id []byte) (*Container, error) { func dumpSingleContainer(bw *io.BufBinWriter, ch util.Uint160, inv *invoker.Invoker, id []byte) (*Container, error) {
bw.Reset() bw.Reset()
emit.AppCall(bw.BinWriter, ch, "get", callflag.All, id) emit.AppCall(bw.BinWriter, ch, "get", callflag.All, id)
emit.AppCall(bw.BinWriter, ch, "eACL", callflag.All, id)
res, err := inv.Run(bw.Bytes()) res, err := inv.Run(bw.Bytes())
if err != nil { if err != nil {
return nil, fmt.Errorf("can't get container info: %w", err) return nil, fmt.Errorf("can't get container info: %w", err)
} }
if len(res.Stack) != 1 { if len(res.Stack) != 2 {
return nil, fmt.Errorf("%w: expected 1 items on stack", errInvalidContainerResponse) return nil, fmt.Errorf("%w: expected 2 items on stack", errInvalidContainerResponse)
} }
cnt := new(Container) cnt := new(Container)
@ -153,18 +149,26 @@ func dumpSingleContainer(bw *io.BufBinWriter, ch util.Uint160, inv *invoker.Invo
return nil, fmt.Errorf("%w: %v", errInvalidContainerResponse, err) 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 return cnt, nil
} }
func listContainers(cmd *cobra.Command, _ []string) error { func listContainers(cmd *cobra.Command, _ []string) error {
c, err := helper.GetN3Client(viper.GetViper()) c, err := getN3Client(viper.GetViper())
if err != nil { if err != nil {
return fmt.Errorf("can't create N3 client: %w", err) return fmt.Errorf("can't create N3 client: %w", err)
} }
inv := invoker.New(c, nil) inv := invoker.New(c, nil)
ch, err := getContainerContractHash(cmd, inv) ch, err := getContainerContractHash(cmd, inv, c)
if err != nil { if err != nil {
return fmt.Errorf("unable to get contaract hash: %w", err) return fmt.Errorf("unable to get contaract hash: %w", err)
} }
@ -186,11 +190,11 @@ func restoreContainers(cmd *cobra.Command, _ []string) error {
return fmt.Errorf("invalid filename: %w", err) return fmt.Errorf("invalid filename: %w", err)
} }
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper()) wCtx, err := newInitializeContext(cmd, viper.GetViper())
if err != nil { if err != nil {
return err return err
} }
defer wCtx.Close() defer wCtx.close()
containers, err := parseContainers(filename) containers, err := parseContainers(filename)
if err != nil { if err != nil {
@ -212,10 +216,10 @@ func restoreContainers(cmd *cobra.Command, _ []string) error {
return err return err
} }
return wCtx.AwaitTx() return wCtx.awaitTx()
} }
func restoreOrPutContainers(containers []Container, isOK func([]byte) bool, cmd *cobra.Command, wCtx *helper.InitializeContext, ch util.Uint160) error { func restoreOrPutContainers(containers []Container, isOK func([]byte) bool, cmd *cobra.Command, wCtx *initializeContext, ch util.Uint160) error {
bw := io.NewBufBinWriter() bw := io.NewBufBinWriter()
for _, cnt := range containers { for _, cnt := range containers {
hv := hash.Sha256(cnt.Value) hv := hash.Sha256(cnt.Value)
@ -239,7 +243,7 @@ func restoreOrPutContainers(containers []Container, isOK func([]byte) bool, cmd
panic(bw.Err) panic(bw.Err)
} }
if err := wCtx.SendConsensusTx(bw.Bytes()); err != nil { if err := wCtx.sendConsensusTx(bw.Bytes()); err != nil {
return err return err
} }
} }
@ -249,9 +253,13 @@ func restoreOrPutContainers(containers []Container, isOK func([]byte) bool, cmd
func putContainer(bw *io.BufBinWriter, ch util.Uint160, cnt Container) { func putContainer(bw *io.BufBinWriter, ch util.Uint160, cnt Container) {
emit.AppCall(bw.BinWriter, ch, "put", callflag.All, emit.AppCall(bw.BinWriter, ch, "put", callflag.All,
cnt.Value, cnt.Signature, cnt.PublicKey, cnt.Token) 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) { func isContainerRestored(cmd *cobra.Command, wCtx *initializeContext, containerHash util.Uint160, bw *io.BufBinWriter, hashValue util.Uint256) (bool, error) {
emit.AppCall(bw.BinWriter, containerHash, "get", callflag.All, hashValue.BytesBE()) emit.AppCall(bw.BinWriter, containerHash, "get", callflag.All, hashValue.BytesBE())
res, err := wCtx.Client.InvokeScript(bw.Bytes(), nil) res, err := wCtx.Client.InvokeScript(bw.Bytes(), nil)
if err != nil { if err != nil {
@ -289,14 +297,13 @@ func parseContainers(filename string) ([]Container, error) {
return containers, nil return containers, nil
} }
func fetchContainerContractHash(wCtx *helper.InitializeContext) (util.Uint160, error) { func fetchContainerContractHash(wCtx *initializeContext) (util.Uint160, error) {
r := management.NewReader(wCtx.ReadOnlyInvoker) nnsCs, err := wCtx.Client.GetContractStateByID(1)
nnsCs, err := helper.GetContractByID(r, 1)
if err != nil { if err != nil {
return util.Uint160{}, fmt.Errorf("can't get NNS contract state: %w", err) 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)) ch, err := nnsResolveHash(wCtx.ReadOnlyInvoker, nnsCs.Hash, containerContract+".frostfs")
if err != nil { if err != nil {
return util.Uint160{}, fmt.Errorf("can't fetch container contract hash: %w", err) return util.Uint160{}, fmt.Errorf("can't fetch container contract hash: %w", err)
} }
@ -309,6 +316,15 @@ type Container struct {
Signature []byte `json:"signature"` Signature []byte `json:"signature"`
PublicKey []byte `json:"public_key"` PublicKey []byte `json:"public_key"`
Token []byte `json:"token"` 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. // ToStackItem implements stackitem.Convertible.
@ -355,6 +371,50 @@ func (c *Container) FromStackItem(item stackitem.Item) error {
return nil 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. // getCIDFilterFunc returns filtering function for container IDs.
// Raw byte slices are used because it works with structures returned // Raw byte slices are used because it works with structures returned
// from contract. // from contract.
@ -381,7 +441,7 @@ func getCIDFilterFunc(cmd *cobra.Command) (func([]byte) bool, error) {
var id cid.ID var id cid.ID
id.SetSHA256(v) id.SetSHA256(v)
idStr := id.EncodeToString() idStr := id.EncodeToString()
_, found := slices.BinarySearch(rawIDs, idStr) n := sort.Search(len(rawIDs), func(i int) bool { return rawIDs[i] >= idStr })
return found return n < len(rawIDs) && rawIDs[n] == idStr
}, nil }, nil
} }

View file

@ -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()
}

View file

@ -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()
}

View file

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

View file

@ -1,4 +1,4 @@
package contract package morph
import ( import (
"encoding/json" "encoding/json"
@ -7,9 +7,6 @@ import (
"strings" "strings"
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns" "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/cli/cmdargs"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/address"
@ -26,9 +23,10 @@ import (
const ( const (
contractPathFlag = "contract" contractPathFlag = "contract"
updateFlag = "update" updateFlag = "update"
customZoneFlag = "domain"
) )
var DeployCmd = &cobra.Command{ var deployCmd = &cobra.Command{
Use: "deploy", Use: "deploy",
Short: "Deploy additional smart-contracts", Short: "Deploy additional smart-contracts",
Long: `Deploy additional smart-contract which are not related to core. Long: `Deploy additional smart-contract which are not related to core.
@ -39,33 +37,33 @@ Compiled contract file name must contain '_contract.nef' suffix.
Contract's manifest file name must be 'config.json'. Contract's manifest file name must be 'config.json'.
NNS name is taken by stripping '_contract.nef' from the NEF file (similar to frostfs contracts).`, NNS name is taken by stripping '_contract.nef' from the NEF file (similar to frostfs contracts).`,
PreRun: func(cmd *cobra.Command, _ []string) { PreRun: func(cmd *cobra.Command, _ []string) {
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag)) _ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag))
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag)) _ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
}, },
RunE: deployContractCmd, RunE: deployContractCmd,
} }
func init() { func init() {
ff := DeployCmd.Flags() ff := deployCmd.Flags()
ff.String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc) ff.String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
_ = DeployCmd.MarkFlagFilename(commonflags.AlphabetWalletsFlag) _ = deployCmd.MarkFlagFilename(alphabetWalletsFlag)
ff.StringP(commonflags.EndpointFlag, "r", "", commonflags.EndpointFlagDesc) ff.StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
ff.String(contractPathFlag, "", "Path to the contract directory") ff.String(contractPathFlag, "", "Path to the contract directory")
_ = DeployCmd.MarkFlagFilename(contractPathFlag) _ = deployCmd.MarkFlagFilename(contractPathFlag)
ff.Bool(updateFlag, false, "Update an existing contract") ff.Bool(updateFlag, false, "Update an existing contract")
ff.String(commonflags.CustomZoneFlag, "frostfs", "Custom zone for NNS") ff.String(customZoneFlag, "frostfs", "Custom zone for NNS")
} }
func deployContractCmd(cmd *cobra.Command, args []string) error { func deployContractCmd(cmd *cobra.Command, args []string) error {
v := viper.GetViper() v := viper.GetViper()
c, err := helper.NewInitializeContext(cmd, v) c, err := newInitializeContext(cmd, v)
if err != nil { if err != nil {
return fmt.Errorf("initialization error: %w", err) return fmt.Errorf("initialization error: %w", err)
} }
defer c.Close() defer c.close()
ctrPath, _ := cmd.Flags().GetString(contractPathFlag) ctrPath, _ := cmd.Flags().GetString(contractPathFlag)
ctrName, err := probeContractName(ctrPath) ctrName, err := probeContractName(ctrPath)
@ -73,29 +71,28 @@ func deployContractCmd(cmd *cobra.Command, args []string) error {
return err return err
} }
cs, err := helper.ReadContract(ctrPath, ctrName) cs, err := readContract(ctrPath, ctrName)
if err != nil { if err != nil {
return err return err
} }
r := management.NewReader(c.ReadOnlyInvoker) nnsCs, err := c.Client.GetContractStateByID(1)
nnsCs, err := helper.GetContractByID(r, 1)
if err != nil { if err != nil {
return fmt.Errorf("can't fetch NNS contract state: %w", err) return fmt.Errorf("can't fetch NNS contract state: %w", err)
} }
callHash := management.Hash callHash := management.Hash
method := constants.DeployMethodName method := deployMethodName
zone, _ := cmd.Flags().GetString(commonflags.CustomZoneFlag) zone, _ := cmd.Flags().GetString(customZoneFlag)
domain := ctrName + "." + zone domain := ctrName + "." + zone
isUpdate, _ := cmd.Flags().GetBool(updateFlag) isUpdate, _ := cmd.Flags().GetBool(updateFlag)
if isUpdate { if isUpdate {
cs.Hash, err = helper.NNSResolveHash(c.ReadOnlyInvoker, nnsCs.Hash, domain) cs.Hash, err = nnsResolveHash(c.ReadOnlyInvoker, nnsCs.Hash, domain)
if err != nil { if err != nil {
return fmt.Errorf("can't fetch contract hash from NNS: %w", err) return fmt.Errorf("can't fetch contract hash from NNS: %w", err)
} }
callHash = cs.Hash callHash = cs.Hash
method = constants.UpdateMethodName method = updateMethodName
} else { } else {
cs.Hash = state.CreateContractHash( cs.Hash = state.CreateContractHash(
c.CommitteeAcc.Contract.ScriptHash(), c.CommitteeAcc.Contract.ScriptHash(),
@ -124,13 +121,13 @@ func deployContractCmd(cmd *cobra.Command, args []string) error {
panic(fmt.Errorf("BUG: can't create deployment script: %w", writer.Err)) panic(fmt.Errorf("BUG: can't create deployment script: %w", writer.Err))
} }
if err := c.SendCommitteeTx(writer.Bytes(), false); err != nil { if err := c.sendCommitteeTx(writer.Bytes(), false); err != nil {
return err return err
} }
return c.AwaitTx() return c.awaitTx()
} }
func registerNNS(nnsCs *state.Contract, c *helper.InitializeContext, zone string, domain string, cs *helper.ContractState, writer *io.BufBinWriter) error { func registerNNS(nnsCs *state.Contract, c *initializeContext, zone string, domain string, cs *contractState, writer *io.BufBinWriter) error {
bw := io.NewBufBinWriter() bw := io.NewBufBinWriter()
emit.Instruction(bw.BinWriter, opcode.INITSSLOT, []byte{1}) emit.Instruction(bw.BinWriter, opcode.INITSSLOT, []byte{1})
emit.AppCall(bw.BinWriter, nnsCs.Hash, "getPrice", callflag.All) emit.AppCall(bw.BinWriter, nnsCs.Hash, "getPrice", callflag.All)
@ -140,7 +137,7 @@ func registerNNS(nnsCs *state.Contract, c *helper.InitializeContext, zone string
start := bw.Len() start := bw.Len()
needRecord := false needRecord := false
ok, err := c.NNSRootRegistered(nnsCs.Hash, zone) ok, err := c.nnsRootRegistered(nnsCs.Hash, zone)
if err != nil { if err != nil {
return err return err
} else if !ok { } else if !ok {
@ -148,17 +145,15 @@ func registerNNS(nnsCs *state.Contract, c *helper.InitializeContext, zone string
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All, emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
zone, c.CommitteeAcc.Contract.ScriptHash(), zone, c.CommitteeAcc.Contract.ScriptHash(),
constants.FrostfsOpsEmail, constants.NNSRefreshDefVal, constants.NNSRetryDefVal, "ops@nspcc.ru", int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
int64(constants.DefaultExpirationTime), constants.NNSTtlDefVal)
emit.Opcodes(bw.BinWriter, opcode.ASSERT) emit.Opcodes(bw.BinWriter, opcode.ASSERT)
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All, emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
domain, c.CommitteeAcc.Contract.ScriptHash(), domain, c.CommitteeAcc.Contract.ScriptHash(),
constants.FrostfsOpsEmail, constants.NNSRefreshDefVal, constants.NNSRetryDefVal, "ops@nspcc.ru", int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
int64(constants.DefaultExpirationTime), constants.NNSTtlDefVal)
emit.Opcodes(bw.BinWriter, opcode.ASSERT) emit.Opcodes(bw.BinWriter, opcode.ASSERT)
} else { } else {
s, ok, err := c.NNSRegisterDomainScript(nnsCs.Hash, cs.Hash, domain) s, ok, err := c.nnsRegisterDomainScript(nnsCs.Hash, cs.Hash, domain)
if err != nil { if err != nil {
return err return err
} }

View file

@ -1,4 +1,4 @@
package contract package morph
import ( import (
"bytes" "bytes"
@ -8,15 +8,10 @@ import (
"text/tabwriter" "text/tabwriter"
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns" "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" morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/emit"
@ -36,27 +31,26 @@ type contractDumpInfo struct {
} }
func dumpContractHashes(cmd *cobra.Command, _ []string) error { func dumpContractHashes(cmd *cobra.Command, _ []string) error {
c, err := helper.GetN3Client(viper.GetViper()) c, err := getN3Client(viper.GetViper())
if err != nil { if err != nil {
return fmt.Errorf("can't create N3 client: %w", err) return fmt.Errorf("can't create N3 client: %w", err)
} }
r := management.NewReader(invoker.New(c, nil)) cs, err := c.GetContractStateByID(1)
cs, err := helper.GetContractByID(r, 1)
if err != nil { if err != nil {
return err return err
} }
zone, _ := cmd.Flags().GetString(commonflags.CustomZoneFlag) zone, _ := cmd.Flags().GetString(customZoneFlag)
if zone != "" { if zone != "" {
return dumpCustomZoneHashes(cmd, cs.Hash, zone, c) return dumpCustomZoneHashes(cmd, cs.Hash, zone, c)
} }
infos := []contractDumpInfo{{name: constants.NNSContract, hash: cs.Hash}} infos := []contractDumpInfo{{name: nnsContract, hash: cs.Hash}}
irSize := 0 irSize := 0
for ; irSize < lastGlagoliticLetter; irSize++ { for ; irSize < lastGlagoliticLetter; irSize++ {
ok, err := helper.NNSIsAvailable(c, cs.Hash, helper.GetAlphabetNNSDomain(irSize)) ok, err := nnsIsAvailable(c, cs.Hash, getAlphabetNNSDomain(irSize))
if err != nil { if err != nil {
return err return err
} else if ok { } else if ok {
@ -68,9 +62,9 @@ func dumpContractHashes(cmd *cobra.Command, _ []string) error {
if irSize != 0 { if irSize != 0 {
bw.Reset() bw.Reset()
for i := range irSize { for i := 0; i < irSize; i++ {
emit.AppCall(bw.BinWriter, cs.Hash, "resolve", callflag.ReadOnly, emit.AppCall(bw.BinWriter, cs.Hash, "resolve", callflag.ReadOnly,
helper.GetAlphabetNNSDomain(i), getAlphabetNNSDomain(i),
int64(nns.TXT)) int64(nns.TXT))
} }
@ -79,19 +73,19 @@ func dumpContractHashes(cmd *cobra.Command, _ []string) error {
return fmt.Errorf("can't fetch info from NNS: %w", err) return fmt.Errorf("can't fetch info from NNS: %w", err)
} }
for i := range irSize { for i := 0; i < irSize; i++ {
info := contractDumpInfo{name: fmt.Sprintf("alphabet %d", i)} info := contractDumpInfo{name: fmt.Sprintf("alphabet %d", i)}
if h, err := helper.ParseNNSResolveResult(alphaRes.Stack[i]); err == nil { if h, err := parseNNSResolveResult(alphaRes.Stack[i]); err == nil {
info.hash = h info.hash = h
} }
infos = append(infos, info) infos = append(infos, info)
} }
} }
for _, ctrName := range constants.ContractList { for _, ctrName := range contractList {
bw.Reset() bw.Reset()
emit.AppCall(bw.BinWriter, cs.Hash, "resolve", callflag.ReadOnly, emit.AppCall(bw.BinWriter, cs.Hash, "resolve", callflag.ReadOnly,
helper.DomainOf(ctrName), int64(nns.TXT)) ctrName+".frostfs", int64(nns.TXT))
res, err := c.InvokeScript(bw.Bytes(), nil) res, err := c.InvokeScript(bw.Bytes(), nil)
if err != nil { if err != nil {
@ -100,7 +94,7 @@ func dumpContractHashes(cmd *cobra.Command, _ []string) error {
info := contractDumpInfo{name: ctrName} info := contractDumpInfo{name: ctrName}
if len(res.Stack) != 0 { if len(res.Stack) != 0 {
if h, err := helper.ParseNNSResolveResult(res.Stack[0]); err == nil { if h, err := parseNNSResolveResult(res.Stack[0]); err == nil {
info.hash = h info.hash = h
} }
} }
@ -113,7 +107,7 @@ func dumpContractHashes(cmd *cobra.Command, _ []string) error {
return nil return nil
} }
func dumpCustomZoneHashes(cmd *cobra.Command, nnsHash util.Uint160, zone string, c helper.Client) error { func dumpCustomZoneHashes(cmd *cobra.Command, nnsHash util.Uint160, zone string, c Client) error {
const nnsMaxTokens = 100 const nnsMaxTokens = 100
inv := invoker.New(c, nil) inv := invoker.New(c, nil)
@ -135,7 +129,7 @@ func dumpCustomZoneHashes(cmd *cobra.Command, nnsHash util.Uint160, zone string,
return return
} }
h, err := helper.NNSResolveHash(inv, nnsHash, string(bs)) h, err := nnsResolveHash(inv, nnsHash, string(bs))
if err != nil { if err != nil {
cmd.PrintErrf("Could not resolve name %s: %v\n", string(bs), err) cmd.PrintErrf("Could not resolve name %s: %v\n", string(bs), err)
return return
@ -147,12 +141,7 @@ func dumpCustomZoneHashes(cmd *cobra.Command, nnsHash util.Uint160, zone string,
}) })
} }
script, err := smartcontract.CreateCallAndPrefetchIteratorScript(nnsHash, "tokens", nnsMaxTokens) sessionID, iter, err := unwrap.SessionIterator(inv.Call(nnsHash, "tokens"))
if err != nil {
return fmt.Errorf("create prefetch script: %w", err)
}
arr, sessionID, iter, err := unwrap.ArrayAndSessionIterator(inv.Run(script))
if err != nil { if err != nil {
if errors.Is(err, unwrap.ErrNoSessionID) { if errors.Is(err, unwrap.ErrNoSessionID) {
items, err := unwrap.Array(inv.CallAndExpandIterator(nnsHash, "tokens", nnsMaxTokens)) items, err := unwrap.Array(inv.CallAndExpandIterator(nnsHash, "tokens", nnsMaxTokens))
@ -169,20 +158,16 @@ func dumpCustomZoneHashes(cmd *cobra.Command, nnsHash util.Uint160, zone string,
return err return err
} }
} else { } else {
for i := range arr {
processItem(arr[i])
}
defer func() { defer func() {
_ = inv.TerminateSession(sessionID) _ = inv.TerminateSession(sessionID)
}() }()
items, err := inv.TraverseIterator(sessionID, &iter, 0) items, err := inv.TraverseIterator(sessionID, &iter, nnsMaxTokens)
for err == nil && len(items) != 0 { for err == nil && len(items) != 0 {
for i := range items { for i := range items {
processItem(items[i]) processItem(items[i])
} }
items, err = inv.TraverseIterator(sessionID, &iter, 0) items, err = inv.TraverseIterator(sessionID, &iter, nnsMaxTokens)
} }
if err != nil { if err != nil {
return fmt.Errorf("error during NNS domains iteration: %w", err) return fmt.Errorf("error during NNS domains iteration: %w", err)
@ -227,7 +212,7 @@ func printContractInfo(cmd *cobra.Command, infos []contractDumpInfo) {
cmd.Print(buf.String()) cmd.Print(buf.String())
} }
func fillContractVersion(cmd *cobra.Command, c helper.Client, infos []contractDumpInfo) { func fillContractVersion(cmd *cobra.Command, c Client, infos []contractDumpInfo) {
bw := io.NewBufBinWriter() bw := io.NewBufBinWriter()
sub := io.NewBufBinWriter() sub := io.NewBufBinWriter()
for i := range infos { for i := range infos {

View file

@ -0,0 +1,57 @@
package morph
import (
"errors"
"fmt"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
func forceNewEpochCmd(cmd *cobra.Command, _ []string) error {
wCtx, err := newInitializeContext(cmd, viper.GetViper())
if err != nil {
return fmt.Errorf("can't to initialize context: %w", err)
}
cs, err := wCtx.Client.GetContractStateByID(1)
if err != nil {
return fmt.Errorf("can't get NNS contract info: %w", err)
}
nmHash, err := nnsResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, netmapContract+".frostfs")
if err != nil {
return fmt.Errorf("can't get netmap contract hash: %w", err)
}
bw := io.NewBufBinWriter()
if err := emitNewEpochCall(bw, wCtx, nmHash); err != nil {
return err
}
if err := wCtx.sendConsensusTx(bw.Bytes()); err != nil {
return err
}
return wCtx.awaitTx()
}
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
}

View file

@ -1,83 +0,0 @@
package frostfsid
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"
"github.com/spf13/viper"
)
var (
frostfsidAddSubjectKeyCmd = &cobra.Command{
Use: "add-subject-key",
Short: "Add a public key to the 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: frostfsidAddSubjectKey,
}
frostfsidRemoveSubjectKeyCmd = &cobra.Command{
Use: "remove-subject-key",
Short: "Remove a public key from the 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: frostfsidRemoveSubjectKey,
}
)
func initFrostfsIDAddSubjectKeyCmd() {
Cmd.AddCommand(frostfsidAddSubjectKeyCmd)
ff := frostfsidAddSubjectKeyCmd.Flags()
ff.StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
ff.String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
ff.String(subjectAddressFlag, "", "Subject address")
_ = frostfsidAddSubjectKeyCmd.MarkFlagRequired(subjectAddressFlag)
ff.String(subjectKeyFlag, "", "Public key to add")
_ = frostfsidAddSubjectKeyCmd.MarkFlagRequired(subjectKeyFlag)
}
func initFrostfsIDRemoveSubjectKeyCmd() {
Cmd.AddCommand(frostfsidRemoveSubjectKeyCmd)
ff := frostfsidRemoveSubjectKeyCmd.Flags()
ff.StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
ff.String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
ff.String(subjectAddressFlag, "", "Subject address")
_ = frostfsidAddSubjectKeyCmd.MarkFlagRequired(subjectAddressFlag)
ff.String(subjectKeyFlag, "", "Public key to remove")
_ = frostfsidAddSubjectKeyCmd.MarkFlagRequired(subjectKeyFlag)
}
func frostfsidAddSubjectKey(cmd *cobra.Command, _ []string) {
addr := getFrostfsIDSubjectAddress(cmd)
pub := getFrostfsIDSubjectKey(cmd)
ffsid, err := newFrostfsIDClient(cmd)
commonCmd.ExitOnErr(cmd, "init contract client: %w", err)
ffsid.addCall(ffsid.roCli.AddSubjectKeyCall(addr, pub))
err = ffsid.sendWait()
commonCmd.ExitOnErr(cmd, "add subject key: %w", err)
}
func frostfsidRemoveSubjectKey(cmd *cobra.Command, _ []string) {
addr := getFrostfsIDSubjectAddress(cmd)
pub := getFrostfsIDSubjectKey(cmd)
ffsid, err := newFrostfsIDClient(cmd)
commonCmd.ExitOnErr(cmd, "init contract client: %w", err)
ffsid.addCall(ffsid.roCli.RemoveSubjectKeyCall(addr, pub))
err = ffsid.sendWait()
commonCmd.ExitOnErr(cmd, "remove subject key: %w", err)
}

View file

@ -1,530 +0,0 @@
package frostfsid
import (
"errors"
"fmt"
"math/big"
"sort"
frostfsidclient "git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
frostfsidrpclient "git.frostfs.info/TrueCloudLab/frostfs-contract/rpcclient/frostfsid"
"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/google/uuid"
"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/neorpc/result"
"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/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"
)
const iteratorBatchSize = 1
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.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.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.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.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)
}
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)")
}
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)
_ = frostfsidCreateGroupCmd.MarkFlagRequired(groupNameFlag)
}
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")
}
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)")
}
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) {
inv, _, hash := initInvoker(cmd)
reader := frostfsidrpclient.NewReader(inv, hash)
sessionID, it, err := reader.ListNamespaces()
commonCmd.ExitOnErr(cmd, "can't get namespace: %w", err)
items, err := readIterator(inv, &it, iteratorBatchSize, sessionID)
commonCmd.ExitOnErr(cmd, "can't read iterator: %w", err)
namespaces, err := frostfsidclient.ParseNamespaces(items)
commonCmd.ExitOnErr(cmd, "can't parse namespace: %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) {
includeNames, _ := cmd.Flags().GetBool(includeNamesFlag)
ns := getFrostfsIDNamespace(cmd)
inv, _, hash := initInvoker(cmd)
reader := frostfsidrpclient.NewReader(inv, hash)
sessionID, it, err := reader.ListNamespaceSubjects(ns)
commonCmd.ExitOnErr(cmd, "can't get namespace: %w", err)
subAddresses, err := frostfsidclient.UnwrapArrayOfUint160(readIterator(inv, &it, iteratorBatchSize, sessionID))
commonCmd.ExitOnErr(cmd, "can't unwrap: %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
}
sessionID, it, err := reader.ListSubjects()
commonCmd.ExitOnErr(cmd, "can't get subject: %w", err)
items, err := readIterator(inv, &it, iteratorBatchSize, sessionID)
commonCmd.ExitOnErr(cmd, "can't read iterator: %w", err)
subj, err := frostfsidclient.ParseSubject(items)
commonCmd.ExitOnErr(cmd, "can't parse 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) {
inv, _, hash := initInvoker(cmd)
ns := getFrostfsIDNamespace(cmd)
reader := frostfsidrpclient.NewReader(inv, hash)
sessionID, it, err := reader.ListGroups(ns)
commonCmd.ExitOnErr(cmd, "can't get namespace: %w", err)
items, err := readIterator(inv, &it, iteratorBatchSize, sessionID)
commonCmd.ExitOnErr(cmd, "can't list groups: %w", err)
groups, err := frostfsidclient.ParseGroups(items)
commonCmd.ExitOnErr(cmd, "can't parse 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)
inv, cs, hash := initInvoker(cmd)
_, err := helper.NNSResolveHash(inv, cs.Hash, helper.DomainOf(constants.FrostfsIDContract))
commonCmd.ExitOnErr(cmd, "can't get netmap contract hash: %w", err)
reader := frostfsidrpclient.NewReader(inv, hash)
sessionID, it, err := reader.ListGroupSubjects(ns, big.NewInt(groupID))
commonCmd.ExitOnErr(cmd, "can't list groups: %w", err)
items, err := readIterator(inv, &it, iteratorBatchSize, sessionID)
commonCmd.ExitOnErr(cmd, "can't read iterator: %w", err)
subjects, err := frostfsidclient.UnwrapArrayOfUint160(items, err)
commonCmd.ExitOnErr(cmd, "can't unwrap: %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
}
items, err := reader.GetSubject(subjAddr)
commonCmd.ExitOnErr(cmd, "can't get subject: %w", err)
subj, err := frostfsidclient.ParseSubject(items)
commonCmd.ExitOnErr(cmd, "can't parse 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 initialize context: %w", err)
}
r := management.NewReader(wCtx.ReadOnlyInvoker)
cs, err := helper.GetContractByID(r, 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)
}
func readIterator(inv *invoker.Invoker, iter *result.Iterator, batchSize int, sessionID uuid.UUID) ([]stackitem.Item, error) {
var shouldStop bool
res := make([]stackitem.Item, 0)
for !shouldStop {
items, err := inv.TraverseIterator(sessionID, iter, batchSize)
if err != nil {
return nil, err
}
res = append(res, items...)
shouldStop = len(items) < batchSize
}
return res, nil
}
func initInvoker(cmd *cobra.Command) (*invoker.Invoker, *state.Contract, util.Uint160) {
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.FrostfsIDContract))
commonCmd.ExitOnErr(cmd, "can't get netmap contract hash: %w", err)
return inv, cs, nmHash
}

View file

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

View file

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

View file

@ -1,17 +0,0 @@
package frostfsid
func init() {
initFrostfsIDCreateNamespaceCmd()
initFrostfsIDListNamespacesCmd()
initFrostfsIDCreateSubjectCmd()
initFrostfsIDDeleteSubjectCmd()
initFrostfsIDListSubjectsCmd()
initFrostfsIDCreateGroupCmd()
initFrostfsIDDeleteGroupCmd()
initFrostfsIDListGroupsCmd()
initFrostfsIDAddSubjectToGroupCmd()
initFrostfsIDRemoveSubjectFromGroupCmd()
initFrostfsIDListGroupSubjectsCmd()
initFrostfsIDAddSubjectKeyCmd()
initFrostfsIDRemoveSubjectKeyCmd()
}

View file

@ -1,4 +1,4 @@
package generate package morph
import ( import (
"errors" "errors"
@ -6,13 +6,11 @@ import (
"os" "os"
"path/filepath" "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/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" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas" "github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
@ -26,27 +24,33 @@ import (
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
) )
func AlphabetCreds(cmd *cobra.Command, _ []string) error { const (
singleAccountName = "single"
committeeAccountName = "committee"
consensusAccountName = "consensus"
)
func generateAlphabetCreds(cmd *cobra.Command, _ []string) error {
// alphabet size is not part of the config // alphabet size is not part of the config
size, err := cmd.Flags().GetUint(commonflags.AlphabetSizeFlag) size, err := cmd.Flags().GetUint(alphabetSizeFlag)
if err != nil { if err != nil {
return err return err
} }
if size == 0 { if size == 0 {
return errors.New("size must be > 0") return errors.New("size must be > 0")
} }
if size > constants.MaxAlphabetNodes { if size > maxAlphabetNodes {
return helper.ErrTooManyAlphabetNodes return ErrTooManyAlphabetNodes
} }
v := viper.GetViper() v := viper.GetViper()
walletDir := config.ResolveHomePath(viper.GetString(commonflags.AlphabetWalletsFlag)) walletDir := config.ResolveHomePath(viper.GetString(alphabetWalletsFlag))
pwds, err := initializeWallets(v, walletDir, int(size)) pwds, err := initializeWallets(v, walletDir, int(size))
if err != nil { if err != nil {
return err return err
} }
_, err = helper.InitializeContractWallet(v, walletDir) _, err = initializeContractWallet(v, walletDir)
if err != nil { if err != nil {
return err return err
} }
@ -65,53 +69,48 @@ func initializeWallets(v *viper.Viper, walletDir string, size int) ([]string, er
pubs := make(keys.PublicKeys, size) pubs := make(keys.PublicKeys, size)
passwords := make([]string, size) passwords := make([]string, size)
var errG errgroup.Group
for i := range wallets { for i := range wallets {
password, err := config.GetPassword(v, innerring.GlagoliticLetter(i).String()) password, err := config.GetPassword(v, innerring.GlagoliticLetter(i).String())
if err != nil { if err != nil {
return nil, fmt.Errorf("can't fetch password: %w", err) return nil, fmt.Errorf("can't fetch password: %w", err)
} }
errG.Go(func() error { p := filepath.Join(walletDir, innerring.GlagoliticLetter(i).String()+".json")
p := filepath.Join(walletDir, innerring.GlagoliticLetter(i).String()+".json") f, err := os.OpenFile(p, os.O_CREATE, 0644)
f, err := os.OpenFile(p, os.O_CREATE, 0o644) if err != nil {
if err != nil { return nil, fmt.Errorf("can't create wallet file: %w", err)
return fmt.Errorf("can't create wallet file: %w", err) }
} if err := f.Close(); err != nil {
if err := f.Close(); err != nil { return nil, fmt.Errorf("can't close wallet file: %w", err)
return fmt.Errorf("can't close wallet file: %w", err) }
} w, err := wallet.NewWallet(p)
w, err := wallet.NewWallet(p) if err != nil {
if err != nil { return nil, fmt.Errorf("can't create wallet: %w", err)
return fmt.Errorf("can't create wallet: %w", err) }
} if err := w.CreateAccount(singleAccountName, password); err != nil {
if err := w.CreateAccount(constants.SingleAccountName, password); err != nil { return nil, fmt.Errorf("can't create account: %w", err)
return fmt.Errorf("can't create account: %w", err) }
}
passwords[i] = password passwords[i] = password
wallets[i] = w wallets[i] = w
pubs[i] = w.Accounts[0].PrivateKey().PublicKey() pubs[i] = w.Accounts[0].PrivateKey().PublicKey()
return nil
})
} }
if err := errG.Wait(); err != nil { var errG errgroup.Group
return nil, err
}
// Create committee account with N/2+1 multi-signature. // Create committee account with N/2+1 multi-signature.
majCount := smartcontract.GetMajorityHonestNodeCount(size) majCount := smartcontract.GetMajorityHonestNodeCount(size)
// Create consensus account with 2*N/3+1 multi-signature. // Create consensus account with 2*N/3+1 multi-signature.
bftCount := smartcontract.GetDefaultHonestNodeCount(size) bftCount := smartcontract.GetDefaultHonestNodeCount(size)
for i := range wallets { for i := range wallets {
ps := pubs.Copy() i := i
ps := make(keys.PublicKeys, len(pubs))
copy(ps, pubs)
errG.Go(func() error { errG.Go(func() error {
if err := addMultisigAccount(wallets[i], majCount, constants.CommitteeAccountName, passwords[i], ps); err != nil { if err := addMultisigAccount(wallets[i], majCount, committeeAccountName, passwords[i], ps); err != nil {
return fmt.Errorf("can't create committee account: %w", err) return fmt.Errorf("can't create committee account: %w", err)
} }
if err := addMultisigAccount(wallets[i], bftCount, constants.ConsensusAccountName, passwords[i], ps); err != nil { if err := addMultisigAccount(wallets[i], bftCount, consensusAccountName, passwords[i], ps); err != nil {
return fmt.Errorf("can't create consentus account: %w", err) return fmt.Errorf("can't create consentus account: %w", err)
} }
if err := wallets[i].SavePretty(); err != nil { if err := wallets[i].SavePretty(); err != nil {
@ -146,7 +145,7 @@ func generateStorageCreds(cmd *cobra.Command, _ []string) error {
func refillGas(cmd *cobra.Command, gasFlag string, createWallet bool) (err error) { func refillGas(cmd *cobra.Command, gasFlag string, createWallet bool) (err error) {
// storage wallet path is not part of the config // storage wallet path is not part of the config
storageWalletPath, _ := cmd.Flags().GetString(commonflags.StorageWalletFlag) storageWalletPath, _ := cmd.Flags().GetString(storageWalletFlag)
// wallet address is not part of the config // wallet address is not part of the config
walletAddress, _ := cmd.Flags().GetString(walletAddressFlag) walletAddress, _ := cmd.Flags().GetString(walletAddressFlag)
@ -159,7 +158,7 @@ func refillGas(cmd *cobra.Command, gasFlag string, createWallet bool) (err error
} }
} else { } else {
if storageWalletPath == "" { if storageWalletPath == "" {
return fmt.Errorf("missing wallet path (use '--%s <out.json>')", commonflags.StorageWalletFlag) return fmt.Errorf("missing wallet path (use '--%s <out.json>')", storageWalletFlag)
} }
var w *wallet.Wallet var w *wallet.Wallet
@ -184,7 +183,7 @@ func refillGas(cmd *cobra.Command, gasFlag string, createWallet bool) (err error
} }
if label == "" { if label == "" {
label = constants.SingleAccountName label = singleAccountName
} }
if err := w.CreateAccount(label, password); err != nil { if err := w.CreateAccount(label, password); err != nil {
@ -197,12 +196,12 @@ func refillGas(cmd *cobra.Command, gasFlag string, createWallet bool) (err error
gasStr := viper.GetString(gasFlag) gasStr := viper.GetString(gasFlag)
gasAmount, err := helper.ParseGASAmount(gasStr) gasAmount, err := parseGASAmount(gasStr)
if err != nil { if err != nil {
return err return err
} }
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper()) wCtx, err := newInitializeContext(cmd, viper.GetViper())
if err != nil { if err != nil {
return err return err
} }
@ -215,9 +214,20 @@ func refillGas(cmd *cobra.Command, gasFlag string, createWallet bool) (err error
return fmt.Errorf("BUG: invalid transfer arguments: %w", bw.Err) return fmt.Errorf("BUG: invalid transfer arguments: %w", bw.Err)
} }
if err := wCtx.SendCommitteeTx(bw.Bytes(), false); err != nil { if err := wCtx.sendCommitteeTx(bw.Bytes(), false); err != nil {
return err return err
} }
return wCtx.AwaitTx() return wCtx.awaitTx()
}
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
} }

View file

@ -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()
}

View file

@ -1,4 +1,4 @@
package generate package morph
import ( import (
"bytes" "bytes"
@ -10,8 +10,6 @@ import (
"sync" "sync"
"testing" "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" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
"github.com/nspcc-dev/neo-go/cli/input" "github.com/nspcc-dev/neo-go/cli/input"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
@ -22,58 +20,61 @@ import (
"golang.org/x/term" "golang.org/x/term"
) )
const testContractPassword = "grouppass"
func TestGenerateAlphabet(t *testing.T) { func TestGenerateAlphabet(t *testing.T) {
const size = 4
walletDir := t.TempDir() walletDir := t.TempDir()
buf := setupTestTerminal(t) buf := setupTestTerminal(t)
cmd := GenerateAlphabetCmd cmd := generateAlphabetCmd
v := viper.GetViper() v := viper.GetViper()
t.Run("zero size", func(t *testing.T) { t.Run("zero size", func(t *testing.T) {
buf.Reset() buf.Reset()
v.Set(commonflags.AlphabetWalletsFlag, walletDir) v.Set(alphabetWalletsFlag, walletDir)
require.NoError(t, cmd.Flags().Set(commonflags.AlphabetSizeFlag, "0")) require.NoError(t, cmd.Flags().Set(alphabetSizeFlag, "0"))
buf.WriteString("pass\r") buf.WriteString("pass\r")
require.Error(t, AlphabetCreds(cmd, nil)) require.Error(t, generateAlphabetCreds(cmd, nil))
}) })
t.Run("no password provided", func(t *testing.T) { t.Run("no password provided", func(t *testing.T) {
buf.Reset() buf.Reset()
v.Set(commonflags.AlphabetWalletsFlag, walletDir) v.Set(alphabetWalletsFlag, walletDir)
require.NoError(t, cmd.Flags().Set(commonflags.AlphabetSizeFlag, "1")) require.NoError(t, cmd.Flags().Set(alphabetSizeFlag, "1"))
require.Error(t, AlphabetCreds(cmd, nil)) require.Error(t, generateAlphabetCreds(cmd, nil))
}) })
t.Run("missing directory", func(t *testing.T) { t.Run("missing directory", func(t *testing.T) {
buf.Reset() buf.Reset()
dir := filepath.Join(os.TempDir(), "notexist."+strconv.FormatUint(rand.Uint64(), 10)) dir := filepath.Join(os.TempDir(), "notexist."+strconv.FormatUint(rand.Uint64(), 10))
v.Set(commonflags.AlphabetWalletsFlag, dir) v.Set(alphabetWalletsFlag, dir)
require.NoError(t, cmd.Flags().Set(commonflags.AlphabetSizeFlag, "1")) require.NoError(t, cmd.Flags().Set(alphabetSizeFlag, "1"))
buf.WriteString("pass\r") buf.WriteString("pass\r")
require.Error(t, AlphabetCreds(cmd, nil)) require.Error(t, generateAlphabetCreds(cmd, nil))
}) })
t.Run("no password for contract group wallet", func(t *testing.T) { t.Run("no password for contract group wallet", func(t *testing.T) {
buf.Reset() buf.Reset()
v.Set(commonflags.AlphabetWalletsFlag, walletDir) v.Set(alphabetWalletsFlag, walletDir)
require.NoError(t, cmd.Flags().Set(commonflags.AlphabetSizeFlag, "1")) require.NoError(t, cmd.Flags().Set(alphabetSizeFlag, strconv.FormatUint(size, 10)))
buf.WriteString("pass\r") for i := uint64(0); i < size; i++ {
require.Error(t, AlphabetCreds(cmd, nil)) buf.WriteString(strconv.FormatUint(i, 10) + "\r")
}
require.Error(t, generateAlphabetCreds(cmd, nil))
}) })
const size = 4
buf.Reset() buf.Reset()
v.Set(commonflags.AlphabetWalletsFlag, walletDir) v.Set(alphabetWalletsFlag, walletDir)
require.NoError(t, GenerateAlphabetCmd.Flags().Set(commonflags.AlphabetSizeFlag, strconv.FormatUint(size, 10))) require.NoError(t, generateAlphabetCmd.Flags().Set(alphabetSizeFlag, strconv.FormatUint(size, 10)))
for i := range uint64(size) { for i := uint64(0); i < size; i++ {
buf.WriteString(strconv.FormatUint(i, 10) + "\r") buf.WriteString(strconv.FormatUint(i, 10) + "\r")
} }
buf.WriteString(constants.TestContractPassword + "\r") buf.WriteString(testContractPassword + "\r")
require.NoError(t, AlphabetCreds(GenerateAlphabetCmd, nil)) require.NoError(t, generateAlphabetCreds(generateAlphabetCmd, nil))
var wg sync.WaitGroup var wg sync.WaitGroup
for i := uint64(0); i < size; i++ { for i := uint64(0); i < size; i++ {
i := i i := i
wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
p := filepath.Join(walletDir, innerring.GlagoliticLetter(i).String()+".json") p := filepath.Join(walletDir, innerring.GlagoliticLetter(i).String()+".json")
@ -85,12 +86,12 @@ func TestGenerateAlphabet(t *testing.T) {
err := a.Decrypt(strconv.FormatUint(i, 10), keys.NEP2ScryptParams()) err := a.Decrypt(strconv.FormatUint(i, 10), keys.NEP2ScryptParams())
require.NoError(t, err, "can't decrypt account") require.NoError(t, err, "can't decrypt account")
switch a.Label { switch a.Label {
case constants.ConsensusAccountName: case consensusAccountName:
require.Equal(t, smartcontract.GetDefaultHonestNodeCount(size), len(a.Contract.Parameters)) require.Equal(t, smartcontract.GetDefaultHonestNodeCount(size), len(a.Contract.Parameters))
case constants.CommitteeAccountName: case committeeAccountName:
require.Equal(t, smartcontract.GetMajorityHonestNodeCount(size), len(a.Contract.Parameters)) require.Equal(t, smartcontract.GetMajorityHonestNodeCount(size), len(a.Contract.Parameters))
default: default:
require.Equal(t, constants.SingleAccountName, a.Label) require.Equal(t, singleAccountName, a.Label)
} }
} }
}() }()
@ -98,11 +99,11 @@ func TestGenerateAlphabet(t *testing.T) {
wg.Wait() wg.Wait()
t.Run("check contract group wallet", func(t *testing.T) { t.Run("check contract group wallet", func(t *testing.T) {
p := filepath.Join(walletDir, constants.ContractWalletFilename) p := filepath.Join(walletDir, contractWalletFilename)
w, err := wallet.NewWalletFromFile(p) w, err := wallet.NewWalletFromFile(p)
require.NoError(t, err, "contract wallet doesn't exist") require.NoError(t, err, "contract wallet doesn't exist")
require.Equal(t, 1, len(w.Accounts), "contract wallet must have 1 accout") require.Equal(t, 1, len(w.Accounts), "contract wallet must have 1 accout")
require.NoError(t, w.Accounts[0].Decrypt(constants.TestContractPassword, keys.NEP2ScryptParams())) require.NoError(t, w.Accounts[0].Decrypt(testContractPassword, keys.NEP2ScryptParams()))
}) })
} }

View file

@ -0,0 +1,106 @@
package morph
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"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 (
contractWalletFilename = "contract.json"
contractWalletPasswordKey = "contract"
)
func initializeContractWallet(v *viper.Viper, walletDir string) (*wallet.Wallet, error) {
password, err := config.GetPassword(v, contractWalletPasswordKey)
if err != nil {
return nil, err
}
w, err := wallet.NewWallet(filepath.Join(walletDir, 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, 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, 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 (c *initializeContext) addManifestGroup(h util.Uint160, cs *contractState) error {
priv := c.ContractWallet.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
}

View file

@ -1,164 +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"
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
rpcInvoker invoker.RPCInvoke
}
// NewLocalActor create LocalActor with accounts form provided wallets.
// In case of empty wallets provided created actor with dummy account only for read operation.
//
// If wallets are provided, the contract client will use accounts with accName name from these wallets.
// To determine which account name should be used in a contract client, refer to how the contract
// verifies the transaction signature.
func NewLocalActor(cmd *cobra.Command, c actor.RPCActor, accName string) (*LocalActor, error) {
walletDir := config.ResolveHomePath(viper.GetString(commonflags.AlphabetWalletsFlag))
var act *actor.Actor
var accounts []*wallet.Account
wallets, err := GetAlphabetWallets(viper.GetViper(), walletDir)
commonCmd.ExitOnErr(cmd, "unable to get alphabet wallets: %w", err)
for _, w := range wallets {
acc, err := GetWalletAccount(w, accName)
commonCmd.ExitOnErr(cmd, fmt.Sprintf("can't find %s account: %%w", accName), 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,
rpcInvoker: c,
}, 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")
}
func (a *LocalActor) GetRPCInvoker() invoker.RPCInvoke {
return a.rpcInvoker
}

View file

@ -1,171 +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 := GetContractByID(r, 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 := GetContractByID(r, 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)
}
c.Command.Println("NNS hash:", invokeHash.StringLE())
return c.AwaitTx()
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,129 +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, countEpoch int64) error {
if countEpoch <= 0 {
return errors.New("number of epochs cannot be less than 1")
}
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 + countEpoch
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 := GetContractByID(r, 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
}

View file

@ -1,200 +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/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/encoding/fixedn"
"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/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 wallets []*wallet.Wallet
var letter string
for i := range constants.MaxAlphabetNodes {
letter = innerring.GlagoliticLetter(i).String()
p := filepath.Join(walletDir, letter+".json")
var w *wallet.Wallet
w, err = wallet.NewWalletFromFile(p)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
err = nil
} else {
err = fmt.Errorf("can't open wallet: %w", err)
}
break
}
var password string
password, err = config.GetPassword(v, letter)
if err != nil {
err = fmt.Errorf("can't fetch password: %w", err)
break
}
for i := range w.Accounts {
if err = w.Accounts[i].Decrypt(password, keys.NEP2ScryptParams()); err != nil {
err = fmt.Errorf("can't unlock wallet: %w", err)
break
}
}
wallets = append(wallets, w)
}
if err != nil {
return nil, fmt.Errorf("can't read wallet for letter '%s': %w", letter, err)
}
if len(wallets) == 0 {
err = errors.New("there are no alphabet wallets in dir (run `generate-alphabet` command first)")
if len(walletFiles) > 0 {
err = fmt.Errorf("use glagolitic names for wallets(run `print-alphabet`): %w", err)
}
return nil, err
}
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)
var h *tar.Header
for h, err = r.Next(); err == nil && h != nil; h, err = r.Next() {
if h.Typeflag != tar.TypeReg {
continue
}
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
}
if err != nil && err != io.EOF {
return nil, fmt.Errorf("can't read contracts from archive: %w", err)
}
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
}
// GetContractByID retrieves a contract by its ID using the standard GetContractByID method.
// However, if the returned state.Contract is nil, it returns an error indicating that the contract was not found.
// See https://git.frostfs.info/TrueCloudLab/frostfs-node/issues/1210
func GetContractByID(r *management.ContractReader, id int32) (*state.Contract, error) {
cs, err := r.GetContractByID(id)
if err != nil {
return nil, err
}
if cs == nil {
return nil, errors.New("contract not found")
}
return cs, nil
}

View file

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

View file

@ -0,0 +1,514 @@
package morph
import (
"errors"
"fmt"
"os"
"path/filepath"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/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/rpcclient/actor"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
const (
// maxAlphabetNodes is the maximum number of candidates allowed, which is currently limited by the size
// of the invocation script.
// See: https://github.com/nspcc-dev/neo-go/blob/740488f7f35e367eaa99a71c0a609c315fe2b0fc/pkg/core/transaction/witness.go#L10
maxAlphabetNodes = 22
)
type cache struct {
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
}
var ErrTooManyAlphabetNodes = fmt.Errorf("too many alphabet nodes (maximum allowed is %d)", maxAlphabetNodes)
func initializeSideChainCmd(cmd *cobra.Command, _ []string) error {
initCtx, err := newInitializeContext(cmd, viper.GetViper())
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 := initCtx.transferFunds(); err != nil {
return err
}
cmd.Println("Stage 2: set notary and alphabet nodes in designate contract.")
if err := initCtx.setNotaryAndAlphabetNodes(); err != nil {
return err
}
// 3. Deploy NNS contract.
cmd.Println("Stage 3: deploy NNS contract.")
if err := initCtx.deployNNS(deployMethodName); err != nil {
return err
}
// 4. Deploy NeoFS contracts.
cmd.Println("Stage 4: deploy NeoFS contracts.")
if err := initCtx.deployContracts(); err != nil {
return err
}
cmd.Println("Stage 4.1: Transfer GAS to proxy contract.")
if err := initCtx.transferGASToProxy(); err != nil {
return err
}
cmd.Println("Stage 5: register candidates.")
if err := initCtx.registerCandidates(); err != nil {
return err
}
cmd.Println("Stage 6: transfer NEO to alphabet contracts.")
if err := initCtx.transferNEOToAlphabetContracts(); err != nil {
return err
}
cmd.Println("Stage 7: set addresses in NNS.")
return initCtx.setNNS()
}
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 newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContext, error) {
walletDir := config.ResolveHomePath(viper.GetString(alphabetWalletsFlag))
wallets, err := openAlphabetWallets(v, walletDir)
if err != nil {
return nil, err
}
if len(wallets) > maxAlphabetNodes {
return nil, ErrTooManyAlphabetNodes
}
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], committeeAccountName)
if err != nil {
return nil, fmt.Errorf("can't find committee account: %w", err)
}
consensusAcc, err := getWalletAccount(wallets[0], 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
}
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,
}
if needContracts {
err := initCtx.readContracts(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(epochDurationInitFlag) <= 0 {
return fmt.Errorf("epoch duration must be positive")
}
if viper.GetInt64(maxObjectSizeInitFlag) <= 0 {
return fmt.Errorf("max object size must be positive")
}
return nil
}
func createClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet) (Client, error) {
var c Client
var err error
if v.GetString(localDumpFlag) != "" {
if v.GetString(endpointFlag) != "" {
return nil, fmt.Errorf("`%s` and `%s` flags are mutually exclusive", endpointFlag, localDumpFlag)
}
c, err = newLocalClient(cmd, v, wallets)
} else {
c, err = getN3Client(v)
}
if err != nil {
return nil, fmt.Errorf("can't create N3 client: %w", err)
}
return c, nil
}
func getWallet(cmd *cobra.Command, v *viper.Viper, needContracts bool, walletDir string) (*wallet.Wallet, error) {
if !needContracts {
return nil, nil
}
return openContractWallet(v, cmd, walletDir)
}
func getContractsPath(cmd *cobra.Command, needContracts bool) (string, error) {
if !needContracts {
return "", nil
}
ctrPath, err := cmd.Flags().GetString(contractsInitFlag)
if err != nil {
return "", fmt.Errorf("invalid contracts path: %w", err)
}
return ctrPath, nil
}
func createWalletAccounts(wallets []*wallet.Wallet) ([]*wallet.Account, error) {
accounts := make([]*wallet.Account, len(wallets))
for i, w := range wallets {
acc, err := getWalletAccount(w, singleAccountName)
if err != nil {
return nil, fmt.Errorf("wallet %s is invalid (no single account): %w", w.Path(), err)
}
accounts[i] = acc
}
return accounts, nil
}
func openAlphabetWallets(v *viper.Viper, walletDir string) ([]*wallet.Wallet, error) {
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 (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
}
cs, err := c.Client.GetContractStateByID(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, morphClient.NNSGroupKeyName)
if err == nil {
c.groupKey = groupKey
signer.Scopes = transaction.CustomGroups
signer.AllowedGroups = keys.PublicKeys{groupKey}
}
return signer
}
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
}
func awaitTx(cmd *cobra.Command, c Client, txs []hashVUBPair) error {
cmd.Println("Waiting for transactions to persist...")
const pollInterval = time.Second
tick := time.NewTicker(pollInterval)
defer tick.Stop()
at := trigger.Application
var retErr error
currBlock, err := c.GetBlockCount()
if err != nil {
return fmt.Errorf("can't fetch current block height: %w", err)
}
loop:
for i := range txs {
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)
}
for range tick.C {
// We must fetch current height before application log, to avoid race condition.
currBlock, err = c.GetBlockCount()
if err != nil {
return fmt.Errorf("can't fetch current block height: %w", err)
}
res, err := c.GetApplicationLog(txs[i].hash, &at)
if err == nil {
if retErr == nil && len(res.Executions) > 0 && res.Executions[0].VMState != vmstate.Halt {
retErr = fmt.Errorf("tx %d persisted in %s state: %s",
i, res.Executions[0].VMState, res.Executions[0].FaultException)
}
continue loop
}
if txs[i].vub < currBlock {
return fmt.Errorf("tx was not persisted: vub=%d, height=%d", txs[i].vub, currBlock)
}
}
}
return retErr
}
// 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, committeeAccountName); err != nil {
return err
}
if withConsensus {
if err := c.multiSign(tx, consensusAccountName); err != nil {
return err
}
}
return c.sendTx(tx, c.Command, false)
}
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 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 = len(ns[i].UpdateHistory) > 0
}
nativeHashes[ns[i].Manifest.Name] = ns[i].Hash
}
if !notaryEnabled {
return errors.New("notary contract must be enabled")
}
return nil
}

View file

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

View file

@ -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()
}

View file

@ -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 := helper.GetContractByID(r, 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)
}

View file

@ -1,168 +0,0 @@
package initialize
import (
"fmt"
"math/big"
"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/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 initialCommitteeGASAmount(c *helper.InitializeContext) int64 {
return (gasInitialTotalSupply - initialAlphabetGASAmount*int64(len(c.Wallets))) / 2
}
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: initialCommitteeGASAmount(c),
},
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()
}
// transferFundsFinished checks balances of accounts we transfer GAS to.
// The stage is considered finished if the balance is greater than the half of what we need to transfer.
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())
if err != nil || res.Cmp(big.NewInt(initialAlphabetGASAmount/2)) != 1 {
return false, err
}
res, err = r.BalanceOf(c.CommitteeAcc.ScriptHash())
return res != nil && res.Cmp(big.NewInt(initialCommitteeGASAmount(c)/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)
}
tx, err := act.MakeRun(w.Bytes())
if err != nil {
sum := make(map[util.Uint160]int64)
for _, recipient := range recipients {
sum[recipient.Token] += recipient.Amount
}
detail := make([]string, 0, len(sum))
for _, value := range sum {
detail = append(detail, fmt.Sprintf("amount=%v", value))
}
err = fmt.Errorf("transfer failed: from=%s(%s) %s: %w", acc.Label, acc.Address, strings.Join(detail, " "), err)
}
return tx, err
}

View file

@ -1,57 +0,0 @@
package initialize
import (
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
"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(commonflags.ProtoConfigPath, cmd.Flags().Lookup(commonflags.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(commonflags.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()
}

View file

@ -0,0 +1,623 @@
package morph
import (
"archive/tar"
"compress/gzip"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
io2 "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
"github.com/nspcc-dev/neo-go/pkg/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/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/stackitem"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
)
const (
nnsContract = "nns"
frostfsContract = "frostfs" // not deployed in side-chain.
processingContract = "processing" // not deployed in side-chain.
alphabetContract = "alphabet"
auditContract = "audit"
balanceContract = "balance"
containerContract = "container"
frostfsIDContract = "frostfsid"
netmapContract = "netmap"
proxyContract = "proxy"
)
var (
contractList = []string{
auditContract,
balanceContract,
containerContract,
frostfsIDContract,
netmapContract,
proxyContract,
}
fullContractList = append([]string{
frostfsContract,
processingContract,
nnsContract,
alphabetContract,
}, contractList...)
netmapConfigKeys = []string{
netmap.EpochDurationConfig,
netmap.MaxObjectSizeConfig,
netmap.AuditFeeConfig,
netmap.ContainerFeeConfig,
netmap.ContainerAliasFeeConfig,
netmap.BasicIncomeRateConfig,
netmap.IrCandidateFeeConfig,
netmap.WithdrawFeeConfig,
netmap.HomomorphicHashingDisabledKey,
netmap.MaintenanceModeAllowedConfig,
}
)
type contractState struct {
NEF *nef.File
RawNEF []byte
Manifest *manifest.Manifest
RawManifest []byte
Hash util.Uint160
}
const (
updateMethodName = "update"
deployMethodName = "deploy"
)
func (c *initializeContext) deployNNS(method string) error {
cs := c.getContract(nnsContract)
h := cs.Hash
nnsCs, err := c.nnsContractState()
if err == nil {
if nnsCs.NEF.Checksum == cs.NEF.Checksum {
if method == deployMethodName {
c.Command.Println("NNS contract is already deployed.")
} else {
c.Command.Println("NNS contract is already updated.")
}
return nil
}
h = nnsCs.Hash
}
err = c.addManifestGroup(h, cs)
if err != nil {
return fmt.Errorf("can't sign manifest group: %v", err)
}
params := getContractDeployParameters(cs, nil)
signer := transaction.Signer{
Account: c.CommitteeAcc.Contract.ScriptHash(),
Scopes: transaction.CalledByEntry,
}
invokeHash := management.Hash
if method == updateMethodName {
invokeHash = nnsCs.Hash
}
res, err := invokeFunction(c.Client, invokeHash, method, params, []transaction.Signer{signer})
if err != nil {
return fmt.Errorf("can't deploy NNS contract: %w", err)
}
if res.State != vmstate.Halt.String() {
return fmt.Errorf("can't deploy NNS contract: %s", res.FaultException)
}
tx, err := c.Client.CreateTxFromScript(res.Script, c.CommitteeAcc, res.GasConsumed, 0, []rpcclient.SignerAccount{{
Signer: signer,
Account: c.CommitteeAcc,
}})
if err != nil {
return fmt.Errorf("failed to create deploy tx for %s: %w", nnsContract, err)
}
if err := c.multiSignAndSend(tx, committeeAccountName); err != nil {
return fmt.Errorf("can't send deploy transaction: %w", err)
}
return c.awaitTx()
}
func (c *initializeContext) updateContracts() error {
alphaCs := c.getContract(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 := c.deployAlphabetAccounts(nnsHash, w, alphaCs)
if err != nil {
return err
}
w.Reset()
if err = c.deployOrUpdateContracts(w, nnsHash, keysParam); err != nil {
return err
}
groupKey := c.ContractWallet.Accounts[0].PrivateKey().PublicKey()
_, _, err = c.emitUpdateNNSGroupScript(w, nnsHash, groupKey)
if err != nil {
return err
}
c.Command.Printf("NNS: Set %s -> %s\n", morphClient.NNSGroupKeyName, hex.EncodeToString(groupKey.Bytes()))
emit.Opcodes(w.BinWriter, opcode.LDSFLD0)
emit.Int(w.BinWriter, 1)
emit.Opcodes(w.BinWriter, opcode.PACK)
emit.AppCallNoArgs(w.BinWriter, nnsHash, "setPrice", callflag.All)
if err := c.sendCommitteeTx(w.Bytes(), false); err != nil {
return err
}
return c.awaitTx()
}
func (c *initializeContext) deployOrUpdateContracts(w *io2.BufBinWriter, nnsHash util.Uint160, keysParam []any) error {
emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1})
emit.AppCall(w.BinWriter, nnsHash, "getPrice", callflag.All)
emit.Opcodes(w.BinWriter, opcode.STSFLD0)
emit.AppCall(w.BinWriter, nnsHash, "setPrice", callflag.All, 1)
for _, ctrName := range contractList {
cs := c.getContract(ctrName)
method := updateMethodName
ctrHash, err := nnsResolveHash(c.ReadOnlyInvoker, nnsHash, ctrName+".frostfs")
if err != nil {
if errors.Is(err, errMissingNNSRecord) {
// if contract not found we deploy it instead of update
method = deployMethodName
} else {
return fmt.Errorf("can't resolve hash for contract update: %w", err)
}
}
err = c.addManifestGroup(ctrHash, cs)
if err != nil {
return fmt.Errorf("can't sign manifest group: %v", err)
}
invokeHash := management.Hash
if method == updateMethodName {
invokeHash = ctrHash
}
params := getContractDeployParameters(cs, c.getContractDeployData(ctrName, keysParam, updateMethodName))
res, err := c.CommitteeAct.MakeCall(invokeHash, method, params...)
if err != nil {
if method != updateMethodName || !strings.Contains(err.Error(), common.ErrAlreadyUpdated) {
return fmt.Errorf("deploy contract: %w", err)
}
c.Command.Printf("%s contract is already updated.\n", ctrName)
continue
}
w.WriteBytes(res.Script)
if method == 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
}
func (c *initializeContext) deployAlphabetAccounts(nnsHash util.Uint160, w *io2.BufBinWriter, alphaCs *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 := nnsResolveHash(c.ReadOnlyInvoker, nnsHash, getAlphabetNNSDomain(i))
if err != nil {
return nil, fmt.Errorf("can't resolve hash for contract update: %w", err)
}
keysParam = append(keysParam, acc.PrivateKey().PublicKey().Bytes())
params := c.getAlphabetDeployItems(i, len(c.Wallets))
emit.Array(w.BinWriter, params...)
alphaCs.Manifest.Groups = baseGroups
err = c.addManifestGroup(ctrHash, alphaCs)
if err != nil {
return nil, fmt.Errorf("can't sign manifest group: %v", err)
}
emit.Bytes(w.BinWriter, alphaCs.RawManifest)
emit.Opcodes(w.BinWriter, opcode.LDSFLD0)
emit.Int(w.BinWriter, 3)
emit.Opcodes(w.BinWriter, opcode.PACK)
emit.AppCallNoArgs(w.BinWriter, ctrHash, updateMethodName, callflag.All)
}
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 (c *initializeContext) deployContracts() error {
alphaCs := c.getContract(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 := c.addManifestGroup(ctrHash, alphaCs)
if err != nil {
return fmt.Errorf("can't sign manifest group: %v", err)
}
keysParam = append(keysParam, acc.PrivateKey().PublicKey().Bytes())
params := 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, deployMethodName, params...)
if err != nil {
return fmt.Errorf("can't deploy alphabet #%d contract: %w", i, err)
}
c.SentTxs = append(c.SentTxs, hashVUBPair{hash: txHash, vub: vub})
}
for _, ctrName := range 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 := c.addManifestGroup(ctrHash, cs)
if err != nil {
return fmt.Errorf("can't sign manifest group: %v", err)
}
params := getContractDeployParameters(cs, c.getContractDeployData(ctrName, keysParam, deployMethodName))
res, err := c.CommitteeAct.MakeCall(management.Hash, 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()
}
func (c *initializeContext) isUpdated(ctrHash util.Uint160, cs *contractState) bool {
realCs, err := c.Client.GetContractStateByHash(ctrHash)
return err == nil && realCs.NEF.Checksum == cs.NEF.Checksum
}
func (c *initializeContext) getContract(ctrName string) *contractState {
return c.Contracts[ctrName]
}
func (c *initializeContext) readContracts(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 io.ReadCloser
if c.ContractPath == "" {
return errors.New("contracts flag is missing")
}
r, err = os.Open(c.ContractPath)
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 != alphabetContract {
cs := c.Contracts[ctrName]
cs.Hash = state.CreateContractHash(c.CommitteeAcc.Contract.ScriptHash(),
cs.NEF.Checksum, cs.Manifest.Name)
}
}
return nil
}
func readContract(ctrPath, ctrName string) (*contractState, error) {
rawNef, err := os.ReadFile(filepath.Join(ctrPath, ctrName+"_contract.nef"))
if err != nil {
return nil, fmt.Errorf("can't read NEF file for %s contract: %w", ctrName, err)
}
rawManif, err := os.ReadFile(filepath.Join(ctrPath, "config.json"))
if err != nil {
return nil, fmt.Errorf("can't read manifest file for %s contract: %w", ctrName, err)
}
cs := &contractState{
RawNEF: rawNef,
RawManifest: rawManif,
}
return cs, cs.parse()
}
func (cs *contractState) parse() error {
nf, err := nef.FileFromBytes(cs.RawNEF)
if err != nil {
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 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 getContractDeployParameters(cs *contractState, deployData []any) []any {
return []any{cs.RawNEF, cs.RawManifest, deployData}
}
func (c *initializeContext) getContractDeployData(ctrName string, keysParam []any, method string) []any {
items := make([]any, 1, 6)
items[0] = false // notaryDisabled is false
switch ctrName {
case frostfsContract:
items = append(items,
c.Contracts[processingContract].Hash,
keysParam,
smartcontract.Parameter{})
case processingContract:
items = append(items, c.Contracts[frostfsContract].Hash)
return items[1:] // no notary info
case auditContract:
items = append(items, c.Contracts[netmapContract].Hash)
case balanceContract:
items = append(items,
c.Contracts[netmapContract].Hash,
c.Contracts[containerContract].Hash)
case containerContract:
// In case if NNS is updated multiple times, we can't calculate
// it's actual hash based on local data, thus query chain.
nnsCs, err := c.Client.GetContractStateByID(1)
if err != nil {
panic("NNS is not yet deployed")
}
items = append(items,
c.Contracts[netmapContract].Hash,
c.Contracts[balanceContract].Hash,
c.Contracts[frostfsIDContract].Hash,
nnsCs.Hash,
"container")
case frostfsIDContract:
items = append(items,
c.Contracts[netmapContract].Hash,
c.Contracts[containerContract].Hash)
case netmapContract:
md := getDefaultNetmapContractConfigMap()
if method == updateMethodName {
arr, err := c.getNetConfigFromNetmapContract()
if err != nil {
panic(err)
}
m, err := parseConfigFromNetmapContract(arr)
if err != nil {
panic(err)
}
for k, v := range m {
for _, key := range netmapConfigKeys {
if k == key {
md[k] = v
break
}
}
}
}
var configParam []any
for k, v := range md {
configParam = append(configParam, k, v)
}
items = append(items,
c.Contracts[balanceContract].Hash,
c.Contracts[containerContract].Hash,
keysParam,
configParam)
case proxyContract:
items = nil
default:
panic(fmt.Sprintf("invalid contract name: %s", ctrName))
}
return items
}
func (c *initializeContext) getNetConfigFromNetmapContract() ([]stackitem.Item, error) {
cs, err := c.Client.GetContractStateByID(1)
if err != nil {
return nil, fmt.Errorf("NNS is not yet deployed: %w", err)
}
nmHash, err := nnsResolveHash(c.ReadOnlyInvoker, cs.Hash, netmapContract+".frostfs")
if err != nil {
return nil, fmt.Errorf("can't get netmap contract hash: %w", err)
}
arr, err := unwrap.Array(c.ReadOnlyInvoker.Call(nmHash, "listConfig"))
if err != nil {
return nil, fmt.Errorf("can't fetch list of network config keys from the netmap contract")
}
return arr, err
}
func (c *initializeContext) getAlphabetDeployItems(i, n int) []any {
items := make([]any, 6)
items[0] = false
items[1] = c.Contracts[netmapContract].Hash
items[2] = c.Contracts[proxyContract].Hash
items[3] = innerring.GlagoliticLetter(i).String()
items[4] = int64(i)
items[5] = int64(n)
return items
}

View file

@ -0,0 +1,301 @@
package morph
import (
"encoding/hex"
"errors"
"fmt"
"strconv"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
nnsClient "github.com/nspcc-dev/neo-go/pkg/rpcclient/nns"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
)
const defaultExpirationTime = 10 * 365 * 24 * time.Hour / time.Second
func (c *initializeContext) setNNS() error {
nnsCs, err := c.Client.GetContractStateByID(1)
if err != nil {
return err
}
ok, err := c.nnsRootRegistered(nnsCs.Hash, "frostfs")
if err != nil {
return err
} else if !ok {
bw := io.NewBufBinWriter()
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
"frostfs", c.CommitteeAcc.Contract.ScriptHash(),
"ops@nspcc.ru", int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
if err := c.sendCommitteeTx(bw.Bytes(), true); err != nil {
return fmt.Errorf("can't add domain root to NNS: %w", err)
}
if err := c.awaitTx(); err != nil {
return err
}
}
alphaCs := c.getContract(alphabetContract)
for i, acc := range c.Accounts {
alphaCs.Hash = state.CreateContractHash(acc.Contract.ScriptHash(), alphaCs.NEF.Checksum, alphaCs.Manifest.Name)
domain := getAlphabetNNSDomain(i)
if err := c.nnsRegisterDomain(nnsCs.Hash, alphaCs.Hash, domain); err != nil {
return err
}
c.Command.Printf("NNS: Set %s -> %s\n", domain, alphaCs.Hash.StringLE())
}
for _, ctrName := range contractList {
cs := c.getContract(ctrName)
domain := ctrName + ".frostfs"
if err := c.nnsRegisterDomain(nnsCs.Hash, cs.Hash, domain); err != nil {
return err
}
c.Command.Printf("NNS: Set %s -> %s\n", domain, cs.Hash.StringLE())
}
groupKey := c.ContractWallet.Accounts[0].PrivateKey().PublicKey()
err = c.updateNNSGroup(nnsCs.Hash, groupKey)
if err != nil {
return err
}
c.Command.Printf("NNS: Set %s -> %s\n", morphClient.NNSGroupKeyName, hex.EncodeToString(groupKey.Bytes()))
return c.awaitTx()
}
func (c *initializeContext) updateNNSGroup(nnsHash util.Uint160, pub *keys.PublicKey) error {
bw := io.NewBufBinWriter()
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)
}
// emitUpdateNNSGroupScript emits script for updating group key stored in NNS.
// First return value is true iff the key is already there and nothing should be done.
// Second return value is true iff a domain registration code was emitted.
func (c *initializeContext) emitUpdateNNSGroupScript(bw *io.BufBinWriter, nnsHash util.Uint160, pub *keys.PublicKey) (bool, bool, error) {
isAvail, err := nnsIsAvailable(c.Client, nnsHash, morphClient.NNSGroupKeyName)
if err != nil {
return false, false, err
}
if !isAvail {
currentPub, err := nnsResolveKey(c.ReadOnlyInvoker, nnsHash, morphClient.NNSGroupKeyName)
if err != nil {
return false, false, err
}
if pub.Equal(currentPub) {
return true, false, nil
}
}
if isAvail {
emit.AppCall(bw.BinWriter, nnsHash, "register", callflag.All,
morphClient.NNSGroupKeyName, c.CommitteeAcc.Contract.ScriptHash(),
"ops@nspcc.ru", int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
}
emit.AppCall(bw.BinWriter, nnsHash, "deleteRecords", callflag.All, "group.frostfs", int64(nns.TXT))
emit.AppCall(bw.BinWriter, nnsHash, "addRecord", callflag.All,
"group.frostfs", int64(nns.TXT), hex.EncodeToString(pub.Bytes()))
return false, isAvail, nil
}
func getAlphabetNNSDomain(i int) string {
return alphabetContract + strconv.FormatUint(uint64(i), 10) + ".frostfs"
}
// wrapRegisterScriptWithPrice wraps a given script with `getPrice`/`setPrice` calls for NNS.
// It is intended to be used for a single transaction, and not as a part of other scripts.
// It is assumed that script already contains static slot initialization code, the first one
// (with index 0) is used to store the price.
func wrapRegisterScriptWithPrice(w *io.BufBinWriter, nnsHash util.Uint160, s []byte) {
if len(s) == 0 {
return
}
emit.AppCall(w.BinWriter, nnsHash, "getPrice", callflag.All)
emit.Opcodes(w.BinWriter, opcode.STSFLD0)
emit.AppCall(w.BinWriter, nnsHash, "setPrice", callflag.All, 1)
w.WriteBytes(s)
emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.PUSH1, opcode.PACK)
emit.AppCallNoArgs(w.BinWriter, nnsHash, "setPrice", callflag.All)
if w.Err != nil {
panic(fmt.Errorf("BUG: can't wrap register script: %w", w.Err))
}
}
func (c *initializeContext) nnsRegisterDomainScript(nnsHash, expectedHash util.Uint160, domain string) ([]byte, bool, error) {
ok, err := nnsIsAvailable(c.Client, nnsHash, domain)
if err != nil {
return nil, false, err
}
if ok {
bw := io.NewBufBinWriter()
emit.AppCall(bw.BinWriter, nnsHash, "register", callflag.All,
domain, c.CommitteeAcc.Contract.ScriptHash(),
"ops@nspcc.ru", int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
if bw.Err != nil {
panic(bw.Err)
}
return bw.Bytes(), false, nil
}
s, err := nnsResolveHash(c.ReadOnlyInvoker, nnsHash, domain)
if err != nil {
return nil, false, err
}
return nil, s == expectedHash, nil
}
func (c *initializeContext) nnsRegisterDomain(nnsHash, expectedHash util.Uint160, domain string) error {
script, ok, err := c.nnsRegisterDomainScript(nnsHash, expectedHash, domain)
if ok || err != nil {
return err
}
w := io.NewBufBinWriter()
emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1})
wrapRegisterScriptWithPrice(w, nnsHash, script)
emit.AppCall(w.BinWriter, nnsHash, "deleteRecords", callflag.All, domain, int64(nns.TXT))
emit.AppCall(w.BinWriter, nnsHash, "addRecord", callflag.All,
domain, int64(nns.TXT), expectedHash.StringLE())
emit.AppCall(w.BinWriter, nnsHash, "addRecord", callflag.All,
domain, int64(nns.TXT), address.Uint160ToString(expectedHash))
return c.sendCommitteeTx(w.Bytes(), true)
}
func (c *initializeContext) nnsRootRegistered(nnsHash util.Uint160, zone string) (bool, error) {
res, err := c.CommitteeAct.Call(nnsHash, "isAvailable", "name."+zone)
if err != nil {
return false, err
}
return res.State == vmstate.Halt.String(), nil
}
var errMissingNNSRecord = errors.New("missing NNS record")
// Returns errMissingNNSRecord if invocation fault exception contains "token not found".
func nnsResolveHash(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (util.Uint160, error) {
item, err := nnsResolve(inv, nnsHash, domain)
if err != nil {
return util.Uint160{}, err
}
return parseNNSResolveResult(item)
}
func nnsResolve(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (stackitem.Item, error) {
return unwrap.Item(inv.Call(nnsHash, "resolve", domain, int64(nns.TXT)))
}
func nnsResolveKey(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (*keys.PublicKey, error) {
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")
}
// parseNNSResolveResult parses the result of resolving NNS record.
// It works with multiple formats (corresponding to multiple NNS versions).
// If array of hashes is provided, it returns only the first one.
func parseNNSResolveResult(res stackitem.Item) (util.Uint160, error) {
arr, ok := res.Value().([]stackitem.Item)
if !ok {
arr = []stackitem.Item{res}
}
if _, ok := res.Value().(stackitem.Null); ok || len(arr) == 0 {
return util.Uint160{}, errors.New("NNS record is missing")
}
for i := range arr {
bs, err := arr[i].TryBytes()
if err != nil {
continue
}
// We support several formats for hash encoding, this logic should be maintained in sync
// with nnsResolve from pkg/morph/client/nns.go
h, err := util.Uint160DecodeStringLE(string(bs))
if err == nil {
return h, nil
}
h, err = address.StringToUint160(string(bs))
if err == nil {
return h, nil
}
}
return util.Uint160{}, errors.New("no valid hashes are found")
}
func nnsIsAvailable(c Client, nnsHash util.Uint160, name string) (bool, error) {
switch c.(type) {
case *rpcclient.Client:
inv := invoker.New(c, nil)
reader := nnsClient.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
}
}

View file

@ -1,21 +1,16 @@
package initialize package morph
import ( import (
"errors" "errors"
"fmt" "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/native"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/rpcclient" "github.com/nspcc-dev/neo-go/pkg/rpcclient"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo" "github.com/nspcc-dev/neo-go/pkg/rpcclient/neo"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
@ -29,8 +24,8 @@ const (
registerBatchSize = transaction.MaxAttributes - 1 registerBatchSize = transaction.MaxAttributes - 1
) )
func registerCandidateRange(c *helper.InitializeContext, start, end int) error { func (c *initializeContext) registerCandidateRange(start, end int) error {
regPrice, err := getCandidateRegisterPrice(c) regPrice, err := c.getCandidateRegisterPrice()
if err != nil { if err != nil {
return fmt.Errorf("can't fetch registration price: %w", err) return fmt.Errorf("can't fetch registration price: %w", err)
} }
@ -46,12 +41,12 @@ func registerCandidateRange(c *helper.InitializeContext, start, end int) error {
panic(fmt.Sprintf("BUG: %v", w.Err)) panic(fmt.Sprintf("BUG: %v", w.Err))
} }
signers := []actor.SignerAccount{{ signers := []rpcclient.SignerAccount{{
Signer: c.GetSigner(false, c.CommitteeAcc), Signer: c.getSigner(false, c.CommitteeAcc),
Account: c.CommitteeAcc, Account: c.CommitteeAcc,
}} }}
for _, acc := range c.Accounts[start:end] { for _, acc := range c.Accounts[start:end] {
signers = append(signers, actor.SignerAccount{ signers = append(signers, rpcclient.SignerAccount{
Signer: transaction.Signer{ Signer: transaction.Signer{
Account: acc.Contract.ScriptHash(), Account: acc.Contract.ScriptHash(),
Scopes: transaction.CustomContracts, Scopes: transaction.CustomContracts,
@ -61,15 +56,11 @@ func registerCandidateRange(c *helper.InitializeContext, start, end int) error {
}) })
} }
act, err := actor.New(c.Client, signers) tx, err := c.Client.CreateTxFromScript(w.Bytes(), c.CommitteeAcc, -1, 0, signers)
if err != nil {
return fmt.Errorf("can't create actor: %w", err)
}
tx, err := act.MakeRun(w.Bytes())
if err != nil { if err != nil {
return fmt.Errorf("can't create tx: %w", err) return fmt.Errorf("can't create tx: %w", err)
} }
if err := c.MultiSign(tx, constants.CommitteeAccountName); err != nil { if err := c.multiSign(tx, committeeAccountName); err != nil {
return fmt.Errorf("can't sign a transaction: %w", err) return fmt.Errorf("can't sign a transaction: %w", err)
} }
@ -80,10 +71,10 @@ func registerCandidateRange(c *helper.InitializeContext, start, end int) error {
} }
} }
return c.SendTx(tx, c.Command, true) return c.sendTx(tx, c.Command, true)
} }
func registerCandidates(c *helper.InitializeContext) error { func (c *initializeContext) registerCandidates() error {
cc, err := unwrap.Array(c.ReadOnlyInvoker.Call(neo.Hash, "getCandidates")) cc, err := unwrap.Array(c.ReadOnlyInvoker.Call(neo.Hash, "getCandidates"))
if err != nil { if err != nil {
return fmt.Errorf("`getCandidates`: %w", err) return fmt.Errorf("`getCandidates`: %w", err)
@ -100,12 +91,15 @@ func registerCandidates(c *helper.InitializeContext) error {
// Register candidates in batches in order to overcome the signers amount limit. // 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 // See: https://github.com/nspcc-dev/neo-go/blob/master/pkg/core/transaction/transaction.go#L27
for i := 0; i < need; i += registerBatchSize { for i := 0; i < need; i += registerBatchSize {
start, end := i, min(i+registerBatchSize, need) start, end := i, i+registerBatchSize
if end > need {
end = need
}
// This check is sound because transactions are accepted/rejected atomically. // This check is sound because transactions are accepted/rejected atomically.
if have >= end { if have >= end {
continue continue
} }
if err := registerCandidateRange(c, start, end); err != nil { if err := c.registerCandidateRange(start, end); err != nil {
return fmt.Errorf("registering candidates %d..%d: %q", start, end-1, err) return fmt.Errorf("registering candidates %d..%d: %q", start, end-1, err)
} }
} }
@ -113,15 +107,15 @@ func registerCandidates(c *helper.InitializeContext) error {
return nil return nil
} }
func transferNEOToAlphabetContracts(c *helper.InitializeContext) error { func (c *initializeContext) transferNEOToAlphabetContracts() error {
neoHash := neo.Hash neoHash := neo.Hash
ok, err := transferNEOFinished(c, neoHash) ok, err := c.transferNEOFinished(neoHash)
if ok || err != nil { if ok || err != nil {
return err return err
} }
cs := c.GetContract(constants.AlphabetContract) cs := c.getContract(alphabetContract)
amount := initialAlphabetNEOAmount / len(c.Wallets) amount := initialAlphabetNEOAmount / len(c.Wallets)
bw := io.NewBufBinWriter() bw := io.NewBufBinWriter()
@ -132,22 +126,21 @@ func transferNEOToAlphabetContracts(c *helper.InitializeContext) error {
emit.Opcodes(bw.BinWriter, opcode.ASSERT) emit.Opcodes(bw.BinWriter, opcode.ASSERT)
} }
if err := c.SendCommitteeTx(bw.Bytes(), false); err != nil { if err := c.sendCommitteeTx(bw.Bytes(), false); err != nil {
return err return err
} }
return c.AwaitTx() return c.awaitTx()
} }
func transferNEOFinished(c *helper.InitializeContext, neoHash util.Uint160) (bool, error) { func (c *initializeContext) transferNEOFinished(neoHash util.Uint160) (bool, error) {
r := nep17.NewReader(c.ReadOnlyInvoker, neoHash) bal, err := c.Client.NEP17BalanceOf(neoHash, c.CommitteeAcc.Contract.ScriptHash())
bal, err := r.BalanceOf(c.CommitteeAcc.Contract.ScriptHash()) return bal < native.NEOTotalSupply, err
return bal.Cmp(big.NewInt(native.NEOTotalSupply)) == -1, err
} }
var errGetPriceInvalid = errors.New("`getRegisterPrice`: invalid response") var errGetPriceInvalid = errors.New("`getRegisterPrice`: invalid response")
func getCandidateRegisterPrice(c *helper.InitializeContext) (int64, error) { func (c *initializeContext) getCandidateRegisterPrice() (int64, error) {
switch c.Client.(type) { switch c.Client.(type) {
case *rpcclient.Client: case *rpcclient.Client:
inv := invoker.New(c.Client, nil) inv := invoker.New(c.Client, nil)
@ -155,7 +148,7 @@ func getCandidateRegisterPrice(c *helper.InitializeContext) (int64, error) {
return reader.GetRegisterPrice() return reader.GetRegisterPrice()
default: default:
neoHash := neo.Hash neoHash := neo.Hash
res, err := helper.InvokeFunction(c.Client, neoHash, "getRegisterPrice", nil, nil) res, err := invokeFunction(c.Client, neoHash, "getRegisterPrice", nil, nil)
if err != nil { if err != nil {
return 0, err return 0, err
} }

View file

@ -1,9 +1,6 @@
package initialize package morph
import ( import (
"fmt"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/rolemgmt" "github.com/nspcc-dev/neo-go/pkg/rpcclient/rolemgmt"
@ -11,8 +8,8 @@ import (
"github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/emit"
) )
func setNotaryAndAlphabetNodes(c *helper.InitializeContext) error { func (c *initializeContext) setNotaryAndAlphabetNodes() error {
if ok, err := setRolesFinished(c); ok || err != nil { if ok, err := c.setRolesFinished(); ok || err != nil {
if err == nil { if err == nil {
c.Command.Println("Stage 2: already performed.") c.Command.Println("Stage 2: already performed.")
} }
@ -30,23 +27,19 @@ func setNotaryAndAlphabetNodes(c *helper.InitializeContext) error {
emit.AppCall(w.BinWriter, rolemgmt.Hash, "designateAsRole", emit.AppCall(w.BinWriter, rolemgmt.Hash, "designateAsRole",
callflag.States|callflag.AllowNotify, int64(noderoles.NeoFSAlphabet), pubs) callflag.States|callflag.AllowNotify, int64(noderoles.NeoFSAlphabet), pubs)
if err := c.SendCommitteeTx(w.Bytes(), false); err != nil { if err := c.sendCommitteeTx(w.Bytes(), false); err != nil {
return fmt.Errorf("send committee transaction: %w", err) return err
} }
err := c.AwaitTx() return c.awaitTx()
if err != nil {
err = fmt.Errorf("await committee transaction: %w", err)
}
return err
} }
func setRolesFinished(c *helper.InitializeContext) (bool, error) { func (c *initializeContext) setRolesFinished() (bool, error) {
height, err := c.Client.GetBlockCount() height, err := c.Client.GetBlockCount()
if err != nil { if err != nil {
return false, err return false, err
} }
pubs, err := helper.GetDesignatedByRole(c.ReadOnlyInvoker, rolemgmt.Hash, noderoles.NeoFSAlphabet, height) pubs, err := getDesignatedByRole(c.ReadOnlyInvoker, rolemgmt.Hash, noderoles.NeoFSAlphabet, height)
return len(pubs) == len(c.Wallets), err return len(pubs) == len(c.Wallets), err
} }

View file

@ -1,4 +1,4 @@
package initialize package morph
import ( import (
"encoding/hex" "encoding/hex"
@ -9,14 +9,6 @@ import (
"testing" "testing"
"time" "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" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
"github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
@ -28,7 +20,7 @@ import (
) )
const ( const (
contractsPath = "../../../../../../contract/frostfs-contract-v0.18.0.tar.gz" contractsPath = "../../../../../../frostfs-contract/frostfs-contract-v0.16.0.tar.gz"
protoFileName = "proto.yml" protoFileName = "proto.yml"
) )
@ -50,10 +42,10 @@ func TestInitialize(t *testing.T) {
testInitialize(t, 16) testInitialize(t, 16)
}) })
t.Run("max nodes", func(t *testing.T) { t.Run("max nodes", func(t *testing.T) {
testInitialize(t, constants.MaxAlphabetNodes) testInitialize(t, maxAlphabetNodes)
}) })
t.Run("too many nodes", func(t *testing.T) { t.Run("too many nodes", func(t *testing.T) {
require.ErrorIs(t, generateTestData(t.TempDir(), constants.MaxAlphabetNodes+1), helper.ErrTooManyAlphabetNodes) require.ErrorIs(t, generateTestData(t, t.TempDir(), maxAlphabetNodes+1), ErrTooManyAlphabetNodes)
}) })
} }
@ -61,66 +53,60 @@ func testInitialize(t *testing.T, committeeSize int) {
testdataDir := t.TempDir() testdataDir := t.TempDir()
v := viper.GetViper() v := viper.GetViper()
require.NoError(t, generateTestData(testdataDir, committeeSize)) require.NoError(t, generateTestData(t, testdataDir, committeeSize))
v.Set(commonflags.ProtoConfigPath, filepath.Join(testdataDir, protoFileName)) v.Set(protoConfigPath, filepath.Join(testdataDir, protoFileName))
// Set to the path or remove the next statement to download from the network. // Set to the path or remove the next statement to download from the network.
require.NoError(t, Cmd.Flags().Set(commonflags.ContractsInitFlag, contractsPath)) require.NoError(t, initCmd.Flags().Set(contractsInitFlag, contractsPath))
v.Set(localDumpFlag, filepath.Join(testdataDir, "out"))
dumpPath := filepath.Join(testdataDir, "out") v.Set(alphabetWalletsFlag, testdataDir)
require.NoError(t, Cmd.Flags().Set(commonflags.LocalDumpFlag, dumpPath)) v.Set(epochDurationInitFlag, 1)
v.Set(commonflags.AlphabetWalletsFlag, testdataDir) v.Set(maxObjectSizeInitFlag, 1024)
v.Set(commonflags.EpochDurationInitFlag, 1)
v.Set(commonflags.MaxObjectSizeInitFlag, 1024)
setTestCredentials(v, committeeSize) setTestCredentials(v, committeeSize)
require.NoError(t, initializeSideChainCmd(Cmd, nil)) require.NoError(t, initializeSideChainCmd(initCmd, nil))
t.Run("force-new-epoch", func(t *testing.T) { t.Run("force-new-epoch", func(t *testing.T) {
require.NoError(t, netmap.ForceNewEpoch.Flags().Set(commonflags.LocalDumpFlag, dumpPath)) require.NoError(t, forceNewEpochCmd(forceNewEpoch, nil))
require.NoError(t, netmap.ForceNewEpochCmd(netmap.ForceNewEpoch, nil))
}) })
t.Run("set-config", func(t *testing.T) { t.Run("set-config", func(t *testing.T) {
require.NoError(t, cmdConfig.SetCmd.Flags().Set(commonflags.LocalDumpFlag, dumpPath)) require.NoError(t, setConfigCmd(setConfig, []string{"MaintenanceModeAllowed=true"}))
require.NoError(t, cmdConfig.SetConfigCmd(cmdConfig.SetCmd, []string{"MaintenanceModeAllowed=true"}))
}) })
t.Run("set-policy", func(t *testing.T) { t.Run("set-policy", func(t *testing.T) {
require.NoError(t, policy.Set.Flags().Set(commonflags.LocalDumpFlag, dumpPath)) require.NoError(t, setPolicyCmd(setPolicy, []string{"ExecFeeFactor=1"}))
require.NoError(t, policy.SetPolicyCmd(policy.Set, []string{"ExecFeeFactor=1"}))
}) })
t.Run("remove-node", func(t *testing.T) { t.Run("remove-node", func(t *testing.T) {
pk, err := keys.NewPrivateKey() pk, err := keys.NewPrivateKey()
require.NoError(t, err) require.NoError(t, err)
pub := hex.EncodeToString(pk.PublicKey().Bytes()) pub := hex.EncodeToString(pk.PublicKey().Bytes())
require.NoError(t, node.RemoveCmd.Flags().Set(commonflags.LocalDumpFlag, dumpPath)) require.NoError(t, removeNodesCmd(removeNodes, []string{pub}))
require.NoError(t, node.RemoveNodesCmd(node.RemoveCmd, []string{pub}))
}) })
} }
func generateTestData(dir string, size int) error { func generateTestData(t *testing.T, dir string, size int) error {
v := viper.GetViper() v := viper.GetViper()
v.Set(commonflags.AlphabetWalletsFlag, dir) v.Set(alphabetWalletsFlag, dir)
sizeStr := strconv.FormatUint(uint64(size), 10) sizeStr := strconv.FormatUint(uint64(size), 10)
if err := generate.GenerateAlphabetCmd.Flags().Set(commonflags.AlphabetSizeFlag, sizeStr); err != nil { if err := generateAlphabetCmd.Flags().Set(alphabetSizeFlag, sizeStr); err != nil {
return err return err
} }
setTestCredentials(v, size) setTestCredentials(v, size)
if err := generate.AlphabetCreds(generate.GenerateAlphabetCmd, nil); err != nil { if err := generateAlphabetCreds(generateAlphabetCmd, nil); err != nil {
return err return err
} }
var pubs []string var pubs []string
for i := range size { for i := 0; i < size; i++ {
p := filepath.Join(dir, innerring.GlagoliticLetter(i).String()+".json") p := filepath.Join(dir, innerring.GlagoliticLetter(i).String()+".json")
w, err := wallet.NewWalletFromFile(p) w, err := wallet.NewWalletFromFile(p)
if err != nil { if err != nil {
return fmt.Errorf("wallet doesn't exist: %w", err) return fmt.Errorf("wallet doesn't exist: %w", err)
} }
for _, acc := range w.Accounts { for _, acc := range w.Accounts {
if acc.Label == constants.SingleAccountName { if acc.Label == singleAccountName {
pub, ok := vm.ParseSignatureContract(acc.Contract.Script) pub, ok := vm.ParseSignatureContract(acc.Contract.Script)
if !ok { if !ok {
return fmt.Errorf("could not parse signature script for %s", acc.Address) return fmt.Errorf("could not parse signature script for %s", acc.Address)
@ -148,8 +134,8 @@ func generateTestData(dir string, size int) error {
} }
func setTestCredentials(v *viper.Viper, size int) { func setTestCredentials(v *viper.Viper, size int) {
for i := range size { for i := 0; i < size; i++ {
v.Set("credentials."+innerring.GlagoliticLetter(i).String(), strconv.FormatUint(uint64(i), 10)) v.Set("credentials."+innerring.GlagoliticLetter(i).String(), strconv.FormatUint(uint64(i), 10))
} }
v.Set("credentials.contract", constants.TestContractPassword) v.Set("credentials.contract", testContractPassword)
} }

View file

@ -0,0 +1,190 @@
package morph
import (
"fmt"
"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"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
scContext "github.com/nspcc-dev/neo-go/pkg/smartcontract/context"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"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 (c *initializeContext) transferFunds() error {
ok, err := c.transferFundsFinished()
if ok || err != nil {
if err == nil {
c.Command.Println("Stage 1: already performed.")
}
return err
}
var transfers []rpcclient.TransferTarget
for _, acc := range c.Accounts {
to := acc.Contract.ScriptHash()
transfers = append(transfers,
rpcclient.TransferTarget{
Token: gas.Hash,
Address: to,
Amount: initialAlphabetGASAmount,
},
)
}
// It is convenient to have all funds at the committee account.
transfers = append(transfers,
rpcclient.TransferTarget{
Token: gas.Hash,
Address: c.CommitteeAcc.Contract.ScriptHash(),
Amount: (gasInitialTotalSupply - initialAlphabetGASAmount*int64(len(c.Wallets))) / 2,
},
rpcclient.TransferTarget{
Token: neo.Hash,
Address: c.CommitteeAcc.Contract.ScriptHash(),
Amount: native.NEOTotalSupply,
},
)
tx, err := createNEP17MultiTransferTx(c.Client, c.ConsensusAcc, 0, transfers, []rpcclient.SignerAccount{{
Signer: transaction.Signer{
Account: c.ConsensusAcc.Contract.ScriptHash(),
Scopes: transaction.CalledByEntry,
},
Account: c.ConsensusAcc,
}})
if err != nil {
return fmt.Errorf("can't create transfer transaction: %w", err)
}
if err := c.multiSignAndSend(tx, consensusAccountName); err != nil {
return fmt.Errorf("can't send transfer transaction: %w", err)
}
return c.awaitTx()
}
func (c *initializeContext) transferFundsFinished() (bool, error) {
acc := c.Accounts[0]
res, err := c.Client.NEP17BalanceOf(gas.Hash, acc.Contract.ScriptHash())
return res > initialAlphabetGASAmount/2, err
}
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 {
network, err := c.Client.GetNetwork()
if err != nil {
// error appears only if client
// has not been initialized
panic(err)
}
// Use parameter context to avoid dealing with signature order.
pc := scContext.NewParameterContext("", network, tx)
h := c.CommitteeAcc.Contract.ScriptHash()
if accType == 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)
}
func (c *initializeContext) transferGASToProxy() error {
proxyCs := c.getContract(proxyContract)
bal, err := c.Client.NEP17BalanceOf(gas.Hash, proxyCs.Hash)
if err != nil || bal > 0 {
return err
}
tx, err := createNEP17MultiTransferTx(c.Client, c.CommitteeAcc, 0, []rpcclient.TransferTarget{{
Token: gas.Hash,
Address: proxyCs.Hash,
Amount: initialProxyGASAmount,
}}, nil)
if err != nil {
return err
}
if err := c.multiSignAndSend(tx, committeeAccountName); err != nil {
return err
}
return c.awaitTx()
}
func createNEP17MultiTransferTx(c Client, acc *wallet.Account, netFee int64,
recipients []rpcclient.TransferTarget, cosigners []rpcclient.SignerAccount) (*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)
}
return c.CreateTxFromScript(w.Bytes(), acc, -1, netFee, append([]rpcclient.SignerAccount{{
Signer: transaction.Signer{
Account: from,
Scopes: transaction.CalledByEntry,
},
Account: acc,
}}, cosigners...))
}

View file

@ -1,4 +1,4 @@
package helper package morph
import ( import (
"crypto/elliptic" "crypto/elliptic"
@ -8,47 +8,51 @@ import (
"sort" "sort"
"time" "time"
"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/google/uuid" "github.com/google/uuid"
"github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/core" "github.com/nspcc-dev/neo-go/pkg/core"
"github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/chaindump" "github.com/nspcc-dev/neo-go/pkg/core/chaindump"
"github.com/nspcc-dev/neo-go/pkg/core/fee"
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/neorpc/result" "github.com/nspcc-dev/neo-go/pkg/neorpc/result"
"github.com/nspcc-dev/neo-go/pkg/network/payload"
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"go.uber.org/zap" "go.uber.org/zap"
) )
type LocalClient struct { type localClient struct {
bc *core.Blockchain bc *core.Blockchain
transactions []*transaction.Transaction transactions []*transaction.Transaction
dumpPath string dumpPath string
accounts []*wallet.Account accounts []*wallet.Account
maxGasInvoke int64
} }
func NewLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet, dumpPath string) (*LocalClient, error) { func newLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet) (*localClient, error) {
cfg, err := config.LoadFile(v.GetString(commonflags.ProtoConfigPath)) cfg, err := config.LoadFile(v.GetString(protoConfigPath))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -61,7 +65,7 @@ func NewLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet
m := smartcontract.GetDefaultHonestNodeCount(int(cfg.ProtocolConfiguration.ValidatorsCount)) m := smartcontract.GetDefaultHonestNodeCount(int(cfg.ProtocolConfiguration.ValidatorsCount))
accounts := make([]*wallet.Account, len(wallets)) accounts := make([]*wallet.Account, len(wallets))
for i := range accounts { for i := range accounts {
accounts[i], err = GetWalletAccount(wallets[i], constants.ConsensusAccountName) accounts[i], err = getWalletAccount(wallets[i], consensusAccountName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -83,8 +87,9 @@ func NewLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet
go bc.Run() go bc.Run()
dumpPath := v.GetString(localDumpFlag)
if cmd.Name() != "init" { if cmd.Name() != "init" {
f, err := os.OpenFile(dumpPath, os.O_RDONLY, 0o600) f, err := os.OpenFile(dumpPath, os.O_RDONLY, 0600)
if err != nil { if err != nil {
return nil, fmt.Errorf("can't open local dump: %w", err) return nil, fmt.Errorf("can't open local dump: %w", err)
} }
@ -103,22 +108,42 @@ func NewLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet
} }
} }
return &LocalClient{ return &localClient{
bc: bc, bc: bc,
dumpPath: dumpPath, dumpPath: dumpPath,
accounts: accounts[:m], accounts: accounts[:m],
maxGasInvoke: 15_0000_0000,
}, nil }, nil
} }
func (l *LocalClient) GetBlockCount() (uint32, error) { func (l *localClient) GetBlockCount() (uint32, error) {
return l.bc.BlockHeight(), nil return l.bc.BlockHeight(), nil
} }
func (l *LocalClient) GetNativeContracts() ([]state.Contract, error) { func (l *localClient) GetContractStateByID(id int32) (*state.Contract, error) {
h, err := l.bc.GetContractScriptHash(id)
if err != nil {
return nil, err
}
return l.GetContractStateByHash(h)
}
func (l *localClient) GetContractStateByHash(h util.Uint160) (*state.Contract, error) {
if cs := l.bc.GetContractState(h); cs != nil {
return cs, nil
}
return nil, storage.ErrKeyNotFound
}
func (l *localClient) GetNativeContracts() ([]state.NativeContract, error) {
return l.bc.GetNatives(), nil return l.bc.GetNatives(), nil
} }
func (l *LocalClient) GetApplicationLog(h util.Uint256, t *trigger.Type) (*result.ApplicationLog, error) { func (l *localClient) GetNetwork() (netmode.Magic, error) {
return l.bc.GetConfig().Magic, nil
}
func (l *localClient) GetApplicationLog(h util.Uint256, t *trigger.Type) (*result.ApplicationLog, error) {
aer, err := l.bc.GetAppExecResults(h, *t) aer, err := l.bc.GetAppExecResults(h, *t)
if err != nil { if err != nil {
return nil, err return nil, err
@ -128,13 +153,41 @@ func (l *LocalClient) GetApplicationLog(h util.Uint256, t *trigger.Type) (*resul
return &a, nil return &a, nil
} }
func (l *LocalClient) GetCommittee() (keys.PublicKeys, error) { func (l *localClient) CreateTxFromScript(script []byte, acc *wallet.Account, sysFee int64, netFee int64, cosigners []rpcclient.SignerAccount) (*transaction.Transaction, error) {
signers, accounts, err := getSigners(acc, cosigners)
if err != nil {
return nil, fmt.Errorf("failed to construct tx signers: %w", err)
}
if sysFee < 0 {
res, err := l.InvokeScript(script, signers)
if err != nil {
return nil, fmt.Errorf("can't add system fee to transaction: %w", err)
}
if res.State != "HALT" {
return nil, fmt.Errorf("can't add system fee to transaction: bad vm state: %s due to an error: %s", res.State, res.FaultException)
}
sysFee = res.GasConsumed
}
tx := transaction.New(script, sysFee)
tx.Signers = signers
tx.ValidUntilBlock = l.bc.BlockHeight() + 2
err = l.AddNetworkFee(tx, netFee, accounts...)
if err != nil {
return nil, fmt.Errorf("failed to add network fee: %w", err)
}
return tx, nil
}
func (l *localClient) GetCommittee() (keys.PublicKeys, error) {
// not used by `morph init` command // not used by `morph init` command
panic("unexpected call") panic("unexpected call")
} }
// InvokeFunction is implemented via `InvokeScript`. // InvokeFunction is implemented via `InvokeScript`.
func (l *LocalClient) InvokeFunction(h util.Uint160, method string, sPrm []smartcontract.Parameter, ss []transaction.Signer) (*result.Invoke, error) { func (l *localClient) InvokeFunction(h util.Uint160, method string, sPrm []smartcontract.Parameter, ss []transaction.Signer) (*result.Invoke, error) {
var err error var err error
pp := make([]any, len(sPrm)) pp := make([]any, len(sPrm))
@ -145,21 +198,36 @@ func (l *LocalClient) InvokeFunction(h util.Uint160, method string, sPrm []smart
} }
} }
return InvokeFunction(l, h, method, pp, ss) return invokeFunction(l, h, method, pp, ss)
} }
func (l *LocalClient) TerminateSession(_ uuid.UUID) (bool, error) { func (l *localClient) CalculateNotaryFee(_ uint8) (int64, error) {
// not used by `morph init` command // not used by `morph init` command
panic("unexpected call") panic("unexpected call")
} }
func (l *LocalClient) TraverseIterator(_, _ uuid.UUID, _ int) ([]stackitem.Item, error) { func (l *localClient) SignAndPushP2PNotaryRequest(_ *transaction.Transaction, _ []byte, _ int64, _ int64, _ uint32, _ *wallet.Account) (*payload.P2PNotaryRequest, error) {
// not used by `morph init` command
panic("unexpected call")
}
func (l *localClient) SignAndPushInvocationTx(_ []byte, _ *wallet.Account, _ int64, _ fixedn.Fixed8, _ []rpcclient.SignerAccount) (util.Uint256, error) {
// not used by `morph init` command
panic("unexpected call")
}
func (l *localClient) TerminateSession(_ uuid.UUID) (bool, error) {
// 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 // not used by `morph init` command
panic("unexpected call") panic("unexpected call")
} }
// GetVersion return default version. // GetVersion return default version.
func (l *LocalClient) GetVersion() (*result.Version, error) { func (l *localClient) GetVersion() (*result.Version, error) {
c := l.bc.GetConfig() c := l.bc.GetConfig()
return &result.Version{ return &result.Version{
Protocol: result.Protocol{ Protocol: result.Protocol{
@ -180,86 +248,120 @@ func (l *LocalClient) GetVersion() (*result.Version, error) {
}, nil }, nil
} }
func (l *LocalClient) InvokeContractVerify(util.Uint160, []smartcontract.Parameter, []transaction.Signer, ...transaction.Witness) (*result.Invoke, error) { func (l *localClient) InvokeContractVerify(util.Uint160, []smartcontract.Parameter, []transaction.Signer, ...transaction.Witness) (*result.Invoke, error) {
// not used by `morph init` command // not used by `morph init` command
panic("unexpected call") panic("unexpected call")
} }
// CalculateNetworkFee calculates network fee for the given transaction. // CalculateNetworkFee calculates network fee for the given transaction.
// Copied from neo-go with minor corrections (no need to support non-notary mode): // Copied from neo-go with minor corrections (no need to support non-notary mode):
// https://github.com/nspcc-dev/neo-go/blob/v0.103.0/pkg/services/rpcsrv/server.go#L911 // https://github.com/nspcc-dev/neo-go/blob/v0.99.2/pkg/services/rpcsrv/server.go#L744
func (l *LocalClient) CalculateNetworkFee(tx *transaction.Transaction) (int64, error) { 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() hashablePart, err := tx.EncodeHashableFields()
if err != nil { if err != nil {
return 0, err return 0, fmt.Errorf("failed to compute tx size: %w", err)
} }
size := len(hashablePart) + io.GetVarSize(len(tx.Signers)) size := len(hashablePart) + io.GetVarSize(len(tx.Signers))
var ( ef := l.bc.GetBaseExecFee()
netFee int64
// Verification GAS cost can't exceed this policy. var netFee int64
gasLimit = l.bc.GetMaxVerificationGAS()
)
for i, signer := range tx.Signers { for i, signer := range tx.Signers {
w := tx.Scripts[i] var verificationScript []byte
if len(w.InvocationScript) == 0 { // No invocation provided, try to infer one. for _, w := range tx.Scripts {
var paramz []manifest.Parameter if w.VerificationScript != nil && hash.Hash160(w.VerificationScript).Equals(signer.Account) {
if len(w.VerificationScript) == 0 { // Contract-based verification verificationScript = w.VerificationScript
cs := l.bc.GetContractState(signer.Account) break
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 := range nSigs {
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 verificationScript == nil {
if err != nil && !errors.Is(err, core.ErrInvalidSignature) { gasConsumed, err := l.bc.VerifyWitness(signer.Account, tx, &tx.Scripts[i], l.maxGasInvoke)
return 0, err if err != nil {
} return 0, fmt.Errorf("invalid signature: %w", err)
gasLimit -= gasConsumed }
netFee += gasConsumed netFee += gasConsumed
size += io.GetVarSize(w.VerificationScript) + io.GetVarSize(w.InvocationScript) size += io.GetVarSize([]byte{}) + io.GetVarSize(tx.Scripts[i].InvocationScript)
} continue
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, sizeDelta := fee.Calculate(ef, verificationScript)
netFee += fee
size += sizeDelta
} }
fee := l.bc.FeePerByte() fee := l.bc.FeePerByte()
netFee += int64(size) * fee netFee += int64(size) * fee
return netFee, nil return netFee, nil
} }
func (l *LocalClient) InvokeScript(script []byte, signers []transaction.Signer) (*result.Invoke, error) { // AddNetworkFee adds network fee for each witness script and optional extra
// network fee to transaction. `accs` is an array signer's accounts.
// Copied from neo-go with minor corrections (no need to support contract signers):
// https://github.com/nspcc-dev/neo-go/blob/6ff11baa1b9e4c71ef0d1de43b92a8c541ca732c/pkg/rpc/client/rpc.go#L960
func (l *localClient) AddNetworkFee(tx *transaction.Transaction, extraFee int64, accs ...*wallet.Account) error {
if len(tx.Signers) != len(accs) {
return errors.New("number of signers must match number of scripts")
}
size := io.GetVarSize(tx)
ef := l.bc.GetBaseExecFee()
for i := range tx.Signers {
netFee, sizeDelta := fee.Calculate(ef, accs[i].Contract.Script)
tx.NetworkFee += netFee
size += sizeDelta
}
tx.NetworkFee += int64(size)*l.bc.FeePerByte() + extraFee
return nil
}
// getSigners returns an array of transaction signers and corresponding accounts from
// given sender and cosigners. If cosigners list already contains sender, the sender
// will be placed at the start of the list.
// Copied from neo-go with minor corrections:
// https://github.com/nspcc-dev/neo-go/blob/6ff11baa1b9e4c71ef0d1de43b92a8c541ca732c/pkg/rpc/client/rpc.go#L735
func getSigners(sender *wallet.Account, cosigners []rpcclient.SignerAccount) ([]transaction.Signer, []*wallet.Account, error) {
var (
signers []transaction.Signer
accounts []*wallet.Account
)
from := sender.Contract.ScriptHash()
s := transaction.Signer{
Account: from,
Scopes: transaction.None,
}
for _, c := range cosigners {
if c.Signer.Account == from {
s = c.Signer
continue
}
signers = append(signers, c.Signer)
accounts = append(accounts, c.Account)
}
signers = append([]transaction.Signer{s}, signers...)
accounts = append([]*wallet.Account{sender}, accounts...)
return signers, accounts, nil
}
func (l *localClient) NEP17BalanceOf(h util.Uint160, acc util.Uint160) (int64, error) {
res, err := invokeFunction(l, h, "balanceOf", []any{acc}, nil)
if err != nil {
return 0, err
}
if res.State != vmstate.Halt.String() || len(res.Stack) == 0 {
return 0, fmt.Errorf("`balance`: invalid response (empty: %t): %s",
len(res.Stack) == 0, res.FaultException)
}
bi, err := res.Stack[0].TryInteger()
if err != nil || !bi.IsInt64() {
return 0, fmt.Errorf("`balance`: invalid response")
}
return bi.Int64(), nil
}
func (l *localClient) InvokeScript(script []byte, signers []transaction.Signer) (*result.Invoke, error) {
lastBlock, err := l.bc.GetBlock(l.bc.CurrentBlockHash()) lastBlock, err := l.bc.GetBlock(l.bc.CurrentBlockHash())
if err != nil { if err != nil {
return nil, err return nil, err
@ -295,7 +397,7 @@ func (l *LocalClient) InvokeScript(script []byte, signers []transaction.Signer)
}, nil }, nil
} }
func (l *LocalClient) SendRawTransaction(tx *transaction.Transaction) (util.Uint256, error) { 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. // We need to test that transaction was formed correctly to catch as many errors as we can.
bs := tx.Bytes() bs := tx.Bytes()
_, err := transaction.NewTransactionFromBytes(bs) _, err := transaction.NewTransactionFromBytes(bs)
@ -307,7 +409,7 @@ func (l *LocalClient) SendRawTransaction(tx *transaction.Transaction) (util.Uint
return tx.Hash(), nil return tx.Hash(), nil
} }
func (l *LocalClient) putTransactions() error { func (l *localClient) putTransactions() error {
// 1. Prepare new block. // 1. Prepare new block.
lastBlock, err := l.bc.GetBlock(l.bc.CurrentBlockHash()) lastBlock, err := l.bc.GetBlock(l.bc.CurrentBlockHash())
if err != nil { if err != nil {
@ -349,7 +451,7 @@ func (l *LocalClient) putTransactions() error {
return l.bc.AddBlock(b) return l.bc.AddBlock(b)
} }
func InvokeFunction(c Client, h util.Uint160, method string, parameters []any, signers []transaction.Signer) (*result.Invoke, error) { func invokeFunction(c Client, h util.Uint160, method string, parameters []any, signers []transaction.Signer) (*result.Invoke, error) {
w := io.NewBufBinWriter() w := io.NewBufBinWriter()
emit.Array(w.BinWriter, parameters...) emit.Array(w.BinWriter, parameters...)
emit.AppCallNoArgs(w.BinWriter, h, method, callflag.All) emit.AppCallNoArgs(w.BinWriter, h, method, callflag.All)
@ -361,7 +463,7 @@ func InvokeFunction(c Client, h util.Uint160, method string, parameters []any, s
var errGetDesignatedByRoleResponse = errors.New("`getDesignatedByRole`: invalid response") var errGetDesignatedByRoleResponse = errors.New("`getDesignatedByRole`: invalid response")
func GetDesignatedByRole(inv *invoker.Invoker, h util.Uint160, role noderoles.Role, u uint32) (keys.PublicKeys, error) { 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))) arr, err := unwrap.Array(inv.Call(h, "getDesignatedByRole", int64(role), int64(u)))
if err != nil { if err != nil {
return nil, errGetDesignatedByRoleResponse return nil, errGetDesignatedByRoleResponse
@ -382,7 +484,7 @@ func GetDesignatedByRole(inv *invoker.Invoker, h util.Uint160, role noderoles.Ro
return pubs, nil return pubs, nil
} }
func (l *LocalClient) Dump() (err error) { func (l *localClient) dump() (err error) {
defer l.bc.Close() defer l.bc.Close()
f, err := os.Create(l.dumpPath) f, err := os.Create(l.dumpPath)

View file

@ -1,17 +1,18 @@
package helper package morph
import ( import (
"context" "context"
"crypto/tls"
"errors" "errors"
"fmt" "fmt"
"time" "time"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags" "github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
"github.com/nspcc-dev/neo-go/pkg/neorpc/result" "github.com/nspcc-dev/neo-go/pkg/neorpc/result"
"github.com/nspcc-dev/neo-go/pkg/network/payload"
"github.com/nspcc-dev/neo-go/pkg/rpcclient" "github.com/nspcc-dev/neo-go/pkg/rpcclient"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
@ -28,27 +29,36 @@ type Client interface {
invoker.RPCInvoke invoker.RPCInvoke
GetBlockCount() (uint32, error) GetBlockCount() (uint32, error)
GetNativeContracts() ([]state.Contract, error) GetContractStateByID(int32) (*state.Contract, error)
GetContractStateByHash(util.Uint160) (*state.Contract, error)
GetNativeContracts() ([]state.NativeContract, error)
GetNetwork() (netmode.Magic, error)
GetApplicationLog(util.Uint256, *trigger.Type) (*result.ApplicationLog, error) GetApplicationLog(util.Uint256, *trigger.Type) (*result.ApplicationLog, error)
GetVersion() (*result.Version, error) GetVersion() (*result.Version, error)
CreateTxFromScript([]byte, *wallet.Account, int64, int64, []rpcclient.SignerAccount) (*transaction.Transaction, error)
NEP17BalanceOf(util.Uint160, util.Uint160) (int64, error)
SendRawTransaction(*transaction.Transaction) (util.Uint256, error) SendRawTransaction(*transaction.Transaction) (util.Uint256, error)
GetCommittee() (keys.PublicKeys, error) GetCommittee() (keys.PublicKeys, error)
CalculateNotaryFee(uint8) (int64, error)
CalculateNetworkFee(tx *transaction.Transaction) (int64, error) CalculateNetworkFee(tx *transaction.Transaction) (int64, error)
AddNetworkFee(*transaction.Transaction, int64, ...*wallet.Account) error
SignAndPushInvocationTx([]byte, *wallet.Account, int64, fixedn.Fixed8, []rpcclient.SignerAccount) (util.Uint256, error)
SignAndPushP2PNotaryRequest(*transaction.Transaction, []byte, int64, int64, uint32, *wallet.Account) (*payload.P2PNotaryRequest, error)
} }
type HashVUBPair struct { type hashVUBPair struct {
Hash util.Uint256 hash util.Uint256
Vub uint32 vub uint32
} }
type ClientContext struct { type clientContext struct {
Client Client // a raw neo-go client OR a local chain implementation Client Client // a raw neo-go client OR a local chain implementation
CommitteeAct *actor.Actor // committee actor with the Global witness scope CommitteeAct *actor.Actor // committee actor with the Global witness scope
ReadOnlyInvoker *invoker.Invoker // R/O contract invoker, does not contain any signer ReadOnlyInvoker *invoker.Invoker // R/O contract invoker, does not contain any signer
SentTxs []HashVUBPair SentTxs []hashVUBPair
} }
func GetN3Client(v *viper.Viper) (Client, error) { func getN3Client(v *viper.Viper) (Client, error) {
// number of opened connections // number of opened connections
// by neo-go client per one host // by neo-go client per one host
const ( const (
@ -57,27 +67,13 @@ func GetN3Client(v *viper.Viper) (Client, error) {
) )
ctx := context.Background() ctx := context.Background()
endpoint := v.GetString(commonflags.EndpointFlag) endpoint := v.GetString(endpointFlag)
if endpoint == "" { if endpoint == "" {
return nil, errors.New("missing endpoint") return nil, errors.New("missing endpoint")
} }
var cfg *tls.Config
if rootCAs := v.GetStringSlice("tls.trusted_ca_list"); len(rootCAs) != 0 {
certFile := v.GetString("tls.certificate")
keyFile := v.GetString("tls.key")
tlsConfig, err := rpcclient.TLSClientConfig(rootCAs, certFile, keyFile)
if err != nil {
return nil, err
}
cfg = tlsConfig
}
c, err := rpcclient.New(ctx, endpoint, rpcclient.Options{ c, err := rpcclient.New(ctx, endpoint, rpcclient.Options{
MaxConnsPerHost: maxConnsPerHost, MaxConnsPerHost: maxConnsPerHost,
RequestTimeout: requestTimeout, RequestTimeout: requestTimeout,
TLSClientConfig: cfg,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@ -88,20 +84,26 @@ func GetN3Client(v *viper.Viper) (Client, error) {
return c, nil return c, nil
} }
func DefaultClientContext(c Client, committeeAcc *wallet.Account) (*ClientContext, error) { func defaultClientContext(c Client, committeeAcc *wallet.Account) (*clientContext, error) {
commAct, err := NewActor(c, committeeAcc) commAct, err := actor.New(c, []actor.SignerAccount{{
Signer: transaction.Signer{
Account: committeeAcc.Contract.ScriptHash(),
Scopes: transaction.Global,
},
Account: committeeAcc,
}})
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &ClientContext{ return &clientContext{
Client: c, Client: c,
CommitteeAct: commAct, CommitteeAct: commAct,
ReadOnlyInvoker: invoker.New(c, nil), ReadOnlyInvoker: invoker.New(c, nil),
}, nil }, nil
} }
func (c *ClientContext) SendTx(tx *transaction.Transaction, cmd *cobra.Command, await bool) error { func (c *clientContext) sendTx(tx *transaction.Transaction, cmd *cobra.Command, await bool) error {
h, err := c.Client.SendRawTransaction(tx) h, err := c.Client.SendRawTransaction(tx)
if err != nil { if err != nil {
return err return err
@ -111,27 +113,10 @@ func (c *ClientContext) SendTx(tx *transaction.Transaction, cmd *cobra.Command,
return fmt.Errorf("sent and actual tx hashes mismatch:\n\tsent: %v\n\tactual: %v", tx.Hash().StringLE(), h.StringLE()) 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}) c.SentTxs = append(c.SentTxs, hashVUBPair{hash: h, vub: tx.ValidUntilBlock})
if await { if await {
return c.AwaitTx(cmd) return c.awaitTx(cmd)
} }
return nil 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
}

View file

@ -1,48 +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"
)
const deltaFlag = "delta"
func ForceNewEpochCmd(cmd *cobra.Command, _ []string) error {
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 := helper.GetContractByID(r, 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()
delta, _ := cmd.Flags().GetInt64(deltaFlag)
if err := helper.EmitNewEpochCall(bw, wCtx, nmHash, delta); 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
}

View file

@ -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))
},
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")
ForceNewEpoch.Flags().Int64(deltaFlag, 1, "Number of epochs to increase the current epoch")
}
func init() {
initNetmapCandidatesCmd()
initForceNewEpochCmd()
}

View file

@ -1,28 +1,24 @@
package netmap package morph
import ( import (
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags" "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" commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap" "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/invoker"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
func listNetmapCandidatesNodes(cmd *cobra.Command, _ []string) { func listNetmapCandidatesNodes(cmd *cobra.Command, _ []string) {
c, err := helper.GetN3Client(viper.GetViper()) c, err := getN3Client(viper.GetViper())
commonCmd.ExitOnErr(cmd, "can't create N3 client: %w", err) commonCmd.ExitOnErr(cmd, "can't create N3 client: %w", err)
inv := invoker.New(c, nil) inv := invoker.New(c, nil)
r := management.NewReader(inv)
cs, err := helper.GetContractByID(r, 1) cs, err := c.GetContractStateByID(1)
commonCmd.ExitOnErr(cmd, "can't get NNS contract info: %w", err) commonCmd.ExitOnErr(cmd, "can't get NNS contract info: %w", err)
nmHash, err := helper.NNSResolveHash(inv, cs.Hash, helper.DomainOf(constants.NetmapContract)) nmHash, err := nnsResolveHash(inv, cs.Hash, netmapContract+".frostfs")
commonCmd.ExitOnErr(cmd, "can't get netmap contract hash: %w", err) commonCmd.ExitOnErr(cmd, "can't get netmap contract hash: %w", err)
res, err := inv.Call(nmHash, "netmapCandidates") res, err := inv.Call(nmHash, "netmapCandidates")

View file

@ -0,0 +1,46 @@
package morph
import (
"errors"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/spf13/viper"
)
func getDefaultNetmapContractConfigMap() map[string]any {
m := make(map[string]any)
m[netmap.EpochDurationConfig] = viper.GetInt64(epochDurationInitFlag)
m[netmap.MaxObjectSizeConfig] = viper.GetInt64(maxObjectSizeInitFlag)
m[netmap.AuditFeeConfig] = viper.GetInt64(auditFeeInitFlag)
m[netmap.ContainerFeeConfig] = viper.GetInt64(containerFeeInitFlag)
m[netmap.ContainerAliasFeeConfig] = viper.GetInt64(containerAliasFeeInitFlag)
m[netmap.BasicIncomeRateConfig] = viper.GetInt64(incomeRateInitFlag)
m[netmap.IrCandidateFeeConfig] = viper.GetInt64(candidateFeeInitFlag)
m[netmap.WithdrawFeeConfig] = viper.GetInt64(withdrawFeeInitFlag)
m[netmap.HomomorphicHashingDisabledKey] = viper.GetBool(homomorphicHashDisabledInitFlag)
m[netmap.MaintenanceModeAllowedConfig] = viper.GetBool(maintenanceModeAllowedInitFlag)
return m
}
func parseConfigFromNetmapContract(arr []stackitem.Item) (map[string][]byte, error) {
m := make(map[string][]byte, len(arr))
for _, param := range arr {
tuple, ok := param.Value().([]stackitem.Item)
if !ok || len(tuple) != 2 {
return nil, errors.New("invalid ListConfig response from netmap contract")
}
k, err := tuple[0].TryBytes()
if err != nil {
return nil, errors.New("invalid config key from netmap contract")
}
v, err := tuple[1].TryBytes()
if err != nil {
return nil, invalidConfigValueErr(string(k))
}
m[string(k)] = v
}
return m, nil
}

View file

@ -1,64 +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 := nnsWriter(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")
}
func initDeleteCmd() {
Cmd.AddCommand(deleteCmd)
deleteCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
deleteCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
deleteCmd.Flags().String(nnsNameFlag, "", nnsNameFlagDesc)
_ = cobra.MarkFlagRequired(deleteCmd.Flags(), nnsNameFlag)
}
func deleteDomain(cmd *cobra.Command, _ []string) {
c, actor := nnsWriter(cmd)
name, _ := cmd.Flags().GetString(nnsNameFlag)
h, vub, err := c.DeleteDomain(name)
_, err = actor.Wait(h, vub, err)
commonCmd.ExitOnErr(cmd, "delete domain error: %w", err)
cmd.Println("Domain deleted successfully")
}

View file

@ -1,38 +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/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/rpcclient/invoker"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
func nnsWriter(cmd *cobra.Command) (*client.Contract, *helper.LocalActor) {
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, constants.CommitteeAccountName)
commonCmd.ExitOnErr(cmd, "can't create actor: %w", err)
r := management.NewReader(ac.Invoker)
nnsCs, err := helper.GetContractByID(r, 1)
commonCmd.ExitOnErr(cmd, "can't get NNS contract state: %w", err)
return client.New(ac, nnsCs.Hash), ac
}
func nnsReader(cmd *cobra.Command) (*client.ContractReader, *invoker.Invoker) {
c, err := helper.GetN3Client(viper.GetViper())
commonCmd.ExitOnErr(cmd, "unable to create NEO rpc client: %w", err)
inv := invoker.New(c, nil)
r := management.NewReader(inv)
nnsCs, err := helper.GetContractByID(r, 1)
commonCmd.ExitOnErr(cmd, "can't get NNS contract state: %w", err)
return client.NewReader(inv, nnsCs.Hash), inv
}

View file

@ -1,175 +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/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(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 initDelRecordCmd() {
Cmd.AddCommand(delRecordCmd)
delRecordCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
delRecordCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
delRecordCmd.Flags().String(nnsNameFlag, "", nnsNameFlagDesc)
delRecordCmd.Flags().String(nnsRecordTypeFlag, "", nnsRecordTypeFlagDesc)
delRecordCmd.Flags().String(nnsRecordDataFlag, "", nnsRecordDataFlagDesc)
_ = cobra.MarkFlagRequired(delRecordCmd.Flags(), nnsNameFlag)
_ = cobra.MarkFlagRequired(delRecordCmd.Flags(), nnsRecordTypeFlag)
_ = cobra.MarkFlagRequired(delRecordCmd.Flags(), nnsRecordDataFlag)
}
func addRecord(cmd *cobra.Command, _ []string) {
c, actor := nnsWriter(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, inv := nnsReader(cmd)
name, _ := cmd.Flags().GetString(nnsNameFlag)
recordType, _ := cmd.Flags().GetString(nnsRecordTypeFlag)
if recordType == "" {
sid, r, err := c.GetAllRecords(name)
commonCmd.ExitOnErr(cmd, "unable to get records: %w", err)
defer func() {
_ = inv.TerminateSession(sid)
}()
items, err := inv.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 = inv.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 := nnsWriter(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 delRecord(cmd *cobra.Command, _ []string) {
c, actor := nnsWriter(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.DeleteRecord(name, typ, data)
commonCmd.ExitOnErr(cmd, "unable to delete record: %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("Record 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 ""
}

View file

@ -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 := nnsWriter(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")
}

View file

@ -1,119 +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,
}
deleteCmd = &cobra.Command{
Use: "delete",
Short: "Delete a domain by name",
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: deleteDomain,
}
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,
}
delRecordCmd = &cobra.Command{
Use: "delete-record",
Short: "Removes domain record with the specified type and data",
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: delRecord,
}
)
func init() {
initTokensCmd()
initRegisterCmd()
initDeleteCmd()
initRenewCmd()
initUpdateCmd()
initAddRecordCmd()
initGetRecordsCmd()
initDelRecordsCmd()
initDelRecordCmd()
}

View file

@ -1,65 +0,0 @@
package nns
import (
"math/big"
"strings"
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
client "git.frostfs.info/TrueCloudLab/frostfs-contract/rpcclient/nns"
"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"
)
const (
verboseDesc = "Include additional information about CNAME record."
)
func initTokensCmd() {
Cmd.AddCommand(tokensCmd)
tokensCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
tokensCmd.Flags().BoolP(commonflags.Verbose, commonflags.VerboseShorthand, false, verboseDesc)
}
func listTokens(cmd *cobra.Command, _ []string) {
c, _ := nnsReader(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 {
output := string(token)
if verbose, _ := cmd.Flags().GetBool(commonflags.Verbose); verbose {
cname, err := getCnameRecord(c, token)
commonCmd.ExitOnErr(cmd, "", err)
if cname != "" {
output += " (CNAME: " + cname + ")"
}
}
cmd.Println(output)
}
}
}
func getCnameRecord(c *client.ContractReader, token []byte) (string, error) {
items, err := c.GetRecords(string(token), big.NewInt(int64(nns.CNAME)))
// GetRecords returns the error "not an array" if the domain does not contain records.
if err != nil && strings.Contains(err.Error(), "not an array") {
return "", nil
}
if err != nil {
return "", err
}
if len(items) == 0 {
return "", nil
}
record, err := items[0].TryBytes()
if err != nil {
return "", err
}
return string(record), nil
}

View file

@ -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 := nnsWriter(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")
}

View file

@ -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()
}

View file

@ -1,13 +1,10 @@
package notary package morph
import ( import (
"errors"
"fmt" "fmt"
"math/big" "math/big"
"strconv" "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/cli/input"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
@ -23,16 +20,9 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
) )
const ( // defaultNotaryDepositLifetime is an amount of blocks notary deposit stays valid.
// 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
// https://github.com/nspcc-dev/neo-go/blob/master/pkg/core/native/notary.go#L48 const defaultNotaryDepositLifetime = 5760
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 { func depositNotary(cmd *cobra.Command, _ []string) error {
w, err := openWallet(cmd) w, err := openWallet(cmd)
@ -64,11 +54,11 @@ func depositNotary(cmd *cobra.Command, _ []string) error {
return fmt.Errorf("can't unlock account: %v", err) return fmt.Errorf("can't unlock account: %v", err)
} }
gasStr, err := cmd.Flags().GetString(commonflags.RefillGasAmountFlag) gasStr, err := cmd.Flags().GetString(refillGasAmountFlag)
if err != nil { if err != nil {
return err return err
} }
gasAmount, err := helper.ParseGASAmount(gasStr) gasAmount, err := parseGASAmount(gasStr)
if err != nil { if err != nil {
return err return err
} }
@ -81,7 +71,7 @@ func depositNotary(cmd *cobra.Command, _ []string) error {
if tillStr != "" { if tillStr != "" {
till, err = strconv.ParseInt(tillStr, 10, 64) till, err = strconv.ParseInt(tillStr, 10, 64)
if err != nil || till <= 0 { if err != nil || till <= 0 {
return errInvalidNotaryDepositLifetime return fmt.Errorf("notary deposit lifetime must be a positive integer")
} }
} }
@ -89,12 +79,12 @@ func depositNotary(cmd *cobra.Command, _ []string) error {
} }
func transferGas(cmd *cobra.Command, acc *wallet.Account, accHash util.Uint160, gasAmount fixedn.Fixed8, till int64) error { func transferGas(cmd *cobra.Command, acc *wallet.Account, accHash util.Uint160, gasAmount fixedn.Fixed8, till int64) error {
c, err := helper.GetN3Client(viper.GetViper()) c, err := getN3Client(viper.GetViper())
if err != nil { if err != nil {
return err return err
} }
if err := helper.CheckNotaryEnabled(c); err != nil { if err := checkNotaryEnabled(c); err != nil {
return err return err
} }
@ -126,15 +116,15 @@ func transferGas(cmd *cobra.Command, acc *wallet.Account, accHash util.Uint160,
return fmt.Errorf("could not send tx: %w", err) return fmt.Errorf("could not send tx: %w", err)
} }
return helper.AwaitTx(cmd, c, []helper.HashVUBPair{{Hash: txHash, Vub: vub}}) return awaitTx(cmd, c, []hashVUBPair{{hash: txHash, vub: vub}})
} }
func openWallet(cmd *cobra.Command) (*wallet.Wallet, error) { func openWallet(cmd *cobra.Command) (*wallet.Wallet, error) {
p, err := cmd.Flags().GetString(commonflags.StorageWalletFlag) p, err := cmd.Flags().GetString(storageWalletFlag)
if err != nil { if err != nil {
return nil, err return nil, err
} else if p == "" { } else if p == "" {
return nil, fmt.Errorf("missing wallet path (use '--%s <out.json>')", commonflags.StorageWalletFlag) return nil, fmt.Errorf("missing wallet path (use '--%s <out.json>')", storageWalletFlag)
} }
w, err := wallet.NewWalletFromFile(p) w, err := wallet.NewWalletFromFile(p)

View file

@ -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()
}

View file

@ -0,0 +1,54 @@
package morph
import (
"fmt"
"strconv"
"strings"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/policy"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
const (
execFeeParam = "ExecFeeFactor"
storagePriceParam = "StoragePrice"
setFeeParam = "FeePerByte"
)
func setPolicyCmd(cmd *cobra.Command, args []string) error {
wCtx, err := newInitializeContext(cmd, viper.GetViper())
if err != nil {
return fmt.Errorf("can't to initialize context: %w", err)
}
bw := io.NewBufBinWriter()
for i := range args {
k, v, found := strings.Cut(args[i], "=")
if !found {
return fmt.Errorf("invalid parameter format, must be Parameter=Value")
}
switch k {
case execFeeParam, storagePriceParam, setFeeParam:
default:
return fmt.Errorf("parameter must be one of %s, %s and %s", execFeeParam, storagePriceParam, setFeeParam)
}
value, err := strconv.ParseUint(v, 10, 32)
if err != nil {
return fmt.Errorf("can't parse parameter value '%s': %w", args[1], err)
}
emit.AppCall(bw.BinWriter, policy.Hash, "set"+k, callflag.All, int64(value))
}
if err := wCtx.sendCommitteeTx(bw.Bytes(), false); err != nil {
return err
}
return wCtx.awaitTx()
}

View file

@ -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 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[i], 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
}

View file

@ -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()
}

View file

@ -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 initialize context: %w", err)
}
r := management.NewReader(wCtx.ReadOnlyInvoker)
cs, err := helper.GetContractByID(r, 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
}

View file

@ -1,45 +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")
AddAccountCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
}
func initProxyRemoveAccount() {
RemoveAccountCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
RemoveAccountCmd.Flags().String(accountAddressFlag, "", "Wallet address string")
RemoveAccountCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
}
func init() {
initProxyAddAccount()
initProxyRemoveAccount()
}

Some files were not shown because too many files have changed in this diff Show more