From b2559857f6fc4a86f05d6dfb3008d236b8c2db1e Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Tue, 16 Nov 2021 13:03:51 +0300 Subject: [PATCH] [#101] neofs: allow to register multiple candidates per block Signed-off-by: Evgenii Stratonikov --- neofs/neofs_contract.go | 65 ++++++++----------- tests/neofs_test.go | 138 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+), 37 deletions(-) create mode 100644 tests/neofs_test.go diff --git a/neofs/neofs_contract.go b/neofs/neofs_contract.go index a46583a..dd306c9 100644 --- a/neofs/neofs_contract.go +++ b/neofs/neofs_contract.go @@ -23,7 +23,8 @@ type ( ) const ( - candidateFeeConfigKey = "InnerRingCandidateFee" + // CandidateFeeConfigKey contains fee for a candidate registration. + CandidateFeeConfigKey = "InnerRingCandidateFee" withdrawFeeConfigKey = "WithdrawFee" alphabetKey = "alphabet" @@ -48,6 +49,14 @@ var ( // _deploy sets up initial alphabet node keys. func _deploy(data interface{}, isUpdate bool) { if isUpdate { + ctx := storage.GetContext() + nodes := getNodes(ctx, candidatesKey) + storage.Delete(ctx, candidatesKey) + + for i := range nodes { + key := append([]byte(candidatesKey), nodes[i].PublicKey...) + storage.Put(ctx, key, []byte{1}) + } return } @@ -79,7 +88,6 @@ func _deploy(data interface{}, isUpdate bool) { // initialize all storage slices common.SetSerialized(ctx, alphabetKey, irList) - common.SetSerialized(ctx, candidatesKey, []common.IRNode{}) storage.Put(ctx, processingContractKey, addrProc) @@ -137,7 +145,14 @@ func AlphabetAddress() interop.Hash160 { // candidate node key. func InnerRingCandidates() []common.IRNode { ctx := storage.GetReadOnlyContext() - return getNodes(ctx, candidatesKey) + nodes := []common.IRNode{} + + it := storage.Find(ctx, candidatesKey, storage.KeysOnly|storage.RemovePrefix) + for iterator.Next(it) { + pub := iterator.Value(it).([]byte) + nodes = append(nodes, common.IRNode{PublicKey: pub}) + } + return nodes } // InnerRingCandidateRemove removes key from the list of Inner Ring candidates. @@ -170,18 +185,6 @@ func InnerRingCandidateRemove(key interop.PublicKey) { } } - nodes := []common.IRNode{} // it is explicit declaration of empty slice, not nil - candidates := getNodes(ctx, candidatesKey) - - for i := range candidates { - c := candidates[i] - if !common.BytesEqual(c.PublicKey, key) { - nodes = append(nodes, c) - } else { - runtime.Log("candidate has been removed") - } - } - if notaryDisabled && !keyOwner { threshold := len(alphabet)*2/3 + 1 id := append(key, []byte("delete")...) @@ -195,7 +198,12 @@ func InnerRingCandidateRemove(key interop.PublicKey) { common.RemoveVotes(ctx, hashID) } - common.SetSerialized(ctx, candidatesKey, nodes) + prefix := []byte(candidatesKey) + stKey := append(prefix, key...) + if storage.Get(ctx, stKey) != nil { + storage.Delete(ctx, stKey) + runtime.Log("candidate has been removed") + } } // InnerRingCandidateAdd adds key to the list of Inner Ring candidates. @@ -208,25 +216,22 @@ func InnerRingCandidateAdd(key interop.PublicKey) { common.CheckWitness(key) - c := common.IRNode{PublicKey: key} - candidates := getNodes(ctx, candidatesKey) - - list, ok := addNode(candidates, c) - if !ok { + stKey := append([]byte(candidatesKey), key...) + if storage.Get(ctx, stKey) != nil { panic("candidate already in the list") } from := contract.CreateStandardAccount(key) to := runtime.GetExecutingScriptHash() - fee := getConfig(ctx, candidateFeeConfigKey).(int) + fee := getConfig(ctx, CandidateFeeConfigKey).(int) transferred := gas.Transfer(from, to, fee, []byte(ignoreDepositNotification)) if !transferred { panic("failed to transfer funds, aborting") } + storage.Put(ctx, stKey, []byte{1}) runtime.Log("candidate has been added") - common.SetSerialized(ctx, candidatesKey, list) } // OnNEP17Payment is a callback for NEP-17 compatible native GAS contract. @@ -559,20 +564,6 @@ func setConfig(ctx storage.Context, key, val interface{}) { storage.Put(ctx, storageKey, val) } -// addNode returns slice of nodes with appended node 'n' and bool flag -// that set to false if node 'n' is already presented in the slice 'lst'. -func addNode(lst []common.IRNode, n common.IRNode) ([]common.IRNode, bool) { - for i := 0; i < len(lst); i++ { - if common.BytesEqual(n.PublicKey, lst[i].PublicKey) { - return nil, false - } - } - - lst = append(lst, n) - - return lst, true -} - // multiaddress returns multi signature address from list of IRNode structures // with m = 2/3n+1. func multiaddress(n []common.IRNode) []byte { diff --git a/tests/neofs_test.go b/tests/neofs_test.go new file mode 100644 index 0000000..9a063bb --- /dev/null +++ b/tests/neofs_test.go @@ -0,0 +1,138 @@ +package tests + +import ( + "bytes" + "path" + "sort" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/neotest" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "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/neo-go/pkg/wallet" + "github.com/nspcc-dev/neofs-contract/neofs" + "github.com/stretchr/testify/require" +) + +const neofsPath = "../neofs" + +func deployNeoFSContract(t *testing.T, e *neotest.Executor, addrProc util.Uint160, + pubs keys.PublicKeys, config ...interface{}) util.Uint160 { + args := make([]interface{}, 5) + args[0] = false + args[1] = addrProc + + arr := make([]interface{}, len(pubs)) + for i := range pubs { + arr[i] = pubs[i].Bytes() + } + args[2] = arr + args[3] = append([]interface{}{}, config...) + + c := neotest.CompileFile(t, e.CommitteeHash, neofsPath, path.Join(neofsPath, "config.yml")) + e.DeployContract(t, c, args) + return c.Hash +} + +func newNeoFSInvoker(t *testing.T, n int, config ...interface{}) (*neotest.ContractInvoker, keys.PublicKeys) { + e := newExecutor(t) + + accounts := make([]*wallet.Account, n) + for i := 0; i < n; i++ { + acc, err := wallet.NewAccount() + require.NoError(t, err) + + accounts[i] = acc + } + + sort.Slice(accounts, func(i, j int) bool { + p1 := accounts[i].PrivateKey().PublicKey() + p2 := accounts[j].PrivateKey().PublicKey() + return p1.Cmp(p2) == -1 + }) + + pubs := make(keys.PublicKeys, n) + for i := range accounts { + pubs[i] = accounts[i].PrivateKey().PublicKey() + } + + m := smartcontract.GetMajorityHonestNodeCount(len(accounts)) + for i := range accounts { + require.NoError(t, accounts[i].ConvertMultisig(m, pubs.Copy())) + } + + alphabet := neotest.NewMultiSigner(accounts...) + h := deployNeoFSContract(t, e, util.Uint160{}, pubs, config...) + + gasHash, err := e.Chain.GetNativeContractScriptHash(nativenames.Gas) + require.NoError(t, err) + + vc := e.CommitteeInvoker(gasHash).WithSigners(e.Validator) + vc.Invoke(t, true, "transfer", + e.Validator.ScriptHash(), alphabet.ScriptHash(), + int64(10_0000_0000), nil) + + return e.CommitteeInvoker(h).WithSigners(alphabet), pubs +} + +func TestNeoFS_AlphabetList(t *testing.T) { + const alphabetSize = 4 + + e, pubs := newNeoFSInvoker(t, alphabetSize) + arr := make([]stackitem.Item, len(pubs)) + for i := range arr { + arr[i] = stackitem.NewStruct([]stackitem.Item{ + stackitem.NewByteArray(pubs[i].Bytes()), + }) + } + + e.Invoke(t, stackitem.NewArray(arr), "alphabetList") +} + +func TestNeoFS_InnerRingCandidate(t *testing.T) { + e, _ := newNeoFSInvoker(t, 4, neofs.CandidateFeeConfigKey, int64(10)) + + const candidateCount = 3 + + accs := make([]neotest.Signer, candidateCount) + for i := range accs { + accs[i] = e.NewAccount(t) + } + + arr := make([]stackitem.Item, candidateCount) + pubs := make([][]byte, candidateCount) + sort.Slice(accs, func(i, j int) bool { + s1 := accs[i].Script() + s2 := accs[j].Script() + return bytes.Compare(s1, s2) == -1 + }) + + for i, acc := range accs { + cAcc := e.WithSigners(acc) + pub, ok := vm.ParseSignatureContract(acc.Script()) + require.True(t, ok) + cAcc.Invoke(t, stackitem.Null{}, "innerRingCandidateAdd", pub) + cAcc.InvokeFail(t, "candidate already in the list", "innerRingCandidateAdd", pub) + + pubs[i] = pub + arr[i] = stackitem.NewStruct([]stackitem.Item{stackitem.NewBuffer(pub)}) + } + + e.Invoke(t, stackitem.NewArray(arr), "innerRingCandidates") + + cAcc := e.WithSigners(accs[1]) + cAcc.Invoke(t, stackitem.Null{}, "innerRingCandidateRemove", pubs[1]) + e.Invoke(t, stackitem.NewArray([]stackitem.Item{arr[0], arr[2]}), "innerRingCandidates") + + cAcc = e.WithSigners(accs[2]) + cAcc.Invoke(t, stackitem.Null{}, "innerRingCandidateRemove", pubs[2]) + e.Invoke(t, stackitem.NewArray([]stackitem.Item{arr[0]}), "innerRingCandidates") + + cAcc = e.WithSigners(accs[0]) + cAcc.Invoke(t, stackitem.Null{}, "innerRingCandidateRemove", pubs[0]) + e.Invoke(t, stackitem.NewArray([]stackitem.Item{}), "innerRingCandidates") +}