forked from TrueCloudLab/neoneo-go
core: refactor GASRecord handling
We should store each GAS record as { Key: ContractID + RecordsPrefix + RecordID, Value: RecordValue, } So don't use state.GASRecord for storing anymore. However, it's still useful to cache GasRecord values by using state.GASRecords, because we have to keep GASIndexPairs sorted by indexes.
This commit is contained in:
parent
0da01fde7f
commit
0232bbcb0c
4 changed files with 80 additions and 145 deletions
13
pkg/core/native/gas_record.go
Normal file
13
pkg/core/native/gas_record.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package native
|
||||
|
||||
import "math/big"
|
||||
|
||||
// gasIndexPair contains block index together with generated gas per block.
|
||||
// It is used to cache NEO GASRecords.
|
||||
type gasIndexPair struct {
|
||||
Index uint32
|
||||
GASPerBlock big.Int
|
||||
}
|
||||
|
||||
// gasRecord contains history of gas per block changes. It is used only by NEO cache.
|
||||
type gasRecord []gasIndexPair
|
|
@ -2,7 +2,9 @@ package native
|
|||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sort"
|
||||
"strings"
|
||||
|
@ -189,13 +191,15 @@ func (n *NEO) Initialize(ic *interop.Context) error {
|
|||
}
|
||||
n.mint(ic, h, big.NewInt(NEOTotalSupply))
|
||||
|
||||
gr := &state.GASRecord{{Index: 0, GASPerBlock: *big.NewInt(5 * GASFactor)}}
|
||||
n.gasPerBlock.Store(*gr)
|
||||
n.gasPerBlockChanged.Store(false)
|
||||
err = ic.DAO.PutStorageItem(n.ContractID, []byte{prefixGASPerBlock}, &state.StorageItem{Value: gr.Bytes()})
|
||||
var index uint32 = 0
|
||||
value := big.NewInt(5 * GASFactor)
|
||||
err = n.putGASRecord(ic.DAO, index, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gr := &gasRecord{{Index: index, GASPerBlock: *value}}
|
||||
n.gasPerBlock.Store(*gr)
|
||||
n.gasPerBlockChanged.Store(false)
|
||||
err = ic.DAO.PutStorageItem(n.ContractID, []byte{prefixVotersCount}, &state.StorageItem{Value: []byte{}})
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -217,9 +221,8 @@ func (n *NEO) InitializeCache(bc blockchainer.Blockchainer, d dao.DAO) error {
|
|||
return err
|
||||
}
|
||||
|
||||
var gr state.GASRecord
|
||||
si = d.GetStorageItem(n.ContractID, []byte{prefixGASPerBlock})
|
||||
if err := gr.FromBytes(si.Value); err != nil {
|
||||
gr, err := n.getSortedGASRecordFromDAO(d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n.gasPerBlock.Store(gr)
|
||||
|
@ -293,7 +296,10 @@ func (n *NEO) PostPersist(ic *interop.Context) error {
|
|||
// OnPersistEnd updates cached values if they've been changed.
|
||||
func (n *NEO) OnPersistEnd(d dao.DAO) {
|
||||
if n.gasPerBlockChanged.Load().(bool) {
|
||||
gr := n.getGASRecordFromDAO(d)
|
||||
gr, err := n.getSortedGASRecordFromDAO(d)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
n.gasPerBlock.Store(gr)
|
||||
n.gasPerBlockChanged.Store(false)
|
||||
}
|
||||
|
@ -365,20 +371,41 @@ func (n *NEO) getGASPerBlock(ic *interop.Context, _ []stackitem.Item) stackitem.
|
|||
return stackitem.NewBigInteger(gas)
|
||||
}
|
||||
|
||||
func (n *NEO) getGASRecordFromDAO(d dao.DAO) state.GASRecord {
|
||||
var gr state.GASRecord
|
||||
si := d.GetStorageItem(n.ContractID, []byte{prefixGASPerBlock})
|
||||
_ = gr.FromBytes(si.Value)
|
||||
return gr
|
||||
func (n *NEO) getSortedGASRecordFromDAO(d dao.DAO) (gasRecord, error) {
|
||||
grMap, err := d.GetStorageItemsWithPrefix(n.ContractID, []byte{prefixGASPerBlock})
|
||||
if err != nil {
|
||||
return gasRecord{}, fmt.Errorf("failed to get gas records from storage: %w", err)
|
||||
}
|
||||
var (
|
||||
i int
|
||||
gr = make(gasRecord, len(grMap))
|
||||
)
|
||||
for indexBytes, gasValue := range grMap {
|
||||
gr[i] = gasIndexPair{
|
||||
Index: binary.BigEndian.Uint32([]byte(indexBytes)),
|
||||
GASPerBlock: *bigint.FromBytes(gasValue.Value),
|
||||
}
|
||||
i++
|
||||
}
|
||||
sort.Slice(gr, func(i, j int) bool {
|
||||
return gr[i].Index < gr[j].Index
|
||||
})
|
||||
return gr, nil
|
||||
}
|
||||
|
||||
// GetGASPerBlock returns gas generated for block with provided index.
|
||||
func (n *NEO) GetGASPerBlock(d dao.DAO, index uint32) *big.Int {
|
||||
var gr state.GASRecord
|
||||
var (
|
||||
gr gasRecord
|
||||
err error
|
||||
)
|
||||
if n.gasPerBlockChanged.Load().(bool) {
|
||||
gr = n.getGASRecordFromDAO(d)
|
||||
gr, err = n.getSortedGASRecordFromDAO(d)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
gr = n.gasPerBlock.Load().(state.GASRecord)
|
||||
gr = n.gasPerBlock.Load().(gasRecord)
|
||||
}
|
||||
for i := len(gr) - 1; i >= 0; i-- {
|
||||
if gr[i].Index <= index {
|
||||
|
@ -413,21 +440,8 @@ func (n *NEO) SetGASPerBlock(ic *interop.Context, index uint32, gas *big.Int) (b
|
|||
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,
|
||||
})
|
||||
}
|
||||
n.gasPerBlockChanged.Store(true)
|
||||
return true, ic.DAO.PutStorageItem(n.ContractID, []byte{prefixGASPerBlock}, &state.StorageItem{Value: gr.Bytes()})
|
||||
return true, n.putGASRecord(ic.DAO, index, gas)
|
||||
}
|
||||
|
||||
// CalculateBonus calculates amount of gas generated for holding `value` NEO from start to end block.
|
||||
|
@ -437,10 +451,17 @@ func (n *NEO) CalculateBonus(ic *interop.Context, value *big.Int, start, end uin
|
|||
} 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 (
|
||||
gr gasRecord
|
||||
err error
|
||||
)
|
||||
if !n.gasPerBlockChanged.Load().(bool) {
|
||||
gr = n.gasPerBlock.Load().(gasRecord)
|
||||
} else {
|
||||
gr, err = n.getSortedGASRecordFromDAO(ic.DAO)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
var sum, tmp big.Int
|
||||
for i := len(gr) - 1; i >= 0; i-- {
|
||||
|
@ -751,3 +772,15 @@ func toPublicKey(s stackitem.Item) *keys.PublicKey {
|
|||
}
|
||||
return pub
|
||||
}
|
||||
|
||||
// putGASRecord is a helper which creates key and puts GASPerBlock value into the storage.
|
||||
func (n *NEO) putGASRecord(dao dao.DAO, index uint32, value *big.Int) error {
|
||||
key := make([]byte, 5)
|
||||
key[0] = prefixGASPerBlock
|
||||
binary.BigEndian.PutUint32(key[1:], index)
|
||||
si := &state.StorageItem{
|
||||
Value: bigint.ToBytes(value),
|
||||
IsConst: false,
|
||||
}
|
||||
return dao.PutStorageItem(n.ContractID, key, si)
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package state
|
|||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
|
@ -22,76 +21,6 @@ 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)
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
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))
|
||||
})
|
||||
}
|
Loading…
Reference in a new issue