[#101] neofsid: allow to have multiple AddKey per block

Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
Evgenii Stratonikov 2021-11-16 17:09:21 +03:00 committed by Alex Vanin
parent c4212e7d2f
commit 75bb382f7b
2 changed files with 152 additions and 43 deletions

View file

@ -3,6 +3,7 @@ package neofsid
import ( import (
"github.com/nspcc-dev/neo-go/pkg/interop" "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/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/crypto"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management" "github.com/nspcc-dev/neo-go/pkg/interop/native/management"
"github.com/nspcc-dev/neo-go/pkg/interop/native/std" "github.com/nspcc-dev/neo-go/pkg/interop/native/std"
@ -21,10 +22,25 @@ const (
netmapContractKey = "netmapScriptHash" netmapContractKey = "netmapScriptHash"
containerContractKey = "containerScriptHash" containerContractKey = "containerScriptHash"
notaryDisabledKey = "notary" notaryDisabledKey = "notary"
ownerKeysPrefix = 'o'
) )
func _deploy(data interface{}, isUpdate bool) { func _deploy(data interface{}, isUpdate bool) {
ctx := storage.GetContext()
if isUpdate { 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 return
} }
@ -33,8 +49,6 @@ func _deploy(data interface{}, isUpdate bool) {
addrNetmap := args[1].(interop.Hash160) addrNetmap := args[1].(interop.Hash160)
addrContainer := args[2].(interop.Hash160) addrContainer := args[2].(interop.Hash160)
ctx := storage.GetContext()
if len(addrNetmap) != 20 || len(addrContainer) != 20 { if len(addrNetmap) != 20 || len(addrContainer) != 20 {
panic("init: incorrect length of contract script hash") panic("init: incorrect length of contract script hash")
} }
@ -102,23 +116,16 @@ func AddKey(owner []byte, keys []interop.PublicKey) {
} }
} }
info := getUserInfo(ctx, owner) for i := range keys {
if len(keys[i]) != 33 {
addLoop:
for i := 0; i < len(keys); i++ {
pubKey := keys[i]
if len(pubKey) != 33 {
panic("addKey: incorrect public key") 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 { if notaryDisabled && !indirectCall {
@ -133,7 +140,6 @@ addLoop:
common.RemoveVotes(ctx, id) common.RemoveVotes(ctx, id)
} }
common.SetSerialized(ctx, owner, info)
runtime.Log("addKey: key bound to the owner") runtime.Log("addKey: key bound to the owner")
} }
@ -168,29 +174,18 @@ func RemoveKey(owner []byte, keys []interop.PublicKey) {
} }
} }
info := getUserInfo(ctx, owner) for i := range keys {
var leftKeys [][]byte if len(keys[i]) != 33 {
panic("addKey: incorrect public key")
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
} }
} }
leftKeys = append(leftKeys, key) ownerKey := append([]byte{ownerKeysPrefix}, owner...)
for i := range keys {
stKey := append(ownerKey, keys[i]...)
storage.Delete(ctx, stKey)
} }
info.Keys = leftKeys
if notaryDisabled { if notaryDisabled {
threshold := len(alphabet)*2/3 + 1 threshold := len(alphabet)*2/3 + 1
id := invokeIDKeys(owner, keys, []byte("remove")) id := invokeIDKeys(owner, keys, []byte("remove"))
@ -202,8 +197,6 @@ rmLoop:
common.RemoveVotes(ctx, id) common.RemoveVotes(ctx, id)
} }
common.SetSerialized(ctx, owner, info)
} }
// Key method returns list of 33-byte public keys bound with OwnerID. // Key method returns list of 33-byte public keys bound with OwnerID.
@ -216,7 +209,8 @@ func Key(owner []byte) [][]byte {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
info := getUserInfo(ctx, owner) ownerKey := append([]byte{ownerKeysPrefix}, owner...)
info := getUserInfo(ctx, ownerKey)
return info.Keys return info.Keys
} }
@ -227,12 +221,14 @@ func Version() int {
} }
func getUserInfo(ctx storage.Context, key interface{}) UserInfo { func getUserInfo(ctx storage.Context, key interface{}) UserInfo {
data := storage.Get(ctx, key) it := storage.Find(ctx, key, storage.KeysOnly|storage.RemovePrefix)
if data != nil { pubs := [][]byte{}
return std.Deserialize(data.([]byte)).(UserInfo) 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 { func invokeIDKeys(owner []byte, keys []interop.PublicKey, prefix []byte) []byte {

113
tests/neofsid_test.go Normal file
View file

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