neo-go/pkg/core/native/native_neo.go
2020-04-16 15:55:35 +03:00

380 lines
10 KiB
Go

package native
import (
"math/big"
"sort"
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
"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/runtime"
"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/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/pkg/errors"
)
// NEO represents NEO native contract.
type NEO struct {
nep5TokenNative
GAS *GAS
}
const neoSyscallName = "Neo.Native.Tokens.NEO"
// NewNEO returns NEO native contract.
func NewNEO() *NEO {
nep5 := newNEP5Native(neoSyscallName)
nep5.name = "NEO"
nep5.symbol = "neo"
nep5.decimals = 0
nep5.factor = 1
n := &NEO{nep5TokenNative: *nep5}
desc := newDescriptor("unclaimedGas", smartcontract.IntegerType,
manifest.NewParameter("account", smartcontract.Hash160Type),
manifest.NewParameter("end", smartcontract.IntegerType))
md := newMethodAndPrice(n.unclaimedGas, 1, smartcontract.NoneFlag)
n.AddMethod(md, desc, true)
desc = newDescriptor("registerValidator", smartcontract.BoolType,
manifest.NewParameter("pubkey", smartcontract.PublicKeyType))
md = newMethodAndPrice(n.registerValidator, 1, smartcontract.NoneFlag)
n.AddMethod(md, desc, false)
desc = newDescriptor("vote", smartcontract.BoolType,
manifest.NewParameter("account", smartcontract.Hash160Type),
manifest.NewParameter("pubkeys", smartcontract.ArrayType))
md = newMethodAndPrice(n.vote, 1, smartcontract.NoneFlag)
n.AddMethod(md, desc, false)
desc = newDescriptor("getRegisteredValidators", smartcontract.ArrayType)
md = newMethodAndPrice(n.getRegisteredValidators, 1, smartcontract.NoneFlag)
n.AddMethod(md, desc, true)
desc = newDescriptor("getValidators", smartcontract.ArrayType)
md = newMethodAndPrice(n.getValidators, 1, smartcontract.NoneFlag)
n.AddMethod(md, desc, true)
desc = newDescriptor("getNextBlockValidators", smartcontract.ArrayType)
md = newMethodAndPrice(n.getNextBlockValidators, 1, smartcontract.NoneFlag)
n.AddMethod(md, desc, true)
n.onPersist = chainOnPersist(n.onPersist, n.OnPersist)
n.incBalance = n.increaseBalance
return n
}
// Initialize initializes NEO contract.
func (n *NEO) Initialize(ic *interop.Context) error {
data, err := ic.DAO.GetNativeContractState(n.Hash)
if err == nil {
return n.initFromStore(data)
} else if err != storage.ErrKeyNotFound {
return err
}
if err := n.nep5TokenNative.Initialize(); err != nil {
return err
}
h, vs, err := getStandbyValidatorsHash(ic)
if err != nil {
return err
}
n.mint(ic, h, big.NewInt(100000000*n.factor))
for i := range vs {
if err := n.registerValidatorInternal(ic, vs[i]); err != nil {
return err
}
}
return ic.DAO.PutNativeContractState(n.Hash, n.serializeState())
}
// initFromStore initializes variable contract parameters from the store.
func (n *NEO) initFromStore(data []byte) error {
n.totalSupply = *emit.BytesToInt(data)
return nil
}
func (n *NEO) serializeState() []byte {
return emit.IntToBytes(&n.totalSupply)
}
// OnPersist implements Contract interface.
func (n *NEO) OnPersist(ic *interop.Context) error {
pubs, err := n.GetValidatorsInternal(ic.Chain, ic.DAO)
if err != nil {
return err
}
if err := ic.DAO.PutNextBlockValidators(pubs); err != nil {
return err
}
return ic.DAO.PutNativeContractState(n.Hash, n.serializeState())
}
func (n *NEO) increaseBalance(ic *interop.Context, acc *state.Account, amount *big.Int) error {
if sign := amount.Sign(); sign == 0 {
return nil
} else if sign == -1 && acc.NEO.Balance.Cmp(new(big.Int).Neg(amount)) == -1 {
return errors.New("insufficient funds")
}
if err := n.distributeGas(ic, acc); err != nil {
return err
}
acc.NEO.Balance.Add(&acc.NEO.Balance, amount)
return nil
}
func (n *NEO) distributeGas(ic *interop.Context, acc *state.Account) error {
if ic.Block == nil {
return nil
}
sys, net, err := ic.Chain.CalculateClaimable(util.Fixed8(acc.NEO.Balance.Int64()), acc.NEO.BalanceHeight, ic.Block.Index)
if err != nil {
return err
}
acc.NEO.BalanceHeight = ic.Block.Index
n.GAS.mint(ic, acc.ScriptHash, big.NewInt(int64(sys+net)))
return nil
}
func (n *NEO) unclaimedGas(ic *interop.Context, args []vm.StackItem) vm.StackItem {
u := toUint160(args[0])
end := uint32(toBigInt(args[1]).Int64())
bs, err := ic.DAO.GetNEP5Balances(u)
if err != nil {
panic(err)
}
tr := bs.Trackers[n.Hash]
sys, net, err := ic.Chain.CalculateClaimable(util.Fixed8(tr.Balance), tr.LastUpdatedBlock, end)
if err != nil {
panic(err)
}
return vm.NewBigIntegerItem(big.NewInt(int64(sys.Add(net))))
}
func (n *NEO) registerValidator(ic *interop.Context, args []vm.StackItem) vm.StackItem {
err := n.registerValidatorInternal(ic, toPublicKey(args[0]))
return vm.NewBoolItem(err == nil)
}
func (n *NEO) registerValidatorInternal(ic *interop.Context, pub *keys.PublicKey) error {
_, err := ic.DAO.GetValidatorState(pub)
if err == nil {
return err
}
return ic.DAO.PutValidatorState(&state.Validator{PublicKey: pub})
}
func (n *NEO) vote(ic *interop.Context, args []vm.StackItem) vm.StackItem {
acc := toUint160(args[0])
arr := args[1].Value().([]vm.StackItem)
var pubs keys.PublicKeys
for i := range arr {
pub := new(keys.PublicKey)
bs, err := arr[i].TryBytes()
if err != nil {
panic(err)
} else if err := pub.DecodeBytes(bs); err != nil {
panic(err)
}
pubs = append(pubs, pub)
}
err := n.VoteInternal(ic, acc, pubs)
return vm.NewBoolItem(err == nil)
}
// VoteInternal votes from account h for validarors specified in pubs.
func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pubs keys.PublicKeys) error {
ok, err := runtime.CheckHashedWitness(ic, h)
if err != nil {
return err
} else if !ok {
return errors.New("invalid signature")
}
acc, err := ic.DAO.GetAccountState(h)
if err != nil {
return err
}
balance := util.Fixed8(acc.NEO.Balance.Int64())
if err := ModifyAccountVotes(acc, ic.DAO, -balance); err != nil {
return err
}
pubs = pubs.Unique()
var newPubs keys.PublicKeys
for _, pub := range pubs {
_, err := ic.DAO.GetValidatorState(pub)
if err != nil {
if err == storage.ErrKeyNotFound {
continue
}
return err
}
newPubs = append(newPubs, pub)
}
if lp, lv := len(newPubs), len(acc.Votes); lp != lv {
vc, err := ic.DAO.GetValidatorsCount()
if err != nil {
return err
}
if lv > 0 {
vc[lv-1] -= balance
}
if len(newPubs) > 0 {
vc[lp-1] += balance
}
if err := ic.DAO.PutValidatorsCount(vc); err != nil {
return err
}
}
acc.Votes = newPubs
return ModifyAccountVotes(acc, ic.DAO, balance)
}
// ModifyAccountVotes modifies votes of the specified account by value (can be negative).
func ModifyAccountVotes(acc *state.Account, d dao.DAO, value util.Fixed8) error {
for _, vote := range acc.Votes {
validator, err := d.GetValidatorStateOrNew(vote)
if err != nil {
return err
}
validator.Votes += value
if validator.UnregisteredAndHasNoVotes() {
if err := d.DeleteValidatorState(validator); err != nil {
return err
}
} else {
if err := d.PutValidatorState(validator); err != nil {
return err
}
}
}
return nil
}
func (n *NEO) getRegisteredValidators(ic *interop.Context, _ []vm.StackItem) vm.StackItem {
vs := ic.DAO.GetValidators()
arr := make([]vm.StackItem, len(vs))
for i := range vs {
arr[i] = vm.NewStructItem([]vm.StackItem{
vm.NewByteArrayItem(vs[i].PublicKey.Bytes()),
vm.NewBigIntegerItem(big.NewInt(int64(vs[i].Votes))),
})
}
return vm.NewArrayItem(arr)
}
// GetValidatorsInternal returns a list of current validators.
func (n *NEO) GetValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) ([]*keys.PublicKey, error) {
validatorsCount, err := d.GetValidatorsCount()
if err != nil {
return nil, err
} else if len(validatorsCount) == 0 {
sb, err := bc.GetStandByValidators()
if err != nil {
return nil, err
}
return sb, nil
}
validators := d.GetValidators()
sort.Slice(validators, func(i, j int) bool {
// Unregistered validators go to the end of the list.
if validators[i].Registered != validators[j].Registered {
return validators[i].Registered
}
// The most-voted validators should end up in the front of the list.
if validators[i].Votes != validators[j].Votes {
return validators[i].Votes > validators[j].Votes
}
// Ties are broken with public keys.
return validators[i].PublicKey.Cmp(validators[j].PublicKey) == -1
})
count := validatorsCount.GetWeightedAverage()
standByValidators, err := bc.GetStandByValidators()
if err != nil {
return nil, err
}
if count < len(standByValidators) {
count = len(standByValidators)
}
uniqueSBValidators := standByValidators.Unique()
result := keys.PublicKeys{}
for _, validator := range validators {
if validator.RegisteredAndHasVotes() || uniqueSBValidators.Contains(validator.PublicKey) {
result = append(result, validator.PublicKey)
}
}
if result.Len() >= count {
result = result[:count]
} else {
for i := 0; i < uniqueSBValidators.Len() && result.Len() < count; i++ {
if !result.Contains(uniqueSBValidators[i]) {
result = append(result, uniqueSBValidators[i])
}
}
}
sort.Sort(result)
return result, nil
}
func (n *NEO) getValidators(ic *interop.Context, _ []vm.StackItem) vm.StackItem {
result, err := n.GetValidatorsInternal(ic.Chain, ic.DAO)
if err != nil {
panic(err)
}
return pubsToArray(result)
}
func (n *NEO) getNextBlockValidators(ic *interop.Context, _ []vm.StackItem) vm.StackItem {
result, err := n.GetNextBlockValidatorsInternal(ic.Chain, ic.DAO)
if err != nil {
panic(err)
}
return pubsToArray(result)
}
// GetNextBlockValidatorsInternal returns next block validators.
func (n *NEO) GetNextBlockValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) ([]*keys.PublicKey, error) {
result, err := d.GetNextBlockValidators()
if err != nil {
return nil, err
} else if result == nil {
return bc.GetStandByValidators()
}
return result, nil
}
func pubsToArray(pubs keys.PublicKeys) vm.StackItem {
arr := make([]vm.StackItem, len(pubs))
for i := range pubs {
arr[i] = vm.NewByteArrayItem(pubs[i].Bytes())
}
return vm.NewArrayItem(arr)
}
func toPublicKey(s vm.StackItem) *keys.PublicKey {
buf, err := s.TryBytes()
if err != nil {
panic(err)
}
pub := new(keys.PublicKey)
if err := pub.DecodeBytes(buf); err != nil {
panic(err)
}
return pub
}