forked from TrueCloudLab/frostfs-contract
[#101] neofsid: allow to have multiple AddKey
per block
Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
parent
c4212e7d2f
commit
75bb382f7b
2 changed files with 152 additions and 43 deletions
|
@ -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
113
tests/neofsid_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in a new issue