From 75bb382f7b826ba9048604bbfdc575cd1160797c Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Tue, 16 Nov 2021 17:09:21 +0300 Subject: [PATCH] [#101] neofsid: allow to have multiple `AddKey` per block Signed-off-by: Evgenii Stratonikov --- neofsid/neofsid_contract.go | 82 +++++++++++++------------- tests/neofsid_test.go | 113 ++++++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+), 43 deletions(-) create mode 100644 tests/neofsid_test.go diff --git a/neofsid/neofsid_contract.go b/neofsid/neofsid_contract.go index b007868..17cb595 100644 --- a/neofsid/neofsid_contract.go +++ b/neofsid/neofsid_contract.go @@ -3,6 +3,7 @@ package neofsid import ( "github.com/nspcc-dev/neo-go/pkg/interop" "github.com/nspcc-dev/neo-go/pkg/interop/contract" + "github.com/nspcc-dev/neo-go/pkg/interop/iterator" "github.com/nspcc-dev/neo-go/pkg/interop/native/crypto" "github.com/nspcc-dev/neo-go/pkg/interop/native/management" "github.com/nspcc-dev/neo-go/pkg/interop/native/std" @@ -21,10 +22,25 @@ const ( netmapContractKey = "netmapScriptHash" containerContractKey = "containerScriptHash" notaryDisabledKey = "notary" + ownerKeysPrefix = 'o' ) func _deploy(data interface{}, isUpdate bool) { + ctx := storage.GetContext() + if isUpdate { + it := storage.Find(ctx, []byte{}, storage.None) + for iterator.Next(it) { + kv := iterator.Value(it).([][]byte) + if len(kv[0]) == 25 { + info := std.Deserialize(kv[1]).(UserInfo) + key := append([]byte{ownerKeysPrefix}, kv[0]...) + for i := range info.Keys { + storage.Put(ctx, append(key, info.Keys[i]...), []byte{1}) + } + storage.Delete(ctx, kv[0]) + } + } return } @@ -33,8 +49,6 @@ func _deploy(data interface{}, isUpdate bool) { addrNetmap := args[1].(interop.Hash160) addrContainer := args[2].(interop.Hash160) - ctx := storage.GetContext() - if len(addrNetmap) != 20 || len(addrContainer) != 20 { panic("init: incorrect length of contract script hash") } @@ -102,23 +116,16 @@ func AddKey(owner []byte, keys []interop.PublicKey) { } } - info := getUserInfo(ctx, owner) - -addLoop: - for i := 0; i < len(keys); i++ { - pubKey := keys[i] - if len(pubKey) != 33 { + for i := range keys { + if len(keys[i]) != 33 { panic("addKey: incorrect public key") } + } - for j := range info.Keys { - key := info.Keys[j] - if common.BytesEqual(key, pubKey) { - continue addLoop - } - } - - info.Keys = append(info.Keys, pubKey) + ownerKey := append([]byte{ownerKeysPrefix}, owner...) + for i := range keys { + stKey := append(ownerKey, keys[i]...) + storage.Put(ctx, stKey, []byte{1}) } if notaryDisabled && !indirectCall { @@ -133,7 +140,6 @@ addLoop: common.RemoveVotes(ctx, id) } - common.SetSerialized(ctx, owner, info) runtime.Log("addKey: key bound to the owner") } @@ -168,28 +174,17 @@ func RemoveKey(owner []byte, keys []interop.PublicKey) { } } - info := getUserInfo(ctx, owner) - var leftKeys [][]byte - -rmLoop: - for i := range info.Keys { - key := info.Keys[i] - - for j := 0; j < len(keys); j++ { - pubKey := keys[j] - if len(pubKey) != 33 { - panic("removeKey: incorrect public key") - } - - if common.BytesEqual(key, pubKey) { - continue rmLoop - } + for i := range keys { + if len(keys[i]) != 33 { + panic("addKey: incorrect public key") } - - leftKeys = append(leftKeys, key) } - info.Keys = leftKeys + ownerKey := append([]byte{ownerKeysPrefix}, owner...) + for i := range keys { + stKey := append(ownerKey, keys[i]...) + storage.Delete(ctx, stKey) + } if notaryDisabled { threshold := len(alphabet)*2/3 + 1 @@ -202,8 +197,6 @@ rmLoop: common.RemoveVotes(ctx, id) } - - common.SetSerialized(ctx, owner, info) } // Key method returns list of 33-byte public keys bound with OwnerID. @@ -216,7 +209,8 @@ func Key(owner []byte) [][]byte { ctx := storage.GetReadOnlyContext() - info := getUserInfo(ctx, owner) + ownerKey := append([]byte{ownerKeysPrefix}, owner...) + info := getUserInfo(ctx, ownerKey) return info.Keys } @@ -227,12 +221,14 @@ func Version() int { } func getUserInfo(ctx storage.Context, key interface{}) UserInfo { - data := storage.Get(ctx, key) - if data != nil { - return std.Deserialize(data.([]byte)).(UserInfo) + it := storage.Find(ctx, key, storage.KeysOnly|storage.RemovePrefix) + pubs := [][]byte{} + for iterator.Next(it) { + pub := iterator.Value(it).([]byte) + pubs = append(pubs, pub) } - return UserInfo{Keys: [][]byte{}} + return UserInfo{Keys: pubs} } func invokeIDKeys(owner []byte, keys []interop.PublicKey, prefix []byte) []byte { diff --git a/tests/neofsid_test.go b/tests/neofsid_test.go new file mode 100644 index 0000000..6118b49 --- /dev/null +++ b/tests/neofsid_test.go @@ -0,0 +1,113 @@ +package tests + +import ( + "bytes" + "path" + "sort" + "testing" + + "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" + "github.com/nspcc-dev/neofs-contract/container" + "github.com/stretchr/testify/require" +) + +const neofsidPath = "../neofsid" + +func deployNeoFSIDContract(t *testing.T, e *neotest.Executor, addrNetmap, addrContainer util.Uint160) util.Uint160 { + args := make([]interface{}, 5) + args[0] = false + args[1] = addrNetmap + args[2] = addrContainer + + c := neotest.CompileFile(t, e.CommitteeHash, neofsidPath, path.Join(neofsidPath, "config.yml")) + e.DeployContract(t, c, args) + return c.Hash +} + +func newNeoFSIDInvoker(t *testing.T) *neotest.ContractInvoker { + e := newExecutor(t) + + ctrNNS := neotest.CompileFile(t, e.CommitteeHash, nnsPath, path.Join(nnsPath, "config.yml")) + ctrNetmap := neotest.CompileFile(t, e.CommitteeHash, netmapPath, path.Join(netmapPath, "config.yml")) + ctrBalance := neotest.CompileFile(t, e.CommitteeHash, balancePath, path.Join(balancePath, "config.yml")) + ctrContainer := neotest.CompileFile(t, e.CommitteeHash, containerPath, path.Join(containerPath, "config.yml")) + + e.DeployContract(t, ctrNNS, nil) + deployNetmapContract(t, e, ctrBalance.Hash, ctrContainer.Hash, + container.RegistrationFeeKey, int64(containerFee), + container.AliasFeeKey, int64(containerAliasFee)) + deployBalanceContract(t, e, ctrNetmap.Hash, ctrContainer.Hash) + deployContainerContract(t, e, ctrNetmap.Hash, ctrBalance.Hash, ctrNNS.Hash) + h := deployNeoFSIDContract(t, e, ctrNetmap.Hash, ctrContainer.Hash) + return e.CommitteeInvoker(h) +} + +func TestNeoFSID_AddKey(t *testing.T) { + e := newNeoFSIDInvoker(t) + + pubs := make([][]byte, 6) + for i := range pubs { + p, err := keys.NewPrivateKey() + require.NoError(t, err) + pubs[i] = p.PublicKey().Bytes() + } + acc := e.NewAccount(t) + owner, _ := base58.Decode(address.Uint160ToString(acc.ScriptHash())) + e.Invoke(t, stackitem.Null{}, "addKey", owner, + []interface{}{pubs[0], pubs[1]}) + + sort.Slice(pubs[:2], func(i, j int) bool { + return bytes.Compare(pubs[i], pubs[j]) == -1 + }) + arr := []stackitem.Item{ + stackitem.NewBuffer(pubs[0]), + stackitem.NewBuffer(pubs[1]), + } + e.Invoke(t, stackitem.NewArray(arr), "key", owner) + + t.Run("multiple addKey per block", func(t *testing.T) { + tx1 := e.PrepareInvoke(t, "addKey", owner, []interface{}{pubs[2]}) + tx2 := e.PrepareInvoke(t, "addKey", owner, []interface{}{pubs[3], pubs[4]}) + e.AddNewBlock(t, tx1, tx2) + e.CheckHalt(t, tx1.Hash(), stackitem.Null{}) + e.CheckHalt(t, tx2.Hash(), stackitem.Null{}) + + sort.Slice(pubs[:5], func(i, j int) bool { + return bytes.Compare(pubs[i], pubs[j]) == -1 + }) + arr = []stackitem.Item{ + stackitem.NewBuffer(pubs[0]), + stackitem.NewBuffer(pubs[1]), + stackitem.NewBuffer(pubs[2]), + stackitem.NewBuffer(pubs[3]), + stackitem.NewBuffer(pubs[4]), + } + e.Invoke(t, stackitem.NewArray(arr), "key", owner) + }) + + e.Invoke(t, stackitem.Null{}, "removeKey", owner, + []interface{}{pubs[1], pubs[5]}) + arr = []stackitem.Item{ + stackitem.NewBuffer(pubs[0]), + stackitem.NewBuffer(pubs[2]), + stackitem.NewBuffer(pubs[3]), + stackitem.NewBuffer(pubs[4]), + } + e.Invoke(t, stackitem.NewArray(arr), "key", owner) + + t.Run("multiple removeKey per block", func(t *testing.T) { + tx1 := e.PrepareInvoke(t, "removeKey", owner, []interface{}{pubs[2]}) + tx2 := e.PrepareInvoke(t, "removeKey", owner, []interface{}{pubs[0], pubs[4]}) + e.AddNewBlock(t, tx1, tx2) + e.CheckHalt(t, tx1.Hash(), stackitem.Null{}) + e.CheckHalt(t, tx2.Hash(), stackitem.Null{}) + + arr = []stackitem.Item{stackitem.NewBuffer(pubs[3])} + e.Invoke(t, stackitem.NewArray(arr), "key", owner) + }) +}