mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-23 03:38:35 +00:00
native: cache committee together with votes
This commit is contained in:
parent
ede2b05f29
commit
a3c7130ab2
3 changed files with 152 additions and 45 deletions
|
@ -1,13 +0,0 @@
|
||||||
package native
|
|
||||||
|
|
||||||
import "math/big"
|
|
||||||
|
|
||||||
// gasIndexPair contains block index together with generated gas per block.
|
|
||||||
// It is used to cache NEO GASRecords.
|
|
||||||
type gasIndexPair struct {
|
|
||||||
Index uint32
|
|
||||||
GASPerBlock big.Int
|
|
||||||
}
|
|
||||||
|
|
||||||
// gasRecord contains history of gas per block changes. It is used only by NEO cache.
|
|
||||||
type gasRecord []gasIndexPair
|
|
|
@ -37,8 +37,8 @@ type NEO struct {
|
||||||
votesChanged atomic.Value
|
votesChanged atomic.Value
|
||||||
nextValidators atomic.Value
|
nextValidators atomic.Value
|
||||||
validators atomic.Value
|
validators atomic.Value
|
||||||
// committee contains cached committee members and
|
// committee contains cached committee members and their votes.
|
||||||
// is updated once in a while depending on committee size
|
// It is updated once in a while depending on committee size
|
||||||
// (every 28 blocks for mainnet). It's value
|
// (every 28 blocks for mainnet). It's value
|
||||||
// is always equal to value stored by `prefixCommittee`.
|
// is always equal to value stored by `prefixCommittee`.
|
||||||
committee atomic.Value
|
committee atomic.Value
|
||||||
|
@ -46,14 +46,6 @@ type NEO struct {
|
||||||
committeeHash atomic.Value
|
committeeHash atomic.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
// keyWithVotes is a serialized key with votes balance. It's not deserialized
|
|
||||||
// because some uses of it imply serialized-only usage and converting to
|
|
||||||
// PublicKey is quite expensive.
|
|
||||||
type keyWithVotes struct {
|
|
||||||
Key string
|
|
||||||
Votes *big.Int
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
neoName = "NEO"
|
neoName = "NEO"
|
||||||
neoContractID = -1
|
neoContractID = -1
|
||||||
|
@ -107,7 +99,7 @@ func newNEO() *NEO {
|
||||||
n.votesChanged.Store(true)
|
n.votesChanged.Store(true)
|
||||||
n.nextValidators.Store(keys.PublicKeys(nil))
|
n.nextValidators.Store(keys.PublicKeys(nil))
|
||||||
n.validators.Store(keys.PublicKeys(nil))
|
n.validators.Store(keys.PublicKeys(nil))
|
||||||
n.committee.Store(keys.PublicKeys(nil))
|
n.committee.Store(keysWithVotes(nil))
|
||||||
n.committeeHash.Store(util.Uint160{})
|
n.committeeHash.Store(util.Uint160{})
|
||||||
|
|
||||||
onp := n.Methods["onPersist"]
|
onp := n.Methods["onPersist"]
|
||||||
|
@ -175,12 +167,13 @@ func (n *NEO) Initialize(ic *interop.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
committee := ic.Chain.GetStandByCommittee()
|
committee := ic.Chain.GetStandByCommittee()
|
||||||
err := n.updateCache(committee, ic.Chain)
|
cvs := toKeysWithVotes(committee)
|
||||||
|
err := n.updateCache(cvs, ic.Chain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ic.DAO.PutStorageItem(n.ContractID, prefixCommittee, &state.StorageItem{Value: committee.Bytes()})
|
err = ic.DAO.PutStorageItem(n.ContractID, prefixCommittee, &state.StorageItem{Value: cvs.Bytes()})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -212,7 +205,7 @@ func (n *NEO) Initialize(ic *interop.Context) error {
|
||||||
// Cache initialisation should be done apart from Initialize because Initialize is
|
// Cache initialisation should be done apart from Initialize because Initialize is
|
||||||
// called only when deploying native contracts.
|
// called only when deploying native contracts.
|
||||||
func (n *NEO) InitializeCache(bc blockchainer.Blockchainer, d dao.DAO) error {
|
func (n *NEO) InitializeCache(bc blockchainer.Blockchainer, d dao.DAO) error {
|
||||||
committee := keys.PublicKeys{}
|
var committee = keysWithVotes{}
|
||||||
si := d.GetStorageItem(n.ContractID, prefixCommittee)
|
si := d.GetStorageItem(n.ContractID, prefixCommittee)
|
||||||
if err := committee.DecodeBytes(si.Value); err != nil {
|
if err := committee.DecodeBytes(si.Value); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -231,8 +224,10 @@ func (n *NEO) InitializeCache(bc blockchainer.Blockchainer, d dao.DAO) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NEO) updateCache(committee keys.PublicKeys, bc blockchainer.Blockchainer) error {
|
func (n *NEO) updateCache(cvs keysWithVotes, bc blockchainer.Blockchainer) error {
|
||||||
n.committee.Store(committee)
|
n.committee.Store(cvs)
|
||||||
|
|
||||||
|
var committee = n.GetCommitteeMembers()
|
||||||
script, err := smartcontract.CreateMajorityMultiSigRedeemScript(committee.Copy())
|
script, err := smartcontract.CreateMajorityMultiSigRedeemScript(committee.Copy())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -249,20 +244,22 @@ func (n *NEO) updateCommittee(ic *interop.Context) error {
|
||||||
votesChanged := n.votesChanged.Load().(bool)
|
votesChanged := n.votesChanged.Load().(bool)
|
||||||
if !votesChanged {
|
if !votesChanged {
|
||||||
// We need to put in storage anyway, as it affects dumps
|
// We need to put in storage anyway, as it affects dumps
|
||||||
committee := n.committee.Load().(keys.PublicKeys)
|
committee := n.committee.Load().(keysWithVotes)
|
||||||
si := &state.StorageItem{Value: committee.Bytes()}
|
si := &state.StorageItem{Value: committee.Bytes()}
|
||||||
return ic.DAO.PutStorageItem(n.ContractID, prefixCommittee, si)
|
return ic.DAO.PutStorageItem(n.ContractID, prefixCommittee, si)
|
||||||
}
|
}
|
||||||
|
|
||||||
committee, err := n.ComputeCommitteeMembers(ic.Chain, ic.DAO)
|
committee, cvs, err := n.computeCommitteeMembers(ic.Chain, ic.DAO)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
} else if cvs == nil {
|
||||||
|
cvs = toKeysWithVotes(committee)
|
||||||
}
|
}
|
||||||
if err := n.updateCache(committee, ic.Chain); err != nil {
|
if err := n.updateCache(cvs, ic.Chain); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
n.votesChanged.Store(false)
|
n.votesChanged.Store(false)
|
||||||
si := &state.StorageItem{Value: committee.Bytes()}
|
si := &state.StorageItem{Value: cvs.Bytes()}
|
||||||
return ic.DAO.PutStorageItem(n.ContractID, prefixCommittee, si)
|
return ic.DAO.PutStorageItem(n.ContractID, prefixCommittee, si)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -623,7 +620,7 @@ func (n *NEO) getCandidates(d dao.DAO) ([]keyWithVotes, error) {
|
||||||
for key, si := range siMap {
|
for key, si := range siMap {
|
||||||
c := new(candidate).FromBytes(si.Value)
|
c := new(candidate).FromBytes(si.Value)
|
||||||
if c.Registered {
|
if c.Registered {
|
||||||
arr = append(arr, keyWithVotes{key, &c.Votes})
|
arr = append(arr, keyWithVotes{Key: key, Votes: &c.Votes})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sort.Slice(arr, func(i, j int) bool { return strings.Compare(arr[i].Key, arr[j].Key) == -1 })
|
sort.Slice(arr, func(i, j int) bool { return strings.Compare(arr[i].Key, arr[j].Key) == -1 })
|
||||||
|
@ -668,7 +665,7 @@ func (n *NEO) ComputeNextBlockValidators(bc blockchainer.Blockchainer, d dao.DAO
|
||||||
if vals := n.validators.Load().(keys.PublicKeys); vals != nil {
|
if vals := n.validators.Load().(keys.PublicKeys); vals != nil {
|
||||||
return vals.Copy(), nil
|
return vals.Copy(), nil
|
||||||
}
|
}
|
||||||
result, err := n.ComputeCommitteeMembers(bc, d)
|
result, _, err := n.computeCommitteeMembers(bc, d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -698,15 +695,34 @@ func (n *NEO) modifyVoterTurnout(d dao.DAO, amount *big.Int) error {
|
||||||
|
|
||||||
// GetCommitteeMembers returns public keys of nodes in committee using cached value.
|
// GetCommitteeMembers returns public keys of nodes in committee using cached value.
|
||||||
func (n *NEO) GetCommitteeMembers() keys.PublicKeys {
|
func (n *NEO) GetCommitteeMembers() keys.PublicKeys {
|
||||||
return n.committee.Load().(keys.PublicKeys).Copy()
|
var cvs = n.committee.Load().(keysWithVotes)
|
||||||
|
var committee = make(keys.PublicKeys, len(cvs))
|
||||||
|
var err error
|
||||||
|
for i := range committee {
|
||||||
|
committee[i], err = cvs[i].PublicKey()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return committee
|
||||||
}
|
}
|
||||||
|
|
||||||
// ComputeCommitteeMembers returns public keys of nodes in committee.
|
func toKeysWithVotes(pubs keys.PublicKeys) keysWithVotes {
|
||||||
func (n *NEO) ComputeCommitteeMembers(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) {
|
ks := make(keysWithVotes, len(pubs))
|
||||||
|
for i := range pubs {
|
||||||
|
ks[i].UnmarshaledKey = pubs[i]
|
||||||
|
ks[i].Key = string(pubs[i].Bytes())
|
||||||
|
ks[i].Votes = big.NewInt(0)
|
||||||
|
}
|
||||||
|
return ks
|
||||||
|
}
|
||||||
|
|
||||||
|
// computeCommitteeMembers returns public keys of nodes in committee.
|
||||||
|
func (n *NEO) computeCommitteeMembers(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, keysWithVotes, error) {
|
||||||
key := []byte{prefixVotersCount}
|
key := []byte{prefixVotersCount}
|
||||||
si := d.GetStorageItem(n.ContractID, key)
|
si := d.GetStorageItem(n.ContractID, key)
|
||||||
if si == nil {
|
if si == nil {
|
||||||
return nil, errors.New("voters count not found")
|
return nil, nil, errors.New("voters count not found")
|
||||||
}
|
}
|
||||||
votersCount := bigint.FromBytes(si.Value)
|
votersCount := bigint.FromBytes(si.Value)
|
||||||
// votersCount / totalSupply must be >= 0.2
|
// votersCount / totalSupply must be >= 0.2
|
||||||
|
@ -714,16 +730,16 @@ func (n *NEO) ComputeCommitteeMembers(bc blockchainer.Blockchainer, d dao.DAO) (
|
||||||
voterTurnout := votersCount.Div(votersCount, n.getTotalSupply(d))
|
voterTurnout := votersCount.Div(votersCount, n.getTotalSupply(d))
|
||||||
if voterTurnout.Sign() != 1 {
|
if voterTurnout.Sign() != 1 {
|
||||||
pubs := bc.GetStandByCommittee()
|
pubs := bc.GetStandByCommittee()
|
||||||
return pubs, nil
|
return pubs, nil, nil
|
||||||
}
|
}
|
||||||
cs, err := n.getCandidates(d)
|
cs, err := n.getCandidates(d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
sbVals := bc.GetStandByCommittee()
|
sbVals := bc.GetStandByCommittee()
|
||||||
count := len(sbVals)
|
count := len(sbVals)
|
||||||
if len(cs) < count {
|
if len(cs) < count {
|
||||||
return sbVals, nil
|
return sbVals, nil, nil
|
||||||
}
|
}
|
||||||
sort.Slice(cs, func(i, j int) bool {
|
sort.Slice(cs, func(i, j int) bool {
|
||||||
// The most-voted validators should end up in the front of the list.
|
// The most-voted validators should end up in the front of the list.
|
||||||
|
@ -736,12 +752,12 @@ func (n *NEO) ComputeCommitteeMembers(bc blockchainer.Blockchainer, d dao.DAO) (
|
||||||
})
|
})
|
||||||
pubs := make(keys.PublicKeys, count)
|
pubs := make(keys.PublicKeys, count)
|
||||||
for i := range pubs {
|
for i := range pubs {
|
||||||
pubs[i], err = keys.NewPublicKeyFromBytes([]byte(cs[i].Key), elliptic.P256())
|
pubs[i], err = cs[i].PublicKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return pubs, nil
|
return pubs, cs[:count], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NEO) getNextBlockValidators(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
func (n *NEO) getNextBlockValidators(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||||
|
|
104
pkg/core/native/neo_types.go
Normal file
104
pkg/core/native/neo_types.go
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
package native
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/elliptic"
|
||||||
|
"errors"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
)
|
||||||
|
|
||||||
|
// gasIndexPair contains block index together with generated gas per block.
|
||||||
|
// It is used to cache NEO GASRecords.
|
||||||
|
type gasIndexPair struct {
|
||||||
|
Index uint32
|
||||||
|
GASPerBlock big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
// gasRecord contains history of gas per block changes. It is used only by NEO cache.
|
||||||
|
type gasRecord []gasIndexPair
|
||||||
|
|
||||||
|
type (
|
||||||
|
// keyWithVotes is a serialized key with votes balance. It's not deserialized
|
||||||
|
// because some uses of it imply serialized-only usage and converting to
|
||||||
|
// PublicKey is quite expensive.
|
||||||
|
keyWithVotes struct {
|
||||||
|
Key string
|
||||||
|
Votes *big.Int
|
||||||
|
// UnmarshaledKey contains public key if it was unmarshaled.
|
||||||
|
UnmarshaledKey *keys.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
keysWithVotes []keyWithVotes
|
||||||
|
)
|
||||||
|
|
||||||
|
// PublicKey unmarshals and returns public key of k.
|
||||||
|
func (k keyWithVotes) PublicKey() (*keys.PublicKey, error) {
|
||||||
|
if k.UnmarshaledKey != nil {
|
||||||
|
return k.UnmarshaledKey, nil
|
||||||
|
}
|
||||||
|
return keys.NewPublicKeyFromBytes([]byte(k.Key), elliptic.P256())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k keysWithVotes) toStackItem() stackitem.Item {
|
||||||
|
arr := make([]stackitem.Item, len(k))
|
||||||
|
for i := range k {
|
||||||
|
arr[i] = stackitem.NewStruct([]stackitem.Item{
|
||||||
|
stackitem.NewByteArray([]byte(k[i].Key)),
|
||||||
|
stackitem.NewBigInteger(k[i].Votes),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return stackitem.NewArray(arr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *keysWithVotes) fromStackItem(item stackitem.Item) error {
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not an array")
|
||||||
|
}
|
||||||
|
|
||||||
|
var kvs = make(keysWithVotes, len(arr))
|
||||||
|
for i := range arr {
|
||||||
|
s, ok := arr[i].Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("element is not a struct")
|
||||||
|
} else if len(s) < 2 {
|
||||||
|
return errors.New("invalid length")
|
||||||
|
}
|
||||||
|
pub, err := s[0].TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
vs, err := s[1].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
kvs[i].Key = string(pub)
|
||||||
|
kvs[i].Votes = vs
|
||||||
|
}
|
||||||
|
*k = kvs
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes serializes keys with votes slice.
|
||||||
|
func (k keysWithVotes) Bytes() []byte {
|
||||||
|
var it = k.toStackItem()
|
||||||
|
var w = io.NewBufBinWriter()
|
||||||
|
stackitem.EncodeBinaryStackItem(it, w.BinWriter)
|
||||||
|
if w.Err != nil {
|
||||||
|
panic(w.Err)
|
||||||
|
}
|
||||||
|
return w.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeBytes deserializes keys and votes slice.
|
||||||
|
func (k *keysWithVotes) DecodeBytes(data []byte) error {
|
||||||
|
var r = io.NewBinReaderFromBuf(data)
|
||||||
|
var it = stackitem.DecodeBinaryStackItem(r)
|
||||||
|
if r.Err != nil {
|
||||||
|
return r.Err
|
||||||
|
}
|
||||||
|
return k.fromStackItem(it)
|
||||||
|
}
|
Loading…
Reference in a new issue