core: refactor (*NEO).GetCandidates and add (*NEO).GetAllCandidates
This commit is contained in:
parent
75d06d18c9
commit
2a8ffd9318
2 changed files with 156 additions and 5 deletions
|
@ -1,6 +1,7 @@
|
||||||
package native
|
package native
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -13,6 +14,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
|
||||||
|
istorage "github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||||
|
@ -91,6 +93,10 @@ const (
|
||||||
committeeRewardRatio = 10
|
committeeRewardRatio = 10
|
||||||
// neoHolderRewardRatio is a percent of generated GAS that is distributed to voters.
|
// neoHolderRewardRatio is a percent of generated GAS that is distributed to voters.
|
||||||
voterRewardRatio = 80
|
voterRewardRatio = 80
|
||||||
|
|
||||||
|
// maxGetCandidatesRespLen is the maximum number of candidates to return from the
|
||||||
|
// getCandidates method.
|
||||||
|
maxGetCandidatesRespLen = 256
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -194,6 +200,15 @@ func newNEO(cfg config.ProtocolConfiguration) *NEO {
|
||||||
md = newMethodAndPrice(n.getCandidatesCall, 1<<22, callflag.ReadStates)
|
md = newMethodAndPrice(n.getCandidatesCall, 1<<22, callflag.ReadStates)
|
||||||
n.AddMethod(md, desc)
|
n.AddMethod(md, desc)
|
||||||
|
|
||||||
|
desc = newDescriptor("getAllCandidates", smartcontract.InteropInterfaceType)
|
||||||
|
md = newMethodAndPrice(n.getAllCandidatesCall, 1<<22, callflag.ReadStates)
|
||||||
|
n.AddMethod(md, desc)
|
||||||
|
|
||||||
|
desc = newDescriptor("getCandidateVote", smartcontract.IntegerType,
|
||||||
|
manifest.NewParameter("pubkey", smartcontract.PublicKeyType))
|
||||||
|
md = newMethodAndPrice(n.getCandidateVoteCall, 1<<15, callflag.ReadStates)
|
||||||
|
n.AddMethod(md, desc)
|
||||||
|
|
||||||
desc = newDescriptor("getAccountState", smartcontract.ArrayType,
|
desc = newDescriptor("getAccountState", smartcontract.ArrayType,
|
||||||
manifest.NewParameter("account", smartcontract.Hash160Type))
|
manifest.NewParameter("account", smartcontract.Hash160Type))
|
||||||
md = newMethodAndPrice(n.getAccountState, 1<<15, callflag.ReadStates)
|
md = newMethodAndPrice(n.getAccountState, 1<<15, callflag.ReadStates)
|
||||||
|
@ -851,7 +866,7 @@ func (n *NEO) ModifyAccountVotes(acc *state.NEOBalance, d *dao.Simple, value *bi
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NEO) getCandidates(d *dao.Simple, sortByKey bool) ([]keyWithVotes, error) {
|
func (n *NEO) getCandidates(d *dao.Simple, sortByKey bool, max int) ([]keyWithVotes, error) {
|
||||||
arr := make([]keyWithVotes, 0)
|
arr := make([]keyWithVotes, 0)
|
||||||
buf := io.NewBufBinWriter()
|
buf := io.NewBufBinWriter()
|
||||||
d.Seek(n.ID, storage.SeekRange{Prefix: []byte{prefixCandidate}}, func(k, v []byte) bool {
|
d.Seek(n.ID, storage.SeekRange{Prefix: []byte{prefixCandidate}}, func(k, v []byte) bool {
|
||||||
|
@ -861,7 +876,7 @@ func (n *NEO) getCandidates(d *dao.Simple, sortByKey bool) ([]keyWithVotes, erro
|
||||||
arr = append(arr, keyWithVotes{Key: string(k), Votes: &c.Votes})
|
arr = append(arr, keyWithVotes{Key: string(k), Votes: &c.Votes})
|
||||||
}
|
}
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
return true
|
return !sortByKey || max > 0 && len(arr) < max
|
||||||
})
|
})
|
||||||
|
|
||||||
if !sortByKey {
|
if !sortByKey {
|
||||||
|
@ -893,7 +908,7 @@ func (n *NEO) getCandidates(d *dao.Simple, sortByKey bool) ([]keyWithVotes, erro
|
||||||
// GetCandidates returns current registered validators list with keys
|
// GetCandidates returns current registered validators list with keys
|
||||||
// and votes.
|
// and votes.
|
||||||
func (n *NEO) GetCandidates(d *dao.Simple) ([]state.Validator, error) {
|
func (n *NEO) GetCandidates(d *dao.Simple) ([]state.Validator, error) {
|
||||||
kvs, err := n.getCandidates(d, true)
|
kvs, err := n.getCandidates(d, true, maxGetCandidatesRespLen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -909,7 +924,7 @@ func (n *NEO) GetCandidates(d *dao.Simple) ([]state.Validator, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NEO) getCandidatesCall(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
func (n *NEO) getCandidatesCall(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||||
validators, err := n.getCandidates(ic.DAO, true)
|
validators, err := n.getCandidates(ic.DAO, true, maxGetCandidatesRespLen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -923,6 +938,51 @@ func (n *NEO) getCandidatesCall(ic *interop.Context, _ []stackitem.Item) stackit
|
||||||
return stackitem.NewArray(arr)
|
return stackitem.NewArray(arr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *NEO) getAllCandidatesCall(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
prefix := []byte{prefixCandidate}
|
||||||
|
buf := io.NewBufBinWriter()
|
||||||
|
keep := func(kv storage.KeyValue) bool {
|
||||||
|
c := new(candidate).FromBytes(kv.Value)
|
||||||
|
emit.CheckSig(buf.BinWriter, kv.Key)
|
||||||
|
if c.Registered && !n.Policy.IsBlocked(ic.DAO, hash.Hash160(buf.Bytes())) {
|
||||||
|
buf.Reset()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
buf.Reset()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
seekres := ic.DAO.SeekAsync(ctx, n.ID, storage.SeekRange{Prefix: prefix})
|
||||||
|
filteredRes := make(chan storage.KeyValue)
|
||||||
|
go func() {
|
||||||
|
for kv := range seekres {
|
||||||
|
if keep(kv) {
|
||||||
|
filteredRes <- kv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(filteredRes)
|
||||||
|
}()
|
||||||
|
|
||||||
|
opts := istorage.FindRemovePrefix | istorage.FindDeserialize | istorage.FindPick1
|
||||||
|
item := istorage.NewIterator(filteredRes, prefix, int64(opts))
|
||||||
|
ic.RegisterCancelFunc(cancel)
|
||||||
|
return stackitem.NewInterop(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NEO) getCandidateVoteCall(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
|
pub := toPublicKey(args[0])
|
||||||
|
key := makeValidatorKey(pub)
|
||||||
|
si := ic.DAO.GetStorageItem(n.ID, key)
|
||||||
|
if si == nil {
|
||||||
|
return stackitem.NewBigInteger(big.NewInt(-1))
|
||||||
|
}
|
||||||
|
c := new(candidate).FromBytes(si)
|
||||||
|
if !c.Registered {
|
||||||
|
return stackitem.NewBigInteger(big.NewInt(-1))
|
||||||
|
}
|
||||||
|
return stackitem.NewBigInteger(&c.Votes)
|
||||||
|
}
|
||||||
|
|
||||||
func (n *NEO) getAccountState(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
func (n *NEO) getAccountState(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
key := makeAccountKey(toUint160(args[0]))
|
key := makeAccountKey(toUint160(args[0]))
|
||||||
si := ic.DAO.GetStorageItem(n.ID, key)
|
si := ic.DAO.GetStorageItem(n.ID, key)
|
||||||
|
@ -1021,7 +1081,7 @@ func (n *NEO) computeCommitteeMembers(blockHeight uint32, d *dao.Simple) (keys.P
|
||||||
count := n.cfg.GetCommitteeSize(blockHeight + 1)
|
count := n.cfg.GetCommitteeSize(blockHeight + 1)
|
||||||
// Can be sorted and/or returned to outside users, thus needs to be copied.
|
// Can be sorted and/or returned to outside users, thus needs to be copied.
|
||||||
sbVals := keys.PublicKeys(n.standbyKeys[:count]).Copy()
|
sbVals := keys.PublicKeys(n.standbyKeys[:count]).Copy()
|
||||||
cs, err := n.getCandidates(d, false)
|
cs, err := n.getCandidates(d, false, -1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package native_test
|
package native_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
@ -9,15 +10,20 @@ import (
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/internal/contracts"
|
"github.com/nspcc-dev/neo-go/internal/contracts"
|
||||||
"github.com/nspcc-dev/neo-go/internal/random"
|
"github.com/nspcc-dev/neo-go/internal/random"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -482,3 +488,88 @@ func TestNEO_CalculateBonus(t *testing.T) {
|
||||||
claimTx.SystemFee-claimTx.NetworkFee + +firstPart + secondPart))
|
claimTx.SystemFee-claimTx.NetworkFee + +firstPart + secondPart))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNEO_GetCandidates(t *testing.T) {
|
||||||
|
neoCommitteeInvoker := newNeoCommitteeClient(t, 100_0000_0000)
|
||||||
|
neoValidatorsInvoker := neoCommitteeInvoker.WithSigners(neoCommitteeInvoker.Validator)
|
||||||
|
policyInvoker := neoCommitteeInvoker.CommitteeInvoker(neoCommitteeInvoker.NativeHash(t, nativenames.Policy))
|
||||||
|
e := neoCommitteeInvoker.Executor
|
||||||
|
|
||||||
|
cfg := e.Chain.GetConfig()
|
||||||
|
candidatesCount := cfg.GetCommitteeSize(0) - 1
|
||||||
|
|
||||||
|
// Register a set of candidates and vote for them.
|
||||||
|
voters := make([]neotest.Signer, candidatesCount)
|
||||||
|
candidates := make([]neotest.Signer, candidatesCount)
|
||||||
|
for i := 0; i < candidatesCount; i++ {
|
||||||
|
voters[i] = e.NewAccount(t, 10_0000_0000)
|
||||||
|
candidates[i] = e.NewAccount(t, 2000_0000_0000) // enough for one registration
|
||||||
|
}
|
||||||
|
txes := make([]*transaction.Transaction, 0, candidatesCount*3)
|
||||||
|
for i := 0; i < candidatesCount; i++ {
|
||||||
|
transferTx := neoValidatorsInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), voters[i].(neotest.SingleSigner).Account().PrivateKey().GetScriptHash(), int64(candidatesCount+1-i)*1000000, nil)
|
||||||
|
txes = append(txes, transferTx)
|
||||||
|
registerTx := neoValidatorsInvoker.WithSigners(candidates[i]).PrepareInvoke(t, "registerCandidate", candidates[i].(neotest.SingleSigner).Account().PrivateKey().PublicKey().Bytes())
|
||||||
|
txes = append(txes, registerTx)
|
||||||
|
voteTx := neoValidatorsInvoker.WithSigners(voters[i]).PrepareInvoke(t, "vote", voters[i].(neotest.SingleSigner).Account().PrivateKey().GetScriptHash(), candidates[i].(neotest.SingleSigner).Account().PrivateKey().PublicKey().Bytes())
|
||||||
|
txes = append(txes, voteTx)
|
||||||
|
}
|
||||||
|
|
||||||
|
neoValidatorsInvoker.AddNewBlock(t, txes...)
|
||||||
|
for _, tx := range txes {
|
||||||
|
e.CheckHalt(t, tx.Hash(), stackitem.Make(true)) // luckily, both `transfer`, `registerCandidate` and `vote` return boolean values
|
||||||
|
}
|
||||||
|
expected := make([]stackitem.Item, candidatesCount)
|
||||||
|
for i := range expected {
|
||||||
|
pub := candidates[i].(neotest.SingleSigner).Account().PrivateKey().PublicKey().Bytes()
|
||||||
|
v := stackitem.NewBigInteger(big.NewInt(int64(candidatesCount-i+1) * 1000000))
|
||||||
|
expected[i] = stackitem.NewStruct([]stackitem.Item{
|
||||||
|
stackitem.NewByteArray(pub),
|
||||||
|
v,
|
||||||
|
})
|
||||||
|
neoCommitteeInvoker.Invoke(t, v, "getCandidateVote", pub)
|
||||||
|
}
|
||||||
|
sort.Slice(expected, func(i, j int) bool {
|
||||||
|
return bytes.Compare(expected[i].Value().([]stackitem.Item)[0].Value().([]byte), expected[j].Value().([]stackitem.Item)[0].Value().([]byte)) < 0
|
||||||
|
})
|
||||||
|
neoCommitteeInvoker.Invoke(t, stackitem.NewArray(expected), "getCandidates")
|
||||||
|
|
||||||
|
// Check that GetAllCandidates works the same way as GetCandidates.
|
||||||
|
checkGetAllCandidates := func(t *testing.T, expected []stackitem.Item) {
|
||||||
|
for i := 0; i < len(expected)+1; i++ {
|
||||||
|
w := io.NewBufBinWriter()
|
||||||
|
emit.AppCall(w.BinWriter, neoCommitteeInvoker.Hash, "getAllCandidates", callflag.All)
|
||||||
|
for j := 0; j < i+1; j++ {
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.DUP)
|
||||||
|
emit.Syscall(w.BinWriter, interopnames.SystemIteratorNext)
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.DROP) // drop the value returned from Next.
|
||||||
|
}
|
||||||
|
emit.Syscall(w.BinWriter, interopnames.SystemIteratorValue)
|
||||||
|
require.NoError(t, w.Err)
|
||||||
|
h := neoCommitteeInvoker.InvokeScript(t, w.Bytes(), neoCommitteeInvoker.Signers)
|
||||||
|
if i < len(expected) {
|
||||||
|
e.CheckHalt(t, h, expected[i])
|
||||||
|
} else {
|
||||||
|
e.CheckFault(t, h, "iterator index out of range") // ensure there are no extra elements.
|
||||||
|
}
|
||||||
|
w.Reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkGetAllCandidates(t, expected)
|
||||||
|
|
||||||
|
// Block candidate and check it won't be returned from getCandidates and getAllCandidates.
|
||||||
|
unlucky := candidates[len(candidates)-1].(neotest.SingleSigner).Account().PrivateKey().PublicKey()
|
||||||
|
policyInvoker.Invoke(t, true, "blockAccount", unlucky.GetScriptHash())
|
||||||
|
for i := range expected {
|
||||||
|
if bytes.Equal(expected[i].Value().([]stackitem.Item)[0].Value().([]byte), unlucky.Bytes()) {
|
||||||
|
if i != len(expected)-1 {
|
||||||
|
expected = append(expected[:i], expected[i+1:]...)
|
||||||
|
} else {
|
||||||
|
expected = expected[:i]
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
neoCommitteeInvoker.Invoke(t, expected, "getCandidates")
|
||||||
|
checkGetAllCandidates(t, expected)
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue