forked from TrueCloudLab/neoneo-go
Merge pull request #1518 from nspcc-dev/fix/economy
Distribute GAS to voters
This commit is contained in:
commit
c2145e3ea5
13 changed files with 474 additions and 138 deletions
|
@ -172,21 +172,48 @@ func TestClaimGas(t *testing.T) {
|
||||||
e := newExecutor(t, true)
|
e := newExecutor(t, true)
|
||||||
defer e.Close(t)
|
defer e.Close(t)
|
||||||
|
|
||||||
start := e.Chain.BlockHeight()
|
const walletPath = "testdata/testwallet.json"
|
||||||
balanceBefore := e.Chain.GetUtilityTokenBalance(validatorHash)
|
w, err := wallet.NewWalletFromFile(walletPath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"neo-go", "wallet", "nep5", "multitransfer",
|
||||||
|
"--rpc-endpoint", "http://" + e.RPC.Addr,
|
||||||
|
"--wallet", validatorWallet,
|
||||||
|
"--from", validatorAddr,
|
||||||
|
"neo:" + w.Accounts[0].Address + ":1000",
|
||||||
|
"gas:" + w.Accounts[0].Address + ":1000", // for tx send
|
||||||
|
}
|
||||||
e.In.WriteString("one\r")
|
e.In.WriteString("one\r")
|
||||||
|
e.Run(t, args...)
|
||||||
|
e.checkTxPersisted(t)
|
||||||
|
|
||||||
|
h, err := address.StringToUint160(w.Accounts[0].Address)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
balanceBefore := e.Chain.GetUtilityTokenBalance(h)
|
||||||
|
claimHeight := e.Chain.BlockHeight() + 1
|
||||||
|
cl, err := e.Chain.CalculateClaimable(h, claimHeight)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, cl.Sign() > 0)
|
||||||
|
|
||||||
|
e.In.WriteString("testpass\r")
|
||||||
e.Run(t, "neo-go", "wallet", "claim",
|
e.Run(t, "neo-go", "wallet", "claim",
|
||||||
"--rpc-endpoint", "http://"+e.RPC.Addr,
|
"--rpc-endpoint", "http://"+e.RPC.Addr,
|
||||||
"--wallet", validatorWallet,
|
"--wallet", walletPath,
|
||||||
"--address", validatorAddr)
|
"--address", w.Accounts[0].Address)
|
||||||
tx, end := e.checkTxPersisted(t)
|
tx, height := e.checkTxPersisted(t)
|
||||||
b, _ := e.Chain.GetGoverningTokenBalance(validatorHash)
|
balanceBefore.Sub(balanceBefore, big.NewInt(tx.NetworkFee+tx.SystemFee))
|
||||||
cl := e.Chain.CalculateClaimable(b, start, end)
|
balanceBefore.Add(balanceBefore, cl)
|
||||||
require.True(t, cl.Sign() > 0)
|
|
||||||
cl.Sub(cl, big.NewInt(tx.NetworkFee+tx.SystemFee))
|
|
||||||
|
|
||||||
balanceAfter := e.Chain.GetUtilityTokenBalance(validatorHash)
|
balanceAfter := e.Chain.GetUtilityTokenBalance(h)
|
||||||
require.Equal(t, 0, balanceAfter.Cmp(balanceBefore.Add(balanceBefore, cl)))
|
// height can be bigger than claimHeight especially when tests are executed with -race.
|
||||||
|
if height == claimHeight {
|
||||||
|
require.Equal(t, 0, balanceAfter.Cmp(balanceBefore))
|
||||||
|
} else {
|
||||||
|
require.Equal(t, 1, balanceAfter.Cmp(balanceBefore))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestImportDeployed(t *testing.T) {
|
func TestImportDeployed(t *testing.T) {
|
||||||
|
|
|
@ -1156,10 +1156,8 @@ func (bc *Blockchain) UnsubscribeFromExecutions(ch chan<- *state.AppExecResult)
|
||||||
|
|
||||||
// CalculateClaimable calculates the amount of GAS generated by owning specified
|
// CalculateClaimable calculates the amount of GAS generated by owning specified
|
||||||
// amount of NEO between specified blocks.
|
// amount of NEO between specified blocks.
|
||||||
func (bc *Blockchain) CalculateClaimable(value *big.Int, startHeight, endHeight uint32) *big.Int {
|
func (bc *Blockchain) CalculateClaimable(acc util.Uint160, endHeight uint32) (*big.Int, error) {
|
||||||
ic := bc.newInteropContext(trigger.Application, bc.dao, nil, nil)
|
return bc.contracts.NEO.CalculateBonus(bc.dao, acc, endHeight)
|
||||||
res, _ := bc.contracts.NEO.CalculateBonus(ic, value, startHeight, endHeight)
|
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FeePerByte returns transaction network fee per byte.
|
// FeePerByte returns transaction network fee per byte.
|
||||||
|
|
|
@ -854,8 +854,9 @@ func TestGetClaimable(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
t.Run("first generation period", func(t *testing.T) {
|
t.Run("first generation period", func(t *testing.T) {
|
||||||
amount := bc.CalculateClaimable(big.NewInt(1), 0, 2)
|
amount, err := bc.CalculateClaimable(neoOwner, 1)
|
||||||
require.EqualValues(t, big.NewInt(1), amount)
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, big.NewInt(5*native.GASFactor/10), amount)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ type Blockchainer interface {
|
||||||
AddHeaders(...*block.Header) error
|
AddHeaders(...*block.Header) error
|
||||||
AddBlock(*block.Block) error
|
AddBlock(*block.Block) error
|
||||||
AddStateRoot(r *state.MPTRoot) error
|
AddStateRoot(r *state.MPTRoot) error
|
||||||
CalculateClaimable(value *big.Int, startHeight, endHeight uint32) *big.Int
|
CalculateClaimable(h util.Uint160, endHeight uint32) (*big.Int, error)
|
||||||
Close()
|
Close()
|
||||||
HeaderHeight() uint32
|
HeaderHeight() uint32
|
||||||
GetBlock(hash util.Uint256) (*block.Block, error)
|
GetBlock(hash util.Uint256) (*block.Block, error)
|
||||||
|
|
|
@ -61,6 +61,7 @@ type DAO interface {
|
||||||
PutNEP5TransferLog(acc util.Uint160, index uint32, lg *state.NEP5TransferLog) error
|
PutNEP5TransferLog(acc util.Uint160, index uint32, lg *state.NEP5TransferLog) error
|
||||||
PutStorageItem(id int32, key []byte, si *state.StorageItem) error
|
PutStorageItem(id int32, key []byte, si *state.StorageItem) error
|
||||||
PutVersion(v string) error
|
PutVersion(v string) error
|
||||||
|
Seek(id int32, prefix []byte, f func(k, v []byte))
|
||||||
StoreAsBlock(block *block.Block, buf *io.BufBinWriter) error
|
StoreAsBlock(block *block.Block, buf *io.BufBinWriter) error
|
||||||
StoreAsCurrentBlock(block *block.Block, buf *io.BufBinWriter) error
|
StoreAsCurrentBlock(block *block.Block, buf *io.BufBinWriter) error
|
||||||
StoreAsTransaction(tx *transaction.Transaction, index uint32, buf *io.BufBinWriter) error
|
StoreAsTransaction(tx *transaction.Transaction, index uint32, buf *io.BufBinWriter) error
|
||||||
|
@ -406,10 +407,6 @@ func (dao *Simple) GetStorageItemsWithPrefix(id int32, prefix []byte) (map[strin
|
||||||
var siMap = make(map[string]*state.StorageItem)
|
var siMap = make(map[string]*state.StorageItem)
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
lookupKey := makeStorageItemKey(id, nil)
|
|
||||||
if prefix != nil {
|
|
||||||
lookupKey = append(lookupKey, prefix...)
|
|
||||||
}
|
|
||||||
saveToMap := func(k, v []byte) {
|
saveToMap := func(k, v []byte) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
@ -421,20 +418,28 @@ func (dao *Simple) GetStorageItemsWithPrefix(id int32, prefix []byte) (map[strin
|
||||||
err = r.Err
|
err = r.Err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cut prefix and hash.
|
// Cut prefix and hash.
|
||||||
// Must copy here, #1468.
|
// Must copy here, #1468.
|
||||||
key := make([]byte, len(k[len(lookupKey):]))
|
key := make([]byte, len(k))
|
||||||
copy(key, k[len(lookupKey):])
|
copy(key, k)
|
||||||
siMap[string(key)] = si
|
siMap[string(key)] = si
|
||||||
}
|
}
|
||||||
dao.Store.Seek(lookupKey, saveToMap)
|
dao.Seek(id, prefix, saveToMap)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return siMap, nil
|
return siMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Seek executes f for all items with a given prefix.
|
||||||
|
// If key is to be used outside of f, they must be copied.
|
||||||
|
func (dao *Simple) Seek(id int32, prefix []byte, f func(k, v []byte)) {
|
||||||
|
lookupKey := makeStorageItemKey(id, nil)
|
||||||
|
if prefix != nil {
|
||||||
|
lookupKey = append(lookupKey, prefix...)
|
||||||
|
}
|
||||||
|
dao.Store.Seek(lookupKey, func(k, v []byte) {
|
||||||
|
f(k[len(lookupKey):], v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// makeStorageItemKey returns a key used to store StorageItem in the DB.
|
// makeStorageItemKey returns a key used to store StorageItem in the DB.
|
||||||
func makeStorageItemKey(id int32, key []byte) []byte {
|
func makeStorageItemKey(id int32, key []byte) []byte {
|
||||||
// 1 for prefix + 4 for Uint32 + len(key) for key
|
// 1 for prefix + 4 for Uint32 + len(key) for key
|
||||||
|
|
|
@ -178,7 +178,7 @@ func TestCreateBasicChain(t *testing.T) {
|
||||||
priv0 := testchain.PrivateKeyByID(0)
|
priv0 := testchain.PrivateKeyByID(0)
|
||||||
priv0ScriptHash := priv0.GetScriptHash()
|
priv0ScriptHash := priv0.GetScriptHash()
|
||||||
|
|
||||||
require.Equal(t, big.NewInt(2500_0000), bc.GetUtilityTokenBalance(priv0ScriptHash)) // gas bounty
|
require.Equal(t, big.NewInt(5000_0000), bc.GetUtilityTokenBalance(priv0ScriptHash)) // gas bounty
|
||||||
// Move some NEO to one simple account.
|
// Move some NEO to one simple account.
|
||||||
txMoveNeo := newNEP5Transfer(neoHash, neoOwner, priv0ScriptHash, neoAmount)
|
txMoveNeo := newNEP5Transfer(neoHash, neoOwner, priv0ScriptHash, neoAmount)
|
||||||
txMoveNeo.ValidUntilBlock = validUntilBlock
|
txMoveNeo.ValidUntilBlock = validUntilBlock
|
||||||
|
|
|
@ -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
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"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"
|
||||||
"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/crypto/hash"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||||
"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/encoding/bigint"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
|
||||||
|
@ -37,8 +38,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 +47,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
|
||||||
|
@ -63,6 +56,11 @@ const (
|
||||||
prefixCandidate = 33
|
prefixCandidate = 33
|
||||||
// prefixVotersCount is a prefix for storing total amount of NEO of voters.
|
// prefixVotersCount is a prefix for storing total amount of NEO of voters.
|
||||||
prefixVotersCount = 1
|
prefixVotersCount = 1
|
||||||
|
// prefixVoterRewardPerCommittee is a prefix for storing committee GAS reward.
|
||||||
|
prefixVoterRewardPerCommittee = 23
|
||||||
|
// voterRewardFactor is a factor by which voter reward per committee is multiplied
|
||||||
|
// to make calculations more precise.
|
||||||
|
voterRewardFactor = 100_000_000
|
||||||
// prefixGasPerBlock is a prefix for storing amount of GAS generated per block.
|
// prefixGasPerBlock is a prefix for storing amount of GAS generated per block.
|
||||||
prefixGASPerBlock = 29
|
prefixGASPerBlock = 29
|
||||||
// effectiveVoterTurnout represents minimal ratio of total supply to total amount voted value
|
// effectiveVoterTurnout represents minimal ratio of total supply to total amount voted value
|
||||||
|
@ -71,9 +69,9 @@ const (
|
||||||
// neoHolderRewardRatio is a percent of generated GAS that is distributed to NEO holders.
|
// neoHolderRewardRatio is a percent of generated GAS that is distributed to NEO holders.
|
||||||
neoHolderRewardRatio = 10
|
neoHolderRewardRatio = 10
|
||||||
// neoHolderRewardRatio is a percent of generated GAS that is distributed to committee.
|
// neoHolderRewardRatio is a percent of generated GAS that is distributed to committee.
|
||||||
committeeRewardRatio = 5
|
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 = 85
|
voterRewardRatio = 80
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -107,7 +105,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 +173,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 +211,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 +230,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 +250,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,13 +290,66 @@ func (n *NEO) OnPersist(ic *interop.Context) error {
|
||||||
func (n *NEO) PostPersist(ic *interop.Context) error {
|
func (n *NEO) PostPersist(ic *interop.Context) error {
|
||||||
gas := n.GetGASPerBlock(ic.DAO, ic.Block.Index)
|
gas := n.GetGASPerBlock(ic.DAO, ic.Block.Index)
|
||||||
pubs := n.GetCommitteeMembers()
|
pubs := n.GetCommitteeMembers()
|
||||||
index := int(ic.Block.Index) % len(ic.Chain.GetConfig().StandbyCommittee)
|
committeeSize := len(ic.Chain.GetConfig().StandbyCommittee)
|
||||||
gas.Mul(gas, big.NewInt(committeeRewardRatio))
|
index := int(ic.Block.Index) % committeeSize
|
||||||
n.GAS.mint(ic, pubs[index].GetScriptHash(), gas.Div(gas, big.NewInt(100)))
|
committeeReward := new(big.Int).Mul(gas, big.NewInt(committeeRewardRatio))
|
||||||
|
n.GAS.mint(ic, pubs[index].GetScriptHash(), committeeReward.Div(committeeReward, big.NewInt(100)))
|
||||||
|
|
||||||
|
if ShouldUpdateCommittee(ic.Block.Index, ic.Chain) {
|
||||||
|
var voterReward = big.NewInt(voterRewardRatio)
|
||||||
|
voterReward.Mul(voterReward, gas)
|
||||||
|
voterReward.Mul(voterReward, big.NewInt(voterRewardFactor*int64(committeeSize)))
|
||||||
|
var validatorsCount = ic.Chain.GetConfig().ValidatorsCount
|
||||||
|
voterReward.Div(voterReward, big.NewInt(int64(committeeSize+validatorsCount)))
|
||||||
|
voterReward.Div(voterReward, big.NewInt(100))
|
||||||
|
|
||||||
|
var cs = n.committee.Load().(keysWithVotes)
|
||||||
|
var si = new(state.StorageItem)
|
||||||
|
var key = make([]byte, 38)
|
||||||
|
for i := range cs {
|
||||||
|
if cs[i].Votes.Sign() > 0 {
|
||||||
|
tmp := big.NewInt(1)
|
||||||
|
if i < validatorsCount {
|
||||||
|
tmp = big.NewInt(2)
|
||||||
|
}
|
||||||
|
tmp.Mul(tmp, voterReward)
|
||||||
|
tmp.Div(tmp, cs[i].Votes)
|
||||||
|
|
||||||
|
key = makeVoterKey([]byte(cs[i].Key), key)
|
||||||
|
var reward = n.getGASPerVote(ic.DAO, key[:34], ic.Block.Index+1)
|
||||||
|
tmp.Add(tmp, &reward[0])
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint32(key[34:], ic.Block.Index+1)
|
||||||
|
|
||||||
|
si.Value = bigint.ToBytes(tmp)
|
||||||
|
if err := ic.DAO.PutStorageItem(n.ContractID, key, si); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
n.OnPersistEnd(ic.DAO)
|
n.OnPersistEnd(ic.DAO)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *NEO) getGASPerVote(d dao.DAO, key []byte, index ...uint32) []big.Int {
|
||||||
|
var max = make([]uint32, len(index))
|
||||||
|
var reward = make([]big.Int, len(index))
|
||||||
|
d.Seek(n.ContractID, key, func(k, v []byte) {
|
||||||
|
if len(k) == 4 {
|
||||||
|
num := binary.BigEndian.Uint32(k)
|
||||||
|
for i, ind := range index {
|
||||||
|
if max[i] < num && num <= ind {
|
||||||
|
max[i] = num
|
||||||
|
reward[i] = *bigint.FromBytes(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return reward
|
||||||
|
}
|
||||||
|
|
||||||
// OnPersistEnd updates cached values if they've been changed.
|
// OnPersistEnd updates cached values if they've been changed.
|
||||||
func (n *NEO) OnPersistEnd(d dao.DAO) {
|
func (n *NEO) OnPersistEnd(d dao.DAO) {
|
||||||
if n.gasPerBlockChanged.Load().(bool) {
|
if n.gasPerBlockChanged.Load().(bool) {
|
||||||
|
@ -342,7 +398,7 @@ func (n *NEO) distributeGas(ic *interop.Context, h util.Uint160, acc *state.NEOB
|
||||||
if ic.Block == nil || ic.Block.Index == 0 {
|
if ic.Block == nil || ic.Block.Index == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
gen, err := n.CalculateBonus(ic, &acc.Balance, acc.BalanceHeight, ic.Block.Index)
|
gen, err := n.calculateBonus(ic.DAO, acc.VoteTo, &acc.Balance, acc.BalanceHeight, ic.Block.Index)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -354,13 +410,7 @@ func (n *NEO) distributeGas(ic *interop.Context, h util.Uint160, acc *state.NEOB
|
||||||
func (n *NEO) unclaimedGas(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
func (n *NEO) unclaimedGas(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
u := toUint160(args[0])
|
u := toUint160(args[0])
|
||||||
end := uint32(toBigInt(args[1]).Int64())
|
end := uint32(toBigInt(args[1]).Int64())
|
||||||
bs, err := ic.DAO.GetNEP5Balances(u)
|
gen, err := n.CalculateBonus(ic.DAO, u, end)
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
tr := bs.Trackers[n.ContractID]
|
|
||||||
|
|
||||||
gen, err := n.CalculateBonus(ic, &tr.Balance, tr.LastUpdatedBlock, end)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -445,8 +495,70 @@ func (n *NEO) SetGASPerBlock(ic *interop.Context, index uint32, gas *big.Int) (b
|
||||||
return true, n.putGASRecord(ic.DAO, index, gas)
|
return true, n.putGASRecord(ic.DAO, index, gas)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CalculateBonus calculates amount of gas generated for holding `value` NEO from start to end block.
|
func (n *NEO) dropCandidateIfZero(d dao.DAO, pub *keys.PublicKey, c *candidate) (bool, error) {
|
||||||
func (n *NEO) CalculateBonus(ic *interop.Context, value *big.Int, start, end uint32) (*big.Int, error) {
|
if c.Registered || c.Votes.Sign() != 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if err := d.DeleteStorageItem(n.ContractID, makeValidatorKey(pub)); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var toRemove []string
|
||||||
|
d.Seek(n.ContractID, makeVoterKey(pub.Bytes()), func(k, v []byte) {
|
||||||
|
toRemove = append(toRemove, string(k))
|
||||||
|
})
|
||||||
|
for i := range toRemove {
|
||||||
|
if err := d.DeleteStorageItem(n.ContractID, []byte(toRemove[i])); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeVoterKey(pub []byte, prealloc ...[]byte) []byte {
|
||||||
|
var key []byte
|
||||||
|
if len(prealloc) != 0 {
|
||||||
|
key = prealloc[0]
|
||||||
|
} else {
|
||||||
|
key = make([]byte, 34, 38)
|
||||||
|
}
|
||||||
|
key[0] = prefixVoterRewardPerCommittee
|
||||||
|
copy(key[1:], pub)
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
// CalculateBonus calculates amount of gas generated for holding value NEO from start to end block
|
||||||
|
// and having voted for active committee member.
|
||||||
|
func (n *NEO) CalculateBonus(d dao.DAO, acc util.Uint160, end uint32) (*big.Int, error) {
|
||||||
|
key := makeAccountKey(acc)
|
||||||
|
si := d.GetStorageItem(n.ContractID, key)
|
||||||
|
if si == nil {
|
||||||
|
return nil, storage.ErrKeyNotFound
|
||||||
|
}
|
||||||
|
st, err := state.NEOBalanceStateFromBytes(si.Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return n.calculateBonus(d, st.VoteTo, &st.Balance, st.BalanceHeight, end)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NEO) calculateBonus(d dao.DAO, vote *keys.PublicKey, value *big.Int, start, end uint32) (*big.Int, error) {
|
||||||
|
r, err := n.CalculateNEOHolderReward(d, value, start, end)
|
||||||
|
if err != nil || vote == nil {
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = makeVoterKey(vote.Bytes())
|
||||||
|
var reward = n.getGASPerVote(d, key, start, end)
|
||||||
|
var tmp = new(big.Int).Sub(&reward[1], &reward[0])
|
||||||
|
tmp.Mul(tmp, value)
|
||||||
|
tmp.Div(tmp, big.NewInt(voterRewardFactor))
|
||||||
|
tmp.Add(tmp, r)
|
||||||
|
return tmp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CalculateNEOHolderReward return GAS reward for holding `value` of NEO from start to end block.
|
||||||
|
func (n *NEO) CalculateNEOHolderReward(d dao.DAO, value *big.Int, start, end uint32) (*big.Int, error) {
|
||||||
if value.Sign() == 0 || start >= end {
|
if value.Sign() == 0 || start >= end {
|
||||||
return big.NewInt(0), nil
|
return big.NewInt(0), nil
|
||||||
} else if value.Sign() < 0 {
|
} else if value.Sign() < 0 {
|
||||||
|
@ -459,7 +571,7 @@ func (n *NEO) CalculateBonus(ic *interop.Context, value *big.Int, start, end uin
|
||||||
if !n.gasPerBlockChanged.Load().(bool) {
|
if !n.gasPerBlockChanged.Load().(bool) {
|
||||||
gr = n.gasPerBlock.Load().(gasRecord)
|
gr = n.gasPerBlock.Load().(gasRecord)
|
||||||
} else {
|
} else {
|
||||||
gr, err = n.getSortedGASRecordFromDAO(ic.DAO)
|
gr, err = n.getSortedGASRecordFromDAO(d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -533,10 +645,11 @@ func (n *NEO) UnregisterCandidateInternal(ic *interop.Context, pub *keys.PublicK
|
||||||
}
|
}
|
||||||
n.validators.Store(keys.PublicKeys(nil))
|
n.validators.Store(keys.PublicKeys(nil))
|
||||||
c := new(candidate).FromBytes(si.Value)
|
c := new(candidate).FromBytes(si.Value)
|
||||||
if c.Votes.Sign() == 0 {
|
|
||||||
return ic.DAO.DeleteStorageItem(n.ContractID, key)
|
|
||||||
}
|
|
||||||
c.Registered = false
|
c.Registered = false
|
||||||
|
ok, err := n.dropCandidateIfZero(ic.DAO, pub, c)
|
||||||
|
if ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
si.Value = c.Bytes()
|
si.Value = c.Bytes()
|
||||||
return ic.DAO.PutStorageItem(n.ContractID, key, si)
|
return ic.DAO.PutStorageItem(n.ContractID, key, si)
|
||||||
}
|
}
|
||||||
|
@ -577,6 +690,9 @@ func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pub *keys.Public
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err := n.distributeGas(ic, h, acc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := n.ModifyAccountVotes(acc, ic.DAO, new(big.Int).Neg(&acc.Balance), false); err != nil {
|
if err := n.ModifyAccountVotes(acc, ic.DAO, new(big.Int).Neg(&acc.Balance), false); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -601,8 +717,9 @@ func (n *NEO) ModifyAccountVotes(acc *state.NEOBalanceState, d dao.DAO, value *b
|
||||||
cd := new(candidate).FromBytes(si.Value)
|
cd := new(candidate).FromBytes(si.Value)
|
||||||
cd.Votes.Add(&cd.Votes, value)
|
cd.Votes.Add(&cd.Votes, value)
|
||||||
if !isNewVote {
|
if !isNewVote {
|
||||||
if !cd.Registered && cd.Votes.Sign() == 0 {
|
ok, err := n.dropCandidateIfZero(d, acc.VoteTo, cd)
|
||||||
return d.DeleteStorageItem(n.ContractID, key)
|
if ok {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
} else if !cd.Registered {
|
} else if !cd.Registered {
|
||||||
return errors.New("validator must be registered")
|
return errors.New("validator must be registered")
|
||||||
|
@ -614,7 +731,7 @@ func (n *NEO) ModifyAccountVotes(acc *state.NEOBalanceState, d dao.DAO, value *b
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NEO) getCandidates(d dao.DAO) ([]keyWithVotes, error) {
|
func (n *NEO) getCandidates(d dao.DAO, sortByKey bool) ([]keyWithVotes, error) {
|
||||||
siMap, err := d.GetStorageItemsWithPrefix(n.ContractID, []byte{prefixCandidate})
|
siMap, err := d.GetStorageItemsWithPrefix(n.ContractID, []byte{prefixCandidate})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -623,17 +740,29 @@ 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 })
|
if sortByKey {
|
||||||
|
sort.Slice(arr, func(i, j int) bool { return strings.Compare(arr[i].Key, arr[j].Key) == -1 })
|
||||||
|
} else {
|
||||||
|
sort.Slice(arr, func(i, j int) bool {
|
||||||
|
// The most-voted validators should end up in the front of the list.
|
||||||
|
cmp := arr[i].Votes.Cmp(arr[j].Votes)
|
||||||
|
if cmp != 0 {
|
||||||
|
return cmp > 0
|
||||||
|
}
|
||||||
|
// Ties are broken with public keys.
|
||||||
|
return strings.Compare(arr[i].Key, arr[j].Key) == -1
|
||||||
|
})
|
||||||
|
}
|
||||||
return arr, nil
|
return arr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.DAO) ([]state.Validator, error) {
|
func (n *NEO) GetCandidates(d dao.DAO) ([]state.Validator, error) {
|
||||||
kvs, err := n.getCandidates(d)
|
kvs, err := n.getCandidates(d, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -649,7 +778,7 @@ func (n *NEO) GetCandidates(d dao.DAO) ([]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)
|
validators, err := n.getCandidates(ic.DAO, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -668,7 +797,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 +827,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,34 +862,25 @@ 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, false)
|
||||||
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 {
|
|
||||||
// The most-voted validators should end up in the front of the list.
|
|
||||||
cmp := cs[i].Votes.Cmp(cs[j].Votes)
|
|
||||||
if cmp != 0 {
|
|
||||||
return cmp > 0
|
|
||||||
}
|
|
||||||
// Ties are broken with public keys.
|
|
||||||
return strings.Compare(cs[i].Key, cs[j].Key) == -1
|
|
||||||
})
|
|
||||||
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)
|
||||||
|
}
|
|
@ -10,10 +10,13 @@ import (
|
||||||
"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/internal/testchain"
|
"github.com/nspcc-dev/neo-go/pkg/internal/testchain"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"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"
|
||||||
|
"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/opcode"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -24,6 +27,12 @@ func setSigner(tx *transaction.Transaction, h util.Uint160) {
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkTxHalt(t *testing.T, bc *Blockchain, h util.Uint256) {
|
||||||
|
aer, err := bc.GetAppExecResult(h)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, vm.HaltState, aer.VMState, aer.FaultException)
|
||||||
|
}
|
||||||
|
|
||||||
func TestNEO_Vote(t *testing.T) {
|
func TestNEO_Vote(t *testing.T) {
|
||||||
bc := newTestChain(t)
|
bc := newTestChain(t)
|
||||||
defer bc.Close()
|
defer bc.Close()
|
||||||
|
@ -37,7 +46,7 @@ func TestNEO_Vote(t *testing.T) {
|
||||||
freq := testchain.ValidatorsCount + testchain.CommitteeSize()
|
freq := testchain.ValidatorsCount + testchain.CommitteeSize()
|
||||||
advanceChain := func(t *testing.T) {
|
advanceChain := func(t *testing.T) {
|
||||||
for i := 0; i < freq; i++ {
|
for i := 0; i < freq; i++ {
|
||||||
require.NoError(t, neo.OnPersist(ic))
|
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||||
ic.Block.Index++
|
ic.Block.Index++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,27 +57,45 @@ func TestNEO_Vote(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, standBySorted, pubs)
|
require.Equal(t, standBySorted, pubs)
|
||||||
|
|
||||||
sz := testchain.Size()
|
sz := testchain.CommitteeSize()
|
||||||
|
accs := make([]*wallet.Account, sz)
|
||||||
candidates := make(keys.PublicKeys, sz)
|
candidates := make(keys.PublicKeys, sz)
|
||||||
|
txs := make([]*transaction.Transaction, 0, len(accs))
|
||||||
for i := 0; i < sz; i++ {
|
for i := 0; i < sz; i++ {
|
||||||
priv, err := keys.NewPrivateKey()
|
priv, err := keys.NewPrivateKey()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
candidates[i] = priv.PublicKey()
|
candidates[i] = priv.PublicKey()
|
||||||
|
accs[i], err = wallet.NewAccount()
|
||||||
|
require.NoError(t, err)
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
require.NoError(t, neo.RegisterCandidateInternal(ic, candidates[i]))
|
require.NoError(t, neo.RegisterCandidateInternal(ic, candidates[i]))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < sz; i++ {
|
to := accs[i].Contract.ScriptHash()
|
||||||
to := testchain.PrivateKeyByID(i).GetScriptHash()
|
w := io.NewBufBinWriter()
|
||||||
ic.VM.Load(testchain.MultisigVerificationScript())
|
emit.AppCallWithOperationAndArgs(w.BinWriter, bc.contracts.NEO.Hash, "transfer",
|
||||||
ic.VM.LoadScriptWithHash(testchain.MultisigVerificationScript(), testchain.MultisigScriptHash(), smartcontract.All)
|
neoOwner.BytesBE(), to.BytesBE(),
|
||||||
require.NoError(t, neo.TransferInternal(ic, testchain.MultisigScriptHash(), to, big.NewInt(int64(sz-i)*10000000)))
|
big.NewInt(int64(sz-i)*1000000).Int64())
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.ASSERT)
|
||||||
|
emit.AppCallWithOperationAndArgs(w.BinWriter, bc.contracts.GAS.Hash, "transfer",
|
||||||
|
neoOwner.BytesBE(), to.BytesBE(),
|
||||||
|
int64(1_000_000_000))
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.ASSERT)
|
||||||
|
require.NoError(t, w.Err)
|
||||||
|
tx := transaction.New(netmode.UnitTestNet, w.Bytes(), 1000_000_000)
|
||||||
|
tx.ValidUntilBlock = bc.BlockHeight() + 1
|
||||||
|
setSigner(tx, testchain.MultisigScriptHash())
|
||||||
|
require.NoError(t, signTx(bc, tx))
|
||||||
|
txs = append(txs, tx)
|
||||||
}
|
}
|
||||||
|
require.NoError(t, bc.AddBlock(bc.newBlock(txs...)))
|
||||||
|
for _, tx := range txs {
|
||||||
|
checkTxHalt(t, bc, tx.Hash())
|
||||||
|
}
|
||||||
|
transferBlock := bc.BlockHeight()
|
||||||
|
|
||||||
for i := 1; i < sz; i++ {
|
for i := 1; i < sz; i++ {
|
||||||
priv := testchain.PrivateKeyByID(i)
|
priv := accs[i].PrivateKey()
|
||||||
h := priv.GetScriptHash()
|
h := priv.GetScriptHash()
|
||||||
setSigner(tx, h)
|
setSigner(tx, h)
|
||||||
ic.VM.Load(priv.PublicKey().GetVerificationScript())
|
ic.VM.Load(priv.PublicKey().GetVerificationScript())
|
||||||
|
@ -86,27 +113,72 @@ func TestNEO_Vote(t *testing.T) {
|
||||||
|
|
||||||
// Register and give some value to the last validator.
|
// Register and give some value to the last validator.
|
||||||
require.NoError(t, neo.RegisterCandidateInternal(ic, candidates[0]))
|
require.NoError(t, neo.RegisterCandidateInternal(ic, candidates[0]))
|
||||||
priv := testchain.PrivateKeyByID(0)
|
priv := accs[0].PrivateKey()
|
||||||
h := priv.GetScriptHash()
|
h := priv.GetScriptHash()
|
||||||
setSigner(tx, h)
|
setSigner(tx, h)
|
||||||
ic.VM.Load(priv.PublicKey().GetVerificationScript())
|
ic.VM.Load(priv.PublicKey().GetVerificationScript())
|
||||||
require.NoError(t, neo.VoteInternal(ic, h, candidates[0]))
|
require.NoError(t, neo.VoteInternal(ic, h, candidates[0]))
|
||||||
|
|
||||||
for i := testchain.ValidatorsCount; i < testchain.CommitteeSize(); i++ {
|
ic.DAO.Persist()
|
||||||
priv := testchain.PrivateKey(i)
|
|
||||||
require.NoError(t, neo.RegisterCandidateInternal(ic, priv.PublicKey()))
|
|
||||||
}
|
|
||||||
|
|
||||||
advanceChain(t)
|
advanceChain(t)
|
||||||
pubs, err = neo.ComputeNextBlockValidators(bc, ic.DAO)
|
pubs, err = neo.ComputeNextBlockValidators(bc, ic.DAO)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
sortedCandidates := candidates.Copy()
|
sortedCandidates := candidates.Copy()[:testchain.Size()]
|
||||||
sort.Sort(sortedCandidates)
|
sort.Sort(sortedCandidates)
|
||||||
require.EqualValues(t, sortedCandidates, pubs)
|
require.EqualValues(t, sortedCandidates, pubs)
|
||||||
|
|
||||||
pubs = neo.GetNextBlockValidatorsInternal()
|
pubs = neo.GetNextBlockValidatorsInternal()
|
||||||
require.EqualValues(t, sortedCandidates, pubs)
|
require.EqualValues(t, sortedCandidates, pubs)
|
||||||
|
|
||||||
|
t.Run("check voter rewards", func(t *testing.T) {
|
||||||
|
gasBalance := make([]*big.Int, len(accs))
|
||||||
|
neoBalance := make([]*big.Int, len(accs))
|
||||||
|
txs := make([]*transaction.Transaction, 0, len(accs))
|
||||||
|
for i := range accs {
|
||||||
|
w := io.NewBufBinWriter()
|
||||||
|
h := accs[i].PrivateKey().GetScriptHash()
|
||||||
|
gasBalance[i] = bc.GetUtilityTokenBalance(h)
|
||||||
|
neoBalance[i], _ = bc.GetGoverningTokenBalance(h)
|
||||||
|
emit.AppCallWithOperationAndArgs(w.BinWriter, bc.contracts.NEO.Hash, "transfer",
|
||||||
|
h.BytesBE(), h.BytesBE(), int64(1))
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.ASSERT)
|
||||||
|
require.NoError(t, w.Err)
|
||||||
|
tx := transaction.New(netmode.UnitTestNet, w.Bytes(), 0)
|
||||||
|
tx.ValidUntilBlock = bc.BlockHeight() + 1
|
||||||
|
tx.NetworkFee = 2_000_000
|
||||||
|
tx.SystemFee = 10_000_000
|
||||||
|
setSigner(tx, h)
|
||||||
|
require.NoError(t, accs[i].SignTx(tx))
|
||||||
|
txs = append(txs, tx)
|
||||||
|
}
|
||||||
|
require.NoError(t, bc.AddBlock(bc.newBlock(txs...)))
|
||||||
|
for _, tx := range txs {
|
||||||
|
checkTxHalt(t, bc, tx.Hash())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GAS increase consists of 2 parts: NEO holding + voting for committee nodes.
|
||||||
|
// Here we check that 2-nd part exists and is proportional to the amount of NEO given.
|
||||||
|
for i := range accs {
|
||||||
|
newGAS := bc.GetUtilityTokenBalance(accs[i].Contract.ScriptHash())
|
||||||
|
newGAS.Sub(newGAS, gasBalance[i])
|
||||||
|
|
||||||
|
gasForHold, err := bc.contracts.NEO.CalculateNEOHolderReward(bc.dao, neoBalance[i], transferBlock, bc.BlockHeight())
|
||||||
|
require.NoError(t, err)
|
||||||
|
newGAS.Sub(newGAS, gasForHold)
|
||||||
|
require.True(t, newGAS.Sign() > 0)
|
||||||
|
gasBalance[i] = newGAS
|
||||||
|
}
|
||||||
|
// First account voted later than the others.
|
||||||
|
require.Equal(t, -1, gasBalance[0].Cmp(gasBalance[1]))
|
||||||
|
for i := 2; i < testchain.ValidatorsCount; i++ {
|
||||||
|
require.Equal(t, 0, gasBalance[i].Cmp(gasBalance[1]))
|
||||||
|
}
|
||||||
|
require.Equal(t, 1, gasBalance[1].Cmp(gasBalance[testchain.ValidatorsCount]))
|
||||||
|
for i := testchain.ValidatorsCount; i < testchain.CommitteeSize(); i++ {
|
||||||
|
require.Equal(t, 0, gasBalance[i].Cmp(gasBalance[testchain.ValidatorsCount]))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
require.NoError(t, neo.UnregisterCandidateInternal(ic, candidates[0]))
|
require.NoError(t, neo.UnregisterCandidateInternal(ic, candidates[0]))
|
||||||
require.Error(t, neo.VoteInternal(ic, h, candidates[0]))
|
require.Error(t, neo.VoteInternal(ic, h, candidates[0]))
|
||||||
advanceChain(t)
|
advanceChain(t)
|
||||||
|
@ -187,11 +259,11 @@ func TestNEO_CalculateBonus(t *testing.T) {
|
||||||
ic.SpawnVM()
|
ic.SpawnVM()
|
||||||
ic.VM.LoadScript([]byte{byte(opcode.RET)})
|
ic.VM.LoadScript([]byte{byte(opcode.RET)})
|
||||||
t.Run("Invalid", func(t *testing.T) {
|
t.Run("Invalid", func(t *testing.T) {
|
||||||
_, err := neo.CalculateBonus(ic, new(big.Int).SetInt64(-1), 0, 1)
|
_, err := neo.CalculateNEOHolderReward(ic.DAO, new(big.Int).SetInt64(-1), 0, 1)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
})
|
})
|
||||||
t.Run("Zero", func(t *testing.T) {
|
t.Run("Zero", func(t *testing.T) {
|
||||||
res, err := neo.CalculateBonus(ic, big.NewInt(0), 0, 100)
|
res, err := neo.CalculateNEOHolderReward(ic.DAO, big.NewInt(0), 0, 100)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.EqualValues(t, 0, res.Int64())
|
require.EqualValues(t, 0, res.Int64())
|
||||||
})
|
})
|
||||||
|
@ -201,7 +273,7 @@ func TestNEO_CalculateBonus(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
res, err := neo.CalculateBonus(ic, big.NewInt(100), 5, 15)
|
res, err := neo.CalculateNEOHolderReward(ic.DAO, big.NewInt(100), 5, 15)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.EqualValues(t, (100*5*5/10)+(100*5*1/10), res.Int64())
|
require.EqualValues(t, (100*5*5/10)+(100*5*1/10), res.Int64())
|
||||||
|
|
||||||
|
@ -217,7 +289,7 @@ func TestNEO_CommitteeBountyOnPersist(t *testing.T) {
|
||||||
hs[i] = testchain.PrivateKeyByID(i).GetScriptHash()
|
hs[i] = testchain.PrivateKeyByID(i).GetScriptHash()
|
||||||
}
|
}
|
||||||
|
|
||||||
const singleBounty = 25000000
|
const singleBounty = 50000000
|
||||||
bs := map[int]int64{0: singleBounty}
|
bs := map[int]int64{0: singleBounty}
|
||||||
checkBalances := func() {
|
checkBalances := func() {
|
||||||
for i := 0; i < testchain.CommitteeSize(); i++ {
|
for i := 0; i < testchain.CommitteeSize(); i++ {
|
||||||
|
|
|
@ -33,7 +33,7 @@ func (chain testChain) ApplyPolicyToTxSet([]*transaction.Transaction) []*transac
|
||||||
func (chain testChain) GetConfig() config.ProtocolConfiguration {
|
func (chain testChain) GetConfig() config.ProtocolConfiguration {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
func (chain testChain) CalculateClaimable(*big.Int, uint32, uint32) *big.Int {
|
func (chain testChain) CalculateClaimable(util.Uint160, uint32) (*big.Int, error) {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -951,13 +951,16 @@ func (s *Server) getUnclaimedGas(ps request.Params) (interface{}, *response.Erro
|
||||||
return nil, response.ErrInvalidParams
|
return nil, response.ErrInvalidParams
|
||||||
}
|
}
|
||||||
|
|
||||||
neo, neoHeight := s.chain.GetGoverningTokenBalance(u)
|
neo, _ := s.chain.GetGoverningTokenBalance(u)
|
||||||
if neo.Sign() == 0 {
|
if neo.Sign() == 0 {
|
||||||
return result.UnclaimedGas{
|
return result.UnclaimedGas{
|
||||||
Address: u,
|
Address: u,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
gas := s.chain.CalculateClaimable(neo, neoHeight, s.chain.BlockHeight()+1) // +1 as in C#, for the next block.
|
gas, err := s.chain.CalculateClaimable(u, s.chain.BlockHeight()+1) // +1 as in C#, for the next block.
|
||||||
|
if err != nil {
|
||||||
|
return nil, response.NewInternalServerError("can't calculate claimable", err)
|
||||||
|
}
|
||||||
return result.UnclaimedGas{
|
return result.UnclaimedGas{
|
||||||
Address: u,
|
Address: u,
|
||||||
Unclaimed: *gas,
|
Unclaimed: *gas,
|
||||||
|
|
|
@ -1214,7 +1214,7 @@ func checkNep5Balances(t *testing.T, e *executor, acc interface{}) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Asset: e.chain.UtilityTokenHash(),
|
Asset: e.chain.UtilityTokenHash(),
|
||||||
Amount: "799.59641770",
|
Amount: "800.09641770",
|
||||||
LastUpdated: 7,
|
LastUpdated: 7,
|
||||||
}},
|
}},
|
||||||
Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(),
|
Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(),
|
||||||
|
@ -1361,7 +1361,7 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcv
|
||||||
Timestamp: blockGASBounty.Timestamp,
|
Timestamp: blockGASBounty.Timestamp,
|
||||||
Asset: e.chain.UtilityTokenHash(),
|
Asset: e.chain.UtilityTokenHash(),
|
||||||
Address: "",
|
Address: "",
|
||||||
Amount: "0.25000000",
|
Amount: "0.50000000",
|
||||||
Index: 6,
|
Index: 6,
|
||||||
NotifyIndex: 0,
|
NotifyIndex: 0,
|
||||||
TxHash: blockGASBounty.Hash(),
|
TxHash: blockGASBounty.Hash(),
|
||||||
|
@ -1406,7 +1406,7 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcv
|
||||||
Timestamp: blockGASBounty0.Timestamp,
|
Timestamp: blockGASBounty0.Timestamp,
|
||||||
Asset: e.chain.UtilityTokenHash(),
|
Asset: e.chain.UtilityTokenHash(),
|
||||||
Address: "",
|
Address: "",
|
||||||
Amount: "0.25000000",
|
Amount: "0.50000000",
|
||||||
Index: 0,
|
Index: 0,
|
||||||
TxHash: blockGASBounty0.Hash(),
|
TxHash: blockGASBounty0.Hash(),
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue