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)
|
||||
}
|
||||
|
||||
// GetStoragePrice returns current storage price.
|
||||
func (bc *Blockchain) GetStoragePrice() int64 {
|
||||
return bc.contracts.Policy.GetStoragePriceInternal(bc.dao)
|
||||
}
|
||||
|
||||
// -- end Policer.
|
||||
|
|
|
@ -6,4 +6,5 @@ type Policer interface {
|
|||
GetMaxBlockSize() uint32
|
||||
GetMaxBlockSystemFee() 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/dao"
|
||||
"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/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
|
@ -201,7 +200,7 @@ func storageDelete(ic *interop.Context) error {
|
|||
if stc.ReadOnly {
|
||||
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()
|
||||
si := ic.DAO.GetStorageItem(stc.ID, key)
|
||||
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)
|
||||
}
|
||||
}
|
||||
if !ic.VM.AddGas(int64(sizeInc) * native.StoragePrice) {
|
||||
if !ic.VM.AddGas(int64(sizeInc) * ic.Chain.GetPolicer().GetStoragePrice()) {
|
||||
return errGasLimitExceeded
|
||||
}
|
||||
si.Value = value
|
||||
|
|
|
@ -119,7 +119,7 @@ func getLimitedSlice(arg stackitem.Item, max int) ([]byte, error) {
|
|||
|
||||
// getNefAndManifestFromItems converts input arguments into NEF and manifest
|
||||
// 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.
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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
|
||||
// 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 {
|
||||
neff, manif, err := getNefAndManifestFromItems(args, ic.VM)
|
||||
neff, manif, err := getNefAndManifestFromItems(ic, args)
|
||||
if err != nil {
|
||||
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
|
||||
// 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 {
|
||||
neff, manif, err := getNefAndManifestFromItems(args, ic.VM)
|
||||
neff, manif, err := getNefAndManifestFromItems(ic, args)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
|
@ -34,6 +34,8 @@ const (
|
|||
maxExecFeeFactor = 1000
|
||||
// maxFeePerByte is the maximum allowed fee per byte value.
|
||||
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 = 15
|
||||
|
@ -52,6 +54,8 @@ var (
|
|||
maxBlockSizeKey = []byte{12}
|
||||
// maxBlockSystemFeeKey is a key used to store the maximum block system fee value.
|
||||
maxBlockSystemFeeKey = []byte{17}
|
||||
// storagePriceKey is a key used to store storage price.
|
||||
storagePriceKey = []byte{19}
|
||||
)
|
||||
|
||||
// Policy represents Policy native contract.
|
||||
|
@ -68,6 +72,7 @@ type Policy struct {
|
|||
feePerByte int64
|
||||
maxBlockSystemFee int64
|
||||
maxVerificationGas int64
|
||||
storagePrice uint32
|
||||
blockedAccounts []util.Uint160
|
||||
}
|
||||
|
||||
|
@ -109,6 +114,15 @@ func newPolicy() *Policy {
|
|||
md = newMethodAndPrice(p.setExecFeeFactor, 3000000, smartcontract.WriteStates)
|
||||
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,
|
||||
manifest.NewParameter("value", smartcontract.IntegerType))
|
||||
md = newMethodAndPrice(p.setMaxBlockSize, 3000000, smartcontract.WriteStates)
|
||||
|
@ -156,6 +170,7 @@ func (p *Policy) Initialize(ic *interop.Context) error {
|
|||
p.feePerByte = defaultFeePerByte
|
||||
p.maxBlockSystemFee = defaultMaxBlockSystemFee
|
||||
p.maxVerificationGas = defaultMaxVerificationGas
|
||||
p.storagePrice = StoragePrice
|
||||
p.blockedAccounts = make([]util.Uint160, 0)
|
||||
|
||||
return nil
|
||||
|
@ -180,6 +195,7 @@ func (p *Policy) PostPersist(ic *interop.Context) error {
|
|||
p.feePerByte = getInt64WithKey(p.ContractID, ic.DAO, feePerByteKey, defaultFeePerByte)
|
||||
p.maxBlockSystemFee = getInt64WithKey(p.ContractID, ic.DAO, maxBlockSystemFeeKey, defaultMaxBlockSystemFee)
|
||||
p.maxVerificationGas = defaultMaxVerificationGas
|
||||
p.storagePrice = getUint32WithKey(p.ContractID, ic.DAO, storagePriceKey, StoragePrice)
|
||||
|
||||
p.blockedAccounts = make([]util.Uint160, 0)
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
// of transactions per block.
|
||||
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) {
|
||||
chain := newTestChain(t)
|
||||
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/interop"
|
||||
"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/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto"
|
||||
|
@ -105,6 +106,9 @@ func (chain *testChain) GetPolicer() blockchainer.Policer {
|
|||
func (chain *testChain) GetBaseExecFee() int64 {
|
||||
return interop.DefaultBaseExecFee
|
||||
}
|
||||
func (chain *testChain) GetStoragePrice() int64 {
|
||||
return native.StoragePrice
|
||||
}
|
||||
func (chain *testChain) GetMaxVerificationGAS() int64 {
|
||||
if chain.maxVerificationGAS != 0 {
|
||||
return chain.maxVerificationGAS
|
||||
|
|
Loading…
Reference in a new issue