forked from TrueCloudLab/frostfs-contract
[#101] neofs: allow to register multiple candidates per block
Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
parent
ec95997f41
commit
b2559857f6
2 changed files with 166 additions and 37 deletions
|
@ -23,7 +23,8 @@ type (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
candidateFeeConfigKey = "InnerRingCandidateFee"
|
// CandidateFeeConfigKey contains fee for a candidate registration.
|
||||||
|
CandidateFeeConfigKey = "InnerRingCandidateFee"
|
||||||
withdrawFeeConfigKey = "WithdrawFee"
|
withdrawFeeConfigKey = "WithdrawFee"
|
||||||
|
|
||||||
alphabetKey = "alphabet"
|
alphabetKey = "alphabet"
|
||||||
|
@ -48,6 +49,14 @@ var (
|
||||||
// _deploy sets up initial alphabet node keys.
|
// _deploy sets up initial alphabet node keys.
|
||||||
func _deploy(data interface{}, isUpdate bool) {
|
func _deploy(data interface{}, isUpdate bool) {
|
||||||
if isUpdate {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +88,6 @@ func _deploy(data interface{}, isUpdate bool) {
|
||||||
|
|
||||||
// initialize all storage slices
|
// initialize all storage slices
|
||||||
common.SetSerialized(ctx, alphabetKey, irList)
|
common.SetSerialized(ctx, alphabetKey, irList)
|
||||||
common.SetSerialized(ctx, candidatesKey, []common.IRNode{})
|
|
||||||
|
|
||||||
storage.Put(ctx, processingContractKey, addrProc)
|
storage.Put(ctx, processingContractKey, addrProc)
|
||||||
|
|
||||||
|
@ -137,7 +145,14 @@ func AlphabetAddress() interop.Hash160 {
|
||||||
// candidate node key.
|
// candidate node key.
|
||||||
func InnerRingCandidates() []common.IRNode {
|
func InnerRingCandidates() []common.IRNode {
|
||||||
ctx := storage.GetReadOnlyContext()
|
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.
|
// 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 {
|
if notaryDisabled && !keyOwner {
|
||||||
threshold := len(alphabet)*2/3 + 1
|
threshold := len(alphabet)*2/3 + 1
|
||||||
id := append(key, []byte("delete")...)
|
id := append(key, []byte("delete")...)
|
||||||
|
@ -195,7 +198,12 @@ func InnerRingCandidateRemove(key interop.PublicKey) {
|
||||||
common.RemoveVotes(ctx, hashID)
|
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.
|
// InnerRingCandidateAdd adds key to the list of Inner Ring candidates.
|
||||||
|
@ -208,25 +216,22 @@ func InnerRingCandidateAdd(key interop.PublicKey) {
|
||||||
|
|
||||||
common.CheckWitness(key)
|
common.CheckWitness(key)
|
||||||
|
|
||||||
c := common.IRNode{PublicKey: key}
|
stKey := append([]byte(candidatesKey), key...)
|
||||||
candidates := getNodes(ctx, candidatesKey)
|
if storage.Get(ctx, stKey) != nil {
|
||||||
|
|
||||||
list, ok := addNode(candidates, c)
|
|
||||||
if !ok {
|
|
||||||
panic("candidate already in the list")
|
panic("candidate already in the list")
|
||||||
}
|
}
|
||||||
|
|
||||||
from := contract.CreateStandardAccount(key)
|
from := contract.CreateStandardAccount(key)
|
||||||
to := runtime.GetExecutingScriptHash()
|
to := runtime.GetExecutingScriptHash()
|
||||||
fee := getConfig(ctx, candidateFeeConfigKey).(int)
|
fee := getConfig(ctx, CandidateFeeConfigKey).(int)
|
||||||
|
|
||||||
transferred := gas.Transfer(from, to, fee, []byte(ignoreDepositNotification))
|
transferred := gas.Transfer(from, to, fee, []byte(ignoreDepositNotification))
|
||||||
if !transferred {
|
if !transferred {
|
||||||
panic("failed to transfer funds, aborting")
|
panic("failed to transfer funds, aborting")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
storage.Put(ctx, stKey, []byte{1})
|
||||||
runtime.Log("candidate has been added")
|
runtime.Log("candidate has been added")
|
||||||
common.SetSerialized(ctx, candidatesKey, list)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnNEP17Payment is a callback for NEP-17 compatible native GAS contract.
|
// 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)
|
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
|
// multiaddress returns multi signature address from list of IRNode structures
|
||||||
// with m = 2/3n+1.
|
// with m = 2/3n+1.
|
||||||
func multiaddress(n []common.IRNode) []byte {
|
func multiaddress(n []common.IRNode) []byte {
|
||||||
|
|
138
tests/neofs_test.go
Normal file
138
tests/neofs_test.go
Normal file
|
@ -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")
|
||||||
|
}
|
Loading…
Reference in a new issue