forked from TrueCloudLab/neoneo-go
native: allow to modify StoragePrice
in the policy contract
This commit is contained in:
parent
4946556830
commit
62da365302
7 changed files with 123 additions and 7 deletions
|
@ -1833,4 +1833,9 @@ func (bc *Blockchain) GetMaxVerificationGAS() int64 {
|
||||||
return bc.contracts.Policy.GetMaxVerificationGas(bc.dao)
|
return bc.contracts.Policy.GetMaxVerificationGas(bc.dao)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetStoragePrice returns current storage price.
|
||||||
|
func (bc *Blockchain) GetStoragePrice() int64 {
|
||||||
|
return bc.contracts.Policy.GetStoragePriceInternal(bc.dao)
|
||||||
|
}
|
||||||
|
|
||||||
// -- end Policer.
|
// -- end Policer.
|
||||||
|
|
|
@ -6,4 +6,5 @@ type Policer interface {
|
||||||
GetMaxBlockSize() uint32
|
GetMaxBlockSize() uint32
|
||||||
GetMaxBlockSystemFee() int64
|
GetMaxBlockSystemFee() int64
|
||||||
GetMaxVerificationGAS() int64
|
GetMaxVerificationGAS() int64
|
||||||
|
GetStoragePrice() int64
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
|
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||||
"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/native"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/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"
|
||||||
|
@ -201,7 +200,7 @@ func storageDelete(ic *interop.Context) error {
|
||||||
if stc.ReadOnly {
|
if stc.ReadOnly {
|
||||||
return errors.New("StorageContext is read only")
|
return errors.New("StorageContext is read only")
|
||||||
}
|
}
|
||||||
ic.VM.AddGas(native.StoragePrice)
|
ic.VM.AddGas(ic.Chain.GetPolicer().GetStoragePrice())
|
||||||
key := ic.VM.Estack().Pop().Bytes()
|
key := ic.VM.Estack().Pop().Bytes()
|
||||||
si := ic.DAO.GetStorageItem(stc.ID, key)
|
si := ic.DAO.GetStorageItem(stc.ID, key)
|
||||||
if si != nil && si.IsConst {
|
if si != nil && si.IsConst {
|
||||||
|
@ -277,7 +276,7 @@ func putWithContextAndFlags(ic *interop.Context, stc *StorageContext, key []byte
|
||||||
sizeInc = (len(si.Value)-1)/4 + 1 + len(value) - len(si.Value)
|
sizeInc = (len(si.Value)-1)/4 + 1 + len(value) - len(si.Value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !ic.VM.AddGas(int64(sizeInc) * native.StoragePrice) {
|
if !ic.VM.AddGas(int64(sizeInc) * ic.Chain.GetPolicer().GetStoragePrice()) {
|
||||||
return errGasLimitExceeded
|
return errGasLimitExceeded
|
||||||
}
|
}
|
||||||
si.Value = value
|
si.Value = value
|
||||||
|
|
|
@ -119,7 +119,7 @@ func getLimitedSlice(arg stackitem.Item, max int) ([]byte, error) {
|
||||||
|
|
||||||
// getNefAndManifestFromItems converts input arguments into NEF and manifest
|
// getNefAndManifestFromItems converts input arguments into NEF and manifest
|
||||||
// adding appropriate deployment GAS price and sanitizing inputs.
|
// adding appropriate deployment GAS price and sanitizing inputs.
|
||||||
func getNefAndManifestFromItems(args []stackitem.Item, v *vm.VM) (*nef.File, *manifest.Manifest, error) {
|
func getNefAndManifestFromItems(ic *interop.Context, args []stackitem.Item) (*nef.File, *manifest.Manifest, error) {
|
||||||
nefBytes, err := getLimitedSlice(args[0], math.MaxInt32) // Upper limits are checked during NEF deserialization.
|
nefBytes, err := getLimitedSlice(args[0], math.MaxInt32) // Upper limits are checked during NEF deserialization.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("invalid NEF file: %w", err)
|
return nil, nil, fmt.Errorf("invalid NEF file: %w", err)
|
||||||
|
@ -129,7 +129,7 @@ func getNefAndManifestFromItems(args []stackitem.Item, v *vm.VM) (*nef.File, *ma
|
||||||
return nil, nil, fmt.Errorf("invalid manifest: %w", err)
|
return nil, nil, fmt.Errorf("invalid manifest: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !v.AddGas(int64(StoragePrice * (len(nefBytes) + len(manifestBytes)))) {
|
if !ic.VM.AddGas(ic.Chain.GetPolicer().GetStoragePrice() * int64(len(nefBytes)+len(manifestBytes))) {
|
||||||
return nil, nil, errGasLimitExceeded
|
return nil, nil, errGasLimitExceeded
|
||||||
}
|
}
|
||||||
var resManifest *manifest.Manifest
|
var resManifest *manifest.Manifest
|
||||||
|
@ -154,7 +154,7 @@ func getNefAndManifestFromItems(args []stackitem.Item, v *vm.VM) (*nef.File, *ma
|
||||||
// deploy is an implementation of public deploy method, it's run under
|
// deploy is an implementation of public deploy method, it's run under
|
||||||
// VM protections, so it's OK for it to panic instead of returning errors.
|
// VM protections, so it's OK for it to panic instead of returning errors.
|
||||||
func (m *Management) deploy(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
func (m *Management) deploy(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
neff, manif, err := getNefAndManifestFromItems(args, ic.VM)
|
neff, manif, err := getNefAndManifestFromItems(ic, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -208,7 +208,7 @@ func (m *Management) Deploy(d dao.DAO, sender util.Uint160, neff *nef.File, mani
|
||||||
// update is an implementation of public update method, it's run under
|
// update is an implementation of public update method, it's run under
|
||||||
// VM protections, so it's OK for it to panic instead of returning errors.
|
// VM protections, so it's OK for it to panic instead of returning errors.
|
||||||
func (m *Management) update(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
func (m *Management) update(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
neff, manif, err := getNefAndManifestFromItems(args, ic.VM)
|
neff, manif, err := getNefAndManifestFromItems(ic, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,8 @@ const (
|
||||||
maxExecFeeFactor = 1000
|
maxExecFeeFactor = 1000
|
||||||
// maxFeePerByte is the maximum allowed fee per byte value.
|
// maxFeePerByte is the maximum allowed fee per byte value.
|
||||||
maxFeePerByte = 100_000_000
|
maxFeePerByte = 100_000_000
|
||||||
|
// maxStoragePrice is the maximum allowed price for a byte of storage.
|
||||||
|
maxStoragePrice = 10000000
|
||||||
|
|
||||||
// blockedAccountPrefix is a prefix used to store blocked account.
|
// blockedAccountPrefix is a prefix used to store blocked account.
|
||||||
blockedAccountPrefix = 15
|
blockedAccountPrefix = 15
|
||||||
|
@ -52,6 +54,8 @@ var (
|
||||||
maxBlockSizeKey = []byte{12}
|
maxBlockSizeKey = []byte{12}
|
||||||
// maxBlockSystemFeeKey is a key used to store the maximum block system fee value.
|
// maxBlockSystemFeeKey is a key used to store the maximum block system fee value.
|
||||||
maxBlockSystemFeeKey = []byte{17}
|
maxBlockSystemFeeKey = []byte{17}
|
||||||
|
// storagePriceKey is a key used to store storage price.
|
||||||
|
storagePriceKey = []byte{19}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Policy represents Policy native contract.
|
// Policy represents Policy native contract.
|
||||||
|
@ -68,6 +72,7 @@ type Policy struct {
|
||||||
feePerByte int64
|
feePerByte int64
|
||||||
maxBlockSystemFee int64
|
maxBlockSystemFee int64
|
||||||
maxVerificationGas int64
|
maxVerificationGas int64
|
||||||
|
storagePrice uint32
|
||||||
blockedAccounts []util.Uint160
|
blockedAccounts []util.Uint160
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,6 +114,15 @@ func newPolicy() *Policy {
|
||||||
md = newMethodAndPrice(p.setExecFeeFactor, 3000000, smartcontract.WriteStates)
|
md = newMethodAndPrice(p.setExecFeeFactor, 3000000, smartcontract.WriteStates)
|
||||||
p.AddMethod(md, desc)
|
p.AddMethod(md, desc)
|
||||||
|
|
||||||
|
desc = newDescriptor("getStoragePrice", smartcontract.IntegerType)
|
||||||
|
md = newMethodAndPrice(p.getStoragePrice, 1000000, smartcontract.ReadStates)
|
||||||
|
p.AddMethod(md, desc)
|
||||||
|
|
||||||
|
desc = newDescriptor("setStoragePrice", smartcontract.BoolType,
|
||||||
|
manifest.NewParameter("value", smartcontract.IntegerType))
|
||||||
|
md = newMethodAndPrice(p.setStoragePrice, 3000000, smartcontract.WriteStates)
|
||||||
|
p.AddMethod(md, desc)
|
||||||
|
|
||||||
desc = newDescriptor("setMaxBlockSize", smartcontract.BoolType,
|
desc = newDescriptor("setMaxBlockSize", smartcontract.BoolType,
|
||||||
manifest.NewParameter("value", smartcontract.IntegerType))
|
manifest.NewParameter("value", smartcontract.IntegerType))
|
||||||
md = newMethodAndPrice(p.setMaxBlockSize, 3000000, smartcontract.WriteStates)
|
md = newMethodAndPrice(p.setMaxBlockSize, 3000000, smartcontract.WriteStates)
|
||||||
|
@ -156,6 +170,7 @@ func (p *Policy) Initialize(ic *interop.Context) error {
|
||||||
p.feePerByte = defaultFeePerByte
|
p.feePerByte = defaultFeePerByte
|
||||||
p.maxBlockSystemFee = defaultMaxBlockSystemFee
|
p.maxBlockSystemFee = defaultMaxBlockSystemFee
|
||||||
p.maxVerificationGas = defaultMaxVerificationGas
|
p.maxVerificationGas = defaultMaxVerificationGas
|
||||||
|
p.storagePrice = StoragePrice
|
||||||
p.blockedAccounts = make([]util.Uint160, 0)
|
p.blockedAccounts = make([]util.Uint160, 0)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -180,6 +195,7 @@ func (p *Policy) PostPersist(ic *interop.Context) error {
|
||||||
p.feePerByte = getInt64WithKey(p.ContractID, ic.DAO, feePerByteKey, defaultFeePerByte)
|
p.feePerByte = getInt64WithKey(p.ContractID, ic.DAO, feePerByteKey, defaultFeePerByte)
|
||||||
p.maxBlockSystemFee = getInt64WithKey(p.ContractID, ic.DAO, maxBlockSystemFeeKey, defaultMaxBlockSystemFee)
|
p.maxBlockSystemFee = getInt64WithKey(p.ContractID, ic.DAO, maxBlockSystemFeeKey, defaultMaxBlockSystemFee)
|
||||||
p.maxVerificationGas = defaultMaxVerificationGas
|
p.maxVerificationGas = defaultMaxVerificationGas
|
||||||
|
p.storagePrice = getUint32WithKey(p.ContractID, ic.DAO, storagePriceKey, StoragePrice)
|
||||||
|
|
||||||
p.blockedAccounts = make([]util.Uint160, 0)
|
p.blockedAccounts = make([]util.Uint160, 0)
|
||||||
siMap, err := ic.DAO.GetStorageItemsWithPrefix(p.ContractID, []byte{blockedAccountPrefix})
|
siMap, err := ic.DAO.GetStorageItemsWithPrefix(p.ContractID, []byte{blockedAccountPrefix})
|
||||||
|
@ -332,6 +348,41 @@ func (p *Policy) IsBlockedInternal(dao dao.DAO, hash util.Uint160) bool {
|
||||||
return dao.GetStorageItem(p.ContractID, key) != nil
|
return dao.GetStorageItem(p.ContractID, key) != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Policy) getStoragePrice(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||||
|
return stackitem.NewBigInteger(big.NewInt(p.GetStoragePriceInternal(ic.DAO)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStoragePriceInternal returns current execution fee factor.
|
||||||
|
func (p *Policy) GetStoragePriceInternal(d dao.DAO) int64 {
|
||||||
|
p.lock.RLock()
|
||||||
|
defer p.lock.RUnlock()
|
||||||
|
if p.isValid {
|
||||||
|
return int64(p.storagePrice)
|
||||||
|
}
|
||||||
|
return int64(getUint32WithKey(p.ContractID, d, storagePriceKey, StoragePrice))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Policy) setStoragePrice(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
|
value := toUint32(args[0])
|
||||||
|
if value <= 0 || maxStoragePrice < value {
|
||||||
|
panic(fmt.Errorf("StoragePrice must be between 0 and %d", maxStoragePrice))
|
||||||
|
}
|
||||||
|
ok, err := checkValidators(ic)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else if !ok {
|
||||||
|
return stackitem.NewBool(false)
|
||||||
|
}
|
||||||
|
p.lock.Lock()
|
||||||
|
defer p.lock.Unlock()
|
||||||
|
err = setUint32WithKey(p.ContractID, ic.DAO, storagePriceKey, uint32(value))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
p.isValid = false
|
||||||
|
return stackitem.NewBool(true)
|
||||||
|
}
|
||||||
|
|
||||||
// setMaxTransactionsPerBlock is Policy contract method and sets the upper limit
|
// setMaxTransactionsPerBlock is Policy contract method and sets the upper limit
|
||||||
// of transactions per block.
|
// of transactions per block.
|
||||||
func (p *Policy) setMaxTransactionsPerBlock(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
func (p *Policy) setMaxTransactionsPerBlock(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
|
|
|
@ -242,6 +242,62 @@ func TestBlockSystemFee(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStoragePrice(t *testing.T) {
|
||||||
|
chain := newTestChain(t)
|
||||||
|
defer chain.Close()
|
||||||
|
policyHash := chain.contracts.Policy.Metadata().Hash
|
||||||
|
|
||||||
|
t.Run("get, internal method", func(t *testing.T) {
|
||||||
|
n := chain.contracts.Policy.GetStoragePriceInternal(chain.dao)
|
||||||
|
require.Equal(t, int64(native.StoragePrice), n)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("get", func(t *testing.T) {
|
||||||
|
res, err := invokeContractMethod(chain, 100000000, policyHash, "getStoragePrice")
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkResult(t, res, stackitem.NewBigInteger(big.NewInt(native.StoragePrice)))
|
||||||
|
require.NoError(t, chain.persist())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("set, zero fee", func(t *testing.T) {
|
||||||
|
res, err := invokeContractMethod(chain, 100000000, policyHash, "setStoragePrice", int64(0))
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkFAULTState(t, res)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("set, too big fee", func(t *testing.T) {
|
||||||
|
res, err := invokeContractMethod(chain, 100000000, policyHash, "setStoragePrice", int64(10000001))
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkFAULTState(t, res)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("set, success", func(t *testing.T) {
|
||||||
|
// Set and get in the same block.
|
||||||
|
txSet, err := prepareContractMethodInvoke(chain, 100000000, policyHash, "setStoragePrice", int64(12345))
|
||||||
|
require.NoError(t, err)
|
||||||
|
txGet1, err := prepareContractMethodInvoke(chain, 100000000, policyHash, "getStoragePrice")
|
||||||
|
require.NoError(t, err)
|
||||||
|
aers, err := persistBlock(chain, txSet, txGet1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkResult(t, aers[0], stackitem.NewBool(true))
|
||||||
|
checkResult(t, aers[1], stackitem.Make(12345))
|
||||||
|
require.NoError(t, chain.persist())
|
||||||
|
|
||||||
|
// Get in the next block.
|
||||||
|
res, err := invokeContractMethod(chain, 100000000, policyHash, "getStoragePrice")
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkResult(t, res, stackitem.NewBigInteger(big.NewInt(12345)))
|
||||||
|
require.NoError(t, chain.persist())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("set, not signed by committee", func(t *testing.T) {
|
||||||
|
signer, err := wallet.NewAccount()
|
||||||
|
require.NoError(t, err)
|
||||||
|
invokeRes, err := invokeContractMethodBy(t, chain, signer, policyHash, "setStoragePrice", int64(100))
|
||||||
|
checkResult(t, invokeRes, stackitem.NewBool(false))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestBlockedAccounts(t *testing.T) {
|
func TestBlockedAccounts(t *testing.T) {
|
||||||
chain := newTestChain(t)
|
chain := newTestChain(t)
|
||||||
defer chain.Close()
|
defer chain.Close()
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
|
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
|
||||||
"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/mempool"
|
"github.com/nspcc-dev/neo-go/pkg/core/mempool"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto"
|
"github.com/nspcc-dev/neo-go/pkg/crypto"
|
||||||
|
@ -105,6 +106,9 @@ func (chain *testChain) GetPolicer() blockchainer.Policer {
|
||||||
func (chain *testChain) GetBaseExecFee() int64 {
|
func (chain *testChain) GetBaseExecFee() int64 {
|
||||||
return interop.DefaultBaseExecFee
|
return interop.DefaultBaseExecFee
|
||||||
}
|
}
|
||||||
|
func (chain *testChain) GetStoragePrice() int64 {
|
||||||
|
return native.StoragePrice
|
||||||
|
}
|
||||||
func (chain *testChain) GetMaxVerificationGAS() int64 {
|
func (chain *testChain) GetMaxVerificationGAS() int64 {
|
||||||
if chain.maxVerificationGAS != 0 {
|
if chain.maxVerificationGAS != 0 {
|
||||||
return chain.maxVerificationGAS
|
return chain.maxVerificationGAS
|
||||||
|
|
Loading…
Reference in a new issue