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,8 +57,6 @@ var (
|
||||||
ErrInvalidBlockIndex error = errors.New("invalid block index")
|
ErrInvalidBlockIndex error = errors.New("invalid block index")
|
||||||
)
|
)
|
||||||
var (
|
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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -98,9 +96,6 @@ type Blockchain struct {
|
||||||
// Number of headers stored in the chain file.
|
// Number of headers stored in the chain file.
|
||||||
storedHeaderCount uint32
|
storedHeaderCount uint32
|
||||||
|
|
||||||
generationAmount []int
|
|
||||||
decrementInterval int
|
|
||||||
|
|
||||||
// Header hashes list with associated lock.
|
// Header hashes list with associated lock.
|
||||||
headerHashesLock sync.RWMutex
|
headerHashesLock sync.RWMutex
|
||||||
headerHashes []util.Uint256
|
headerHashes []util.Uint256
|
||||||
|
@ -162,9 +157,6 @@ func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration, log *zap.L
|
||||||
subCh: make(chan interface{}),
|
subCh: make(chan interface{}),
|
||||||
unsubCh: make(chan interface{}),
|
unsubCh: make(chan interface{}),
|
||||||
|
|
||||||
generationAmount: genAmount,
|
|
||||||
decrementInterval: decrementInterval,
|
|
||||||
|
|
||||||
contracts: *native.NewContracts(),
|
contracts: *native.NewContracts(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -373,6 +365,19 @@ func (bc *Blockchain) notificationDispatcher() {
|
||||||
ch <- tx
|
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 {
|
for ch := range blockFeed {
|
||||||
ch <- event.block
|
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 {
|
func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error {
|
||||||
cache := dao.NewCached(bc.dao)
|
cache := dao.NewCached(bc.dao)
|
||||||
writeBuf := io.NewBufBinWriter()
|
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 {
|
if err := cache.StoreAsBlock(block, writeBuf); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -548,28 +553,12 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
||||||
writeBuf.Reset()
|
writeBuf.Reset()
|
||||||
|
|
||||||
if block.Index > 0 {
|
if block.Index > 0 {
|
||||||
systemInterop := bc.newInteropContext(trigger.System, cache, block, nil)
|
aer, err := bc.runPersist(bc.contracts.GetPersistScript(), block, cache)
|
||||||
v := systemInterop.SpawnVM()
|
if err != nil {
|
||||||
v.LoadScriptWithFlags(bc.contracts.GetPersistScript(), smartcontract.AllowModifyStates|smartcontract.AllowCall)
|
return fmt.Errorf("onPersist failed: %w", err)
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
appExecResults = append(appExecResults, aer)
|
appExecResults = append(appExecResults, aer)
|
||||||
err := cache.PutAppExecResult(aer, writeBuf)
|
err = cache.PutAppExecResult(aer, writeBuf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to store onPersist exec result: %w", err)
|
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()
|
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()
|
root := bc.dao.MPT.StateRoot()
|
||||||
var prevHash util.Uint256
|
var prevHash util.Uint256
|
||||||
if block.Index > 0 {
|
if block.Index > 0 {
|
||||||
|
@ -628,7 +628,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
||||||
}
|
}
|
||||||
prevHash = hash.DoubleSha256(prev.GetSignedPart())
|
prevHash = hash.DoubleSha256(prev.GetSignedPart())
|
||||||
}
|
}
|
||||||
err := bc.AddStateRoot(&state.MPTRoot{
|
err = bc.AddStateRoot(&state.MPTRoot{
|
||||||
MPTRootBase: state.MPTRootBase{
|
MPTRootBase: state.MPTRootBase{
|
||||||
Index: block.Index,
|
Index: block.Index,
|
||||||
PrevHash: prevHash,
|
PrevHash: prevHash,
|
||||||
|
@ -672,6 +672,29 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
||||||
return nil
|
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) {
|
func (bc *Blockchain) handleNotification(note *state.NotificationEvent, d *dao.Cached, b *block.Block, h util.Uint256) {
|
||||||
if note.Name != "transfer" && note.Name != "Transfer" {
|
if note.Name != "transfer" && note.Name != "Transfer" {
|
||||||
return
|
return
|
||||||
|
@ -1083,36 +1106,11 @@ 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. The amount of NEO being passed is in
|
// amount of NEO between specified blocks.
|
||||||
// its natural non-divisible form (1 NEO as 1, 2 NEO as 2, no multiplication by
|
|
||||||
// 10⁸ is needed as for Fixed8).
|
|
||||||
func (bc *Blockchain) CalculateClaimable(value *big.Int, startHeight, endHeight uint32) *big.Int {
|
func (bc *Blockchain) CalculateClaimable(value *big.Int, startHeight, endHeight uint32) *big.Int {
|
||||||
var amount int64
|
ic := bc.newInteropContext(trigger.System, bc.dao, nil, nil)
|
||||||
di := uint32(bc.decrementInterval)
|
res, _ := bc.contracts.NEO.CalculateBonus(ic, value, startHeight, endHeight)
|
||||||
|
return res
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FeePerByte returns transaction network fee per byte.
|
// FeePerByte returns transaction network fee per byte.
|
||||||
|
@ -1245,10 +1243,7 @@ func (bc *Blockchain) verifyTxAttributes(tx *transaction.Transaction) error {
|
||||||
for i := range tx.Attributes {
|
for i := range tx.Attributes {
|
||||||
switch tx.Attributes[i].Type {
|
switch tx.Attributes[i].Type {
|
||||||
case transaction.HighPriority:
|
case transaction.HighPriority:
|
||||||
pubs, err := bc.contracts.NEO.GetCommitteeMembers(bc, bc.dao)
|
pubs := bc.contracts.NEO.GetCommitteeMembers()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s, err := smartcontract.CreateMajorityMultiSigRedeemScript(pubs)
|
s, err := smartcontract.CreateMajorityMultiSigRedeemScript(pubs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1409,22 +1404,19 @@ func (bc *Blockchain) GetStandByCommittee() keys.PublicKeys {
|
||||||
|
|
||||||
// GetCommittee returns the sorted list of public keys of nodes in committee.
|
// GetCommittee returns the sorted list of public keys of nodes in committee.
|
||||||
func (bc *Blockchain) GetCommittee() (keys.PublicKeys, error) {
|
func (bc *Blockchain) GetCommittee() (keys.PublicKeys, error) {
|
||||||
pubs, err := bc.contracts.NEO.GetCommitteeMembers(bc, bc.dao)
|
pubs := bc.contracts.NEO.GetCommitteeMembers()
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
sort.Sort(pubs)
|
sort.Sort(pubs)
|
||||||
return pubs, nil
|
return pubs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetValidators returns current validators.
|
// GetValidators returns current validators.
|
||||||
func (bc *Blockchain) GetValidators() ([]*keys.PublicKey, error) {
|
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.
|
// GetNextBlockValidators returns next block validators.
|
||||||
func (bc *Blockchain) GetNextBlockValidators() ([]*keys.PublicKey, error) {
|
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.
|
// GetEnrollments returns all registered validators.
|
||||||
|
|
|
@ -536,29 +536,12 @@ func TestGetClaimable(t *testing.T) {
|
||||||
bc := newTestChain(t)
|
bc := newTestChain(t)
|
||||||
defer bc.Close()
|
defer bc.Close()
|
||||||
|
|
||||||
bc.generationAmount = []int{4, 3, 2, 1}
|
|
||||||
bc.decrementInterval = 2
|
|
||||||
_, err := bc.genBlocks(10)
|
_, err := bc.genBlocks(10)
|
||||||
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 := bc.CalculateClaimable(big.NewInt(1), 0, 2)
|
||||||
require.EqualValues(t, big.NewInt(8), amount)
|
require.EqualValues(t, big.NewInt(1), 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)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -604,8 +587,8 @@ func TestSubscriptions(t *testing.T) {
|
||||||
blocks, err := bc.genBlocks(1)
|
blocks, err := bc.genBlocks(1)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Eventually(t, func() bool { return len(blockCh) != 0 }, time.Second, 10*time.Millisecond)
|
require.Eventually(t, func() bool { return len(blockCh) != 0 }, time.Second, 10*time.Millisecond)
|
||||||
assert.Empty(t, notificationCh)
|
assert.Len(t, notificationCh, 1) // validator bounty
|
||||||
assert.Len(t, executionCh, 1)
|
assert.Len(t, executionCh, 2)
|
||||||
assert.Empty(t, txCh)
|
assert.Empty(t, txCh)
|
||||||
|
|
||||||
b := <-blockCh
|
b := <-blockCh
|
||||||
|
@ -614,6 +597,11 @@ func TestSubscriptions(t *testing.T) {
|
||||||
|
|
||||||
aer := <-executionCh
|
aer := <-executionCh
|
||||||
assert.Equal(t, b.Hash(), aer.TxHash)
|
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()
|
script := io.NewBufBinWriter()
|
||||||
emit.Bytes(script.BinWriter, []byte("yay!"))
|
emit.Bytes(script.BinWriter, []byte("yay!"))
|
||||||
|
@ -682,8 +670,15 @@ func TestSubscriptions(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert.Empty(t, txCh)
|
assert.Empty(t, txCh)
|
||||||
assert.Empty(t, notificationCh)
|
assert.Len(t, notificationCh, 1)
|
||||||
assert.Empty(t, executionCh)
|
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.UnsubscribeFromBlocks(blockCh)
|
||||||
bc.UnsubscribeFromTransactions(txCh)
|
bc.UnsubscribeFromTransactions(txCh)
|
||||||
|
|
|
@ -177,7 +177,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(0), bc.GetUtilityTokenBalance(priv0ScriptHash))
|
require.Equal(t, big.NewInt(2500_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,8 +1,11 @@
|
||||||
package native
|
package native
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
"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/io"
|
"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/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
"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"
|
||||||
|
@ -16,6 +19,8 @@ type Contracts struct {
|
||||||
Contracts []interop.Contract
|
Contracts []interop.Contract
|
||||||
// persistScript is vm script which executes "onPersist" method of every native contract.
|
// persistScript is vm script which executes "onPersist" method of every native contract.
|
||||||
persistScript []byte
|
persistScript []byte
|
||||||
|
// postPersistScript is vm script which executes "postPersist" method of every native contract.
|
||||||
|
postPersistScript []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// ByHash returns native contract with the specified hash.
|
// ByHash returns native contract with the specified hash.
|
||||||
|
@ -71,3 +76,33 @@ func (cs *Contracts) GetPersistScript() []byte {
|
||||||
cs.persistScript = w.Bytes()
|
cs.persistScript = w.Bytes()
|
||||||
return cs.persistScript
|
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 (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
"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)
|
absAmount := big.NewInt(tx.SystemFee + tx.NetworkFee)
|
||||||
g.burn(ic, tx.Sender(), absAmount)
|
g.burn(ic, tx.Sender(), absAmount)
|
||||||
}
|
}
|
||||||
validators, err := g.NEO.getNextBlockValidatorsInternal(ic.Chain, ic.DAO)
|
validators := g.NEO.GetNextBlockValidatorsInternal()
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't get block validators: %w", err)
|
|
||||||
}
|
|
||||||
primary := validators[ic.Block.ConsensusData.PrimaryIndex].GetScriptHash()
|
primary := validators[ic.Block.ConsensusData.PrimaryIndex].GetScriptHash()
|
||||||
var netFee int64
|
var netFee int64
|
||||||
for _, tx := range ic.Block.Transactions {
|
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"
|
||||||
"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/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"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
@ -29,6 +30,11 @@ 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
|
||||||
|
// 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
|
// keyWithVotes is a serialized key with votes balance. It's not deserialized
|
||||||
|
@ -48,15 +54,22 @@ 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
|
||||||
|
// 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
|
// effectiveVoterTurnout represents minimal ratio of total supply to total amount voted value
|
||||||
// which is require to use non-standby validators.
|
// which is require to use non-standby validators.
|
||||||
effectiveVoterTurnout = 5
|
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 (
|
var (
|
||||||
// nextValidatorsKey is a key used to store validators for the
|
// prefixCommittee is a key used to store committee.
|
||||||
// next block.
|
prefixCommittee = []byte{14}
|
||||||
nextValidatorsKey = []byte{14}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// makeValidatorKey creates a key from account script hash.
|
// makeValidatorKey creates a key from account script hash.
|
||||||
|
@ -77,6 +90,7 @@ func NewNEO() *NEO {
|
||||||
nep5.decimals = 0
|
nep5.decimals = 0
|
||||||
nep5.factor = 1
|
nep5.factor = 1
|
||||||
nep5.onPersist = chainOnPersist(nep5.OnPersist, n.OnPersist)
|
nep5.onPersist = chainOnPersist(nep5.OnPersist, n.OnPersist)
|
||||||
|
nep5.postPersist = chainOnPersist(nep5.postPersist, n.PostPersist)
|
||||||
nep5.incBalance = n.increaseBalance
|
nep5.incBalance = n.increaseBalance
|
||||||
nep5.ContractID = neoContractID
|
nep5.ContractID = neoContractID
|
||||||
|
|
||||||
|
@ -84,11 +98,16 @@ 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))
|
||||||
|
|
||||||
onp := n.Methods["onPersist"]
|
onp := n.Methods["onPersist"]
|
||||||
onp.Func = getOnPersistWrapper(n.onPersist)
|
onp.Func = getOnPersistWrapper(n.onPersist)
|
||||||
n.Methods["onPersist"] = onp
|
n.Methods["onPersist"] = onp
|
||||||
|
|
||||||
|
pp := n.Methods["postPersist"]
|
||||||
|
pp.Func = getOnPersistWrapper(n.postPersist)
|
||||||
|
n.Methods["postPersist"] = pp
|
||||||
|
|
||||||
desc := newDescriptor("unclaimedGas", smartcontract.IntegerType,
|
desc := newDescriptor("unclaimedGas", smartcontract.IntegerType,
|
||||||
manifest.NewParameter("account", smartcontract.Hash160Type),
|
manifest.NewParameter("account", smartcontract.Hash160Type),
|
||||||
manifest.NewParameter("end", smartcontract.IntegerType))
|
manifest.NewParameter("end", smartcontract.IntegerType))
|
||||||
|
@ -119,14 +138,19 @@ func NewNEO() *NEO {
|
||||||
md = newMethodAndPrice(n.getCommittee, 100000000, smartcontract.AllowStates)
|
md = newMethodAndPrice(n.getCommittee, 100000000, smartcontract.AllowStates)
|
||||||
n.AddMethod(md, desc, true)
|
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)
|
desc = newDescriptor("getNextBlockValidators", smartcontract.ArrayType)
|
||||||
md = newMethodAndPrice(n.getNextBlockValidators, 100000000, smartcontract.AllowStates)
|
md = newMethodAndPrice(n.getNextBlockValidators, 100000000, smartcontract.AllowStates)
|
||||||
n.AddMethod(md, desc, true)
|
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
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,12 +164,26 @@ func (n *NEO) Initialize(ic *interop.Context) error {
|
||||||
return errors.New("already initialized")
|
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)
|
h, err := getStandbyValidatorsHash(ic)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
n.mint(ic, h, big.NewInt(NEOTotalSupply))
|
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{}})
|
err = ic.DAO.PutStorageItem(n.ContractID, []byte{prefixVotersCount}, &state.StorageItem{Value: []byte{}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -154,33 +192,59 @@ func (n *NEO) Initialize(ic *interop.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnPersist implements Contract interface.
|
func (n *NEO) updateNextValidators(committee keys.PublicKeys, bc blockchainer.Blockchainer) {
|
||||||
func (n *NEO) OnPersist(ic *interop.Context) error {
|
nextVals := committee[:bc.GetConfig().ValidatorsCount].Copy()
|
||||||
if !n.votesChanged.Load().(bool) {
|
sort.Sort(nextVals)
|
||||||
return nil
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
prev := n.nextValidators.Load().(keys.PublicKeys)
|
n.committee.Store(committee)
|
||||||
if len(prev) == len(pubs) {
|
n.updateNextValidators(committee, ic.Chain)
|
||||||
var needUpdate bool
|
|
||||||
for i := range pubs {
|
|
||||||
if !pubs[i].Equal(prev[i]) {
|
|
||||||
needUpdate = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !needUpdate {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
n.votesChanged.Store(false)
|
n.votesChanged.Store(false)
|
||||||
n.nextValidators.Store(pubs)
|
si := &state.StorageItem{Value: committee.Bytes()}
|
||||||
si := new(state.StorageItem)
|
return ic.DAO.PutStorageItem(n.ContractID, prefixCommittee, si)
|
||||||
si.Value = pubs.Bytes()
|
}
|
||||||
return ic.DAO.PutStorageItem(n.ContractID, nextValidatorsKey, 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 {
|
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 {
|
if ic.Block == nil || ic.Block.Index == 0 {
|
||||||
return nil
|
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
|
acc.BalanceHeight = ic.Block.Index
|
||||||
n.GAS.mint(ic, h, gen)
|
n.GAS.mint(ic, h, gen)
|
||||||
return nil
|
return nil
|
||||||
|
@ -234,10 +301,117 @@ func (n *NEO) unclaimedGas(ic *interop.Context, args []stackitem.Item) stackitem
|
||||||
}
|
}
|
||||||
tr := bs.Trackers[n.ContractID]
|
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)
|
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 {
|
func (n *NEO) registerCandidate(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
pub := toPublicKey(args[0])
|
pub := toPublicKey(args[0])
|
||||||
ok, err := runtime.CheckKeyedWitness(ic, pub)
|
ok, err := runtime.CheckKeyedWitness(ic, pub)
|
||||||
|
@ -425,38 +599,23 @@ func (n *NEO) getCandidatesCall(ic *interop.Context, _ []stackitem.Item) stackit
|
||||||
return stackitem.NewArray(arr)
|
return stackitem.NewArray(arr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetValidatorsInternal returns a list of current validators.
|
// ComputeNextBlockValidators returns an actual list of current validators.
|
||||||
func (n *NEO) GetValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) {
|
func (n *NEO) ComputeNextBlockValidators(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) {
|
||||||
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.GetCommitteeMembers(bc, d)
|
result, err := n.ComputeCommitteeMembers(bc, d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
count := bc.GetConfig().ValidatorsCount
|
result = result[:bc.GetConfig().ValidatorsCount]
|
||||||
if len(result) < count {
|
|
||||||
count = len(result)
|
|
||||||
}
|
|
||||||
result = result[:count]
|
|
||||||
sort.Sort(result)
|
sort.Sort(result)
|
||||||
n.validators.Store(result)
|
n.validators.Store(result)
|
||||||
return result, nil
|
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 {
|
func (n *NEO) getCommittee(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||||
pubs, err := n.GetCommitteeMembers(ic.Chain, ic.DAO)
|
pubs := n.GetCommitteeMembers()
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
sort.Sort(pubs)
|
sort.Sort(pubs)
|
||||||
return pubsToArray(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)
|
return d.PutStorageItem(n.ContractID, key, si)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCommitteeMembers returns public keys of nodes in committee.
|
// GetCommitteeMembers returns public keys of nodes in committee using cached value.
|
||||||
func (n *NEO) GetCommitteeMembers(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) {
|
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}
|
key := []byte{prefixVotersCount}
|
||||||
si := d.GetStorageItem(n.ContractID, key)
|
si := d.GetStorageItem(n.ContractID, key)
|
||||||
if si == nil {
|
if si == nil {
|
||||||
|
@ -485,7 +649,8 @@ func (n *NEO) GetCommitteeMembers(bc blockchainer.Blockchainer, d dao.DAO) (keys
|
||||||
votersCount.Mul(votersCount, big.NewInt(effectiveVoterTurnout))
|
votersCount.Mul(votersCount, big.NewInt(effectiveVoterTurnout))
|
||||||
voterTurnout := votersCount.Div(votersCount, n.getTotalSupply(d))
|
voterTurnout := votersCount.Div(votersCount, n.getTotalSupply(d))
|
||||||
if voterTurnout.Sign() != 1 {
|
if voterTurnout.Sign() != 1 {
|
||||||
return bc.GetStandByCommittee(), nil
|
pubs := bc.GetStandByCommittee()
|
||||||
|
return pubs, nil
|
||||||
}
|
}
|
||||||
cs, err := n.getCandidates(d)
|
cs, err := n.getCandidates(d)
|
||||||
if err != nil {
|
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 {
|
func (n *NEO) getNextBlockValidators(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||||
result, err := n.getNextBlockValidatorsInternal(ic.Chain, ic.DAO)
|
result := n.GetNextBlockValidatorsInternal()
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return pubsToArray(result)
|
return pubsToArray(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNextBlockValidatorsInternal returns next block validators.
|
// GetNextBlockValidatorsInternal returns next block validators.
|
||||||
func (n *NEO) GetNextBlockValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) {
|
func (n *NEO) GetNextBlockValidatorsInternal() keys.PublicKeys {
|
||||||
pubs, err := n.getNextBlockValidatorsInternal(bc, d)
|
return n.nextValidators.Load().(keys.PublicKeys).Copy()
|
||||||
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 pubsToArray(pubs keys.PublicKeys) stackitem.Item {
|
func pubsToArray(pubs keys.PublicKeys) stackitem.Item {
|
||||||
|
|
|
@ -35,6 +35,7 @@ type nep5TokenNative struct {
|
||||||
decimals int64
|
decimals int64
|
||||||
factor int64
|
factor int64
|
||||||
onPersist func(*interop.Context) error
|
onPersist func(*interop.Context) error
|
||||||
|
postPersist func(*interop.Context) error
|
||||||
incBalance func(*interop.Context, util.Uint160, *state.StorageItem, *big.Int) error
|
incBalance func(*interop.Context, util.Uint160, *state.StorageItem, *big.Int) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,6 +85,10 @@ func newNEP5Native(name string) *nep5TokenNative {
|
||||||
md = newMethodAndPrice(getOnPersistWrapper(n.OnPersist), 0, smartcontract.AllowModifyStates)
|
md = newMethodAndPrice(getOnPersistWrapper(n.OnPersist), 0, smartcontract.AllowModifyStates)
|
||||||
n.AddMethod(md, desc, false)
|
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...)
|
n.AddEvent("Transfer", desc.Parameters...)
|
||||||
|
|
||||||
return n
|
return n
|
||||||
|
|
|
@ -124,6 +124,10 @@ func newPolicy() *Policy {
|
||||||
desc = newDescriptor("onPersist", smartcontract.VoidType)
|
desc = newDescriptor("onPersist", smartcontract.VoidType)
|
||||||
md = newMethodAndPrice(getOnPersistWrapper(p.OnPersist), 0, smartcontract.AllowModifyStates)
|
md = newMethodAndPrice(getOnPersistWrapper(p.OnPersist), 0, smartcontract.AllowModifyStates)
|
||||||
p.AddMethod(md, desc, false)
|
p.AddMethod(md, desc, false)
|
||||||
|
|
||||||
|
desc = newDescriptor("postPersist", smartcontract.VoidType)
|
||||||
|
md = newMethodAndPrice(getOnPersistWrapper(postPersistBase), 0, smartcontract.AllowModifyStates)
|
||||||
|
p.AddMethod(md, desc, false)
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
"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/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"
|
||||||
|
@ -13,6 +14,7 @@ import (
|
||||||
"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"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -28,13 +30,22 @@ func TestNEO_Vote(t *testing.T) {
|
||||||
defer bc.Close()
|
defer bc.Close()
|
||||||
|
|
||||||
neo := bc.contracts.NEO
|
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 := bc.newInteropContext(trigger.System, bc.dao, nil, tx)
|
||||||
ic.VM = vm.New()
|
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()
|
standBySorted := bc.GetStandByValidators()
|
||||||
sort.Sort(standBySorted)
|
sort.Sort(standBySorted)
|
||||||
pubs, err := neo.GetValidatorsInternal(bc, ic.DAO)
|
pubs, err := neo.ComputeNextBlockValidators(bc, ic.DAO)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, standBySorted, pubs)
|
require.Equal(t, standBySorted, pubs)
|
||||||
|
|
||||||
|
@ -66,13 +77,12 @@ func TestNEO_Vote(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// We still haven't voted enough validators in.
|
// 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.NoError(t, err)
|
||||||
require.Equal(t, standBySorted, pubs)
|
require.Equal(t, standBySorted, pubs)
|
||||||
|
|
||||||
require.NoError(t, neo.OnPersist(ic))
|
advanceChain(t)
|
||||||
pubs, err = neo.GetNextBlockValidatorsInternal(bc, ic.DAO)
|
pubs = neo.GetNextBlockValidatorsInternal()
|
||||||
require.NoError(t, err)
|
|
||||||
require.EqualValues(t, standBySorted, pubs)
|
require.EqualValues(t, standBySorted, pubs)
|
||||||
|
|
||||||
// Register and give some value to the last validator.
|
// 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()))
|
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)
|
require.NoError(t, err)
|
||||||
sortedCandidates := candidates.Copy()
|
sortedCandidates := candidates.Copy()
|
||||||
sort.Sort(sortedCandidates)
|
sort.Sort(sortedCandidates)
|
||||||
require.EqualValues(t, sortedCandidates, pubs)
|
require.EqualValues(t, sortedCandidates, pubs)
|
||||||
|
|
||||||
require.NoError(t, neo.OnPersist(ic))
|
pubs = neo.GetNextBlockValidatorsInternal()
|
||||||
pubs, err = neo.GetNextBlockValidatorsInternal(bc, ic.DAO)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.EqualValues(t, sortedCandidates, pubs)
|
require.EqualValues(t, sortedCandidates, pubs)
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
pubs, err = neo.GetValidatorsInternal(bc, ic.DAO)
|
pubs, err = neo.ComputeNextBlockValidators(bc, ic.DAO)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
for i := range pubs {
|
for i := range pubs {
|
||||||
require.NotEqual(t, candidates[0], pubs[i])
|
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 (
|
import (
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
|
"errors"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
@ -21,6 +22,76 @@ type NEOBalanceState struct {
|
||||||
VoteTo *keys.PublicKey
|
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.
|
// NEP5BalanceStateFromBytes converts serialized NEP5BalanceState to structure.
|
||||||
func NEP5BalanceStateFromBytes(b []byte) (*NEP5BalanceState, error) {
|
func NEP5BalanceStateFromBytes(b []byte) (*NEP5BalanceState, error) {
|
||||||
balance := new(NEP5BalanceState)
|
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
|
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.
|
// headerSliceReverse reverses the given slice of *Header.
|
||||||
func headerSliceReverse(dest []*block.Header) {
|
func headerSliceReverse(dest []*block.Header) {
|
||||||
for i, j := 0, len(dest)-1; i < j; i, j = i+1, j-1 {
|
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)
|
require.True(t, ok)
|
||||||
expected := result.UnclaimedGas{
|
expected := result.UnclaimedGas{
|
||||||
Address: testchain.MultisigScriptHash(),
|
Address: testchain.MultisigScriptHash(),
|
||||||
Unclaimed: *big.NewInt(42000),
|
Unclaimed: *big.NewInt(3500),
|
||||||
}
|
}
|
||||||
assert.Equal(t, expected, *actual)
|
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))
|
require.NoError(t, json.Unmarshal(res, actual))
|
||||||
checkNep5TransfersAux(t, e, actual, sent, rcvd)
|
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("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", 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{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{3, 4}, []int{0}) })
|
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{5, 6}, []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(),
|
Asset: e.chain.UtilityTokenHash(),
|
||||||
Amount: "815.59478530",
|
Amount: "799.59495030",
|
||||||
LastUpdated: 7,
|
LastUpdated: 7,
|
||||||
}},
|
}},
|
||||||
Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(),
|
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{}) {
|
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) {
|
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.NoError(t, err)
|
||||||
require.Equal(t, 1, len(blockSendRubles.Transactions))
|
require.Equal(t, 1, len(blockSendRubles.Transactions))
|
||||||
txSendRubles := blockSendRubles.Transactions[0]
|
txSendRubles := blockSendRubles.Transactions[0]
|
||||||
|
blockGASBounty := blockSendRubles // index 6 = size of committee
|
||||||
|
|
||||||
blockReceiveRubles, err := e.chain.GetBlock(e.chain.GetHeaderHash(5))
|
blockReceiveRubles, err := e.chain.GetBlock(e.chain.GetHeaderHash(5))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -1131,6 +1132,9 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcv
|
||||||
txReceiveNEO := blockReceiveGAS.Transactions[0]
|
txReceiveNEO := blockReceiveGAS.Transactions[0]
|
||||||
txReceiveGAS := blockReceiveGAS.Transactions[1]
|
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:
|
// These are laid out here explicitly for 2 purposes:
|
||||||
// * to be able to reference any particular event for paging
|
// * to be able to reference any particular event for paging
|
||||||
// * to check chain events consistency
|
// * to check chain events consistency
|
||||||
|
@ -1214,6 +1218,15 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcv
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Received: []result.NEP5Transfer{
|
Received: []result.NEP5Transfer{
|
||||||
|
{
|
||||||
|
Timestamp: blockGASBounty.Timestamp,
|
||||||
|
Asset: e.chain.UtilityTokenHash(),
|
||||||
|
Address: "",
|
||||||
|
Amount: "0.25000000",
|
||||||
|
Index: 6,
|
||||||
|
NotifyIndex: 0,
|
||||||
|
TxHash: blockGASBounty.Hash(),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Timestamp: blockReceiveRubles.Timestamp,
|
Timestamp: blockReceiveRubles.Timestamp,
|
||||||
Asset: rublesHash,
|
Asset: rublesHash,
|
||||||
|
@ -1227,7 +1240,7 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcv
|
||||||
Timestamp: blockSendNEO.Timestamp,
|
Timestamp: blockSendNEO.Timestamp,
|
||||||
Asset: e.chain.UtilityTokenHash(),
|
Asset: e.chain.UtilityTokenHash(),
|
||||||
Address: "", // Minted GAS.
|
Address: "", // Minted GAS.
|
||||||
Amount: "17.99982000",
|
Amount: "1.49998500",
|
||||||
Index: 4,
|
Index: 4,
|
||||||
NotifyIndex: 0,
|
NotifyIndex: 0,
|
||||||
TxHash: txSendNEO.Hash(),
|
TxHash: txSendNEO.Hash(),
|
||||||
|
@ -1250,6 +1263,14 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcv
|
||||||
NotifyIndex: 0,
|
NotifyIndex: 0,
|
||||||
TxHash: txReceiveNEO.Hash(),
|
TxHash: txReceiveNEO.Hash(),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Timestamp: blockGASBounty0.Timestamp,
|
||||||
|
Asset: e.chain.UtilityTokenHash(),
|
||||||
|
Address: "",
|
||||||
|
Amount: "0.25000000",
|
||||||
|
Index: 0,
|
||||||
|
TxHash: blockGASBounty0.Hash(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Address: testchain.PrivateKeyByID(0).Address(),
|
Address: testchain.PrivateKeyByID(0).Address(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,7 +100,7 @@ func TestSubscriptions(t *testing.T) {
|
||||||
resp := getNotification(t, respMsgs)
|
resp := getNotification(t, respMsgs)
|
||||||
require.Equal(t, response.ExecutionEventID, resp.Event)
|
require.Equal(t, response.ExecutionEventID, resp.Event)
|
||||||
for {
|
for {
|
||||||
resp := getNotification(t, respMsgs)
|
resp = getNotification(t, respMsgs)
|
||||||
if resp.Event != response.NotificationEventID {
|
if resp.Event != response.NotificationEventID {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -120,6 +120,13 @@ func TestSubscriptions(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resp = getNotification(t, respMsgs)
|
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)
|
require.Equal(t, response.BlockEventID, resp.Event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue