forked from TrueCloudLab/neoneo-go
Merge pull request #1364 from nspcc-dev/fix/economy
Update GAS distribution logic, part1
This commit is contained in:
commit
e4d82f5956
14 changed files with 628 additions and 209 deletions
|
@ -57,9 +57,7 @@ var (
|
|||
ErrInvalidBlockIndex error = errors.New("invalid block index")
|
||||
)
|
||||
var (
|
||||
genAmount = []int{6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
|
||||
decrementInterval = 2000000
|
||||
persistInterval = 1 * time.Second
|
||||
persistInterval = 1 * time.Second
|
||||
)
|
||||
|
||||
// Blockchain represents the blockchain. It maintans internal state representing
|
||||
|
@ -98,9 +96,6 @@ type Blockchain struct {
|
|||
// Number of headers stored in the chain file.
|
||||
storedHeaderCount uint32
|
||||
|
||||
generationAmount []int
|
||||
decrementInterval int
|
||||
|
||||
// Header hashes list with associated lock.
|
||||
headerHashesLock sync.RWMutex
|
||||
headerHashes []util.Uint256
|
||||
|
@ -162,9 +157,6 @@ func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration, log *zap.L
|
|||
subCh: make(chan interface{}),
|
||||
unsubCh: make(chan interface{}),
|
||||
|
||||
generationAmount: genAmount,
|
||||
decrementInterval: decrementInterval,
|
||||
|
||||
contracts: *native.NewContracts(),
|
||||
}
|
||||
|
||||
|
@ -373,6 +365,19 @@ func (bc *Blockchain) notificationDispatcher() {
|
|||
ch <- tx
|
||||
}
|
||||
}
|
||||
|
||||
aer = event.appExecResults[aerIdx]
|
||||
if !aer.TxHash.Equals(event.block.Hash()) {
|
||||
panic("inconsistent application execution results")
|
||||
}
|
||||
for ch := range executionFeed {
|
||||
ch <- aer
|
||||
}
|
||||
for i := range aer.Events {
|
||||
for ch := range notificationFeed {
|
||||
ch <- &aer.Events[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
for ch := range blockFeed {
|
||||
ch <- event.block
|
||||
|
@ -536,7 +541,7 @@ func (bc *Blockchain) GetStateRoot(height uint32) (*state.MPTRootState, error) {
|
|||
func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error {
|
||||
cache := dao.NewCached(bc.dao)
|
||||
writeBuf := io.NewBufBinWriter()
|
||||
appExecResults := make([]*state.AppExecResult, 0, 1+len(block.Transactions))
|
||||
appExecResults := make([]*state.AppExecResult, 0, 2+len(block.Transactions))
|
||||
if err := cache.StoreAsBlock(block, writeBuf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -548,28 +553,12 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
|||
writeBuf.Reset()
|
||||
|
||||
if block.Index > 0 {
|
||||
systemInterop := bc.newInteropContext(trigger.System, cache, block, nil)
|
||||
v := systemInterop.SpawnVM()
|
||||
v.LoadScriptWithFlags(bc.contracts.GetPersistScript(), smartcontract.AllowModifyStates|smartcontract.AllowCall)
|
||||
v.SetPriceGetter(getPrice)
|
||||
if err := v.Run(); err != nil {
|
||||
return fmt.Errorf("onPersist run failed: %w", err)
|
||||
} else if _, err := systemInterop.DAO.Persist(); err != nil {
|
||||
return fmt.Errorf("can't save onPersist changes: %w", err)
|
||||
}
|
||||
for i := range systemInterop.Notifications {
|
||||
bc.handleNotification(&systemInterop.Notifications[i], cache, block, block.Hash())
|
||||
}
|
||||
aer := &state.AppExecResult{
|
||||
TxHash: block.Hash(), // application logs can be retrieved by block hash
|
||||
Trigger: trigger.System,
|
||||
VMState: v.State(),
|
||||
GasConsumed: v.GasConsumed(),
|
||||
Stack: v.Estack().ToArray(),
|
||||
Events: systemInterop.Notifications,
|
||||
aer, err := bc.runPersist(bc.contracts.GetPersistScript(), block, cache)
|
||||
if err != nil {
|
||||
return fmt.Errorf("onPersist failed: %w", err)
|
||||
}
|
||||
appExecResults = append(appExecResults, aer)
|
||||
err := cache.PutAppExecResult(aer, writeBuf)
|
||||
err = cache.PutAppExecResult(aer, writeBuf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to store onPersist exec result: %w", err)
|
||||
}
|
||||
|
@ -619,6 +608,17 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
|||
writeBuf.Reset()
|
||||
}
|
||||
|
||||
aer, err := bc.runPersist(bc.contracts.GetPostPersistScript(), block, cache)
|
||||
if err != nil {
|
||||
return fmt.Errorf("postPersist failed: %w", err)
|
||||
}
|
||||
appExecResults = append(appExecResults, aer)
|
||||
err = cache.PutAppExecResult(aer, writeBuf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to store postPersist exec result: %w", err)
|
||||
}
|
||||
writeBuf.Reset()
|
||||
|
||||
root := bc.dao.MPT.StateRoot()
|
||||
var prevHash util.Uint256
|
||||
if block.Index > 0 {
|
||||
|
@ -628,7 +628,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
|||
}
|
||||
prevHash = hash.DoubleSha256(prev.GetSignedPart())
|
||||
}
|
||||
err := bc.AddStateRoot(&state.MPTRoot{
|
||||
err = bc.AddStateRoot(&state.MPTRoot{
|
||||
MPTRootBase: state.MPTRootBase{
|
||||
Index: block.Index,
|
||||
PrevHash: prevHash,
|
||||
|
@ -672,6 +672,29 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
|||
return nil
|
||||
}
|
||||
|
||||
func (bc *Blockchain) runPersist(script []byte, block *block.Block, cache *dao.Cached) (*state.AppExecResult, error) {
|
||||
systemInterop := bc.newInteropContext(trigger.System, cache, block, nil)
|
||||
v := systemInterop.SpawnVM()
|
||||
v.LoadScriptWithFlags(script, smartcontract.AllowModifyStates|smartcontract.AllowCall)
|
||||
v.SetPriceGetter(getPrice)
|
||||
if err := v.Run(); err != nil {
|
||||
return nil, fmt.Errorf("VM has failed: %w", err)
|
||||
} else if _, err := systemInterop.DAO.Persist(); err != nil {
|
||||
return nil, fmt.Errorf("can't save changes: %w", err)
|
||||
}
|
||||
for i := range systemInterop.Notifications {
|
||||
bc.handleNotification(&systemInterop.Notifications[i], cache, block, block.Hash())
|
||||
}
|
||||
return &state.AppExecResult{
|
||||
TxHash: block.Hash(), // application logs can be retrieved by block hash
|
||||
Trigger: trigger.System,
|
||||
VMState: v.State(),
|
||||
GasConsumed: v.GasConsumed(),
|
||||
Stack: v.Estack().ToArray(),
|
||||
Events: systemInterop.Notifications,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (bc *Blockchain) handleNotification(note *state.NotificationEvent, d *dao.Cached, b *block.Block, h util.Uint256) {
|
||||
if note.Name != "transfer" && note.Name != "Transfer" {
|
||||
return
|
||||
|
@ -1083,36 +1106,11 @@ func (bc *Blockchain) UnsubscribeFromExecutions(ch chan<- *state.AppExecResult)
|
|||
}
|
||||
|
||||
// CalculateClaimable calculates the amount of GAS generated by owning specified
|
||||
// amount of NEO between specified blocks. The amount of NEO being passed is in
|
||||
// its natural non-divisible form (1 NEO as 1, 2 NEO as 2, no multiplication by
|
||||
// 10⁸ is needed as for Fixed8).
|
||||
// amount of NEO between specified blocks.
|
||||
func (bc *Blockchain) CalculateClaimable(value *big.Int, startHeight, endHeight uint32) *big.Int {
|
||||
var amount int64
|
||||
di := uint32(bc.decrementInterval)
|
||||
|
||||
ustart := startHeight / di
|
||||
if genSize := uint32(len(bc.generationAmount)); ustart < genSize {
|
||||
uend := endHeight / di
|
||||
iend := endHeight % di
|
||||
if uend >= genSize {
|
||||
uend = genSize - 1
|
||||
iend = di
|
||||
} else if iend == 0 {
|
||||
uend--
|
||||
iend = di
|
||||
}
|
||||
|
||||
istart := startHeight % di
|
||||
for ustart < uend {
|
||||
amount += int64(di-istart) * int64(bc.generationAmount[ustart])
|
||||
ustart++
|
||||
istart = 0
|
||||
}
|
||||
|
||||
amount += int64(iend-istart) * int64(bc.generationAmount[ustart])
|
||||
}
|
||||
|
||||
return new(big.Int).Mul(big.NewInt(amount), value)
|
||||
ic := bc.newInteropContext(trigger.System, bc.dao, nil, nil)
|
||||
res, _ := bc.contracts.NEO.CalculateBonus(ic, value, startHeight, endHeight)
|
||||
return res
|
||||
}
|
||||
|
||||
// FeePerByte returns transaction network fee per byte.
|
||||
|
@ -1245,10 +1243,7 @@ func (bc *Blockchain) verifyTxAttributes(tx *transaction.Transaction) error {
|
|||
for i := range tx.Attributes {
|
||||
switch tx.Attributes[i].Type {
|
||||
case transaction.HighPriority:
|
||||
pubs, err := bc.contracts.NEO.GetCommitteeMembers(bc, bc.dao)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pubs := bc.contracts.NEO.GetCommitteeMembers()
|
||||
s, err := smartcontract.CreateMajorityMultiSigRedeemScript(pubs)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1409,22 +1404,19 @@ func (bc *Blockchain) GetStandByCommittee() keys.PublicKeys {
|
|||
|
||||
// GetCommittee returns the sorted list of public keys of nodes in committee.
|
||||
func (bc *Blockchain) GetCommittee() (keys.PublicKeys, error) {
|
||||
pubs, err := bc.contracts.NEO.GetCommitteeMembers(bc, bc.dao)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pubs := bc.contracts.NEO.GetCommitteeMembers()
|
||||
sort.Sort(pubs)
|
||||
return pubs, nil
|
||||
}
|
||||
|
||||
// GetValidators returns current validators.
|
||||
func (bc *Blockchain) GetValidators() ([]*keys.PublicKey, error) {
|
||||
return bc.contracts.NEO.GetValidatorsInternal(bc, bc.dao)
|
||||
return bc.contracts.NEO.ComputeNextBlockValidators(bc, bc.dao)
|
||||
}
|
||||
|
||||
// GetNextBlockValidators returns next block validators.
|
||||
func (bc *Blockchain) GetNextBlockValidators() ([]*keys.PublicKey, error) {
|
||||
return bc.contracts.NEO.GetNextBlockValidatorsInternal(bc, bc.dao)
|
||||
return bc.contracts.NEO.GetNextBlockValidatorsInternal(), nil
|
||||
}
|
||||
|
||||
// GetEnrollments returns all registered validators.
|
||||
|
|
|
@ -536,29 +536,12 @@ func TestGetClaimable(t *testing.T) {
|
|||
bc := newTestChain(t)
|
||||
defer bc.Close()
|
||||
|
||||
bc.generationAmount = []int{4, 3, 2, 1}
|
||||
bc.decrementInterval = 2
|
||||
_, err := bc.genBlocks(10)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("first generation period", func(t *testing.T) {
|
||||
amount := bc.CalculateClaimable(big.NewInt(1), 0, 2)
|
||||
require.EqualValues(t, big.NewInt(8), amount)
|
||||
})
|
||||
|
||||
t.Run("a number of full periods", func(t *testing.T) {
|
||||
amount := bc.CalculateClaimable(big.NewInt(1), 0, 6)
|
||||
require.EqualValues(t, big.NewInt(4+4+3+3+2+2), amount)
|
||||
})
|
||||
|
||||
t.Run("start from the 2-nd block", func(t *testing.T) {
|
||||
amount := bc.CalculateClaimable(big.NewInt(1), 1, 7)
|
||||
require.EqualValues(t, big.NewInt(4+3+3+2+2+1), amount)
|
||||
})
|
||||
|
||||
t.Run("end height after generation has ended", func(t *testing.T) {
|
||||
amount := bc.CalculateClaimable(big.NewInt(1), 1, 10)
|
||||
require.EqualValues(t, big.NewInt(4+3+3+2+2+1+1), amount)
|
||||
require.EqualValues(t, big.NewInt(1), amount)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -604,8 +587,8 @@ func TestSubscriptions(t *testing.T) {
|
|||
blocks, err := bc.genBlocks(1)
|
||||
require.NoError(t, err)
|
||||
require.Eventually(t, func() bool { return len(blockCh) != 0 }, time.Second, 10*time.Millisecond)
|
||||
assert.Empty(t, notificationCh)
|
||||
assert.Len(t, executionCh, 1)
|
||||
assert.Len(t, notificationCh, 1) // validator bounty
|
||||
assert.Len(t, executionCh, 2)
|
||||
assert.Empty(t, txCh)
|
||||
|
||||
b := <-blockCh
|
||||
|
@ -614,6 +597,11 @@ func TestSubscriptions(t *testing.T) {
|
|||
|
||||
aer := <-executionCh
|
||||
assert.Equal(t, b.Hash(), aer.TxHash)
|
||||
aer = <-executionCh
|
||||
assert.Equal(t, b.Hash(), aer.TxHash)
|
||||
|
||||
notif := <-notificationCh
|
||||
require.Equal(t, bc.UtilityTokenHash(), notif.ScriptHash)
|
||||
|
||||
script := io.NewBufBinWriter()
|
||||
emit.Bytes(script.BinWriter, []byte("yay!"))
|
||||
|
@ -682,8 +670,15 @@ func TestSubscriptions(t *testing.T) {
|
|||
}
|
||||
}
|
||||
assert.Empty(t, txCh)
|
||||
assert.Empty(t, notificationCh)
|
||||
assert.Empty(t, executionCh)
|
||||
assert.Len(t, notificationCh, 1)
|
||||
assert.Len(t, executionCh, 1)
|
||||
|
||||
notif = <-notificationCh
|
||||
require.Equal(t, bc.UtilityTokenHash(), notif.ScriptHash)
|
||||
|
||||
exec = <-executionCh
|
||||
require.Equal(t, b.Hash(), exec.TxHash)
|
||||
require.Equal(t, exec.VMState, vm.HaltState)
|
||||
|
||||
bc.UnsubscribeFromBlocks(blockCh)
|
||||
bc.UnsubscribeFromTransactions(txCh)
|
||||
|
|
|
@ -177,7 +177,7 @@ func TestCreateBasicChain(t *testing.T) {
|
|||
priv0 := testchain.PrivateKeyByID(0)
|
||||
priv0ScriptHash := priv0.GetScriptHash()
|
||||
|
||||
require.Equal(t, big.NewInt(0), bc.GetUtilityTokenBalance(priv0ScriptHash))
|
||||
require.Equal(t, big.NewInt(2500_0000), bc.GetUtilityTokenBalance(priv0ScriptHash)) // gas bounty
|
||||
// Move some NEO to one simple account.
|
||||
txMoveNeo := newNEP5Transfer(neoHash, neoOwner, priv0ScriptHash, neoAmount)
|
||||
txMoveNeo.ValidUntilBlock = validUntilBlock
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
package native
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||
"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"
|
||||
|
@ -16,6 +19,8 @@ type Contracts struct {
|
|||
Contracts []interop.Contract
|
||||
// persistScript is vm script which executes "onPersist" method of every native contract.
|
||||
persistScript []byte
|
||||
// postPersistScript is vm script which executes "postPersist" method of every native contract.
|
||||
postPersistScript []byte
|
||||
}
|
||||
|
||||
// ByHash returns native contract with the specified hash.
|
||||
|
@ -71,3 +76,33 @@ func (cs *Contracts) GetPersistScript() []byte {
|
|||
cs.persistScript = w.Bytes()
|
||||
return cs.persistScript
|
||||
}
|
||||
|
||||
// GetPostPersistScript returns VM script calling "postPersist" method of some native contracts.
|
||||
func (cs *Contracts) GetPostPersistScript() []byte {
|
||||
if cs.postPersistScript != nil {
|
||||
return cs.postPersistScript
|
||||
}
|
||||
w := io.NewBufBinWriter()
|
||||
for i := range cs.Contracts {
|
||||
md := cs.Contracts[i].Metadata()
|
||||
// Not every contract is persisted:
|
||||
// https://github.com/neo-project/neo/blob/master/src/neo/Ledger/Blockchain.cs#L103
|
||||
if md.ContractID == policyContractID || md.ContractID == gasContractID {
|
||||
continue
|
||||
}
|
||||
emit.Int(w.BinWriter, 0)
|
||||
emit.Opcode(w.BinWriter, opcode.NEWARRAY)
|
||||
emit.String(w.BinWriter, "postPersist")
|
||||
emit.AppCall(w.BinWriter, md.Hash)
|
||||
emit.Opcode(w.BinWriter, opcode.DROP)
|
||||
}
|
||||
cs.postPersistScript = w.Bytes()
|
||||
return cs.postPersistScript
|
||||
}
|
||||
|
||||
func postPersistBase(ic *interop.Context) error {
|
||||
if ic.Trigger != trigger.System {
|
||||
return errors.New("'postPersist' should be trigered by system")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package native
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
|
@ -89,10 +88,7 @@ func (g *GAS) OnPersist(ic *interop.Context) error {
|
|||
absAmount := big.NewInt(tx.SystemFee + tx.NetworkFee)
|
||||
g.burn(ic, tx.Sender(), absAmount)
|
||||
}
|
||||
validators, err := g.NEO.getNextBlockValidatorsInternal(ic.Chain, ic.DAO)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get block validators: %w", err)
|
||||
}
|
||||
validators := g.NEO.GetNextBlockValidatorsInternal()
|
||||
primary := validators[ic.Block.ConsensusData.PrimaryIndex].GetScriptHash()
|
||||
var netFee int64
|
||||
for _, tx := range ic.Block.Transactions {
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"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/crypto/hash"
|
||||
"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/smartcontract"
|
||||
|
@ -29,6 +30,11 @@ type NEO struct {
|
|||
votesChanged atomic.Value
|
||||
nextValidators atomic.Value
|
||||
validators atomic.Value
|
||||
// committee contains cached committee members and
|
||||
// is updated once in a while depending on committee size
|
||||
// (every 28 blocks for mainnet). It's value
|
||||
// is always equal to value stored by `prefixCommittee`.
|
||||
committee atomic.Value
|
||||
}
|
||||
|
||||
// keyWithVotes is a serialized key with votes balance. It's not deserialized
|
||||
|
@ -48,15 +54,22 @@ const (
|
|||
prefixCandidate = 33
|
||||
// prefixVotersCount is a prefix for storing total amount of NEO of voters.
|
||||
prefixVotersCount = 1
|
||||
// prefixGasPerBlock is a prefix for storing amount of GAS generated per block.
|
||||
prefixGASPerBlock = 29
|
||||
// effectiveVoterTurnout represents minimal ratio of total supply to total amount voted value
|
||||
// which is require to use non-standby validators.
|
||||
effectiveVoterTurnout = 5
|
||||
// neoHolderRewardRatio is a percent of generated GAS that is distributed to NEO holders.
|
||||
neoHolderRewardRatio = 10
|
||||
// neoHolderRewardRatio is a percent of generated GAS that is distributed to committee.
|
||||
committeeRewardRatio = 5
|
||||
// neoHolderRewardRatio is a percent of generated GAS that is distributed to voters.
|
||||
voterRewardRatio = 85
|
||||
)
|
||||
|
||||
var (
|
||||
// nextValidatorsKey is a key used to store validators for the
|
||||
// next block.
|
||||
nextValidatorsKey = []byte{14}
|
||||
// prefixCommittee is a key used to store committee.
|
||||
prefixCommittee = []byte{14}
|
||||
)
|
||||
|
||||
// makeValidatorKey creates a key from account script hash.
|
||||
|
@ -77,6 +90,7 @@ func NewNEO() *NEO {
|
|||
nep5.decimals = 0
|
||||
nep5.factor = 1
|
||||
nep5.onPersist = chainOnPersist(nep5.OnPersist, n.OnPersist)
|
||||
nep5.postPersist = chainOnPersist(nep5.postPersist, n.PostPersist)
|
||||
nep5.incBalance = n.increaseBalance
|
||||
nep5.ContractID = neoContractID
|
||||
|
||||
|
@ -84,11 +98,16 @@ func NewNEO() *NEO {
|
|||
n.votesChanged.Store(true)
|
||||
n.nextValidators.Store(keys.PublicKeys(nil))
|
||||
n.validators.Store(keys.PublicKeys(nil))
|
||||
n.committee.Store(keys.PublicKeys(nil))
|
||||
|
||||
onp := n.Methods["onPersist"]
|
||||
onp.Func = getOnPersistWrapper(n.onPersist)
|
||||
n.Methods["onPersist"] = onp
|
||||
|
||||
pp := n.Methods["postPersist"]
|
||||
pp.Func = getOnPersistWrapper(n.postPersist)
|
||||
n.Methods["postPersist"] = pp
|
||||
|
||||
desc := newDescriptor("unclaimedGas", smartcontract.IntegerType,
|
||||
manifest.NewParameter("account", smartcontract.Hash160Type),
|
||||
manifest.NewParameter("end", smartcontract.IntegerType))
|
||||
|
@ -119,14 +138,19 @@ func NewNEO() *NEO {
|
|||
md = newMethodAndPrice(n.getCommittee, 100000000, smartcontract.AllowStates)
|
||||
n.AddMethod(md, desc, true)
|
||||
|
||||
desc = newDescriptor("getValidators", smartcontract.ArrayType)
|
||||
md = newMethodAndPrice(n.getValidators, 100000000, smartcontract.AllowStates)
|
||||
n.AddMethod(md, desc, true)
|
||||
|
||||
desc = newDescriptor("getNextBlockValidators", smartcontract.ArrayType)
|
||||
md = newMethodAndPrice(n.getNextBlockValidators, 100000000, smartcontract.AllowStates)
|
||||
n.AddMethod(md, desc, true)
|
||||
|
||||
desc = newDescriptor("getGasPerBlock", smartcontract.IntegerType)
|
||||
md = newMethodAndPrice(n.getGASPerBlock, 100_0000, smartcontract.AllowStates)
|
||||
n.AddMethod(md, desc, false)
|
||||
|
||||
desc = newDescriptor("setGasPerBlock", smartcontract.BoolType,
|
||||
manifest.NewParameter("gasPerBlock", smartcontract.IntegerType))
|
||||
md = newMethodAndPrice(n.setGASPerBlock, 500_0000, smartcontract.AllowModifyStates)
|
||||
n.AddMethod(md, desc, false)
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
|
@ -140,12 +164,26 @@ func (n *NEO) Initialize(ic *interop.Context) error {
|
|||
return errors.New("already initialized")
|
||||
}
|
||||
|
||||
committee := ic.Chain.GetStandByCommittee()
|
||||
n.committee.Store(committee)
|
||||
n.updateNextValidators(committee, ic.Chain)
|
||||
|
||||
err := ic.DAO.PutStorageItem(n.ContractID, prefixCommittee, &state.StorageItem{Value: committee.Bytes()})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h, err := getStandbyValidatorsHash(ic)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n.mint(ic, h, big.NewInt(NEOTotalSupply))
|
||||
|
||||
gr := &state.GASRecord{{Index: 0, GASPerBlock: *big.NewInt(5 * GASFactor)}}
|
||||
err = ic.DAO.PutStorageItem(n.ContractID, []byte{prefixGASPerBlock}, &state.StorageItem{Value: gr.Bytes()})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ic.DAO.PutStorageItem(n.ContractID, []byte{prefixVotersCount}, &state.StorageItem{Value: []byte{}})
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -154,33 +192,59 @@ func (n *NEO) Initialize(ic *interop.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// OnPersist implements Contract interface.
|
||||
func (n *NEO) OnPersist(ic *interop.Context) error {
|
||||
if !n.votesChanged.Load().(bool) {
|
||||
return nil
|
||||
func (n *NEO) updateNextValidators(committee keys.PublicKeys, bc blockchainer.Blockchainer) {
|
||||
nextVals := committee[:bc.GetConfig().ValidatorsCount].Copy()
|
||||
sort.Sort(nextVals)
|
||||
n.nextValidators.Store(nextVals)
|
||||
}
|
||||
|
||||
func (n *NEO) updateCommittee(ic *interop.Context) error {
|
||||
votesChanged := n.votesChanged.Load().(bool)
|
||||
if !votesChanged {
|
||||
// We need to put in storage anyway, as it affects dumps
|
||||
committee := n.committee.Load().(keys.PublicKeys)
|
||||
si := &state.StorageItem{Value: committee.Bytes()}
|
||||
return ic.DAO.PutStorageItem(n.ContractID, prefixCommittee, si)
|
||||
}
|
||||
pubs, err := n.GetValidatorsInternal(ic.Chain, ic.DAO)
|
||||
|
||||
committee, err := n.ComputeCommitteeMembers(ic.Chain, ic.DAO)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
prev := n.nextValidators.Load().(keys.PublicKeys)
|
||||
if len(prev) == len(pubs) {
|
||||
var needUpdate bool
|
||||
for i := range pubs {
|
||||
if !pubs[i].Equal(prev[i]) {
|
||||
needUpdate = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !needUpdate {
|
||||
return nil
|
||||
n.committee.Store(committee)
|
||||
n.updateNextValidators(committee, ic.Chain)
|
||||
n.votesChanged.Store(false)
|
||||
si := &state.StorageItem{Value: committee.Bytes()}
|
||||
return ic.DAO.PutStorageItem(n.ContractID, prefixCommittee, si)
|
||||
}
|
||||
|
||||
func shouldUpdateCommittee(h uint32, bc blockchainer.Blockchainer) bool {
|
||||
cfg := bc.GetConfig()
|
||||
r := cfg.ValidatorsCount + len(cfg.StandbyCommittee)
|
||||
return h%uint32(r) == 0
|
||||
}
|
||||
|
||||
// OnPersist implements Contract interface.
|
||||
func (n *NEO) OnPersist(ic *interop.Context) error {
|
||||
if shouldUpdateCommittee(ic.Block.Index, ic.Chain) {
|
||||
if err := n.updateCommittee(ic); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
n.votesChanged.Store(false)
|
||||
n.nextValidators.Store(pubs)
|
||||
si := new(state.StorageItem)
|
||||
si.Value = pubs.Bytes()
|
||||
return ic.DAO.PutStorageItem(n.ContractID, nextValidatorsKey, si)
|
||||
return nil
|
||||
}
|
||||
|
||||
// PostPersist implements Contract interface.
|
||||
func (n *NEO) PostPersist(ic *interop.Context) error {
|
||||
gas, err := n.GetGASPerBlock(ic, ic.Block.Index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pubs := n.GetCommitteeMembers()
|
||||
index := int(ic.Block.Index) % len(ic.Chain.GetConfig().StandbyCommittee)
|
||||
gas.Mul(gas, big.NewInt(committeeRewardRatio))
|
||||
n.GAS.mint(ic, pubs[index].GetScriptHash(), gas.Div(gas, big.NewInt(100)))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NEO) increaseBalance(ic *interop.Context, h util.Uint160, si *state.StorageItem, amount *big.Int) error {
|
||||
|
@ -219,7 +283,10 @@ func (n *NEO) distributeGas(ic *interop.Context, h util.Uint160, acc *state.NEOB
|
|||
if ic.Block == nil || ic.Block.Index == 0 {
|
||||
return nil
|
||||
}
|
||||
gen := ic.Chain.CalculateClaimable(&acc.Balance, acc.BalanceHeight, ic.Block.Index)
|
||||
gen, err := n.CalculateBonus(ic, &acc.Balance, acc.BalanceHeight, ic.Block.Index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
acc.BalanceHeight = ic.Block.Index
|
||||
n.GAS.mint(ic, h, gen)
|
||||
return nil
|
||||
|
@ -234,10 +301,117 @@ func (n *NEO) unclaimedGas(ic *interop.Context, args []stackitem.Item) stackitem
|
|||
}
|
||||
tr := bs.Trackers[n.ContractID]
|
||||
|
||||
gen := ic.Chain.CalculateClaimable(&tr.Balance, tr.LastUpdatedBlock, end)
|
||||
gen, err := n.CalculateBonus(ic, &tr.Balance, tr.LastUpdatedBlock, end)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return stackitem.NewBigInteger(gen)
|
||||
}
|
||||
|
||||
func (n *NEO) getGASPerBlock(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||
gas, err := n.GetGASPerBlock(ic, ic.Block.Index)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return stackitem.NewBigInteger(gas)
|
||||
}
|
||||
|
||||
// GetGASPerBlock returns gas generated for block with provided index.
|
||||
func (n *NEO) GetGASPerBlock(ic *interop.Context, index uint32) (*big.Int, error) {
|
||||
si := ic.DAO.GetStorageItem(n.ContractID, []byte{prefixGASPerBlock})
|
||||
var gr state.GASRecord
|
||||
if err := gr.FromBytes(si.Value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := len(gr) - 1; i >= 0; i-- {
|
||||
if gr[i].Index <= index {
|
||||
return &gr[i].GASPerBlock, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("contract not initialized")
|
||||
}
|
||||
|
||||
// GetCommitteeAddress returns address of the committee.
|
||||
func (n *NEO) GetCommitteeAddress(bc blockchainer.Blockchainer, d dao.DAO) (util.Uint160, error) {
|
||||
pubs := n.GetCommitteeMembers()
|
||||
script, err := smartcontract.CreateMajorityMultiSigRedeemScript(pubs)
|
||||
if err != nil {
|
||||
return util.Uint160{}, err
|
||||
}
|
||||
return hash.Hash160(script), nil
|
||||
}
|
||||
|
||||
func (n *NEO) setGASPerBlock(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
gas := toBigInt(args[0])
|
||||
ok, err := n.SetGASPerBlock(ic, ic.Block.Index+1, gas)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return stackitem.NewBool(ok)
|
||||
}
|
||||
|
||||
// SetGASPerBlock sets gas generated for blocks after index.
|
||||
func (n *NEO) SetGASPerBlock(ic *interop.Context, index uint32, gas *big.Int) (bool, error) {
|
||||
if gas.Sign() == -1 || gas.Cmp(big.NewInt(10*GASFactor)) == 1 {
|
||||
return false, errors.New("invalid value for GASPerBlock")
|
||||
}
|
||||
h, err := n.GetCommitteeAddress(ic.Chain, ic.DAO)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
ok, err := runtime.CheckHashedWitness(ic, h)
|
||||
if err != nil || !ok {
|
||||
return ok, err
|
||||
}
|
||||
si := ic.DAO.GetStorageItem(n.ContractID, []byte{prefixGASPerBlock})
|
||||
var gr state.GASRecord
|
||||
if err := gr.FromBytes(si.Value); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(gr) > 0 && gr[len(gr)-1].Index == index {
|
||||
gr[len(gr)-1].GASPerBlock = *gas
|
||||
} else {
|
||||
gr = append(gr, state.GASIndexPair{
|
||||
Index: index,
|
||||
GASPerBlock: *gas,
|
||||
})
|
||||
}
|
||||
return true, ic.DAO.PutStorageItem(n.ContractID, []byte{prefixGASPerBlock}, &state.StorageItem{Value: gr.Bytes()})
|
||||
}
|
||||
|
||||
// CalculateBonus calculates amount of gas generated for holding `value` NEO from start to end block.
|
||||
func (n *NEO) CalculateBonus(ic *interop.Context, value *big.Int, start, end uint32) (*big.Int, error) {
|
||||
if value.Sign() == 0 || start >= end {
|
||||
return big.NewInt(0), nil
|
||||
} else if value.Sign() < 0 {
|
||||
return nil, errors.New("negative value")
|
||||
}
|
||||
si := ic.DAO.GetStorageItem(n.ContractID, []byte{prefixGASPerBlock})
|
||||
var gr state.GASRecord
|
||||
if err := gr.FromBytes(si.Value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var sum, tmp big.Int
|
||||
for i := len(gr) - 1; i >= 0; i-- {
|
||||
if gr[i].Index >= end {
|
||||
continue
|
||||
} else if gr[i].Index <= start {
|
||||
tmp.SetInt64(int64(end - start))
|
||||
tmp.Mul(&tmp, &gr[i].GASPerBlock)
|
||||
sum.Add(&sum, &tmp)
|
||||
break
|
||||
}
|
||||
tmp.SetInt64(int64(end - gr[i].Index))
|
||||
tmp.Mul(&tmp, &gr[i].GASPerBlock)
|
||||
sum.Add(&sum, &tmp)
|
||||
end = gr[i].Index
|
||||
}
|
||||
res := new(big.Int).Mul(value, &sum)
|
||||
res.Mul(res, tmp.SetInt64(neoHolderRewardRatio))
|
||||
res.Div(res, tmp.SetInt64(100*NEOTotalSupply))
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (n *NEO) registerCandidate(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
pub := toPublicKey(args[0])
|
||||
ok, err := runtime.CheckKeyedWitness(ic, pub)
|
||||
|
@ -425,38 +599,23 @@ func (n *NEO) getCandidatesCall(ic *interop.Context, _ []stackitem.Item) stackit
|
|||
return stackitem.NewArray(arr)
|
||||
}
|
||||
|
||||
// GetValidatorsInternal returns a list of current validators.
|
||||
func (n *NEO) GetValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) {
|
||||
// ComputeNextBlockValidators returns an actual list of current validators.
|
||||
func (n *NEO) ComputeNextBlockValidators(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) {
|
||||
if vals := n.validators.Load().(keys.PublicKeys); vals != nil {
|
||||
return vals.Copy(), nil
|
||||
}
|
||||
result, err := n.GetCommitteeMembers(bc, d)
|
||||
result, err := n.ComputeCommitteeMembers(bc, d)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
count := bc.GetConfig().ValidatorsCount
|
||||
if len(result) < count {
|
||||
count = len(result)
|
||||
}
|
||||
result = result[:count]
|
||||
result = result[:bc.GetConfig().ValidatorsCount]
|
||||
sort.Sort(result)
|
||||
n.validators.Store(result)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (n *NEO) getValidators(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||
result, err := n.GetValidatorsInternal(ic.Chain, ic.DAO)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return pubsToArray(result)
|
||||
}
|
||||
|
||||
func (n *NEO) getCommittee(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||
pubs, err := n.GetCommitteeMembers(ic.Chain, ic.DAO)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pubs := n.GetCommitteeMembers()
|
||||
sort.Sort(pubs)
|
||||
return pubsToArray(pubs)
|
||||
}
|
||||
|
@ -473,8 +632,13 @@ func (n *NEO) modifyVoterTurnout(d dao.DAO, amount *big.Int) error {
|
|||
return d.PutStorageItem(n.ContractID, key, si)
|
||||
}
|
||||
|
||||
// GetCommitteeMembers returns public keys of nodes in committee.
|
||||
func (n *NEO) GetCommitteeMembers(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) {
|
||||
// GetCommitteeMembers returns public keys of nodes in committee using cached value.
|
||||
func (n *NEO) GetCommitteeMembers() keys.PublicKeys {
|
||||
return n.committee.Load().(keys.PublicKeys).Copy()
|
||||
}
|
||||
|
||||
// ComputeCommitteeMembers returns public keys of nodes in committee.
|
||||
func (n *NEO) ComputeCommitteeMembers(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) {
|
||||
key := []byte{prefixVotersCount}
|
||||
si := d.GetStorageItem(n.ContractID, key)
|
||||
if si == nil {
|
||||
|
@ -485,7 +649,8 @@ func (n *NEO) GetCommitteeMembers(bc blockchainer.Blockchainer, d dao.DAO) (keys
|
|||
votersCount.Mul(votersCount, big.NewInt(effectiveVoterTurnout))
|
||||
voterTurnout := votersCount.Div(votersCount, n.getTotalSupply(d))
|
||||
if voterTurnout.Sign() != 1 {
|
||||
return bc.GetStandByCommittee(), nil
|
||||
pubs := bc.GetStandByCommittee()
|
||||
return pubs, nil
|
||||
}
|
||||
cs, err := n.getCandidates(d)
|
||||
if err != nil {
|
||||
|
@ -516,34 +681,13 @@ func (n *NEO) GetCommitteeMembers(bc blockchainer.Blockchainer, d dao.DAO) (keys
|
|||
}
|
||||
|
||||
func (n *NEO) getNextBlockValidators(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||
result, err := n.getNextBlockValidatorsInternal(ic.Chain, ic.DAO)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
result := n.GetNextBlockValidatorsInternal()
|
||||
return pubsToArray(result)
|
||||
}
|
||||
|
||||
// GetNextBlockValidatorsInternal returns next block validators.
|
||||
func (n *NEO) GetNextBlockValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) {
|
||||
pubs, err := n.getNextBlockValidatorsInternal(bc, d)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pubs.Copy(), nil
|
||||
}
|
||||
|
||||
// getNextBlockValidatorsInternal returns next block validators.
|
||||
func (n *NEO) getNextBlockValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) {
|
||||
si := d.GetStorageItem(n.ContractID, nextValidatorsKey)
|
||||
if si == nil {
|
||||
return n.GetValidatorsInternal(bc, d)
|
||||
}
|
||||
pubs := keys.PublicKeys{}
|
||||
err := pubs.DecodeBytes(si.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pubs, nil
|
||||
func (n *NEO) GetNextBlockValidatorsInternal() keys.PublicKeys {
|
||||
return n.nextValidators.Load().(keys.PublicKeys).Copy()
|
||||
}
|
||||
|
||||
func pubsToArray(pubs keys.PublicKeys) stackitem.Item {
|
||||
|
|
|
@ -31,11 +31,12 @@ func makeAccountKey(h util.Uint160) []byte {
|
|||
// nep5TokenNative represents NEP-5 token contract.
|
||||
type nep5TokenNative struct {
|
||||
interop.ContractMD
|
||||
symbol string
|
||||
decimals int64
|
||||
factor int64
|
||||
onPersist func(*interop.Context) error
|
||||
incBalance func(*interop.Context, util.Uint160, *state.StorageItem, *big.Int) error
|
||||
symbol string
|
||||
decimals int64
|
||||
factor int64
|
||||
onPersist func(*interop.Context) error
|
||||
postPersist func(*interop.Context) error
|
||||
incBalance func(*interop.Context, util.Uint160, *state.StorageItem, *big.Int) error
|
||||
}
|
||||
|
||||
// totalSupplyKey is the key used to store totalSupply value.
|
||||
|
@ -84,6 +85,10 @@ func newNEP5Native(name string) *nep5TokenNative {
|
|||
md = newMethodAndPrice(getOnPersistWrapper(n.OnPersist), 0, smartcontract.AllowModifyStates)
|
||||
n.AddMethod(md, desc, false)
|
||||
|
||||
desc = newDescriptor("postPersist", smartcontract.VoidType)
|
||||
md = newMethodAndPrice(getOnPersistWrapper(postPersistBase), 0, smartcontract.AllowModifyStates)
|
||||
n.AddMethod(md, desc, false)
|
||||
|
||||
n.AddEvent("Transfer", desc.Parameters...)
|
||||
|
||||
return n
|
||||
|
|
|
@ -124,6 +124,10 @@ func newPolicy() *Policy {
|
|||
desc = newDescriptor("onPersist", smartcontract.VoidType)
|
||||
md = newMethodAndPrice(getOnPersistWrapper(p.OnPersist), 0, smartcontract.AllowModifyStates)
|
||||
p.AddMethod(md, desc, false)
|
||||
|
||||
desc = newDescriptor("postPersist", smartcontract.VoidType)
|
||||
md = newMethodAndPrice(getOnPersistWrapper(postPersistBase), 0, smartcontract.AllowModifyStates)
|
||||
p.AddMethod(md, desc, false)
|
||||
return p
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||
"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/internal/testchain"
|
||||
|
@ -13,6 +14,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -28,13 +30,22 @@ func TestNEO_Vote(t *testing.T) {
|
|||
defer bc.Close()
|
||||
|
||||
neo := bc.contracts.NEO
|
||||
tx := transaction.New(netmode.UnitTestNet, []byte{}, 0)
|
||||
tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
|
||||
ic := bc.newInteropContext(trigger.System, bc.dao, nil, tx)
|
||||
ic.VM = vm.New()
|
||||
ic.Block = bc.newBlock(tx)
|
||||
|
||||
freq := testchain.ValidatorsCount + testchain.CommitteeSize()
|
||||
advanceChain := func(t *testing.T) {
|
||||
for i := 0; i < freq; i++ {
|
||||
require.NoError(t, neo.OnPersist(ic))
|
||||
ic.Block.Index++
|
||||
}
|
||||
}
|
||||
|
||||
standBySorted := bc.GetStandByValidators()
|
||||
sort.Sort(standBySorted)
|
||||
pubs, err := neo.GetValidatorsInternal(bc, ic.DAO)
|
||||
pubs, err := neo.ComputeNextBlockValidators(bc, ic.DAO)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, standBySorted, pubs)
|
||||
|
||||
|
@ -66,13 +77,12 @@ func TestNEO_Vote(t *testing.T) {
|
|||
}
|
||||
|
||||
// We still haven't voted enough validators in.
|
||||
pubs, err = neo.GetValidatorsInternal(bc, ic.DAO)
|
||||
pubs, err = neo.ComputeNextBlockValidators(bc, ic.DAO)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, standBySorted, pubs)
|
||||
|
||||
require.NoError(t, neo.OnPersist(ic))
|
||||
pubs, err = neo.GetNextBlockValidatorsInternal(bc, ic.DAO)
|
||||
require.NoError(t, err)
|
||||
advanceChain(t)
|
||||
pubs = neo.GetNextBlockValidatorsInternal()
|
||||
require.EqualValues(t, standBySorted, pubs)
|
||||
|
||||
// Register and give some value to the last validator.
|
||||
|
@ -88,23 +98,130 @@ func TestNEO_Vote(t *testing.T) {
|
|||
require.NoError(t, neo.RegisterCandidateInternal(ic, priv.PublicKey()))
|
||||
}
|
||||
|
||||
pubs, err = neo.GetValidatorsInternal(bc, ic.DAO)
|
||||
advanceChain(t)
|
||||
pubs, err = neo.ComputeNextBlockValidators(bc, ic.DAO)
|
||||
require.NoError(t, err)
|
||||
sortedCandidates := candidates.Copy()
|
||||
sort.Sort(sortedCandidates)
|
||||
require.EqualValues(t, sortedCandidates, pubs)
|
||||
|
||||
require.NoError(t, neo.OnPersist(ic))
|
||||
pubs, err = neo.GetNextBlockValidatorsInternal(bc, ic.DAO)
|
||||
require.NoError(t, err)
|
||||
pubs = neo.GetNextBlockValidatorsInternal()
|
||||
require.EqualValues(t, sortedCandidates, pubs)
|
||||
|
||||
require.NoError(t, neo.UnregisterCandidateInternal(ic, candidates[0]))
|
||||
require.Error(t, neo.VoteInternal(ic, h, candidates[0]))
|
||||
advanceChain(t)
|
||||
|
||||
pubs, err = neo.GetValidatorsInternal(bc, ic.DAO)
|
||||
pubs, err = neo.ComputeNextBlockValidators(bc, ic.DAO)
|
||||
require.NoError(t, err)
|
||||
for i := range pubs {
|
||||
require.NotEqual(t, candidates[0], pubs[i])
|
||||
}
|
||||
}
|
||||
|
||||
func TestNEO_SetGasPerBlock(t *testing.T) {
|
||||
bc := newTestChain(t)
|
||||
defer bc.Close()
|
||||
|
||||
neo := bc.contracts.NEO
|
||||
tx := transaction.New(netmode.UnitTestNet, []byte{}, 0)
|
||||
ic := bc.newInteropContext(trigger.System, bc.dao, nil, tx)
|
||||
ic.VM = vm.New()
|
||||
|
||||
h, err := neo.GetCommitteeAddress(bc, bc.dao)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("Default", func(t *testing.T) {
|
||||
g, err := neo.GetGASPerBlock(ic, 0)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 5*native.GASFactor, g.Int64())
|
||||
})
|
||||
t.Run("Invalid", func(t *testing.T) {
|
||||
t.Run("InvalidSignature", func(t *testing.T) {
|
||||
setSigner(tx, util.Uint160{})
|
||||
ok, err := neo.SetGASPerBlock(ic, 10, big.NewInt(native.GASFactor))
|
||||
require.NoError(t, err)
|
||||
require.False(t, ok)
|
||||
})
|
||||
t.Run("TooBigValue", func(t *testing.T) {
|
||||
setSigner(tx, h)
|
||||
_, err := neo.SetGASPerBlock(ic, 10, big.NewInt(10*native.GASFactor+1))
|
||||
require.Error(t, err)
|
||||
})
|
||||
})
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
setSigner(tx, h)
|
||||
ok, err := neo.SetGASPerBlock(ic, 10, big.NewInt(native.GASFactor))
|
||||
require.NoError(t, err)
|
||||
require.True(t, ok)
|
||||
|
||||
t.Run("Again", func(t *testing.T) {
|
||||
setSigner(tx, h)
|
||||
ok, err := neo.SetGASPerBlock(ic, 10, big.NewInt(native.GASFactor))
|
||||
require.NoError(t, err)
|
||||
require.True(t, ok)
|
||||
})
|
||||
|
||||
g, err := neo.GetGASPerBlock(ic, 9)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 5*native.GASFactor, g.Int64())
|
||||
|
||||
g, err = neo.GetGASPerBlock(ic, 10)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, native.GASFactor, g.Int64())
|
||||
})
|
||||
}
|
||||
|
||||
func TestNEO_CalculateBonus(t *testing.T) {
|
||||
bc := newTestChain(t)
|
||||
defer bc.Close()
|
||||
|
||||
neo := bc.contracts.NEO
|
||||
tx := transaction.New(netmode.UnitTestNet, []byte{}, 0)
|
||||
ic := bc.newInteropContext(trigger.System, bc.dao, nil, tx)
|
||||
t.Run("Invalid", func(t *testing.T) {
|
||||
_, err := neo.CalculateBonus(ic, new(big.Int).SetInt64(-1), 0, 1)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("Zero", func(t *testing.T) {
|
||||
res, err := neo.CalculateBonus(ic, big.NewInt(0), 0, 100)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 0, res.Int64())
|
||||
})
|
||||
t.Run("ManyBlocks", func(t *testing.T) {
|
||||
h, err := neo.GetCommitteeAddress(bc, bc.dao)
|
||||
require.NoError(t, err)
|
||||
setSigner(tx, h)
|
||||
ok, err := neo.SetGASPerBlock(ic, 10, big.NewInt(1*native.GASFactor))
|
||||
require.NoError(t, err)
|
||||
require.True(t, ok)
|
||||
|
||||
res, err := neo.CalculateBonus(ic, big.NewInt(100), 5, 15)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, (100*5*5/10)+(100*5*1/10), res.Int64())
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestNEO_CommitteeBountyOnPersist(t *testing.T) {
|
||||
bc := newTestChain(t)
|
||||
defer bc.Close()
|
||||
|
||||
hs := make([]util.Uint160, testchain.CommitteeSize())
|
||||
for i := range hs {
|
||||
hs[i] = testchain.PrivateKeyByID(i).GetScriptHash()
|
||||
}
|
||||
|
||||
const singleBounty = 25000000
|
||||
bs := map[int]int64{0: singleBounty}
|
||||
checkBalances := func() {
|
||||
for i := 0; i < testchain.CommitteeSize(); i++ {
|
||||
require.EqualValues(t, bs[i], bc.GetUtilityTokenBalance(hs[i]).Int64(), i)
|
||||
}
|
||||
}
|
||||
for i := 0; i < testchain.CommitteeSize()*2; i++ {
|
||||
require.NoError(t, bc.AddBlock(bc.newBlock()))
|
||||
bs[(i+1)%testchain.CommitteeSize()] += singleBounty
|
||||
checkBalances()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package state
|
|||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
|
@ -21,6 +22,76 @@ type NEOBalanceState struct {
|
|||
VoteTo *keys.PublicKey
|
||||
}
|
||||
|
||||
// GASIndexPair contains block index together with generated gas per block.
|
||||
type GASIndexPair struct {
|
||||
Index uint32
|
||||
GASPerBlock big.Int
|
||||
}
|
||||
|
||||
// GASRecord contains history of gas per block changes.
|
||||
type GASRecord []GASIndexPair
|
||||
|
||||
// Bytes serializes g to []byte.
|
||||
func (g *GASRecord) Bytes() []byte {
|
||||
w := io.NewBufBinWriter()
|
||||
g.EncodeBinary(w.BinWriter)
|
||||
return w.Bytes()
|
||||
}
|
||||
|
||||
// FromBytes deserializes g from data.
|
||||
func (g *GASRecord) FromBytes(data []byte) error {
|
||||
r := io.NewBinReaderFromBuf(data)
|
||||
g.DecodeBinary(r)
|
||||
return r.Err
|
||||
}
|
||||
|
||||
// DecodeBinary implements io.Serializable.
|
||||
func (g *GASRecord) DecodeBinary(r *io.BinReader) {
|
||||
item := stackitem.DecodeBinaryStackItem(r)
|
||||
if r.Err == nil {
|
||||
r.Err = g.fromStackItem(item)
|
||||
}
|
||||
}
|
||||
|
||||
// EncodeBinary implements io.Serializable.
|
||||
func (g *GASRecord) EncodeBinary(w *io.BinWriter) {
|
||||
item := g.toStackItem()
|
||||
stackitem.EncodeBinaryStackItem(item, w)
|
||||
}
|
||||
|
||||
// toStackItem converts GASRecord to a stack item.
|
||||
func (g *GASRecord) toStackItem() stackitem.Item {
|
||||
items := make([]stackitem.Item, len(*g))
|
||||
for i := range items {
|
||||
items[i] = stackitem.NewStruct([]stackitem.Item{
|
||||
stackitem.NewBigInteger(big.NewInt(int64((*g)[i].Index))),
|
||||
stackitem.NewBigInteger(&(*g)[i].GASPerBlock),
|
||||
})
|
||||
}
|
||||
return stackitem.NewArray(items)
|
||||
}
|
||||
|
||||
var errInvalidFormat = errors.New("invalid item format")
|
||||
|
||||
// fromStackItem converts item to a GASRecord.
|
||||
func (g *GASRecord) fromStackItem(item stackitem.Item) error {
|
||||
arr, ok := item.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
return errInvalidFormat
|
||||
}
|
||||
for i := range arr {
|
||||
s, ok := arr[i].Value().([]stackitem.Item)
|
||||
if !ok || len(s) != 2 || s[0].Type() != stackitem.IntegerT || s[1].Type() != stackitem.IntegerT {
|
||||
return errInvalidFormat
|
||||
}
|
||||
*g = append(*g, GASIndexPair{
|
||||
Index: uint32(s[0].Value().(*big.Int).Uint64()),
|
||||
GASPerBlock: *s[1].Value().(*big.Int),
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NEP5BalanceStateFromBytes converts serialized NEP5BalanceState to structure.
|
||||
func NEP5BalanceStateFromBytes(b []byte) (*NEP5BalanceState, error) {
|
||||
balance := new(NEP5BalanceState)
|
||||
|
|
40
pkg/core/state/native_state_test.go
Normal file
40
pkg/core/state/native_state_test.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/internal/testserdes"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGASRecord_EncodeBinary(t *testing.T) {
|
||||
expected := &GASRecord{
|
||||
GASIndexPair{
|
||||
Index: 1,
|
||||
GASPerBlock: *big.NewInt(123),
|
||||
},
|
||||
GASIndexPair{
|
||||
Index: 2,
|
||||
GASPerBlock: *big.NewInt(7),
|
||||
},
|
||||
}
|
||||
testserdes.EncodeDecodeBinary(t, expected, new(GASRecord))
|
||||
}
|
||||
|
||||
func TestGASRecord_fromStackItem(t *testing.T) {
|
||||
t.Run("NotArray", func(t *testing.T) {
|
||||
item := stackitem.Null{}
|
||||
require.Error(t, new(GASRecord).fromStackItem(item))
|
||||
})
|
||||
t.Run("InvalidFormat", func(t *testing.T) {
|
||||
item := stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.NewStruct([]stackitem.Item{
|
||||
stackitem.NewBigInteger(big.NewInt(1)),
|
||||
stackitem.NewBool(true),
|
||||
}),
|
||||
})
|
||||
require.Error(t, new(GASRecord).fromStackItem(item))
|
||||
})
|
||||
}
|
|
@ -123,14 +123,6 @@ func getNextConsensusAddress(validators []*keys.PublicKey) (val util.Uint160, er
|
|||
return hash.Hash160(raw), nil
|
||||
}
|
||||
|
||||
func calculateUtilityAmount() util.Fixed8 {
|
||||
sum := 0
|
||||
for i := 0; i < len(genAmount); i++ {
|
||||
sum += genAmount[i]
|
||||
}
|
||||
return util.Fixed8FromInt64(int64(sum * decrementInterval))
|
||||
}
|
||||
|
||||
// headerSliceReverse reverses the given slice of *Header.
|
||||
func headerSliceReverse(dest []*block.Header) {
|
||||
for i, j := 0, len(dest)-1; i < j; i, j = i+1, j-1 {
|
||||
|
|
|
@ -485,7 +485,7 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
require.True(t, ok)
|
||||
expected := result.UnclaimedGas{
|
||||
Address: testchain.MultisigScriptHash(),
|
||||
Unclaimed: *big.NewInt(42000),
|
||||
Unclaimed: *big.NewInt(3500),
|
||||
}
|
||||
assert.Equal(t, expected, *actual)
|
||||
},
|
||||
|
@ -980,12 +980,12 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
|
|||
require.NoError(t, json.Unmarshal(res, actual))
|
||||
checkNep5TransfersAux(t, e, actual, sent, rcvd)
|
||||
}
|
||||
t.Run("time frame only", func(t *testing.T) { testNEP5T(t, 4, 5, 0, 0, []int{3, 4, 5, 6}, []int{0, 1}) })
|
||||
t.Run("time frame only", func(t *testing.T) { testNEP5T(t, 4, 5, 0, 0, []int{3, 4, 5, 6}, []int{1, 2}) })
|
||||
t.Run("no res", func(t *testing.T) { testNEP5T(t, 100, 100, 0, 0, []int{}, []int{}) })
|
||||
t.Run("limit", func(t *testing.T) { testNEP5T(t, 1, 7, 3, 0, []int{0, 1, 2}, []int{}) })
|
||||
t.Run("limit 2", func(t *testing.T) { testNEP5T(t, 4, 5, 2, 0, []int{3}, []int{0}) })
|
||||
t.Run("limit with page", func(t *testing.T) { testNEP5T(t, 1, 7, 3, 1, []int{3, 4}, []int{0}) })
|
||||
t.Run("limit with page 2", func(t *testing.T) { testNEP5T(t, 1, 7, 3, 2, []int{5, 6}, []int{1}) })
|
||||
t.Run("limit", func(t *testing.T) { testNEP5T(t, 1, 7, 3, 0, []int{0, 1}, []int{0}) })
|
||||
t.Run("limit 2", func(t *testing.T) { testNEP5T(t, 4, 5, 2, 0, []int{3}, []int{1}) })
|
||||
t.Run("limit with page", func(t *testing.T) { testNEP5T(t, 1, 7, 3, 1, []int{2, 3}, []int{1}) })
|
||||
t.Run("limit with page 2", func(t *testing.T) { testNEP5T(t, 1, 7, 3, 2, []int{4, 5}, []int{2}) })
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1075,7 +1075,7 @@ func checkNep5Balances(t *testing.T, e *executor, acc interface{}) {
|
|||
},
|
||||
{
|
||||
Asset: e.chain.UtilityTokenHash(),
|
||||
Amount: "815.59478530",
|
||||
Amount: "799.59495030",
|
||||
LastUpdated: 7,
|
||||
}},
|
||||
Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(),
|
||||
|
@ -1085,7 +1085,7 @@ func checkNep5Balances(t *testing.T, e *executor, acc interface{}) {
|
|||
}
|
||||
|
||||
func checkNep5Transfers(t *testing.T, e *executor, acc interface{}) {
|
||||
checkNep5TransfersAux(t, e, acc, []int{0, 1, 2, 3, 4, 5, 6, 7, 8}, []int{0, 1, 2, 3})
|
||||
checkNep5TransfersAux(t, e, acc, []int{0, 1, 2, 3, 4, 5, 6, 7, 8}, []int{0, 1, 2, 3, 4, 5, 6})
|
||||
}
|
||||
|
||||
func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcvd []int) {
|
||||
|
@ -1103,6 +1103,7 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcv
|
|||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(blockSendRubles.Transactions))
|
||||
txSendRubles := blockSendRubles.Transactions[0]
|
||||
blockGASBounty := blockSendRubles // index 6 = size of committee
|
||||
|
||||
blockReceiveRubles, err := e.chain.GetBlock(e.chain.GetHeaderHash(5))
|
||||
require.NoError(t, err)
|
||||
|
@ -1131,6 +1132,9 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcv
|
|||
txReceiveNEO := blockReceiveGAS.Transactions[0]
|
||||
txReceiveGAS := blockReceiveGAS.Transactions[1]
|
||||
|
||||
blockGASBounty0, err := e.chain.GetBlock(e.chain.GetHeaderHash(0))
|
||||
require.NoError(t, err)
|
||||
|
||||
// These are laid out here explicitly for 2 purposes:
|
||||
// * to be able to reference any particular event for paging
|
||||
// * to check chain events consistency
|
||||
|
@ -1214,6 +1218,15 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcv
|
|||
},
|
||||
},
|
||||
Received: []result.NEP5Transfer{
|
||||
{
|
||||
Timestamp: blockGASBounty.Timestamp,
|
||||
Asset: e.chain.UtilityTokenHash(),
|
||||
Address: "",
|
||||
Amount: "0.25000000",
|
||||
Index: 6,
|
||||
NotifyIndex: 0,
|
||||
TxHash: blockGASBounty.Hash(),
|
||||
},
|
||||
{
|
||||
Timestamp: blockReceiveRubles.Timestamp,
|
||||
Asset: rublesHash,
|
||||
|
@ -1227,7 +1240,7 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcv
|
|||
Timestamp: blockSendNEO.Timestamp,
|
||||
Asset: e.chain.UtilityTokenHash(),
|
||||
Address: "", // Minted GAS.
|
||||
Amount: "17.99982000",
|
||||
Amount: "1.49998500",
|
||||
Index: 4,
|
||||
NotifyIndex: 0,
|
||||
TxHash: txSendNEO.Hash(),
|
||||
|
@ -1250,6 +1263,14 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcv
|
|||
NotifyIndex: 0,
|
||||
TxHash: txReceiveNEO.Hash(),
|
||||
},
|
||||
{
|
||||
Timestamp: blockGASBounty0.Timestamp,
|
||||
Asset: e.chain.UtilityTokenHash(),
|
||||
Address: "",
|
||||
Amount: "0.25000000",
|
||||
Index: 0,
|
||||
TxHash: blockGASBounty0.Hash(),
|
||||
},
|
||||
},
|
||||
Address: testchain.PrivateKeyByID(0).Address(),
|
||||
}
|
||||
|
|
|
@ -100,7 +100,7 @@ func TestSubscriptions(t *testing.T) {
|
|||
resp := getNotification(t, respMsgs)
|
||||
require.Equal(t, response.ExecutionEventID, resp.Event)
|
||||
for {
|
||||
resp := getNotification(t, respMsgs)
|
||||
resp = getNotification(t, respMsgs)
|
||||
if resp.Event != response.NotificationEventID {
|
||||
break
|
||||
}
|
||||
|
@ -120,6 +120,13 @@ func TestSubscriptions(t *testing.T) {
|
|||
}
|
||||
}
|
||||
resp = getNotification(t, respMsgs)
|
||||
require.Equal(t, response.ExecutionEventID, resp.Event)
|
||||
for {
|
||||
resp = getNotification(t, respMsgs)
|
||||
if resp.Event != response.NotificationEventID {
|
||||
break
|
||||
}
|
||||
}
|
||||
require.Equal(t, response.BlockEventID, resp.Event)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue