From ed6f90c1806ca1a7a0ee9a306a31fd65b73212cd Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Thu, 18 Nov 2021 15:21:14 +0300 Subject: [PATCH] [#122] subnet: implement `put` method Signed-off-by: Evgenii Stratonikov --- subnet/config.yml | 10 +++++- subnet/subnet_contract.go | 73 +++++++++++++++++++++++++++++++++++++++ tests/subnet_test.go | 31 ++++++++++++++++- 3 files changed, 112 insertions(+), 2 deletions(-) diff --git a/subnet/config.yml b/subnet/config.yml index 5d978d1..db13b90 100644 --- a/subnet/config.yml +++ b/subnet/config.yml @@ -1,4 +1,12 @@ name: "NeoFS Subnet" safemethods: ["version"] permissions: - - methods: ["update"] \ No newline at end of file + - methods: ["update"] +events: + - name: SubnetPut + parameters: + - name: ownerKey + type: PublicKey + - name: info + type: ByteArray + diff --git a/subnet/subnet_contract.go b/subnet/subnet_contract.go index 0cc9221..5d5224b 100644 --- a/subnet/subnet_contract.go +++ b/subnet/subnet_contract.go @@ -10,6 +10,17 @@ import ( ) const ( + // ErrInvalidSubnetID is thrown when subnet id is not a slice of 4 bytes. + ErrInvalidSubnetID = "invalid subnet ID" + // ErrInvalidOwner is thrown when owner has invalid format. + ErrInvalidOwner = "invalid owner" + // 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" + + ownerPrefix = 'o' + infoPrefix = 'i' notaryDisabledKey = 'z' ) @@ -27,6 +38,68 @@ func _deploy(data interface{}, isUpdate bool) { storage.Put(ctx, []byte{notaryDisabledKey}, args.notaryDisabled) } +// Put creates new subnet with the specified owner and info. +func Put(id []byte, ownerKey interop.PublicKey, info []byte) { + if len(id) != 4 { + panic("put: " + ErrInvalidSubnetID) + } + if len(ownerKey) != interop.PublicKeyCompressedLen { + panic("put: " + ErrInvalidOwner) + } + + ctx := storage.GetContext() + stKey := append([]byte{ownerPrefix}, id...) + if storage.Get(ctx, stKey) != nil { + panic("put: " + ErrAlreadyExists) + } + + notaryDisabled := storage.Get(ctx, notaryDisabledKey).(bool) + if notaryDisabled { + alphabet := common.AlphabetNodes() + nodeKey := common.InnerRingInvoker(alphabet) + if len(nodeKey) == 0 { + if !runtime.CheckWitness(ownerKey) { + panic("put: witness check failed") + } + runtime.Notify("SubnetPut", ownerKey, info) + return + } + + threshold := len(alphabet)*2/3 + 1 + id := common.InvokeID([]interface{}{ownerKey, info}, []byte("put")) + n := common.Vote(ctx, id, nodeKey) + if n < threshold { + return + } + + common.RemoveVotes(ctx, id) + } else { + if !runtime.CheckWitness(ownerKey) { + panic("put: owner witness check failed") + } + + multiaddr := common.AlphabetAddress() + if !runtime.CheckWitness(multiaddr) { + panic("put: alphabet witness check failed") + } + } + + storage.Put(ctx, stKey, ownerKey) + stKey[0] = infoPrefix + storage.Put(ctx, stKey, info) +} + +// Get returns info about subnet with the specified id. +func Get(id []byte) []byte { + ctx := storage.GetContext() + key := append([]byte{infoPrefix}, id...) + raw := storage.Get(ctx, key) + if raw == nil { + panic("get: " + ErrNotExist) + } + return raw.([]byte) +} + // Update method updates contract source code and manifest. Can be invoked // only by committee. func Update(script []byte, manifest []byte, data interface{}) { diff --git a/tests/subnet_test.go b/tests/subnet_test.go index b5ed73b..2862db0 100644 --- a/tests/subnet_test.go +++ b/tests/subnet_test.go @@ -1,19 +1,24 @@ package tests import ( + "encoding/binary" "path" "testing" "github.com/nspcc-dev/neo-go/pkg/neotest" "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neofs-contract/common" + "github.com/nspcc-dev/neofs-contract/subnet" + "github.com/stretchr/testify/require" ) const subnetPath = "../subnet" func deploySubnetContract(t *testing.T, e *neotest.Executor) util.Uint160 { c := neotest.CompileFile(t, e.CommitteeHash, subnetPath, path.Join(subnetPath, "config.yml")) - args := []interface{}{true} + args := []interface{}{false} e.DeployContract(t, c, args) return c.Hash } @@ -28,3 +33,27 @@ 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, 4) + binary.LittleEndian.PutUint32(id, 123) + info := randomBytes(10) + + e.InvokeFail(t, "witness check failed", "put", id, pub, info) + + cAcc := e.WithSigners(acc) + cAcc.InvokeFail(t, "alphabet witness check failed", "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) +}