native: allow to modify ExecFeeFactor in the policy contract

This commit is contained in:
Evgenii Stratonikov 2020-12-14 12:18:59 +03:00
parent 1840c1c80d
commit 4946556830
6 changed files with 151 additions and 8 deletions

View file

@ -1815,7 +1815,7 @@ func (bc *Blockchain) GetPolicer() blockchainer.Policer {
// GetBaseExecFee return execution price for `NOP`.
func (bc *Blockchain) GetBaseExecFee() int64 {
return interop.DefaultBaseExecFee
return bc.contracts.Policy.GetExecFeeFactorInternal(bc.dao)
}
// GetMaxBlockSize returns maximum allowed block size from native Policy contract.

View file

@ -413,7 +413,8 @@ func addNetworkFee(bc *Blockchain, tx *transaction.Transaction, sender *wallet.A
return nil
}
func invokeContractMethod(chain *Blockchain, sysfee int64, hash util.Uint160, method string, args ...interface{}) (*state.AppExecResult, error) {
func prepareContractMethodInvoke(chain *Blockchain, sysfee int64,
hash util.Uint160, method string, args ...interface{}) (*transaction.Transaction, error) {
w := io.NewBufBinWriter()
emit.AppCallWithOperationAndArgs(w.BinWriter, hash, method, args...)
if w.Err != nil {
@ -427,17 +428,37 @@ func invokeContractMethod(chain *Blockchain, sysfee int64, hash util.Uint160, me
if err != nil {
return nil, err
}
b := chain.newBlock(tx)
err = chain.AddBlock(b)
return tx, nil
}
func persistBlock(chain *Blockchain, txs ...*transaction.Transaction) ([]*state.AppExecResult, error) {
b := chain.newBlock(txs...)
err := chain.AddBlock(b)
if err != nil {
return nil, err
}
aers := make([]*state.AppExecResult, len(txs))
for i, tx := range txs {
res, err := chain.GetAppExecResults(tx.Hash(), trigger.Application)
if err != nil {
return nil, err
}
return &res[0], nil
aers[i] = &res[0]
}
return aers, nil
}
func invokeContractMethod(chain *Blockchain, sysfee int64, hash util.Uint160, method string, args ...interface{}) (*state.AppExecResult, error) {
tx, err := prepareContractMethodInvoke(chain, sysfee, hash, method, args...)
if err != nil {
return nil, err
}
aers, err := persistBlock(chain, tx)
if err != nil {
return nil, err
}
return aers[0], nil
}
func invokeContractMethodBy(t *testing.T, chain *Blockchain, signer *wallet.Account, hash util.Uint160, method string, args ...interface{}) (*state.AppExecResult, error) {

View file

@ -24,11 +24,14 @@ const (
defaultMaxBlockSize = 1024 * 256
defaultMaxTransactionsPerBlock = 512
defaultExecFeeFactor = interop.DefaultBaseExecFee
defaultFeePerByte = 1000
defaultMaxVerificationGas = 50000000
defaultMaxBlockSystemFee = 9000 * GASFactor
// minBlockSystemFee is the minimum allowed system fee per block.
minBlockSystemFee = 4007600
// maxExecFeeFactor is the maximum allowed execution fee factor.
maxExecFeeFactor = 1000
// maxFeePerByte is the maximum allowed fee per byte value.
maxFeePerByte = 100_000_000
@ -40,6 +43,8 @@ var (
// maxTransactionsPerBlockKey is a key used to store the maximum number of
// transactions allowed in block.
maxTransactionsPerBlockKey = []byte{23}
// execFeeFactorKey is a key used to store execution fee factor.
execFeeFactorKey = []byte{18}
// feePerByteKey is a key used to store the minimum fee per byte for
// transaction.
feePerByteKey = []byte{10}
@ -59,6 +64,7 @@ type Policy struct {
isValid bool
maxTransactionsPerBlock uint32
maxBlockSize uint32
execFeeFactor uint32
feePerByte int64
maxBlockSystemFee int64
maxVerificationGas int64
@ -94,6 +100,15 @@ func newPolicy() *Policy {
md = newMethodAndPrice(p.getMaxBlockSystemFee, 1000000, smartcontract.ReadStates)
p.AddMethod(md, desc)
desc = newDescriptor("getExecFeeFactor", smartcontract.IntegerType)
md = newMethodAndPrice(p.getExecFeeFactor, 1000000, smartcontract.ReadStates)
p.AddMethod(md, desc)
desc = newDescriptor("setExecFeeFactor", smartcontract.BoolType,
manifest.NewParameter("value", smartcontract.IntegerType))
md = newMethodAndPrice(p.setExecFeeFactor, 3000000, smartcontract.WriteStates)
p.AddMethod(md, desc)
desc = newDescriptor("setMaxBlockSize", smartcontract.BoolType,
manifest.NewParameter("value", smartcontract.IntegerType))
md = newMethodAndPrice(p.setMaxBlockSize, 3000000, smartcontract.WriteStates)
@ -137,6 +152,7 @@ func (p *Policy) Initialize(ic *interop.Context) error {
p.isValid = true
p.maxTransactionsPerBlock = defaultMaxTransactionsPerBlock
p.maxBlockSize = defaultMaxBlockSize
p.execFeeFactor = defaultExecFeeFactor
p.feePerByte = defaultFeePerByte
p.maxBlockSystemFee = defaultMaxBlockSystemFee
p.maxVerificationGas = defaultMaxVerificationGas
@ -160,6 +176,7 @@ func (p *Policy) PostPersist(ic *interop.Context) error {
p.maxTransactionsPerBlock = getUint32WithKey(p.ContractID, ic.DAO, maxTransactionsPerBlockKey, defaultMaxTransactionsPerBlock)
p.maxBlockSize = getUint32WithKey(p.ContractID, ic.DAO, maxBlockSizeKey, defaultMaxBlockSize)
p.execFeeFactor = getUint32WithKey(p.ContractID, ic.DAO, execFeeFactorKey, defaultExecFeeFactor)
p.feePerByte = getInt64WithKey(p.ContractID, ic.DAO, feePerByteKey, defaultFeePerByte)
p.maxBlockSystemFee = getInt64WithKey(p.ContractID, ic.DAO, maxBlockSystemFeeKey, defaultMaxBlockSystemFee)
p.maxVerificationGas = defaultMaxVerificationGas
@ -256,6 +273,41 @@ func (p *Policy) GetMaxBlockSystemFeeInternal(dao dao.DAO) int64 {
return getInt64WithKey(p.ContractID, dao, maxBlockSystemFeeKey, defaultMaxBlockSystemFee)
}
func (p *Policy) getExecFeeFactor(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
return stackitem.NewBigInteger(big.NewInt(int64(p.GetExecFeeFactorInternal(ic.DAO))))
}
// GetExecFeeFactorInternal returns current execution fee factor.
func (p *Policy) GetExecFeeFactorInternal(d dao.DAO) int64 {
p.lock.RLock()
defer p.lock.RUnlock()
if p.isValid {
return int64(p.execFeeFactor)
}
return int64(getUint32WithKey(p.ContractID, d, execFeeFactorKey, defaultExecFeeFactor))
}
func (p *Policy) setExecFeeFactor(ic *interop.Context, args []stackitem.Item) stackitem.Item {
value := toUint32(args[0])
if value <= 0 || maxExecFeeFactor < value {
panic(fmt.Errorf("ExecFeeFactor must be between 0 and %d", maxExecFeeFactor))
}
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, execFeeFactorKey, uint32(value))
if err != nil {
panic(err)
}
p.isValid = false
return stackitem.NewBool(true)
}
// isBlocked is Policy contract method and checks whether provided account is blocked.
func (p *Policy) isBlocked(ic *interop.Context, args []stackitem.Item) stackitem.Item {
hash := toUint160(args[0])

View file

@ -6,6 +6,7 @@ import (
"github.com/nspcc-dev/neo-go/internal/random"
"github.com/nspcc-dev/neo-go/pkg/core/block"
"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/encoding/bigint"
"github.com/nspcc-dev/neo-go/pkg/network/payload"
@ -143,6 +144,62 @@ func TestFeePerByte(t *testing.T) {
})
}
func TestExecFeeFactor(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.GetExecFeeFactorInternal(chain.dao)
require.EqualValues(t, interop.DefaultBaseExecFee, n)
})
t.Run("get", func(t *testing.T) {
res, err := invokeContractMethod(chain, 100000000, policyHash, "getExecFeeFactor")
require.NoError(t, err)
checkResult(t, res, stackitem.NewBigInteger(big.NewInt(interop.DefaultBaseExecFee)))
require.NoError(t, chain.persist())
})
t.Run("set, zero fee", func(t *testing.T) {
res, err := invokeContractMethod(chain, 100000000, policyHash, "setExecFeeFactor", 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, "setExecFeeFactor", int64(1001))
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, "setExecFeeFactor", int64(123))
require.NoError(t, err)
txGet1, err := prepareContractMethodInvoke(chain, 100000000, policyHash, "getExecFeeFactor")
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(123))
require.NoError(t, chain.persist())
// Get in the next block.
res, err := invokeContractMethod(chain, 100000000, policyHash, "getExecFeeFactor")
require.NoError(t, err)
checkResult(t, res, stackitem.NewBigInteger(big.NewInt(123)))
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, "setExecFeeFactor", int64(100))
checkResult(t, invokeRes, stackitem.NewBool(false))
})
}
func TestBlockSystemFee(t *testing.T) {
chain := newTestChain(t)
defer chain.Close()

View file

@ -25,6 +25,11 @@ func (c *Client) GetFeePerByte() (int64, error) {
return c.invokeNativePolicyMethod("getFeePerByte")
}
// GetExecFeeFactor invokes `getExecFeeFactor` method on a native Policy contract.
func (c *Client) GetExecFeeFactor() (int64, error) {
return c.invokeNativePolicyMethod("getExecFeeFactor")
}
func (c *Client) invokeNativePolicyMethod(operation string) (int64, error) {
if !c.initDone {
return 0, errNetworkNotInitialized

View file

@ -9,7 +9,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core"
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/fee"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"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"
@ -570,6 +569,7 @@ func (c *Client) AddNetworkFee(tx *transaction.Transaction, extraFee int64, accs
return errors.New("number of signers must match number of scripts")
}
size := io.GetVarSize(tx)
var ef int64
for i, cosigner := range tx.Signers {
if accs[i].Contract.Deployed {
res, err := c.InvokeFunction(cosigner.Account, manifest.MethodVerify, []smartcontract.Parameter{}, tx.Signers)
@ -590,7 +590,15 @@ func (c *Client) AddNetworkFee(tx *transaction.Transaction, extraFee int64, accs
size += io.GetVarSize([]byte{}) * 2 // both scripts are empty
continue
}
netFee, sizeDelta := fee.Calculate(interop.DefaultBaseExecFee, accs[i].Contract.Script)
if ef == 0 {
var err error
ef, err = c.GetExecFeeFactor()
if err != nil {
return fmt.Errorf("can't get `ExecFeeFactor`: %w", err)
}
}
netFee, sizeDelta := fee.Calculate(ef, accs[i].Contract.Script)
tx.NetworkFee += netFee
size += sizeDelta
}