mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-26 19:42:23 +00:00
core: restrict GAS available for invocation transactions
There are 10 GAS available for free plus any amount of GAS attached to a transaction.
This commit is contained in:
parent
d72d978a19
commit
4eae55143f
3 changed files with 236 additions and 1 deletions
|
@ -514,6 +514,11 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
|
||||||
v := bc.spawnVMWithInterops(systemInterop)
|
v := bc.spawnVMWithInterops(systemInterop)
|
||||||
v.SetCheckedHash(tx.VerificationHash().BytesBE())
|
v.SetCheckedHash(tx.VerificationHash().BytesBE())
|
||||||
v.LoadScript(t.Script)
|
v.LoadScript(t.Script)
|
||||||
|
v.SetPriceGetter(getPrice)
|
||||||
|
|
||||||
|
gasAmount := util.Fixed8FromInt64(10) + t.Gas
|
||||||
|
v.SetGasLimit(gasAmount)
|
||||||
|
|
||||||
err := v.Run()
|
err := v.Run()
|
||||||
if !v.HasFailed() {
|
if !v.HasFailed() {
|
||||||
_, err := systemInterop.dao.Persist()
|
_, err := systemInterop.dao.Persist()
|
||||||
|
@ -554,7 +559,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
|
||||||
TxHash: tx.Hash(),
|
TxHash: tx.Hash(),
|
||||||
Trigger: trigger.Application,
|
Trigger: trigger.Application,
|
||||||
VMState: v.State(),
|
VMState: v.State(),
|
||||||
GasConsumed: util.Fixed8(0),
|
GasConsumed: v.GasConsumed(),
|
||||||
Stack: v.Stack("estack"),
|
Stack: v.Stack("estack"),
|
||||||
Events: systemInterop.notifications,
|
Events: systemInterop.notifications,
|
||||||
}
|
}
|
||||||
|
|
112
pkg/core/gas_price.go
Normal file
112
pkg/core/gas_price.go
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/vm/opcode"
|
||||||
|
)
|
||||||
|
|
||||||
|
// interopGasRatio is a multiplier by which a number returned from price getter
|
||||||
|
// and Fixed8 amount of GAS differ. Numbers defined in syscall tables are a multiple
|
||||||
|
// of 0.001 GAS = Fixed8(10^5).
|
||||||
|
const interopGasRatio = 100000
|
||||||
|
|
||||||
|
// getPrice returns a price for executing op with the provided parameter.
|
||||||
|
// Some SYSCALLs have variable price depending on their arguments.
|
||||||
|
func getPrice(v *vm.VM, op opcode.Opcode, parameter []byte) util.Fixed8 {
|
||||||
|
if op <= opcode.NOP {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
switch op {
|
||||||
|
case opcode.APPCALL, opcode.TAILCALL:
|
||||||
|
return util.Fixed8FromInt64(10)
|
||||||
|
case opcode.SYSCALL:
|
||||||
|
interopID := vm.GetInteropID(parameter)
|
||||||
|
return getSyscallPrice(v, interopID)
|
||||||
|
case opcode.SHA1, opcode.SHA256:
|
||||||
|
return util.Fixed8FromInt64(10)
|
||||||
|
case opcode.HASH160, opcode.HASH256:
|
||||||
|
return util.Fixed8FromInt64(20)
|
||||||
|
case opcode.CHECKSIG, opcode.VERIFY:
|
||||||
|
return util.Fixed8FromInt64(100)
|
||||||
|
case opcode.CHECKMULTISIG:
|
||||||
|
estack := v.Estack()
|
||||||
|
if estack.Len() == 0 {
|
||||||
|
return util.Fixed8FromInt64(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var cost int
|
||||||
|
|
||||||
|
item := estack.Peek(0)
|
||||||
|
switch item.Item().(type) {
|
||||||
|
case *vm.ArrayItem, *vm.StructItem:
|
||||||
|
cost = len(item.Array())
|
||||||
|
default:
|
||||||
|
cost = int(item.BigInt().Int64())
|
||||||
|
}
|
||||||
|
|
||||||
|
if cost < 1 {
|
||||||
|
return util.Fixed8FromInt64(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.Fixed8FromInt64(int64(100 * cost))
|
||||||
|
default:
|
||||||
|
return util.Fixed8FromInt64(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSyscallPrice returns cost of executing syscall with provided id.
|
||||||
|
// Is SYSCALL is not found, cost is 1.
|
||||||
|
func getSyscallPrice(v *vm.VM, id uint32) util.Fixed8 {
|
||||||
|
ifunc := v.GetInteropByID(id)
|
||||||
|
if ifunc != nil && ifunc.Price > 0 {
|
||||||
|
return util.Fixed8(ifunc.Price) * interopGasRatio
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
neoAssetCreate = 0x1fc6c583 // Neo.Asset.Create
|
||||||
|
antSharesAssetCreate = 0x99025068 // AntShares.Asset.Create
|
||||||
|
neoAssetRenew = 0x71908478 // Neo.Asset.Renew
|
||||||
|
antSharesAssetRenew = 0xaf22447b // AntShares.Asset.Renew
|
||||||
|
neoContractCreate = 0x6ea56cf6 // Neo.Contract.Create
|
||||||
|
neoContractMigrate = 0x90621b47 // Neo.Contract.Migrate
|
||||||
|
antSharesContractCreate = 0x2a28d29b // AntShares.Contract.Create
|
||||||
|
antSharesContractMigrate = 0xa934c8bb // AntShares.Contract.Migrate
|
||||||
|
systemStoragePut = 0x84183fe6 // System.Storage.Put
|
||||||
|
systemStoragePutEx = 0x3a9be173 // System.Storage.PutEx
|
||||||
|
neoStoragePut = 0xf541a152 // Neo.Storage.Put
|
||||||
|
antSharesStoragePut = 0x5f300a9e // AntShares.Storage.Put
|
||||||
|
)
|
||||||
|
|
||||||
|
estack := v.Estack()
|
||||||
|
|
||||||
|
switch id {
|
||||||
|
case neoAssetCreate, antSharesAssetCreate:
|
||||||
|
return util.Fixed8FromInt64(5000)
|
||||||
|
case neoAssetRenew, antSharesAssetRenew:
|
||||||
|
arg := estack.Peek(1).BigInt().Int64()
|
||||||
|
return util.Fixed8FromInt64(arg * 5000)
|
||||||
|
case neoContractCreate, neoContractMigrate, antSharesContractCreate, antSharesContractMigrate:
|
||||||
|
fee := int64(100)
|
||||||
|
props := smartcontract.PropertyState(estack.Peek(3).BigInt().Int64())
|
||||||
|
|
||||||
|
if props&smartcontract.HasStorage != 0 {
|
||||||
|
fee += 400
|
||||||
|
}
|
||||||
|
|
||||||
|
if props&smartcontract.HasDynamicInvoke != 0 {
|
||||||
|
fee += 500
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.Fixed8FromInt64(fee)
|
||||||
|
case systemStoragePut, systemStoragePutEx, neoStoragePut, antSharesStoragePut:
|
||||||
|
// price for storage PUT is 1 GAS per 1 KiB
|
||||||
|
keySize := len(estack.Peek(1).Bytes())
|
||||||
|
valSize := len(estack.Peek(2).Bytes())
|
||||||
|
return util.Fixed8FromInt64(int64((keySize+valSize-1)/1024 + 1))
|
||||||
|
default:
|
||||||
|
return util.Fixed8FromInt64(1)
|
||||||
|
}
|
||||||
|
}
|
118
pkg/core/gas_price_test.go
Normal file
118
pkg/core/gas_price_test.go
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/smartcontract/trigger"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// These tests are taken from C# code
|
||||||
|
// https://github.com/neo-project/neo/blob/master-2.x/neo.UnitTests/UT_InteropPrices.cs#L245
|
||||||
|
func TestGetPrice(t *testing.T) {
|
||||||
|
bc := newTestChain(t)
|
||||||
|
systemInterop := bc.newInteropContext(trigger.Application, storage.NewMemoryStore(), nil, nil)
|
||||||
|
|
||||||
|
v := bc.spawnVMWithInterops(systemInterop)
|
||||||
|
v.SetPriceGetter(getPrice)
|
||||||
|
|
||||||
|
t.Run("Neo.Asset.Create", func(t *testing.T) {
|
||||||
|
// Neo.Asset.Create: 83c5c61f
|
||||||
|
v.Load([]byte{0x68, 0x04, 0x83, 0xc5, 0xc6, 0x1f})
|
||||||
|
checkGas(t, util.Fixed8FromInt64(5000), v)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Neo.Asset.Renew", func(t *testing.T) {
|
||||||
|
// Neo.Asset.Renew: 78849071 (requires push 09 push 09 before)
|
||||||
|
v.Load([]byte{0x59, 0x59, 0x68, 0x04, 0x78, 0x84, 0x90, 0x71})
|
||||||
|
require.NoError(t, v.StepInto()) // push 9
|
||||||
|
require.NoError(t, v.StepInto()) // push 9
|
||||||
|
|
||||||
|
checkGas(t, util.Fixed8FromInt64(9*5000), v)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Neo.Contract.Create (no props)", func(t *testing.T) {
|
||||||
|
// Neo.Contract.Create: f66ca56e (requires push properties on fourth position)
|
||||||
|
v.Load([]byte{0x00, 0x00, 0x00, 0x00, 0x68, 0x04, 0xf6, 0x6c, 0xa5, 0x6e})
|
||||||
|
require.NoError(t, v.StepInto()) // push 0 - ContractPropertyState.NoProperty
|
||||||
|
require.NoError(t, v.StepInto()) // push 0
|
||||||
|
require.NoError(t, v.StepInto()) // push 0
|
||||||
|
require.NoError(t, v.StepInto()) // push 0
|
||||||
|
|
||||||
|
checkGas(t, util.Fixed8FromInt64(100), v)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Neo.Contract.Create (has storage)", func(t *testing.T) {
|
||||||
|
// Neo.Contract.Create: f66ca56e (requires push properties on fourth position)
|
||||||
|
v.Load([]byte{0x51, 0x00, 0x00, 0x00, 0x68, 0x04, 0xf6, 0x6c, 0xa5, 0x6e})
|
||||||
|
require.NoError(t, v.StepInto()) // push 01 - ContractPropertyState.HasStorage
|
||||||
|
require.NoError(t, v.StepInto()) // push 0
|
||||||
|
require.NoError(t, v.StepInto()) // push 0
|
||||||
|
require.NoError(t, v.StepInto()) // push 0
|
||||||
|
|
||||||
|
checkGas(t, util.Fixed8FromInt64(500), v)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Neo.Contract.Create (has dynamic invoke)", func(t *testing.T) {
|
||||||
|
// Neo.Contract.Create: f66ca56e (requires push properties on fourth position)
|
||||||
|
v.Load([]byte{0x52, 0x00, 0x00, 0x00, 0x68, 0x04, 0xf6, 0x6c, 0xa5, 0x6e})
|
||||||
|
require.NoError(t, v.StepInto()) // push 02 - ContractPropertyState.HasDynamicInvoke
|
||||||
|
require.NoError(t, v.StepInto()) // push 0
|
||||||
|
require.NoError(t, v.StepInto()) // push 0
|
||||||
|
require.NoError(t, v.StepInto()) // push 0
|
||||||
|
|
||||||
|
checkGas(t, util.Fixed8FromInt64(600), v)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Neo.Contract.Create (has both storage and dynamic invoke)", func(t *testing.T) {
|
||||||
|
// Neo.Contract.Create: f66ca56e (requires push properties on fourth position)
|
||||||
|
v.Load([]byte{0x53, 0x00, 0x00, 0x00, 0x68, 0x04, 0xf6, 0x6c, 0xa5, 0x6e})
|
||||||
|
require.NoError(t, v.StepInto()) // push 03 - HasStorage and HasDynamicInvoke
|
||||||
|
require.NoError(t, v.StepInto()) // push 0
|
||||||
|
require.NoError(t, v.StepInto()) // push 0
|
||||||
|
require.NoError(t, v.StepInto()) // push 0
|
||||||
|
|
||||||
|
checkGas(t, util.Fixed8FromInt64(1000), v)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Neo.Contract.Migrate", func(t *testing.T) {
|
||||||
|
// Neo.Contract.Migrate: 471b6290 (requires push properties on fourth position)
|
||||||
|
v.Load([]byte{0x00, 0x00, 0x00, 0x00, 0x68, 0x04, 0x47, 0x1b, 0x62, 0x90})
|
||||||
|
require.NoError(t, v.StepInto()) // push 0 - ContractPropertyState.NoProperty
|
||||||
|
require.NoError(t, v.StepInto()) // push 0
|
||||||
|
require.NoError(t, v.StepInto()) // push 0
|
||||||
|
require.NoError(t, v.StepInto()) // push 0
|
||||||
|
|
||||||
|
checkGas(t, util.Fixed8FromInt64(100), v)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("System.Storage.Put", func(t *testing.T) {
|
||||||
|
// System.Storage.Put: e63f1884 (requires push key and value)
|
||||||
|
v.Load([]byte{0x53, 0x53, 0x00, 0x68, 0x04, 0xe6, 0x3f, 0x18, 0x84})
|
||||||
|
require.NoError(t, v.StepInto()) // push 03 (length 1)
|
||||||
|
require.NoError(t, v.StepInto()) // push 03 (length 1)
|
||||||
|
require.NoError(t, v.StepInto()) // push 00
|
||||||
|
|
||||||
|
checkGas(t, util.Fixed8FromInt64(1), v)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("System.Storage.PutEx", func(t *testing.T) {
|
||||||
|
// System.Storage.PutEx: 73e19b3a (requires push key and value)
|
||||||
|
v.Load([]byte{0x53, 0x53, 0x00, 0x68, 0x04, 0x73, 0xe1, 0x9b, 0x3a})
|
||||||
|
require.NoError(t, v.StepInto()) // push 03 (length 1)
|
||||||
|
require.NoError(t, v.StepInto()) // push 03 (length 1)
|
||||||
|
require.NoError(t, v.StepInto()) // push 00
|
||||||
|
|
||||||
|
checkGas(t, util.Fixed8FromInt64(1), v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkGas(t *testing.T, expected util.Fixed8, v *vm.VM) {
|
||||||
|
op, par, err := v.Context().Next()
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected, getPrice(v, op, par))
|
||||||
|
}
|
Loading…
Reference in a new issue