Compare commits

..

2 commits

Author SHA1 Message Date
9b81572b9d [#1] tests: Update domain email after rebranding
Signed-off-by: Alex Vanin <a.vanin@yadro.com>
2023-05-17 10:51:44 +03:00
e11dfc84af [#1] container: Update domain email after rebranding
Signed-off-by: Alex Vanin <a.vanin@yadro.com>
2023-05-17 10:51:10 +03:00
45 changed files with 1163 additions and 1104 deletions

View file

@ -1,20 +0,0 @@
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.20'
- name: Run commit format checker
uses: https://git.alexvan.in/alexvanin/dco-go@v1
with:
from: e19fe15e

View file

@ -1,20 +0,0 @@
on: [pull_request]
jobs:
tests:
name: Tests
runs-on: ubuntu-latest
strategy:
matrix:
go_versions: [ '1.19', '1.20' ]
fail-fast: false
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: '${{ matrix.go_versions }}'
- name: Run tests
run: make test

21
.github/workflows/dco.yml vendored Normal file
View file

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

20
.github/workflows/go.yml vendored Normal file
View file

@ -0,0 +1,20 @@
name: Go
on:
pull_request:
branches: [ master ]
jobs:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.17
- name: Test
run: go test -v ./...

View file

@ -6,8 +6,6 @@ Changelog for FrostFS Contract
### Added
### Changed
### Removed
- `subnet` contract (#20)
### Updated
### Fixed
### Updating from v0.17.0

View file

@ -22,7 +22,7 @@ all: sidechain mainnet
sidechain: alphabet morph nns
alphabet_sc = alphabet
morph_sc = audit balance container frostfsid netmap proxy reputation
morph_sc = audit balance container frostfsid netmap proxy reputation subnet
mainnet_sc = frostfs processing
nns_sc = nns

View file

@ -29,6 +29,7 @@ Sidechain contracts:
- nns
- proxy
- reputation
- subnet
# Getting started
@ -54,6 +55,7 @@ $ make all
/home/user/go/bin/cli contract compile -i netmap -c netmap/config.yml -m netmap/config.json -o netmap/netmap_contract.nef
/home/user/go/bin/cli contract compile -i proxy -c proxy/config.yml -m proxy/config.json -o proxy/proxy_contract.nef
/home/user/go/bin/cli contract compile -i reputation -c reputation/config.yml -m reputation/config.json -o reputation/reputation_contract.nef
/home/user/go/bin/cli contract compile -i subnet -c subnet/config.yml -m subnet/config.json -o subnet/subnet_contract.nef
/home/user/go/bin/cli contract compile -i nns -c nns/config.yml -m nns/config.json -o nns/nns_contract.nef
/home/user/go/bin/cli contract compile -i frostfs -c frostfs/config.yml -m frostfs/config.json -o frostfs/frostfs_contract.nef
/home/user/go/bin/cli contract compile -i processing -c processing/config.yml -m processing/config.json -o processing/processing_contract.nef

View file

@ -1 +1 @@
v0.18.0
v0.17.0

View file

@ -72,7 +72,8 @@ func Update(script []byte, manifest []byte, data interface{}) {
panic("only committee can update contract")
}
management.UpdateWithData(script, manifest, common.AppendVersion(data))
contract.Call(interop.Hash160(management.Hash), "update",
contract.All, script, manifest, common.AppendVersion(data))
runtime.Log("alphabet contract updated")
}

View file

@ -17,16 +17,5 @@ for each alphabet contract.
# Contract notifications
Alphabet contract does not produce notifications to process.
# Contract storage scheme
| Key | Value | Description |
|--------------------|------------|-------------------------------------------------|
| `netmapScriptHash` | Hash160 | netmap contract hash |
| `proxyScriptHash` | Hash160 | proxy contract hash |
| `name` | string | assigned glagolitic letter |
| `index` | int | the index of deployed alphabet contract |
| `threshold` | int | the total number of deployed alphabet contracts |
*/
package alphabet

View file

@ -3,6 +3,7 @@ package audit
import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
"github.com/nspcc-dev/neo-go/pkg/interop/native/crypto"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
@ -74,7 +75,8 @@ func Update(script []byte, manifest []byte, data interface{}) {
panic("only committee can update contract")
}
management.UpdateWithData(script, manifest, common.AppendVersion(data))
contract.Call(interop.Hash160(management.Hash), "update",
contract.All, script, manifest, common.AppendVersion(data))
runtime.Log("audit contract updated")
}

View file

@ -18,13 +18,5 @@ they make a list and get these AuditResultStructures from the audit contract.
# Contract notifications
Audit contract does not produce notifications to process.
# Contract storage scheme
| Key | Value | Description |
|--------------------|------------|-----------------------------------------------------------|
| `netmapScriptHash` | Hash160 | netmap contract hash |
| auditID | ByteArray | serialized DataAuditResult structure |
*/
package audit

View file

@ -3,6 +3,7 @@ package balance
import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
"github.com/nspcc-dev/neo-go/pkg/interop/native/std"
@ -92,7 +93,8 @@ func Update(script []byte, manifest []byte, data interface{}) {
panic("only committee can update contract")
}
management.UpdateWithData(script, manifest, common.AppendVersion(data))
contract.Call(interop.Hash160(management.Hash), "update",
contract.All, script, manifest, common.AppendVersion(data))
runtime.Log("balance contract updated")
}

View file

@ -1,11 +1,6 @@
name: "Balance"
supportedstandards: ["NEP-17"]
safemethods:
- "balanceOf"
- "decimals"
- "symbol"
- "totalSupply"
- "version"
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "version"]
permissions:
- methods: ["update"]
events:

View file

@ -74,14 +74,5 @@ when FrostFS contract has transferred GAS assets back to the user.
type: Hash160
- name: amount
type: Integer
# Contract storage scheme
| Key | Value | Description |
|-----------------------|------------|----------------------------------|
| `netmapScriptHash` | Hash160 | netmap contract hash |
| `containerScriptHash` | Hash160 | container contract hash |
| circulationKey | int | the token circulation key value |
*/
package balance

View file

@ -4,7 +4,7 @@ import "github.com/nspcc-dev/neo-go/pkg/interop/native/std"
const (
major = 0
minor = 18
minor = 17
patch = 0
// Versions from which an update should be performed.

View file

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

View file

@ -64,7 +64,6 @@ const (
estimateKeyPrefix = "cnr"
containerKeyPrefix = 'x'
ownerKeyPrefix = 'o'
graveKeyPrefix = 'g'
estimatePostfixSize = 10
// CleanupDelta contains the number of the last epochs for which container estimations are present.
CleanupDelta = 3
@ -172,12 +171,13 @@ func Update(script []byte, manifest []byte, data interface{}) {
panic("only committee can update contract")
}
management.UpdateWithData(script, manifest, common.AppendVersion(data))
contract.Call(interop.Hash160(management.Hash), "update",
contract.All, script, manifest, common.AppendVersion(data))
runtime.Log("container contract updated")
}
// Put method creates a new container if it has been invoked by Alphabet nodes
// of the Inner Ring.
// of the Inner Ring. Otherwise, it produces containerPut notification.
//
// Container should be a stable marshaled Container structure from API.
// Signature is a RFC6979 signature of the Container.
@ -303,14 +303,15 @@ func checkNiceNameAvailable(nnsContractAddr interop.Hash160, domain string) bool
}
// Delete method removes a container from the contract storage if it has been
// invoked by Alphabet nodes of the Inner Ring.
// invoked by Alphabet nodes of the Inner Ring. Otherwise, it produces
// containerDelete notification.
//
// Signature is a RFC6979 signature of the container ID.
// Token is optional and should be a stable marshaled SessionToken structure from
// API.
//
// If the container doesn't exist, it panics with NotFoundError.
func Delete(containerID []byte, signature interop.Signature, publicKey interop.PublicKey, token []byte) {
func Delete(containerID []byte, signature interop.Signature, token []byte) {
ctx := storage.GetContext()
ownerID := getOwnerByID(ctx, containerID)
@ -338,25 +339,6 @@ func Delete(containerID []byte, signature interop.Signature, publicKey interop.P
runtime.Notify("DeleteSuccess", containerID)
}
type DelInfo struct {
Owner interop.Hash160
Epoch int
}
// DeletionInfo method returns container deletion info.
// If the container had never existed, NotFoundError is throwed.
// It can be used to check whether non-existing container was indeed deleted
// or does not yet exist at some height.
func DeletionInfo(containerID []byte) DelInfo {
ctx := storage.GetReadOnlyContext()
graveKey := append([]byte{graveKeyPrefix}, containerID...)
data := storage.Get(ctx, graveKey).([]byte)
if data == nil {
panic(NotFoundError)
}
return std.Deserialize(data).(DelInfo)
}
// Get method returns a structure that contains a stable marshaled Container structure,
// the signature, the public key of the container creator and a stable marshaled SessionToken
// structure if it was provided.
@ -425,7 +407,8 @@ func List(owner []byte) [][]byte {
}
// SetEACL method sets a new extended ACL table related to the contract
// if it was invoked by Alphabet nodes of the Inner Ring.
// if it was invoked by Alphabet nodes of the Inner Ring. Otherwise, it produces
// setEACL notification.
//
// EACL should be a stable marshaled EACLTable structure from API.
// Signature is a RFC6979 signature of the Container.
@ -616,9 +599,6 @@ func addContainer(ctx storage.Context, id, owner []byte, container Container) {
idKey := append([]byte{containerKeyPrefix}, id...)
common.SetSerialized(ctx, idKey, container)
graveKey := append([]byte{graveKeyPrefix}, id...)
storage.Delete(ctx, graveKey)
}
func removeContainer(ctx storage.Context, id []byte, owner []byte) {
@ -627,14 +607,6 @@ func removeContainer(ctx storage.Context, id []byte, owner []byte) {
storage.Delete(ctx, containerListKey)
storage.Delete(ctx, append([]byte{containerKeyPrefix}, id...))
graveKey := append([]byte{graveKeyPrefix}, id...)
netmapContractAddr := storage.Get(ctx, netmapContractKey).(interop.Hash160)
epoch := contract.Call(netmapContractAddr, "epoch", contract.ReadOnly).(int)
common.SetSerialized(ctx, graveKey, DelInfo{
Owner: owner,
Epoch: epoch,
})
}
func getAllContainers(ctx storage.Context) [][]byte {

View file

@ -9,6 +9,47 @@ the same arguments.
# Contract notifications
containerPut notification. This notification is produced when a user wants to
create a new container. Alphabet nodes of the Inner Ring catch the notification and
validate container data, signature and token if present.
containerPut:
- name: container
type: ByteArray
- name: signature
type: Signature
- name: publicKey
type: PublicKey
- name: token
type: ByteArray
containerDelete notification. This notification is produced when a container owner
wants to delete a container. Alphabet nodes of the Inner Ring catch the notification
and validate container ownership, signature and token if present.
containerDelete:
- name: containerID
type: ByteArray
- name: signature
type: Signature
- name: token
type: ByteArray
setEACL notification. This notification is produced when a container owner wants
to update an extended ACL of a container. Alphabet nodes of the Inner Ring catch
the notification and validate container ownership, signature and token if
present.
setEACL:
- name: eACL
type: ByteArray
- name: signature
type: Signature
- name: publicKey
type: PublicKey
- name: token
type: ByteArray
StartEstimation notification. This notification is produced when Storage nodes
should exchange estimation values of container sizes among other Storage nodes.
@ -23,22 +64,5 @@ it in Container contract.
StopEstimation:
- name: epoch
type: Integer
# Contract storage scheme
| Key | Value | Description |
|-----------------------------------------------------------------------------------------------------|
| `netmapScriptHash` | Hash160 | netmap contract hash |
| `balanceScriptHash` | Hash160 | balance contract hash |
| `identityScriptHash` | Hash160 | frostfsID contract hash |
| `nnsContractKey` | Hash160 | nns contract hash |
| `nnsRoot` | string | default value for domain zone |
| `cnr` + epoch + containerID + publicKeyHash[:10] | ByteArray | estimated container size |
| `est` + containerID + publicKeyHash | ByteArray | serialized epochs array |
| `o` + ownerID + containerID | ByteArray | container ID |
| `x` + containerID | ByteArray | serialized container struct |
| `nnsHasAlias` + containerID | string | domain name |
*/
package container

1
debian/control vendored
View file

@ -31,3 +31,4 @@ Description: FrostFS-Contract contains all FrostFS related contracts.
- nns
- proxy
- reputation
- subnet

View file

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

View file

@ -80,15 +80,5 @@ FrostFS network configuration value.
type: ByteArray
- name: value
type: ByteArray
# Contract storage scheme
| Key | Value | Description |
|-----------------------------------------------------------------------------|
| `processingScriptHash` | Hash160 | processing contract hash |
| `candidates` + candidateKey | ByteArray | it flags inner ring candidate |
| `config` + postfix | ByteArray | serialized config data |
*/
package frostfs

View file

@ -109,7 +109,8 @@ func Update(script []byte, manifest []byte, data interface{}) {
panic(common.ErrAlphabetWitnessFailed)
}
management.UpdateWithData(script, manifest, common.AppendVersion(data))
contract.Call(interop.Hash160(management.Hash), "update",
contract.All, script, manifest, common.AppendVersion(data))
runtime.Log("frostfs contract updated")
}

View file

@ -16,14 +16,5 @@ contract.
# Contract notifications
FrostFSID contract does not produce notifications to process.
# Contract storage scheme
| Key | Value | Description |
|-----------------------------|------------|----------------------------------|
| `processingScriptHash` | Hash160 | netmap contract hash |
| `containerScriptHash` | Hash160 | container contract hash |
| `o` + ownerID + publicKey | ByteArray | it flags owner's public key |
*/
package frostfsid

View file

@ -3,6 +3,7 @@ package frostfsid
import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
@ -61,7 +62,8 @@ func Update(script []byte, manifest []byte, data interface{}) {
panic("only committee can update contract")
}
management.UpdateWithData(script, manifest, common.AppendVersion(data))
contract.Call(interop.Hash160(management.Hash), "update",
contract.All, script, manifest, common.AppendVersion(data))
runtime.Log("frostfsid contract updated")
}

6
go.mod
View file

@ -4,7 +4,7 @@ go 1.14
require (
github.com/mr-tron/base58 v1.2.0
github.com/nspcc-dev/neo-go v0.101.5-0.20230808195420-5fc61be5f6c5
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20230808195420-5fc61be5f6c5
github.com/stretchr/testify v1.8.1
github.com/nspcc-dev/neo-go v0.99.4
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20220927123257-24c107e3a262
github.com/stretchr/testify v1.7.0
)

840
go.sum

File diff suppressed because it is too large Load diff

View file

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

View file

@ -29,18 +29,5 @@ in the network by invoking NewEpoch method.
NewEpoch
- name: epoch
type: Integer
# Contract storage scheme
| Key | Value | Description |
|-----------------------------|------------|-----------------------------------|
| `snapshotCount` | int | snapshot count |
| `snapshotEpoch` | int | snapshot epoch |
| `snapshotBlock` | int | snapshot block |
| `snapshot_` + snapshotNum | ByteArray | serialized '[]Node' array |
| `snapshotCurrent` | int | current snapshot |
| `balanceScriptHash` | Hash160 | balance contract hash |
| `containerScriptHash` | Hash160 | container contract hash |
*/
package netmap

View file

@ -127,7 +127,8 @@ func Update(script []byte, manifest []byte, data interface{}) {
panic("only committee can update contract")
}
management.UpdateWithData(script, manifest, common.AppendVersion(data))
contract.Call(interop.Hash160(management.Hash), "update",
contract.All, script, manifest, common.AppendVersion(data))
runtime.Log("netmap contract updated")
}

View file

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

View file

@ -1,20 +1,8 @@
name: "NameService"
supportedstandards: ["NEP-11"]
safemethods:
- "balanceOf"
- "decimals"
- "getAllRecords"
- "getPrice"
- "getRecord"
- "isAvailable"
- "ownerOf"
- "properties"
- "symbol"
- "totalSupply"
- "tokensOf"
- "tokens"
- "resolve"
- "roots"
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf",
"tokens", "properties", "roots", "getPrice", "isAvailable", "getRecord",
"resolve", "getAllRecords"]
events:
- name: Transfer
parameters:

View file

@ -82,7 +82,8 @@ func Update(nef []byte, manifest string, data interface{}) {
// std and crypto contracts. This can be helpful on update
// thus we provide `AllowCall` to management.Update.
// management.Update(nef, []byte(manifest))
management.UpdateWithData(nef, []byte(manifest), common.AppendVersion(data))
contract.Call(interop.Hash160(management.Hash), "update",
contract.All, nef, manifest, common.AppendVersion(data))
runtime.Log("nns contract updated")
}

View file

@ -18,12 +18,5 @@ execution.
# Contract notifications
Processing contract does not produce notifications to process.
# Contract storage scheme
| Key | Value | Description |
|-----------------------------|------------|----------------------------------|
| `frostfsScriptHash` | Hash160 | frostFS contract hash |
*/
package processing

View file

@ -59,7 +59,8 @@ func Update(script []byte, manifest []byte, data interface{}) {
panic("only side chain committee can update contract")
}
management.UpdateWithData(script, manifest, common.AppendVersion(data))
contract.Call(interop.Hash160(management.Hash), "update",
contract.All, script, manifest, common.AppendVersion(data))
runtime.Log("processing contract updated")
}

View file

@ -17,10 +17,5 @@ verified, Proxy contract pays for the execution.
# Contract notifications
Proxy contract does not produce notifications to process.
# Contract storage scheme
Proxy contract does not use storage
*/
package proxy

View file

@ -3,6 +3,7 @@ package proxy
import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/native/gas"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
"github.com/nspcc-dev/neo-go/pkg/interop/native/neo"
@ -34,7 +35,8 @@ func Update(script []byte, manifest []byte, data interface{}) {
panic("only committee can update contract")
}
management.UpdateWithData(script, manifest, common.AppendVersion(data))
contract.Call(interop.Hash160(management.Hash), "update",
contract.All, script, manifest, common.AppendVersion(data))
runtime.Log("proxy contract updated")
}

View file

@ -13,13 +13,5 @@ Inner Ring nodes if data audit succeeds.
# Contract notifications
Reputation contract does not produce notifications to process.
# Contract storage scheme
| Key | Value | Description |
|-----------------------------|------------|-----------------------------------|
| `c` + epoch + peerID | int | peer reputation count |
| `r` + count | ByteArray | serialized DataAuditResult struct |
*/
package reputation

View file

@ -2,6 +2,8 @@ package reputation
import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/convert"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
@ -34,7 +36,8 @@ func Update(script []byte, manifest []byte, data interface{}) {
panic("only committee can update contract")
}
management.UpdateWithData(script, manifest, common.AppendVersion(data))
contract.Call(interop.Hash160(management.Hash), "update",
contract.All, script, manifest, common.AppendVersion(data))
runtime.Log("reputation contract updated")
}

23
subnet/config.yml Normal file
View file

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

37
subnet/doc.go Normal file
View file

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

559
subnet/subnet_contract.go Normal file
View file

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

View file

@ -58,12 +58,8 @@ func newContainerInvoker(t *testing.T) (*neotest.ContractInvoker, *neotest.Contr
}
func setContainerOwner(c []byte, acc neotest.Signer) {
copy(c[6:], signerToOwner(acc))
}
func signerToOwner(acc neotest.Signer) []byte {
owner, _ := base58.Decode(address.Uint160ToString(acc.ScriptHash()))
return owner
copy(c[6:], owner)
}
type testContainer struct {
@ -109,15 +105,15 @@ func TestContainerCount(t *testing.T) {
c.Invoke(t, stackitem.Null{}, "put", cnt3.value, cnt3.sig, cnt3.pub, cnt3.token)
checkContainerList(t, c, [][]byte{cnt1.id[:], cnt2.id[:], cnt3.id[:]})
c.Invoke(t, stackitem.Null{}, "delete", cnt1.id[:], cnt1.sig, cnt1.pub, cnt1.token)
c.Invoke(t, stackitem.Null{}, "delete", cnt1.id[:], cnt1.sig, cnt1.token)
checkCount(t, 2)
checkContainerList(t, c, [][]byte{cnt2.id[:], cnt3.id[:]})
c.Invoke(t, stackitem.Null{}, "delete", cnt2.id[:], cnt2.sig, cnt2.pub, cnt2.token)
c.Invoke(t, stackitem.Null{}, "delete", cnt2.id[:], cnt2.sig, cnt2.token)
checkCount(t, 1)
checkContainerList(t, c, [][]byte{cnt3.id[:]})
c.Invoke(t, stackitem.Null{}, "delete", cnt3.id[:], cnt3.sig, cnt3.pub, cnt3.token)
c.Invoke(t, stackitem.Null{}, "delete", cnt3.id[:], cnt3.sig, cnt3.token)
checkCount(t, 0)
checkContainerList(t, c, [][]byte{})
}
@ -205,7 +201,7 @@ func TestContainerPut(t *testing.T) {
c.InvokeFail(t, "name is already taken", "putNamed", putArgs...)
})
c.Invoke(t, stackitem.Null{}, "delete", cnt.id[:], cnt.sig, cnt.pub, cnt.token)
c.Invoke(t, stackitem.Null{}, "delete", cnt.id[:], cnt.sig, cnt.token)
cNNS.Invoke(t, stackitem.Null{}, "resolve", "mycnt.frostfs", int64(nns.TXT))
t.Run("register in advance", func(t *testing.T) {
@ -243,43 +239,19 @@ func addContainer(t *testing.T, c, cBal *neotest.ContractInvoker) (neotest.Signe
}
func TestContainerDelete(t *testing.T) {
c, cBal, cNm := newContainerInvoker(t)
c, cBal, _ := newContainerInvoker(t)
acc, cnt := addContainer(t, c, cBal)
cAcc := c.WithSigners(acc)
cAcc.InvokeFail(t, common.ErrAlphabetWitnessFailed, "delete",
cnt.id[:], cnt.sig, cnt.pub, cnt.token)
cnt.id[:], cnt.sig, cnt.token)
newDelInfo := func(acc neotest.Signer, epoch int64) *stackitem.Struct {
return stackitem.NewStruct([]stackitem.Item{
stackitem.NewBuffer([]byte(signerToOwner(acc))),
stackitem.NewBigInteger(big.NewInt(epoch)),
})
}
c.InvokeFail(t, container.NotFoundError, "deletionInfo", cnt.id[:])
c.Invoke(t, stackitem.Null{}, "delete", cnt.id[:], cnt.sig, cnt.pub, cnt.token)
c.Invoke(t, newDelInfo(acc, 0), "deletionInfo", cnt.id[:])
t.Run("multi-epoch", func(t *testing.T) {
cNm.Invoke(t, stackitem.Null{}, "newEpoch", 1)
t.Run("epoch tick does not change deletion info", func(t *testing.T) {
c.Invoke(t, stackitem.Null{}, "delete", cnt.id[:], cnt.sig, cnt.pub, cnt.token)
c.Invoke(t, newDelInfo(acc, 0), "deletionInfo", cnt.id[:])
})
acc1, cnt1 := addContainer(t, c, cBal)
c.Invoke(t, stackitem.Null{}, "delete", cnt1.id[:], cnt1.sig, cnt1.pub, cnt1.token)
c.Invoke(t, newDelInfo(acc, 0), "deletionInfo", cnt.id[:])
c.Invoke(t, newDelInfo(acc1, 1), "deletionInfo", cnt1.id[:])
})
c.Invoke(t, stackitem.Null{}, "delete", cnt.id[:], cnt.sig, cnt.token)
t.Run("missing container", func(t *testing.T) {
id := cnt.id
id[0] ^= 0xFF
c.Invoke(t, stackitem.Null{}, "delete", cnt.id[:], cnt.sig, cnt.pub, cnt.token)
c.InvokeFail(t, container.NotFoundError, "deletionInfo", id[:])
c.Invoke(t, stackitem.Null{}, "delete", cnt.id[:], cnt.sig, cnt.token)
})
c.InvokeFail(t, container.NotFoundError, "get", cnt.id[:])
@ -296,7 +268,7 @@ func TestContainerOwner(t *testing.T) {
c.InvokeFail(t, container.NotFoundError, "owner", id[:])
})
owner := signerToOwner(acc)
owner, _ := base58.Decode(address.Uint160ToString(acc.ScriptHash()))
c.Invoke(t, stackitem.NewBuffer(owner), "owner", cnt.id[:])
}

View file

@ -7,7 +7,9 @@ import (
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-contract/container"
"github.com/mr-tron/base58"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
@ -55,7 +57,7 @@ func TestFrostFSID_AddKey(t *testing.T) {
pubs[i] = p.PublicKey().Bytes()
}
acc := e.NewAccount(t)
owner := signerToOwner(acc)
owner, _ := base58.Decode(address.Uint160ToString(acc.ScriptHash()))
e.Invoke(t, stackitem.Null{}, "addKey", owner,
[]interface{}{pubs[0], pubs[1]})

330
tests/subnet_test.go Normal file
View file

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