From 1840c1c80da625c1935bec4387a08d7ec72ad014 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Fri, 11 Dec 2020 15:22:49 +0300 Subject: [PATCH 1/4] core: redefine opcode prices Prices are defined in as a coefficients to `BaseExecFee` which is defined by Policy contract (TBD later). Native method prices are defined without need to multiply. --- internal/testchain/transaction.go | 2 +- pkg/consensus/consensus_test.go | 27 +- pkg/core/blockchain.go | 13 +- pkg/core/blockchain_test.go | 24 +- pkg/core/blockchainer/policer.go | 1 + pkg/core/fee/calculate.go | 16 +- pkg/core/fee/opcode.go | 378 ++++++++++++++------------- pkg/core/gas_price.go | 4 +- pkg/core/helper_test.go | 4 +- pkg/core/interop/context.go | 15 +- pkg/core/interop/crypto/ecdsa.go | 4 +- pkg/core/interop_system.go | 1 + pkg/core/interops.go | 108 ++++---- pkg/core/mempool/mem_pool_test.go | 4 + pkg/core/native/interop.go | 1 + pkg/core/native_contract_test.go | 15 +- pkg/network/helper_test.go | 5 +- pkg/network/server_test.go | 2 + pkg/rpc/client/rpc.go | 3 +- pkg/rpc/server/client_test.go | 6 +- pkg/rpc/server/server_helper_test.go | 5 + pkg/rpc/server/server_test.go | 2 +- 22 files changed, 342 insertions(+), 298 deletions(-) diff --git a/internal/testchain/transaction.go b/internal/testchain/transaction.go index 14186be9a..b0dcb003d 100644 --- a/internal/testchain/transaction.go +++ b/internal/testchain/transaction.go @@ -92,7 +92,7 @@ func NewDeployTx(bc blockchainer.Blockchainer, name string, sender util.Uint160, func SignTx(bc blockchainer.Blockchainer, txs ...*transaction.Transaction) error { for _, tx := range txs { size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(ownerScript) + netFee, sizeDelta := fee.Calculate(bc.GetPolicer().GetBaseExecFee(), ownerScript) tx.NetworkFee += netFee size += sizeDelta tx.NetworkFee += int64(size) * bc.FeePerByte() diff --git a/pkg/consensus/consensus_test.go b/pkg/consensus/consensus_test.go index 19912da09..d3983f9bf 100644 --- a/pkg/consensus/consensus_test.go +++ b/pkg/consensus/consensus_test.go @@ -11,6 +11,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core" + "github.com/nspcc-dev/neo-go/pkg/core/blockchainer" "github.com/nspcc-dev/neo-go/pkg/core/fee" "github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/core/storage" @@ -32,7 +33,7 @@ func TestNewService(t *testing.T) { tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 100000) tx.ValidUntilBlock = 1 addSender(t, tx) - signTx(t, srv.Chain.FeePerByte(), tx) + signTx(t, srv.Chain, tx) require.NoError(t, srv.Chain.PoolTx(tx)) var txx []block.Transaction @@ -170,7 +171,7 @@ func TestService_GetVerified(t *testing.T) { txs = append(txs, tx) } addSender(t, txs...) - signTx(t, srv.Chain.FeePerByte(), txs...) + signTx(t, srv.Chain, txs...) require.NoError(t, srv.Chain.PoolTx(txs[3])) hashes := []util.Uint256{txs[0].Hash(), txs[1].Hash(), txs[2].Hash()} @@ -257,7 +258,7 @@ func TestService_getTx(t *testing.T) { tx.Nonce = 1234 tx.ValidUntilBlock = 1 addSender(t, tx) - signTx(t, srv.Chain.FeePerByte(), tx) + signTx(t, srv.Chain, tx) h := tx.Hash() require.Equal(t, nil, srv.getTx(h)) @@ -351,7 +352,7 @@ func TestVerifyBlock(t *testing.T) { tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.RET)}, 100000) tx.ValidUntilBlock = 1 addSender(t, tx) - signTx(t, srv.Chain.FeePerByte(), tx) + signTx(t, srv.Chain, tx) require.NoError(t, srv.Chain.PoolTx(tx)) b := testchain.NewBlock(t, srv.Chain, 1, 0, tx) require.True(t, srv.verifyBlock(&neoBlock{Block: *b})) @@ -360,7 +361,7 @@ func TestVerifyBlock(t *testing.T) { tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.RET)}, 100000) tx.ValidUntilBlock = 1 addSender(t, tx) - signTx(t, srv.Chain.FeePerByte(), tx) + signTx(t, srv.Chain, tx) b := testchain.NewBlock(t, srv.Chain, 1, 0, tx) require.True(t, srv.verifyBlock(&neoBlock{Block: *b})) }) @@ -369,12 +370,12 @@ func TestVerifyBlock(t *testing.T) { tx1.NetworkFee = 20_000_000 * native.GASFactor tx1.ValidUntilBlock = 1 addSender(t, tx1) - signTx(t, srv.Chain.FeePerByte(), tx1) + signTx(t, srv.Chain, tx1) tx2 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.RET)}, 100000) tx2.NetworkFee = 20_000_000 * native.GASFactor tx2.ValidUntilBlock = 1 addSender(t, tx2) - signTx(t, srv.Chain.FeePerByte(), tx2) + signTx(t, srv.Chain, tx2) require.NoError(t, srv.Chain.PoolTx(tx1)) require.Error(t, srv.Chain.PoolTx(tx2)) b := testchain.NewBlock(t, srv.Chain, 1, 0, tx2) @@ -391,7 +392,7 @@ func TestVerifyBlock(t *testing.T) { tx := transaction.New(netmode.UnitTestNet, script, 100000) tx.ValidUntilBlock = 1 addSender(t, tx) - signTx(t, srv.Chain.FeePerByte(), tx) + signTx(t, srv.Chain, tx) b := testchain.NewBlock(t, srv.Chain, 1, 0, tx) require.False(t, srv.verifyBlock(&neoBlock{Block: *b})) }) @@ -399,7 +400,7 @@ func TestVerifyBlock(t *testing.T) { tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.RET)}, 100000) tx.ValidUntilBlock = 1 addSender(t, tx) - signTx(t, srv.Chain.FeePerByte(), tx) + signTx(t, srv.Chain, tx) tx.Scripts[0].InvocationScript[16] = ^tx.Scripts[0].InvocationScript[16] b := testchain.NewBlock(t, srv.Chain, 1, 0, tx) require.False(t, srv.verifyBlock(&neoBlock{Block: *b})) @@ -410,7 +411,7 @@ func TestVerifyBlock(t *testing.T) { txes[i] = transaction.New(netmode.UnitTestNet, []byte{byte(opcode.RET)}, srv.Chain.GetPolicer().GetMaxBlockSystemFee()/2+1) txes[i].ValidUntilBlock = 1 addSender(t, txes[i]) - signTx(t, srv.Chain.FeePerByte(), txes[i]) + signTx(t, srv.Chain, txes[i]) } b := testchain.NewBlock(t, srv.Chain, 1, 0, txes...) require.False(t, srv.verifyBlock(&neoBlock{Block: *b})) @@ -500,7 +501,7 @@ func addSender(t *testing.T, txs ...*transaction.Transaction) { } } -func signTx(t *testing.T, feePerByte int64, txs ...*transaction.Transaction) { +func signTx(t *testing.T, bc blockchainer.Blockchainer, txs ...*transaction.Transaction) { validators := make([]*keys.PublicKey, 4) privNetKeys := make([]*keys.PrivateKey, 4) for i := 0; i < 4; i++ { @@ -512,10 +513,10 @@ func signTx(t *testing.T, feePerByte int64, txs ...*transaction.Transaction) { require.NoError(t, err) for _, tx := range txs { size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(rawScript) + netFee, sizeDelta := fee.Calculate(bc.GetPolicer().GetBaseExecFee(), rawScript) tx.NetworkFee += +netFee size += sizeDelta - tx.NetworkFee += int64(size) * feePerByte + tx.NetworkFee += int64(size) * bc.FeePerByte() data := tx.GetSignedPart() buf := io.NewBufBinWriter() diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 443f8609c..fe1998a89 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -620,7 +620,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error systemInterop := bc.newInteropContext(trigger.Application, cache, block, tx) v := systemInterop.SpawnVM() v.LoadScriptWithFlags(tx.Script, smartcontract.All) - v.SetPriceGetter(getPrice) + v.SetPriceGetter(bc.getPrice) v.GasLimit = tx.SystemFee err := v.Run() @@ -753,7 +753,7 @@ func (bc *Blockchain) runPersist(script []byte, block *block.Block, cache *dao.C systemInterop := bc.newInteropContext(trig, cache, block, nil) v := systemInterop.SpawnVM() v.LoadScriptWithFlags(script, smartcontract.WriteStates|smartcontract.AllowCall) - v.SetPriceGetter(getPrice) + v.SetPriceGetter(bc.getPrice) if err := v.Run(); err != nil { return nil, fmt.Errorf("VM has failed: %w", err) } else if _, err := systemInterop.DAO.Persist(); err != nil { @@ -1622,7 +1622,7 @@ func (bc *Blockchain) GetTestVM(tx *transaction.Transaction, b *block.Block) *vm d.MPT = nil systemInterop := bc.newInteropContext(trigger.Application, d, b, tx) vm := systemInterop.SpawnVM() - vm.SetPriceGetter(getPrice) + vm.SetPriceGetter(bc.getPrice) return vm } @@ -1692,7 +1692,7 @@ func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transa } vm := interopCtx.SpawnVM() - vm.SetPriceGetter(getPrice) + vm.SetPriceGetter(bc.getPrice) vm.GasLimit = gas if err := bc.initVerificationVM(interopCtx, hash, witness); err != nil { return 0, err @@ -1813,6 +1813,11 @@ func (bc *Blockchain) GetPolicer() blockchainer.Policer { return bc } +// GetBaseExecFee return execution price for `NOP`. +func (bc *Blockchain) GetBaseExecFee() int64 { + return interop.DefaultBaseExecFee +} + // GetMaxBlockSize returns maximum allowed block size from native Policy contract. func (bc *Blockchain) GetMaxBlockSize() uint32 { return bc.contracts.Policy.GetMaxBlockSizeInternal(bc.dao) diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index b2d50d93d..8256ad16a 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -338,7 +338,7 @@ func TestVerifyTx(t *testing.T) { }) t.Run("AlmostEnoughNetworkFee", func(t *testing.T) { tx := bc.newTestTx(h, testScript) - verificationNetFee, calcultedScriptSize := fee.Calculate(accs[0].Contract.Script) + verificationNetFee, calcultedScriptSize := fee.Calculate(bc.GetBaseExecFee(), accs[0].Contract.Script) expectedSize := io.GetVarSize(tx) + calcultedScriptSize calculatedNetFee := verificationNetFee + int64(expectedSize)*bc.FeePerByte() tx.NetworkFee = calculatedNetFee - 1 @@ -348,7 +348,7 @@ func TestVerifyTx(t *testing.T) { }) t.Run("EnoughNetworkFee", func(t *testing.T) { tx := bc.newTestTx(h, testScript) - verificationNetFee, calcultedScriptSize := fee.Calculate(accs[0].Contract.Script) + verificationNetFee, calcultedScriptSize := fee.Calculate(bc.GetBaseExecFee(), accs[0].Contract.Script) expectedSize := io.GetVarSize(tx) + calcultedScriptSize calculatedNetFee := verificationNetFee + int64(expectedSize)*bc.FeePerByte() tx.NetworkFee = calculatedNetFee @@ -359,7 +359,7 @@ func TestVerifyTx(t *testing.T) { t.Run("CalculateNetworkFee, signature script", func(t *testing.T) { tx := bc.newTestTx(h, testScript) expectedSize := io.GetVarSize(tx) - verificationNetFee, calculatedScriptSize := fee.Calculate(accs[0].Contract.Script) + verificationNetFee, calculatedScriptSize := fee.Calculate(bc.GetBaseExecFee(), accs[0].Contract.Script) expectedSize += calculatedScriptSize expectedNetFee := verificationNetFee + int64(expectedSize)*bc.FeePerByte() tx.NetworkFee = expectedNetFee @@ -378,7 +378,7 @@ func TestVerifyTx(t *testing.T) { require.NoError(t, multisigAcc.ConvertMultisig(1, pKeys)) multisigHash := hash.Hash160(multisigAcc.Contract.Script) tx := bc.newTestTx(multisigHash, testScript) - verificationNetFee, calculatedScriptSize := fee.Calculate(multisigAcc.Contract.Script) + verificationNetFee, calculatedScriptSize := fee.Calculate(bc.GetBaseExecFee(), multisigAcc.Contract.Script) expectedSize := io.GetVarSize(tx) + calculatedScriptSize expectedNetFee := verificationNetFee + int64(expectedSize)*bc.FeePerByte() tx.NetworkFee = expectedNetFee @@ -478,7 +478,7 @@ func TestVerifyTx(t *testing.T) { rawScript := testchain.CommitteeVerificationScript() require.NoError(t, err) size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(rawScript) + netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), rawScript) tx.NetworkFee += netFee tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte() data := tx.GetSignedPart() @@ -517,7 +517,7 @@ func TestVerifyTx(t *testing.T) { Scopes: transaction.None, }} size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(oracleScript) + netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), oracleScript) tx.NetworkFee += netFee tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte() return tx @@ -614,7 +614,7 @@ func TestVerifyTx(t *testing.T) { rawScript := testchain.CommitteeVerificationScript() require.NoError(t, err) size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(rawScript) + netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), rawScript) tx.NetworkFee += netFee tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte() data := tx.GetSignedPart() @@ -652,7 +652,7 @@ func TestVerifyTx(t *testing.T) { rawScript := testchain.CommitteeVerificationScript() require.NoError(t, err) size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(rawScript) + netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), rawScript) tx.NetworkFee += netFee tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte() data := tx.GetSignedPart() @@ -692,7 +692,7 @@ func TestVerifyTx(t *testing.T) { rawScript := testchain.CommitteeVerificationScript() require.NoError(t, err) size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(rawScript) + netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), rawScript) tx.NetworkFee += netFee tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte() data := tx.GetSignedPart() @@ -772,7 +772,7 @@ func TestVerifyTx(t *testing.T) { } rawScript := testchain.CommitteeVerificationScript() size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(rawScript) + netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), rawScript) tx.NetworkFee += netFee tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte() data := tx.GetSignedPart() @@ -964,14 +964,14 @@ func TestVerifyTx(t *testing.T) { }, } size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(testchain.MultisigVerificationScript()) + netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), testchain.MultisigVerificationScript()) tx.NetworkFee = netFee + // multisig witness verification price int64(size)*bc.FeePerByte() + // fee for unsigned size int64(sizeDelta)*bc.FeePerByte() + //fee for multisig size 66*bc.FeePerByte() + // fee for Notary signature size (66 bytes for Invocation script and 0 bytes for Verification script) 2*bc.FeePerByte() + // fee for the length of each script in Notary witness (they are nil, so we did not take them into account during `size` calculation) transaction.NotaryServiceFeePerKey + // fee for Notary attribute - fee.Opcode( // Notary verification script + fee.Opcode(bc.GetBaseExecFee(), // Notary verification script opcode.PUSHDATA1, opcode.RET, // invocation script opcode.DEPTH, opcode.PACK, opcode.PUSHDATA1, opcode.RET, // arguments for native verification call opcode.PUSHDATA1, opcode.SYSCALL, opcode.RET) + // Neo.Native.Call diff --git a/pkg/core/blockchainer/policer.go b/pkg/core/blockchainer/policer.go index 56888de1d..48ae7c0af 100644 --- a/pkg/core/blockchainer/policer.go +++ b/pkg/core/blockchainer/policer.go @@ -2,6 +2,7 @@ package blockchainer // Policer is an interface that abstracts the implementation of policy methods. type Policer interface { + GetBaseExecFee() int64 GetMaxBlockSize() uint32 GetMaxBlockSystemFee() int64 GetMaxVerificationGAS() int64 diff --git a/pkg/core/fee/calculate.go b/pkg/core/fee/calculate.go index 38ad41a22..0cf4e6383 100644 --- a/pkg/core/fee/calculate.go +++ b/pkg/core/fee/calculate.go @@ -9,31 +9,31 @@ import ( ) // Calculate returns network fee for transaction -func Calculate(script []byte) (int64, int) { +func Calculate(base int64, script []byte) (int64, int) { var ( netFee int64 size int ) if vm.IsSignatureContract(script) { size += 67 + io.GetVarSize(script) - netFee += Opcode(opcode.PUSHDATA1, opcode.PUSHNULL, opcode.PUSHDATA1) + crypto.ECDSAVerifyPrice + netFee += Opcode(base, opcode.PUSHDATA1, opcode.PUSHNULL, opcode.PUSHDATA1) + base*crypto.ECDSAVerifyPrice } else if m, pubs, ok := vm.ParseMultiSigContract(script); ok { n := len(pubs) sizeInv := 66 * m size += io.GetVarSize(sizeInv) + sizeInv + io.GetVarSize(script) - netFee += calculateMultisig(m) + calculateMultisig(n) - netFee += Opcode(opcode.PUSHNULL) + crypto.ECDSAVerifyPrice*int64(n) + netFee += calculateMultisig(base, m) + calculateMultisig(base, n) + netFee += Opcode(base, opcode.PUSHNULL) + base*crypto.ECDSAVerifyPrice*int64(n) } else { // We can support more contract types in the future. } return netFee, size } -func calculateMultisig(n int) int64 { - result := Opcode(opcode.PUSHDATA1) * int64(n) +func calculateMultisig(base int64, n int) int64 { + result := Opcode(base, opcode.PUSHDATA1) * int64(n) bw := io.NewBufBinWriter() emit.Int(bw.BinWriter, int64(n)) - // it's a hack because prices of small PUSH* opcodes are equal - result += Opcode(opcode.Opcode(bw.Bytes()[0])) + // it's a hack because coefficients of small PUSH* opcodes are equal + result += Opcode(base, opcode.Opcode(bw.Bytes()[0])) return result } diff --git a/pkg/core/fee/opcode.go b/pkg/core/fee/opcode.go index af8604993..822a229ad 100644 --- a/pkg/core/fee/opcode.go +++ b/pkg/core/fee/opcode.go @@ -1,199 +1,201 @@ package fee -import "github.com/nspcc-dev/neo-go/pkg/vm/opcode" +import ( + "github.com/nspcc-dev/neo-go/pkg/vm/opcode" +) -// Opcode returns the deployment prices of specified opcodes. -func Opcode(opcodes ...opcode.Opcode) int64 { +// Opcode returns the deployment coefficients of specified opcodes. +func Opcode(base int64, opcodes ...opcode.Opcode) int64 { var result int64 for _, op := range opcodes { - result += prices[op] + result += coefficients[op] } - return result + return result * base } -var prices = map[opcode.Opcode]int64{ - opcode.PUSHINT8: 30, - opcode.PUSHINT32: 30, - opcode.PUSHINT64: 30, - opcode.PUSHINT16: 30, - opcode.PUSHINT128: 120, - opcode.PUSHINT256: 120, - opcode.PUSHA: 120, - opcode.PUSHNULL: 30, - opcode.PUSHDATA1: 180, - opcode.PUSHDATA2: 13000, - opcode.PUSHDATA4: 110000, - opcode.PUSHM1: 30, - opcode.PUSH0: 30, - opcode.PUSH1: 30, - opcode.PUSH2: 30, - opcode.PUSH3: 30, - opcode.PUSH4: 30, - opcode.PUSH5: 30, - opcode.PUSH6: 30, - opcode.PUSH7: 30, - opcode.PUSH8: 30, - opcode.PUSH9: 30, - opcode.PUSH10: 30, - opcode.PUSH11: 30, - opcode.PUSH12: 30, - opcode.PUSH13: 30, - opcode.PUSH14: 30, - opcode.PUSH15: 30, - opcode.PUSH16: 30, - opcode.NOP: 30, - opcode.JMP: 70, - opcode.JMPL: 70, - opcode.JMPIF: 70, - opcode.JMPIFL: 70, - opcode.JMPIFNOT: 70, - opcode.JMPIFNOTL: 70, - opcode.JMPEQ: 70, - opcode.JMPEQL: 70, - opcode.JMPNE: 70, - opcode.JMPNEL: 70, - opcode.JMPGT: 70, - opcode.JMPGTL: 70, - opcode.JMPGE: 70, - opcode.JMPGEL: 70, - opcode.JMPLT: 70, - opcode.JMPLTL: 70, - opcode.JMPLE: 70, - opcode.JMPLEL: 70, - opcode.CALL: 22000, - opcode.CALLL: 22000, - opcode.CALLA: 22000, - opcode.ABORT: 30, - opcode.ASSERT: 30, - opcode.THROW: 22000, - opcode.TRY: 100, - opcode.TRYL: 100, - opcode.ENDTRY: 100, - opcode.ENDTRYL: 100, - opcode.ENDFINALLY: 100, +var coefficients = map[opcode.Opcode]int64{ + opcode.PUSHINT8: 1 << 0, + opcode.PUSHINT16: 1 << 0, + opcode.PUSHINT32: 1 << 0, + opcode.PUSHINT64: 1 << 0, + opcode.PUSHINT128: 1 << 2, + opcode.PUSHINT256: 1 << 2, + opcode.PUSHA: 1 << 2, + opcode.PUSHNULL: 1 << 0, + opcode.PUSHDATA1: 1 << 3, + opcode.PUSHDATA2: 1 << 9, + opcode.PUSHDATA4: 1 << 12, + opcode.PUSHM1: 1 << 0, + opcode.PUSH0: 1 << 0, + opcode.PUSH1: 1 << 0, + opcode.PUSH2: 1 << 0, + opcode.PUSH3: 1 << 0, + opcode.PUSH4: 1 << 0, + opcode.PUSH5: 1 << 0, + opcode.PUSH6: 1 << 0, + opcode.PUSH7: 1 << 0, + opcode.PUSH8: 1 << 0, + opcode.PUSH9: 1 << 0, + opcode.PUSH10: 1 << 0, + opcode.PUSH11: 1 << 0, + opcode.PUSH12: 1 << 0, + opcode.PUSH13: 1 << 0, + opcode.PUSH14: 1 << 0, + opcode.PUSH15: 1 << 0, + opcode.PUSH16: 1 << 0, + opcode.NOP: 1 << 0, + opcode.JMP: 1 << 1, + opcode.JMPL: 1 << 1, + opcode.JMPIF: 1 << 1, + opcode.JMPIFL: 1 << 1, + opcode.JMPIFNOT: 1 << 1, + opcode.JMPIFNOTL: 1 << 1, + opcode.JMPEQ: 1 << 1, + opcode.JMPEQL: 1 << 1, + opcode.JMPNE: 1 << 1, + opcode.JMPNEL: 1 << 1, + opcode.JMPGT: 1 << 1, + opcode.JMPGTL: 1 << 1, + opcode.JMPGE: 1 << 1, + opcode.JMPGEL: 1 << 1, + opcode.JMPLT: 1 << 1, + opcode.JMPLTL: 1 << 1, + opcode.JMPLE: 1 << 1, + opcode.JMPLEL: 1 << 1, + opcode.CALL: 1 << 9, + opcode.CALLL: 1 << 9, + opcode.CALLA: 1 << 9, + opcode.ABORT: 0, + opcode.ASSERT: 1 << 0, + opcode.THROW: 1 << 9, + opcode.TRY: 1 << 2, + opcode.TRYL: 1 << 2, + opcode.ENDTRY: 1 << 2, + opcode.ENDTRYL: 1 << 2, + opcode.ENDFINALLY: 1 << 2, opcode.RET: 0, opcode.SYSCALL: 0, - opcode.DEPTH: 60, - opcode.DROP: 60, - opcode.NIP: 60, - opcode.XDROP: 400, - opcode.CLEAR: 400, - opcode.DUP: 60, - opcode.OVER: 60, - opcode.PICK: 60, - opcode.TUCK: 60, - opcode.SWAP: 60, - opcode.ROT: 60, - opcode.ROLL: 400, - opcode.REVERSE3: 60, - opcode.REVERSE4: 60, - opcode.REVERSEN: 400, - opcode.INITSSLOT: 400, - opcode.INITSLOT: 1600, - opcode.LDSFLD0: 60, - opcode.LDSFLD1: 60, - opcode.LDSFLD2: 60, - opcode.LDSFLD3: 60, - opcode.LDSFLD4: 60, - opcode.LDSFLD5: 60, - opcode.LDSFLD6: 60, - opcode.LDSFLD: 60, - opcode.STSFLD0: 60, - opcode.STSFLD1: 60, - opcode.STSFLD2: 60, - opcode.STSFLD3: 60, - opcode.STSFLD4: 60, - opcode.STSFLD5: 60, - opcode.STSFLD6: 60, - opcode.STSFLD: 60, - opcode.LDLOC0: 60, - opcode.LDLOC1: 60, - opcode.LDLOC2: 60, - opcode.LDLOC3: 60, - opcode.LDLOC4: 60, - opcode.LDLOC5: 60, - opcode.LDLOC6: 60, - opcode.LDLOC: 60, - opcode.STLOC0: 60, - opcode.STLOC1: 60, - opcode.STLOC2: 60, - opcode.STLOC3: 60, - opcode.STLOC4: 60, - opcode.STLOC5: 60, - opcode.STLOC6: 60, - opcode.STLOC: 60, - opcode.LDARG0: 60, - opcode.LDARG1: 60, - opcode.LDARG2: 60, - opcode.LDARG3: 60, - opcode.LDARG4: 60, - opcode.LDARG5: 60, - opcode.LDARG6: 60, - opcode.LDARG: 60, - opcode.STARG0: 60, - opcode.STARG1: 60, - opcode.STARG2: 60, - opcode.STARG3: 60, - opcode.STARG4: 60, - opcode.STARG5: 60, - opcode.STARG6: 60, - opcode.STARG: 60, - opcode.NEWBUFFER: 80000, - opcode.MEMCPY: 80000, - opcode.CAT: 80000, - opcode.SUBSTR: 80000, - opcode.LEFT: 80000, - opcode.RIGHT: 80000, - opcode.INVERT: 100, - opcode.AND: 200, - opcode.OR: 200, - opcode.XOR: 200, - opcode.EQUAL: 1000, - opcode.NOTEQUAL: 1000, - opcode.SIGN: 100, - opcode.ABS: 100, - opcode.NEGATE: 100, - opcode.INC: 100, - opcode.DEC: 100, - opcode.ADD: 200, - opcode.SUB: 200, - opcode.MUL: 300, - opcode.DIV: 300, - opcode.MOD: 300, - opcode.SHL: 300, - opcode.SHR: 300, - opcode.NOT: 100, - opcode.BOOLAND: 200, - opcode.BOOLOR: 200, - opcode.NZ: 100, - opcode.NUMEQUAL: 200, - opcode.NUMNOTEQUAL: 200, - opcode.LT: 200, - opcode.LTE: 200, - opcode.GT: 200, - opcode.GTE: 200, - opcode.MIN: 200, - opcode.MAX: 200, - opcode.WITHIN: 200, - opcode.PACK: 15000, - opcode.UNPACK: 15000, - opcode.NEWARRAY0: 400, - opcode.NEWARRAY: 15000, - opcode.NEWARRAYT: 15000, - opcode.NEWSTRUCT0: 400, - opcode.NEWSTRUCT: 15000, - opcode.NEWMAP: 200, - opcode.SIZE: 150, - opcode.HASKEY: 270000, - opcode.KEYS: 500, - opcode.VALUES: 270000, - opcode.PICKITEM: 270000, - opcode.APPEND: 270000, - opcode.SETITEM: 270000, - opcode.REVERSEITEMS: 270000, - opcode.REMOVE: 500, - opcode.CLEARITEMS: 400, - opcode.ISNULL: 60, - opcode.ISTYPE: 60, - opcode.CONVERT: 80000, + opcode.DEPTH: 1 << 1, + opcode.DROP: 1 << 1, + opcode.NIP: 1 << 1, + opcode.XDROP: 1 << 4, + opcode.CLEAR: 1 << 4, + opcode.DUP: 1 << 1, + opcode.OVER: 1 << 1, + opcode.PICK: 1 << 1, + opcode.TUCK: 1 << 1, + opcode.SWAP: 1 << 1, + opcode.ROT: 1 << 1, + opcode.ROLL: 1 << 4, + opcode.REVERSE3: 1 << 1, + opcode.REVERSE4: 1 << 1, + opcode.REVERSEN: 1 << 4, + opcode.INITSSLOT: 1 << 4, + opcode.INITSLOT: 1 << 6, + opcode.LDSFLD0: 1 << 1, + opcode.LDSFLD1: 1 << 1, + opcode.LDSFLD2: 1 << 1, + opcode.LDSFLD3: 1 << 1, + opcode.LDSFLD4: 1 << 1, + opcode.LDSFLD5: 1 << 1, + opcode.LDSFLD6: 1 << 1, + opcode.LDSFLD: 1 << 1, + opcode.STSFLD0: 1 << 1, + opcode.STSFLD1: 1 << 1, + opcode.STSFLD2: 1 << 1, + opcode.STSFLD3: 1 << 1, + opcode.STSFLD4: 1 << 1, + opcode.STSFLD5: 1 << 1, + opcode.STSFLD6: 1 << 1, + opcode.STSFLD: 1 << 1, + opcode.LDLOC0: 1 << 1, + opcode.LDLOC1: 1 << 1, + opcode.LDLOC2: 1 << 1, + opcode.LDLOC3: 1 << 1, + opcode.LDLOC4: 1 << 1, + opcode.LDLOC5: 1 << 1, + opcode.LDLOC6: 1 << 1, + opcode.LDLOC: 1 << 1, + opcode.STLOC0: 1 << 1, + opcode.STLOC1: 1 << 1, + opcode.STLOC2: 1 << 1, + opcode.STLOC3: 1 << 1, + opcode.STLOC4: 1 << 1, + opcode.STLOC5: 1 << 1, + opcode.STLOC6: 1 << 1, + opcode.STLOC: 1 << 1, + opcode.LDARG0: 1 << 1, + opcode.LDARG1: 1 << 1, + opcode.LDARG2: 1 << 1, + opcode.LDARG3: 1 << 1, + opcode.LDARG4: 1 << 1, + opcode.LDARG5: 1 << 1, + opcode.LDARG6: 1 << 1, + opcode.LDARG: 1 << 1, + opcode.STARG0: 1 << 1, + opcode.STARG1: 1 << 1, + opcode.STARG2: 1 << 1, + opcode.STARG3: 1 << 1, + opcode.STARG4: 1 << 1, + opcode.STARG5: 1 << 1, + opcode.STARG6: 1 << 1, + opcode.STARG: 1 << 1, + opcode.NEWBUFFER: 1 << 8, + opcode.MEMCPY: 1 << 11, + opcode.CAT: 1 << 11, + opcode.SUBSTR: 1 << 11, + opcode.LEFT: 1 << 11, + opcode.RIGHT: 1 << 11, + opcode.INVERT: 1 << 2, + opcode.AND: 1 << 3, + opcode.OR: 1 << 3, + opcode.XOR: 1 << 3, + opcode.EQUAL: 1 << 5, + opcode.NOTEQUAL: 1 << 5, + opcode.SIGN: 1 << 2, + opcode.ABS: 1 << 2, + opcode.NEGATE: 1 << 2, + opcode.INC: 1 << 2, + opcode.DEC: 1 << 2, + opcode.ADD: 1 << 3, + opcode.SUB: 1 << 3, + opcode.MUL: 1 << 3, + opcode.DIV: 1 << 3, + opcode.MOD: 1 << 3, + opcode.SHL: 1 << 3, + opcode.SHR: 1 << 3, + opcode.NOT: 1 << 2, + opcode.BOOLAND: 1 << 3, + opcode.BOOLOR: 1 << 3, + opcode.NZ: 1 << 2, + opcode.NUMEQUAL: 1 << 3, + opcode.NUMNOTEQUAL: 1 << 3, + opcode.LT: 1 << 3, + opcode.LTE: 1 << 3, + opcode.GT: 1 << 3, + opcode.GTE: 1 << 3, + opcode.MIN: 1 << 3, + opcode.MAX: 1 << 3, + opcode.WITHIN: 1 << 3, + opcode.PACK: 1 << 9, + opcode.UNPACK: 1 << 9, + opcode.NEWARRAY0: 1 << 4, + opcode.NEWARRAY: 1 << 9, + opcode.NEWARRAYT: 1 << 9, + opcode.NEWSTRUCT0: 1 << 4, + opcode.NEWSTRUCT: 1 << 9, + opcode.NEWMAP: 1 << 3, + opcode.SIZE: 1 << 2, + opcode.HASKEY: 1 << 6, + opcode.KEYS: 1 << 4, + opcode.VALUES: 1 << 13, + opcode.PICKITEM: 1 << 6, + opcode.APPEND: 1 << 13, + opcode.SETITEM: 1 << 13, + opcode.REVERSEITEMS: 1 << 13, + opcode.REMOVE: 1 << 4, + opcode.CLEARITEMS: 1 << 4, + opcode.ISNULL: 1 << 1, + opcode.ISTYPE: 1 << 1, + opcode.CONVERT: 1 << 11, } diff --git a/pkg/core/gas_price.go b/pkg/core/gas_price.go index b5ead6426..406e1e65a 100644 --- a/pkg/core/gas_price.go +++ b/pkg/core/gas_price.go @@ -7,6 +7,6 @@ import ( ) // getPrice returns a price for executing op with the provided parameter. -func getPrice(v *vm.VM, op opcode.Opcode, parameter []byte) int64 { - return fee.Opcode(op) +func (bc *Blockchain) getPrice(v *vm.VM, op opcode.Opcode, parameter []byte) int64 { + return fee.Opcode(bc.GetBaseExecFee(), op) } diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index 5f0738e30..2742ecfbc 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -398,13 +398,13 @@ func addSigners(txs ...*transaction.Transaction) { func addNetworkFee(bc *Blockchain, tx *transaction.Transaction, sender *wallet.Account) error { size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(sender.Contract.Script) + netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), sender.Contract.Script) tx.NetworkFee += netFee size += sizeDelta for _, cosigner := range tx.Signers { contract := bc.GetContractState(cosigner.Account) if contract != nil { - netFee, sizeDelta = fee.Calculate(contract.Script) + netFee, sizeDelta = fee.Calculate(bc.GetBaseExecFee(), contract.Script) tx.NetworkFee += netFee size += sizeDelta } diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index 4322491b7..ee141cfdf 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -20,6 +20,11 @@ import ( "go.uber.org/zap" ) +const ( + // DefaultBaseExecFee specifies default multiplier for opcode and syscall prices. + DefaultBaseExecFee = 30 +) + // Context represents context in which interops are executed. type Context struct { Chain blockchainer.Blockchainer @@ -155,6 +160,14 @@ func (ic *Context) GetFunction(id uint32) *Function { return nil } +// BaseExecFee represents factor to multiply syscall prices with. +func (ic *Context) BaseExecFee() int64 { + if ic.Chain == nil { + return DefaultBaseExecFee + } + return ic.Chain.GetPolicer().GetBaseExecFee() +} + // SyscallHandler handles syscall with id. func (ic *Context) SyscallHandler(_ *vm.VM, id uint32) error { f := ic.GetFunction(id) @@ -165,7 +178,7 @@ func (ic *Context) SyscallHandler(_ *vm.VM, id uint32) error { if !cf.Has(f.RequiredFlags) { return fmt.Errorf("missing call flags: %05b vs %05b", cf, f.RequiredFlags) } - if !ic.VM.AddGas(f.Price) { + if !ic.VM.AddGas(f.Price * ic.BaseExecFee()) { return errors.New("insufficient amount of gas") } return f.Func(ic) diff --git a/pkg/core/interop/crypto/ecdsa.go b/pkg/core/interop/crypto/ecdsa.go index 5f79e02ad..3e27810af 100644 --- a/pkg/core/interop/crypto/ecdsa.go +++ b/pkg/core/interop/crypto/ecdsa.go @@ -16,7 +16,7 @@ import ( ) // ECDSAVerifyPrice is a gas price of a single verification. -const ECDSAVerifyPrice = 1000000 +const ECDSAVerifyPrice = 1 << 15 // ECDSASecp256r1Verify checks ECDSA signature using Secp256r1 elliptic curve. func ECDSASecp256r1Verify(ic *interop.Context) error { @@ -69,7 +69,7 @@ func ecdsaCheckMultisig(ic *interop.Context, curve elliptic.Curve) error { if err != nil { return fmt.Errorf("wrong parameters: %w", err) } - if !ic.VM.AddGas(ECDSAVerifyPrice * int64(len(pkeys))) { + if !ic.VM.AddGas(ic.BaseExecFee() * ECDSAVerifyPrice * int64(len(pkeys))) { return errors.New("gas limit exceeded") } sigs, err := ic.VM.Estack().PopSigElements() diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index e455f482c..254ab9c44 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -201,6 +201,7 @@ func storageDelete(ic *interop.Context) error { if stc.ReadOnly { return errors.New("StorageContext is read only") } + ic.VM.AddGas(native.StoragePrice) key := ic.VM.Estack().Pop().Bytes() si := ic.DAO.GetStorageItem(stc.ID, key) if si != nil && si.IsConst { diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 668fdb956..4e061ac8c 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -33,80 +33,80 @@ func SpawnVM(ic *interop.Context) *vm.VM { // All lists are sorted, keep 'em this way, please. var systemInterops = []interop.Function{ - {Name: interopnames.SystemBinaryAtoi, Func: binary.Atoi, Price: 100000, ParamCount: 2}, - {Name: interopnames.SystemBinaryBase58Decode, Func: binary.DecodeBase58, Price: 100000, ParamCount: 1}, - {Name: interopnames.SystemBinaryBase58Encode, Func: binary.EncodeBase58, Price: 100000, ParamCount: 1}, - {Name: interopnames.SystemBinaryBase64Decode, Func: binary.DecodeBase64, Price: 100000, ParamCount: 1}, - {Name: interopnames.SystemBinaryBase64Encode, Func: binary.EncodeBase64, Price: 100000, ParamCount: 1}, - {Name: interopnames.SystemBinaryDeserialize, Func: binary.Deserialize, Price: 500000, ParamCount: 1}, - {Name: interopnames.SystemBinaryItoa, Func: binary.Itoa, Price: 100000, ParamCount: 2}, - {Name: interopnames.SystemBinarySerialize, Func: binary.Serialize, Price: 100000, ParamCount: 1}, - {Name: interopnames.SystemBlockchainGetBlock, Func: bcGetBlock, Price: 2500000, + {Name: interopnames.SystemBinaryAtoi, Func: binary.Atoi, Price: 1 << 12, ParamCount: 2}, + {Name: interopnames.SystemBinaryBase58Decode, Func: binary.DecodeBase58, Price: 1 << 12, ParamCount: 1}, + {Name: interopnames.SystemBinaryBase58Encode, Func: binary.EncodeBase58, Price: 1 << 12, ParamCount: 1}, + {Name: interopnames.SystemBinaryBase64Decode, Func: binary.DecodeBase64, Price: 1 << 12, ParamCount: 1}, + {Name: interopnames.SystemBinaryBase64Encode, Func: binary.EncodeBase64, Price: 1 << 12, ParamCount: 1}, + {Name: interopnames.SystemBinaryDeserialize, Func: binary.Deserialize, Price: 1 << 14, ParamCount: 1}, + {Name: interopnames.SystemBinaryItoa, Func: binary.Itoa, Price: 1 << 12, ParamCount: 2}, + {Name: interopnames.SystemBinarySerialize, Func: binary.Serialize, Price: 1 << 12, ParamCount: 1}, + {Name: interopnames.SystemBlockchainGetBlock, Func: bcGetBlock, Price: 1 << 16, RequiredFlags: smartcontract.ReadStates, ParamCount: 1}, - {Name: interopnames.SystemBlockchainGetHeight, Func: bcGetHeight, Price: 400, + {Name: interopnames.SystemBlockchainGetHeight, Func: bcGetHeight, Price: 1 << 4, RequiredFlags: smartcontract.ReadStates}, - {Name: interopnames.SystemBlockchainGetTransaction, Func: bcGetTransaction, Price: 1000000, + {Name: interopnames.SystemBlockchainGetTransaction, Func: bcGetTransaction, Price: 1 << 15, RequiredFlags: smartcontract.ReadStates, ParamCount: 1}, - {Name: interopnames.SystemBlockchainGetTransactionFromBlock, Func: bcGetTransactionFromBlock, Price: 1000000, + {Name: interopnames.SystemBlockchainGetTransactionFromBlock, Func: bcGetTransactionFromBlock, Price: 1 << 15, RequiredFlags: smartcontract.ReadStates, ParamCount: 2}, - {Name: interopnames.SystemBlockchainGetTransactionHeight, Func: bcGetTransactionHeight, Price: 1000000, + {Name: interopnames.SystemBlockchainGetTransactionHeight, Func: bcGetTransactionHeight, Price: 1 << 15, RequiredFlags: smartcontract.ReadStates, ParamCount: 1}, - {Name: interopnames.SystemCallbackCreate, Func: callback.Create, Price: 400, ParamCount: 3, DisallowCallback: true}, - {Name: interopnames.SystemCallbackCreateFromMethod, Func: callback.CreateFromMethod, Price: 1000000, RequiredFlags: smartcontract.ReadStates, ParamCount: 2, DisallowCallback: true}, - {Name: interopnames.SystemCallbackCreateFromSyscall, Func: callback.CreateFromSyscall, Price: 400, ParamCount: 1, DisallowCallback: true}, - {Name: interopnames.SystemCallbackInvoke, Func: callback.Invoke, Price: 1000000, RequiredFlags: smartcontract.AllowCall, ParamCount: 2, DisallowCallback: true}, - {Name: interopnames.SystemContractCall, Func: contract.Call, Price: 1000000, + {Name: interopnames.SystemCallbackCreate, Func: callback.Create, Price: 1 << 4, ParamCount: 3, DisallowCallback: true}, + {Name: interopnames.SystemCallbackCreateFromMethod, Func: callback.CreateFromMethod, Price: 1 << 15, ParamCount: 2, DisallowCallback: true}, + {Name: interopnames.SystemCallbackCreateFromSyscall, Func: callback.CreateFromSyscall, Price: 1 << 4, ParamCount: 1, DisallowCallback: true}, + {Name: interopnames.SystemCallbackInvoke, Func: callback.Invoke, Price: 1 << 15, ParamCount: 2, DisallowCallback: true}, + {Name: interopnames.SystemContractCall, Func: contract.Call, Price: 1 << 15, RequiredFlags: smartcontract.AllowCall, ParamCount: 3, DisallowCallback: true}, - {Name: interopnames.SystemContractCallEx, Func: contract.CallEx, Price: 1000000, + {Name: interopnames.SystemContractCallEx, Func: contract.CallEx, Price: 1 << 15, RequiredFlags: smartcontract.AllowCall, ParamCount: 4, DisallowCallback: true}, {Name: interopnames.SystemContractCallNative, Func: native.Call, Price: 0, ParamCount: 1, DisallowCallback: true}, - {Name: interopnames.SystemContractCreateStandardAccount, Func: contractCreateStandardAccount, Price: 10000, ParamCount: 1, DisallowCallback: true}, - {Name: interopnames.SystemContractIsStandard, Func: contractIsStandard, Price: 30000, RequiredFlags: smartcontract.ReadStates, ParamCount: 1}, - {Name: interopnames.SystemContractGetCallFlags, Func: contractGetCallFlags, Price: 30000, DisallowCallback: true}, + {Name: interopnames.SystemContractCreateStandardAccount, Func: contractCreateStandardAccount, Price: 1 << 8, ParamCount: 1, DisallowCallback: true}, + {Name: interopnames.SystemContractIsStandard, Func: contractIsStandard, Price: 1 << 10, RequiredFlags: smartcontract.ReadStates, ParamCount: 1}, + {Name: interopnames.SystemContractGetCallFlags, Func: contractGetCallFlags, Price: 1 << 10, DisallowCallback: true}, {Name: interopnames.SystemContractNativeOnPersist, Func: native.OnPersist, Price: 0, RequiredFlags: smartcontract.WriteStates, DisallowCallback: true}, {Name: interopnames.SystemContractNativePostPersist, Func: native.PostPersist, Price: 0, RequiredFlags: smartcontract.WriteStates, DisallowCallback: true}, - {Name: interopnames.SystemEnumeratorConcat, Func: enumerator.Concat, Price: 400, ParamCount: 2, DisallowCallback: true}, - {Name: interopnames.SystemEnumeratorCreate, Func: enumerator.Create, Price: 400, ParamCount: 1, DisallowCallback: true}, - {Name: interopnames.SystemEnumeratorNext, Func: enumerator.Next, Price: 1000000, ParamCount: 1, DisallowCallback: true}, - {Name: interopnames.SystemEnumeratorValue, Func: enumerator.Value, Price: 400, ParamCount: 1, DisallowCallback: true}, - {Name: interopnames.SystemIteratorConcat, Func: iterator.Concat, Price: 400, ParamCount: 2, DisallowCallback: true}, - {Name: interopnames.SystemIteratorCreate, Func: iterator.Create, Price: 400, ParamCount: 1, DisallowCallback: true}, - {Name: interopnames.SystemIteratorKey, Func: iterator.Key, Price: 400, ParamCount: 1, DisallowCallback: true}, - {Name: interopnames.SystemIteratorKeys, Func: iterator.Keys, Price: 400, ParamCount: 1, DisallowCallback: true}, - {Name: interopnames.SystemIteratorValues, Func: iterator.Values, Price: 400, ParamCount: 1, DisallowCallback: true}, - {Name: interopnames.SystemJSONDeserialize, Func: json.Deserialize, Price: 500000, ParamCount: 1}, - {Name: interopnames.SystemJSONSerialize, Func: json.Serialize, Price: 100000, ParamCount: 1}, - {Name: interopnames.SystemRuntimeCheckWitness, Func: runtime.CheckWitness, Price: 30000, + {Name: interopnames.SystemEnumeratorConcat, Func: enumerator.Concat, Price: 1 << 4, ParamCount: 2, DisallowCallback: true}, + {Name: interopnames.SystemEnumeratorCreate, Func: enumerator.Create, Price: 1 << 4, ParamCount: 1, DisallowCallback: true}, + {Name: interopnames.SystemEnumeratorNext, Func: enumerator.Next, Price: 1 << 15, ParamCount: 1, DisallowCallback: true}, + {Name: interopnames.SystemEnumeratorValue, Func: enumerator.Value, Price: 1 << 4, ParamCount: 1, DisallowCallback: true}, + {Name: interopnames.SystemIteratorConcat, Func: iterator.Concat, Price: 1 << 4, ParamCount: 2, DisallowCallback: true}, + {Name: interopnames.SystemIteratorCreate, Func: iterator.Create, Price: 1 << 4, ParamCount: 1, DisallowCallback: true}, + {Name: interopnames.SystemIteratorKey, Func: iterator.Key, Price: 1 << 4, ParamCount: 1, DisallowCallback: true}, + {Name: interopnames.SystemIteratorKeys, Func: iterator.Keys, Price: 1 << 4, ParamCount: 1, DisallowCallback: true}, + {Name: interopnames.SystemIteratorValues, Func: iterator.Values, Price: 1 << 4, ParamCount: 1, DisallowCallback: true}, + {Name: interopnames.SystemJSONDeserialize, Func: json.Deserialize, Price: 1 << 14, ParamCount: 1}, + {Name: interopnames.SystemJSONSerialize, Func: json.Serialize, Price: 1 << 12, ParamCount: 1}, + {Name: interopnames.SystemRuntimeCheckWitness, Func: runtime.CheckWitness, Price: 1 << 10, RequiredFlags: smartcontract.NoneFlag, ParamCount: 1}, - {Name: interopnames.SystemRuntimeGasLeft, Func: runtime.GasLeft, Price: 400}, - {Name: interopnames.SystemRuntimeGetCallingScriptHash, Func: runtime.GetCallingScriptHash, Price: 400}, - {Name: interopnames.SystemRuntimeGetEntryScriptHash, Func: runtime.GetEntryScriptHash, Price: 400}, - {Name: interopnames.SystemRuntimeGetExecutingScriptHash, Func: runtime.GetExecutingScriptHash, Price: 400}, - {Name: interopnames.SystemRuntimeGetInvocationCounter, Func: runtime.GetInvocationCounter, Price: 400}, - {Name: interopnames.SystemRuntimeGetNotifications, Func: runtime.GetNotifications, Price: 10000, ParamCount: 1}, - {Name: interopnames.SystemRuntimeGetScriptContainer, Func: engineGetScriptContainer, Price: 250}, - {Name: interopnames.SystemRuntimeGetTime, Func: runtime.GetTime, Price: 250}, - {Name: interopnames.SystemRuntimeGetTrigger, Func: runtime.GetTrigger, Price: 250}, - {Name: interopnames.SystemRuntimeLog, Func: runtime.Log, Price: 1000000, RequiredFlags: smartcontract.AllowNotify, + {Name: interopnames.SystemRuntimeGasLeft, Func: runtime.GasLeft, Price: 1 << 4}, + {Name: interopnames.SystemRuntimeGetCallingScriptHash, Func: runtime.GetCallingScriptHash, Price: 1 << 4}, + {Name: interopnames.SystemRuntimeGetEntryScriptHash, Func: runtime.GetEntryScriptHash, Price: 1 << 4}, + {Name: interopnames.SystemRuntimeGetExecutingScriptHash, Func: runtime.GetExecutingScriptHash, Price: 1 << 4}, + {Name: interopnames.SystemRuntimeGetInvocationCounter, Func: runtime.GetInvocationCounter, Price: 1 << 4}, + {Name: interopnames.SystemRuntimeGetNotifications, Func: runtime.GetNotifications, Price: 1 << 8, ParamCount: 1}, + {Name: interopnames.SystemRuntimeGetScriptContainer, Func: engineGetScriptContainer, Price: 1 << 3}, + {Name: interopnames.SystemRuntimeGetTime, Func: runtime.GetTime, Price: 1 << 3, RequiredFlags: smartcontract.ReadStates}, + {Name: interopnames.SystemRuntimeGetTrigger, Func: runtime.GetTrigger, Price: 1 << 3}, + {Name: interopnames.SystemRuntimeLog, Func: runtime.Log, Price: 1 << 15, RequiredFlags: smartcontract.AllowNotify, ParamCount: 1, DisallowCallback: true}, - {Name: interopnames.SystemRuntimeNotify, Func: runtime.Notify, Price: 1000000, RequiredFlags: smartcontract.AllowNotify, + {Name: interopnames.SystemRuntimeNotify, Func: runtime.Notify, Price: 1 << 15, RequiredFlags: smartcontract.AllowNotify, ParamCount: 2, DisallowCallback: true}, - {Name: interopnames.SystemRuntimePlatform, Func: runtime.Platform, Price: 250}, - {Name: interopnames.SystemStorageDelete, Func: storageDelete, Price: native.StoragePrice, + {Name: interopnames.SystemRuntimePlatform, Func: runtime.Platform, Price: 1 << 3}, + {Name: interopnames.SystemStorageDelete, Func: storageDelete, Price: 0, RequiredFlags: smartcontract.WriteStates, ParamCount: 2, DisallowCallback: true}, - {Name: interopnames.SystemStorageFind, Func: storageFind, Price: 1000000, RequiredFlags: smartcontract.ReadStates, + {Name: interopnames.SystemStorageFind, Func: storageFind, Price: 1 << 15, RequiredFlags: smartcontract.ReadStates, ParamCount: 2, DisallowCallback: true}, - {Name: interopnames.SystemStorageGet, Func: storageGet, Price: 1000000, RequiredFlags: smartcontract.ReadStates, + {Name: interopnames.SystemStorageGet, Func: storageGet, Price: 1 << 15, RequiredFlags: smartcontract.ReadStates, ParamCount: 2, DisallowCallback: true}, - {Name: interopnames.SystemStorageGetContext, Func: storageGetContext, Price: 400, + {Name: interopnames.SystemStorageGetContext, Func: storageGetContext, Price: 1 << 4, RequiredFlags: smartcontract.ReadStates, DisallowCallback: true}, - {Name: interopnames.SystemStorageGetReadOnlyContext, Func: storageGetReadOnlyContext, Price: 400, + {Name: interopnames.SystemStorageGetReadOnlyContext, Func: storageGetReadOnlyContext, Price: 1 << 4, RequiredFlags: smartcontract.ReadStates, DisallowCallback: true}, {Name: interopnames.SystemStoragePut, Func: storagePut, Price: 0, RequiredFlags: smartcontract.WriteStates, ParamCount: 3, DisallowCallback: true}, // These don't have static price in C# code. {Name: interopnames.SystemStoragePutEx, Func: storagePutEx, Price: 0, RequiredFlags: smartcontract.WriteStates, ParamCount: 4, DisallowCallback: true}, - {Name: interopnames.SystemStorageAsReadOnly, Func: storageContextAsReadOnly, Price: 400, + {Name: interopnames.SystemStorageAsReadOnly, Func: storageContextAsReadOnly, Price: 1 << 4, RequiredFlags: smartcontract.ReadStates, ParamCount: 1, DisallowCallback: true}, } @@ -117,8 +117,8 @@ var neoInterops = []interop.Function{ Price: crypto.ECDSAVerifyPrice, ParamCount: 3}, {Name: interopnames.NeoCryptoCheckMultisigWithECDsaSecp256r1, Func: crypto.ECDSASecp256r1CheckMultisig, Price: 0, ParamCount: 3}, {Name: interopnames.NeoCryptoCheckMultisigWithECDsaSecp256k1, Func: crypto.ECDSASecp256k1CheckMultisig, Price: 0, ParamCount: 3}, - {Name: interopnames.NeoCryptoSHA256, Func: crypto.Sha256, Price: 1000000, ParamCount: 1}, - {Name: interopnames.NeoCryptoRIPEMD160, Func: crypto.RipeMD160, Price: 1000000, ParamCount: 1}, + {Name: interopnames.NeoCryptoSHA256, Func: crypto.Sha256, Price: 1 << 15, ParamCount: 1}, + {Name: interopnames.NeoCryptoRIPEMD160, Func: crypto.RipeMD160, Price: 1 << 15, ParamCount: 1}, } // initIDinInteropsSlice initializes IDs from names in one given diff --git a/pkg/core/mempool/mem_pool_test.go b/pkg/core/mempool/mem_pool_test.go index 6dc3d2a6b..0f00f54bf 100644 --- a/pkg/core/mempool/mem_pool_test.go +++ b/pkg/core/mempool/mem_pool_test.go @@ -24,6 +24,10 @@ type FeerStub struct { balance int64 } +func (fs *FeerStub) GetBaseExecFee() int64 { + return 30 +} + func (fs *FeerStub) FeePerByte() int64 { return fs.feePerByte } diff --git a/pkg/core/native/interop.go b/pkg/core/native/interop.go index 78737da08..ef5e434de 100644 --- a/pkg/core/native/interop.go +++ b/pkg/core/native/interop.go @@ -35,6 +35,7 @@ func Call(ic *interop.Context) error { if !ic.VM.Context().GetCallFlags().Has(m.RequiredFlags) { return fmt.Errorf("missing call flags for native %s `%s` operation call: %05b vs %05b", name, operation, ic.VM.Context().GetCallFlags(), m.RequiredFlags) } + // Native contract prices are not multiplied by `BaseExecFee`. if !ic.VM.AddGas(m.Price) { return errors.New("gas limit exceeded") } diff --git a/pkg/core/native_contract_test.go b/pkg/core/native_contract_test.go index 074a0c7ce..7bcd848a4 100644 --- a/pkg/core/native_contract_test.go +++ b/pkg/core/native_contract_test.go @@ -7,6 +7,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core/dao" + "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/interop/contract" "github.com/nspcc-dev/neo-go/pkg/core/native" @@ -17,6 +18,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/require" ) @@ -54,7 +56,7 @@ func (bc *Blockchain) registerNative(c interop.Contract) { bc.contracts.Contracts = append(bc.contracts.Contracts, c) } -const testSumPrice = 1000000 +const testSumPrice = 1 << 15 * interop.DefaultBaseExecFee // same as contract.Call func newTestNative() *testNative { tn := &testNative{ @@ -176,8 +178,12 @@ func TestNativeContract_Invoke(t *testing.T) { }) require.NoError(t, err) - // System.Contract.Call + "sum" itself + opcodes for pushing arguments (PACK is 15000) - res, err := invokeContractMethod(chain, testSumPrice*2+18000, tn.Metadata().Hash, "sum", int64(14), int64(28)) + // System.Contract.Call + "sum" itself + opcodes for pushing arguments. + price := int64(testSumPrice * 2) + price += 3 * fee.Opcode(chain.GetBaseExecFee(), opcode.PUSHINT8, opcode.PUSHDATA1) + price += 2 * fee.Opcode(chain.GetBaseExecFee(), opcode.SYSCALL) + price += fee.Opcode(chain.GetBaseExecFee(), opcode.PACK) + res, err := invokeContractMethod(chain, price, tn.Metadata().Hash, "sum", int64(14), int64(28)) require.NoError(t, err) checkResult(t, res, stackitem.Make(42)) require.NoError(t, chain.persist()) @@ -190,10 +196,9 @@ func TestNativeContract_Invoke(t *testing.T) { } // Enough for Call and other opcodes, but not enough for "sum" call. - res, err = invokeContractMethod(chain, testSumPrice*2+8000, tn.Metadata().Hash, "sum", int64(14), int64(28)) + res, err = invokeContractMethod(chain, price-1, tn.Metadata().Hash, "sum", int64(14), int64(28)) require.NoError(t, err) checkFAULTState(t, res) - } func TestNativeContract_InvokeInternal(t *testing.T) { diff --git a/pkg/network/helper_test.go b/pkg/network/helper_test.go index 764e0efe5..7394cdf4c 100644 --- a/pkg/network/helper_test.go +++ b/pkg/network/helper_test.go @@ -14,6 +14,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core/block" "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/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" @@ -101,7 +102,9 @@ func (chain *testChain) GetNotaryBalance(acc util.Uint160) *big.Int { func (chain *testChain) GetPolicer() blockchainer.Policer { return chain } - +func (chain *testChain) GetBaseExecFee() int64 { + return interop.DefaultBaseExecFee +} func (chain *testChain) GetMaxVerificationGAS() int64 { if chain.maxVerificationGAS != 0 { return chain.maxVerificationGAS diff --git a/pkg/network/server_test.go b/pkg/network/server_test.go index 2926b78fc..9e457f1a2 100644 --- a/pkg/network/server_test.go +++ b/pkg/network/server_test.go @@ -15,6 +15,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/consensus" "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/interop" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/network/capability" "github.com/nspcc-dev/neo-go/pkg/network/payload" @@ -798,6 +799,7 @@ func (f feerStub) FeePerByte() int64 { return 1 } func (f feerStub) GetUtilityTokenBalance(util.Uint160) *big.Int { return big.NewInt(100000000) } func (f feerStub) BlockHeight() uint32 { return f.blockHeight } func (f feerStub) P2PSigExtensionsEnabled() bool { return false } +func (f feerStub) GetBaseExecFee() int64 { return interop.DefaultBaseExecFee } func TestMemPool(t *testing.T) { s, shutdown := startTestServer(t) diff --git a/pkg/rpc/client/rpc.go b/pkg/rpc/client/rpc.go index 3f4b59c6d..c6eff745f 100644 --- a/pkg/rpc/client/rpc.go +++ b/pkg/rpc/client/rpc.go @@ -9,6 +9,7 @@ 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" @@ -589,7 +590,7 @@ func (c *Client) AddNetworkFee(tx *transaction.Transaction, extraFee int64, accs size += io.GetVarSize([]byte{}) * 2 // both scripts are empty continue } - netFee, sizeDelta := fee.Calculate(accs[i].Contract.Script) + netFee, sizeDelta := fee.Calculate(interop.DefaultBaseExecFee, accs[i].Contract.Script) tx.NetworkFee += netFee size += sizeDelta } diff --git a/pkg/rpc/server/client_test.go b/pkg/rpc/server/client_test.go index 500f2c3e6..b72dec8e9 100644 --- a/pkg/rpc/server/client_test.go +++ b/pkg/rpc/server/client_test.go @@ -102,7 +102,7 @@ func TestAddNetworkFee(t *testing.T) { }} require.NoError(t, c.AddNetworkFee(tx, 10, accs[0])) require.NoError(t, accs[0].SignTx(tx)) - cFee, _ := fee.Calculate(accs[0].Contract.Script) + cFee, _ := fee.Calculate(chain.GetBaseExecFee(), accs[0].Contract.Script) require.Equal(t, int64(io.GetVarSize(tx))*feePerByte+cFee+10, tx.NetworkFee) }) @@ -126,8 +126,8 @@ func TestAddNetworkFee(t *testing.T) { require.NoError(t, accs[0].SignTx(tx)) require.NoError(t, accs[1].SignTx(tx)) require.NoError(t, accs[2].SignTx(tx)) - cFee, _ := fee.Calculate(accs[0].Contract.Script) - cFeeM, _ := fee.Calculate(accs[1].Contract.Script) + cFee, _ := fee.Calculate(chain.GetBaseExecFee(), accs[0].Contract.Script) + cFeeM, _ := fee.Calculate(chain.GetBaseExecFee(), accs[1].Contract.Script) require.Equal(t, int64(io.GetVarSize(tx))*feePerByte+cFee+cFeeM+10, tx.NetworkFee) }) t.Run("Contract", func(t *testing.T) { diff --git a/pkg/rpc/server/server_helper_test.go b/pkg/rpc/server/server_helper_test.go index a62c70820..6f49e8bf9 100644 --- a/pkg/rpc/server/server_helper_test.go +++ b/pkg/rpc/server/server_helper_test.go @@ -11,6 +11,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/config/netmode" "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/interop" "github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/io" @@ -102,3 +103,7 @@ func (fs *FeerStub) GetUtilityTokenBalance(acc util.Uint160) *big.Int { func (fs FeerStub) P2PSigExtensionsEnabled() bool { return false } + +func (fs FeerStub) GetBaseExecFee() int64 { + return interop.DefaultBaseExecFee +} diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 830680ee6..27d1d2e84 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -1003,7 +1003,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] addNetworkFee := func(tx *transaction.Transaction) { size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(acc0.Contract.Script) + netFee, sizeDelta := fee.Calculate(chain.GetBaseExecFee(), acc0.Contract.Script) tx.NetworkFee += netFee size += sizeDelta tx.NetworkFee += int64(size) * chain.FeePerByte() From 49465568305934be143a859383fbebce24c17001 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Mon, 14 Dec 2020 12:18:59 +0300 Subject: [PATCH 2/4] native: allow to modify `ExecFeeFactor` in the policy contract --- pkg/core/blockchain.go | 2 +- pkg/core/helper_test.go | 31 +++++++++++++++--- pkg/core/native/policy.go | 52 +++++++++++++++++++++++++++++++ pkg/core/native_policy_test.go | 57 ++++++++++++++++++++++++++++++++++ pkg/rpc/client/policy.go | 5 +++ pkg/rpc/client/rpc.go | 12 +++++-- 6 files changed, 151 insertions(+), 8 deletions(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index fe1998a89..2be2c50d1 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -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. diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index 2742ecfbc..003fea1f4 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -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 } - res, err := chain.GetAppExecResults(tx.Hash(), trigger.Application) + 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 + } + 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 } - return &res[0], nil + 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) { diff --git a/pkg/core/native/policy.go b/pkg/core/native/policy.go index 192aa7728..e72b7b337 100644 --- a/pkg/core/native/policy.go +++ b/pkg/core/native/policy.go @@ -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]) diff --git a/pkg/core/native_policy_test.go b/pkg/core/native_policy_test.go index 8ecfd0a2a..2fd372cbd 100644 --- a/pkg/core/native_policy_test.go +++ b/pkg/core/native_policy_test.go @@ -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() diff --git a/pkg/rpc/client/policy.go b/pkg/rpc/client/policy.go index 6c89baf68..26ff5ae9c 100644 --- a/pkg/rpc/client/policy.go +++ b/pkg/rpc/client/policy.go @@ -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 diff --git a/pkg/rpc/client/rpc.go b/pkg/rpc/client/rpc.go index c6eff745f..9103cea3b 100644 --- a/pkg/rpc/client/rpc.go +++ b/pkg/rpc/client/rpc.go @@ -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 } From 62da365302bc8024b52e01cc314914409bc811d5 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Mon, 14 Dec 2020 12:41:23 +0300 Subject: [PATCH 3/4] native: allow to modify `StoragePrice` in the policy contract --- pkg/core/blockchain.go | 5 +++ pkg/core/blockchainer/policer.go | 1 + pkg/core/interop_system.go | 5 ++- pkg/core/native/management.go | 8 ++--- pkg/core/native/policy.go | 51 +++++++++++++++++++++++++++++ pkg/core/native_policy_test.go | 56 ++++++++++++++++++++++++++++++++ pkg/network/helper_test.go | 4 +++ 7 files changed, 123 insertions(+), 7 deletions(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 2be2c50d1..8098eef19 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -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. diff --git a/pkg/core/blockchainer/policer.go b/pkg/core/blockchainer/policer.go index 48ae7c0af..abfe4aa02 100644 --- a/pkg/core/blockchainer/policer.go +++ b/pkg/core/blockchainer/policer.go @@ -6,4 +6,5 @@ type Policer interface { GetMaxBlockSize() uint32 GetMaxBlockSystemFee() int64 GetMaxVerificationGAS() int64 + GetStoragePrice() int64 } diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index 254ab9c44..1edeb9012 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -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 diff --git a/pkg/core/native/management.go b/pkg/core/native/management.go index 04631c5e8..92be3e91f 100644 --- a/pkg/core/native/management.go +++ b/pkg/core/native/management.go @@ -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) } diff --git a/pkg/core/native/policy.go b/pkg/core/native/policy.go index e72b7b337..3c7411315 100644 --- a/pkg/core/native/policy.go +++ b/pkg/core/native/policy.go @@ -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 { diff --git a/pkg/core/native_policy_test.go b/pkg/core/native_policy_test.go index 2fd372cbd..d843b3109 100644 --- a/pkg/core/native_policy_test.go +++ b/pkg/core/native_policy_test.go @@ -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() diff --git a/pkg/network/helper_test.go b/pkg/network/helper_test.go index 7394cdf4c..b4c98b223 100644 --- a/pkg/network/helper_test.go +++ b/pkg/network/helper_test.go @@ -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 From 65d147c890c791b0ecaf711293ec18ae4ca7d2c0 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Mon, 14 Dec 2020 13:09:35 +0300 Subject: [PATCH 4/4] core/test: simplify tests for policy contract Most of the methods are just get/set with some boundaries. This simplifies writing tests for new methods. Also add missing test regarding cache behaviour for current methods when value is set and read in the same block. --- pkg/core/native_policy_test.go | 277 +++++++-------------------------- 1 file changed, 59 insertions(+), 218 deletions(-) diff --git a/pkg/core/native_policy_test.go b/pkg/core/native_policy_test.go index d843b3109..94d11a729 100644 --- a/pkg/core/native_policy_test.go +++ b/pkg/core/native_policy_test.go @@ -1,14 +1,12 @@ package core import ( - "math/big" "testing" "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" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" @@ -16,286 +14,129 @@ import ( "github.com/stretchr/testify/require" ) +func testPolicyGetSet(t *testing.T, chain *Blockchain, name string, defaultValue, minValue, maxValue int64) { + policyHash := chain.contracts.Policy.Metadata().Hash + getName := "get" + name + setName := "set" + name + + 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, setName, minValue+1) + checkResult(t, invokeRes, stackitem.NewBool(false)) + }) + + t.Run("get", func(t *testing.T) { + res, err := invokeContractMethod(chain, 100000000, policyHash, getName) + require.NoError(t, err) + checkResult(t, res, stackitem.Make(defaultValue)) + require.NoError(t, chain.persist()) + }) + + t.Run("set, zero fee", func(t *testing.T) { + res, err := invokeContractMethod(chain, 100000000, policyHash, setName, minValue-1) + require.NoError(t, err) + checkFAULTState(t, res) + }) + + if maxValue != 0 { + t.Run("set, too big fee", func(t *testing.T) { + res, err := invokeContractMethod(chain, 100000000, policyHash, setName, maxValue+1) + 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, setName, defaultValue+1) + require.NoError(t, err) + txGet1, err := prepareContractMethodInvoke(chain, 100000000, policyHash, getName) + 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(defaultValue+1)) + require.NoError(t, chain.persist()) + + // Get in the next block. + res, err := invokeContractMethod(chain, 100000000, policyHash, getName) + require.NoError(t, err) + checkResult(t, res, stackitem.Make(defaultValue+1)) + require.NoError(t, chain.persist()) + }) +} + func TestMaxTransactionsPerBlock(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.GetMaxTransactionsPerBlockInternal(chain.dao) require.Equal(t, 512, int(n)) }) - t.Run("get, contract method", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, "getMaxTransactionsPerBlock") - require.NoError(t, err) - checkResult(t, res, stackitem.NewBigInteger(big.NewInt(512))) - require.NoError(t, chain.persist()) - }) - - t.Run("set", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, "setMaxTransactionsPerBlock", bigint.ToBytes(big.NewInt(1024))) - require.NoError(t, err) - checkResult(t, res, stackitem.NewBool(true)) - require.NoError(t, chain.persist()) - n := chain.contracts.Policy.GetMaxTransactionsPerBlockInternal(chain.dao) - require.Equal(t, 1024, int(n)) - }) - - t.Run("set, too big value", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, "setMaxTransactionsPerBlock", bigint.ToBytes(big.NewInt(block.MaxContentsPerBlock))) - require.NoError(t, err) - checkFAULTState(t, res) - }) - - 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, "setMaxTransactionsPerBlock", bigint.ToBytes(big.NewInt(1024))) - checkResult(t, invokeRes, stackitem.NewBool(false)) - }) + testPolicyGetSet(t, chain, "MaxTransactionsPerBlock", 512, 0, block.MaxTransactionsPerBlock) } func TestMaxBlockSize(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.GetMaxBlockSizeInternal(chain.dao) require.Equal(t, 1024*256, int(n)) }) - t.Run("get, contract method", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, "getMaxBlockSize") - require.NoError(t, err) - checkResult(t, res, stackitem.NewBigInteger(big.NewInt(1024*256))) - require.NoError(t, chain.persist()) - }) - - t.Run("set", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, "setMaxBlockSize", bigint.ToBytes(big.NewInt(102400))) - require.NoError(t, err) - checkResult(t, res, stackitem.NewBool(true)) - require.NoError(t, chain.persist()) - res, err = invokeContractMethod(chain, 100000000, policyHash, "getMaxBlockSize") - require.NoError(t, err) - checkResult(t, res, stackitem.NewBigInteger(big.NewInt(102400))) - require.NoError(t, chain.persist()) - }) - - t.Run("set, too big value", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, "setMaxBlockSize", bigint.ToBytes(big.NewInt(payload.MaxSize+1))) - require.NoError(t, err) - checkFAULTState(t, res) - }) - - 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, "setMaxBlockSize", bigint.ToBytes(big.NewInt(102400))) - checkResult(t, invokeRes, stackitem.NewBool(false)) - }) + testPolicyGetSet(t, chain, "MaxBlockSize", 1024*256, 0, payload.MaxSize) } func TestFeePerByte(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.GetFeePerByteInternal(chain.dao) require.Equal(t, 1000, int(n)) }) - t.Run("get, contract method", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, "getFeePerByte") - require.NoError(t, err) - checkResult(t, res, stackitem.NewBigInteger(big.NewInt(1000))) - require.NoError(t, chain.persist()) - }) - - t.Run("set", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, "setFeePerByte", bigint.ToBytes(big.NewInt(1024))) - require.NoError(t, err) - checkResult(t, res, stackitem.NewBool(true)) - require.NoError(t, chain.persist()) - n := chain.contracts.Policy.GetFeePerByteInternal(chain.dao) - require.Equal(t, 1024, int(n)) - }) - - t.Run("set, negative value", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, "setFeePerByte", bigint.ToBytes(big.NewInt(-1))) - require.NoError(t, err) - checkFAULTState(t, res) - }) - - t.Run("set, too big value", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, "setFeePerByte", bigint.ToBytes(big.NewInt(100_000_000+1))) - require.NoError(t, err) - checkFAULTState(t, res) - }) - - 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, "setFeePerByte", bigint.ToBytes(big.NewInt(1024))) - checkResult(t, invokeRes, stackitem.NewBool(false)) - }) + testPolicyGetSet(t, chain, "FeePerByte", 1000, 0, 100_000_000) } 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)) - }) + testPolicyGetSet(t, chain, "ExecFeeFactor", interop.DefaultBaseExecFee, 1, 1000) } func TestBlockSystemFee(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.GetMaxBlockSystemFeeInternal(chain.dao) require.Equal(t, 9000*native.GASFactor, int(n)) }) - t.Run("get", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, "getMaxBlockSystemFee") - require.NoError(t, err) - checkResult(t, res, stackitem.NewBigInteger(big.NewInt(9000*native.GASFactor))) - require.NoError(t, chain.persist()) - }) - - t.Run("set, too low fee", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, "setMaxBlockSystemFee", bigint.ToBytes(big.NewInt(4007600))) - require.NoError(t, err) - checkFAULTState(t, res) - }) - - t.Run("set, success", func(t *testing.T) { - res, err := invokeContractMethod(chain, 100000000, policyHash, "setMaxBlockSystemFee", bigint.ToBytes(big.NewInt(100000000))) - require.NoError(t, err) - checkResult(t, res, stackitem.NewBool(true)) - require.NoError(t, chain.persist()) - res, err = invokeContractMethod(chain, 100000000, policyHash, "getMaxBlockSystemFee") - require.NoError(t, err) - checkResult(t, res, stackitem.NewBigInteger(big.NewInt(100000000))) - 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, "setMaxBlockSystemFee", bigint.ToBytes(big.NewInt(100000000))) - checkResult(t, invokeRes, stackitem.NewBool(false)) - }) + testPolicyGetSet(t, chain, "MaxBlockSystemFee", 9000*native.GASFactor, 4007600, 0) } 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)) - }) + testPolicyGetSet(t, chain, "StoragePrice", native.StoragePrice, 1, 10000000) } func TestBlockedAccounts(t *testing.T) {