native: allow to modify StoragePrice in the policy contract

This commit is contained in:
Evgenii Stratonikov 2020-12-14 12:41:23 +03:00
parent 4946556830
commit 62da365302
7 changed files with 123 additions and 7 deletions

View file

@ -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.

View file

@ -6,4 +6,5 @@ type Policer interface {
GetMaxBlockSize() uint32 GetMaxBlockSize() uint32
GetMaxBlockSystemFee() int64 GetMaxBlockSystemFee() int64
GetMaxVerificationGAS() int64 GetMaxVerificationGAS() int64
GetStoragePrice() int64
} }

View file

@ -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

View file

@ -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)
} }

View file

@ -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 {

View file

@ -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()

View file

@ -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