vm: calculate GAS spent during execution

If getPrice callback is provided, accumulate spent GAS.
This commit is contained in:
Evgenii Stratonikov 2020-01-20 14:42:36 +03:00
parent 8e10bf668d
commit 0662a7e3c2
2 changed files with 55 additions and 0 deletions

View file

@ -63,6 +63,9 @@ type VM struct {
// callbacks to get interops. // callbacks to get interops.
getInterop []InteropGetterFunc getInterop []InteropGetterFunc
// callback to get interop price
getPrice func(*VM, opcode.Opcode, []byte) util.Fixed8
// callback to get scripts. // callback to get scripts.
getScript func(util.Uint160) []byte getScript func(util.Uint160) []byte
@ -76,6 +79,8 @@ type VM struct {
itemCount map[StackItem]int itemCount map[StackItem]int
size int size int
gasConsumed util.Fixed8
// Public keys cache. // Public keys cache.
keys map[string]*keys.PublicKey keys map[string]*keys.PublicKey
} }
@ -114,6 +119,17 @@ func (v *VM) RegisterInteropGetter(f InteropGetterFunc) {
v.getInterop = append(v.getInterop, f) v.getInterop = append(v.getInterop, f)
} }
// SetPriceGetter registers the given PriceGetterFunc in v.
// f accepts vm's Context, current instruction and instruction parameter.
func (v *VM) SetPriceGetter(f func(*VM, opcode.Opcode, []byte) util.Fixed8) {
v.getPrice = f
}
// GasConsumed returns the amount of GAS consumed during execution.
func (v *VM) GasConsumed() util.Fixed8 {
return v.gasConsumed
}
// Estack returns the evaluation stack so interop hooks can utilize this. // Estack returns the evaluation stack so interop hooks can utilize this.
func (v *VM) Estack() *Stack { func (v *VM) Estack() *Stack {
return v.estack return v.estack
@ -225,6 +241,7 @@ func (v *VM) Load(prog []byte) {
v.estack.Clear() v.estack.Clear()
v.astack.Clear() v.astack.Clear()
v.state = noneState v.state = noneState
v.gasConsumed = 0
v.LoadScript(prog) v.LoadScript(prog)
} }
@ -464,6 +481,10 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
} }
}() }()
if v.getPrice != nil && ctx.ip < len(ctx.prog) {
v.gasConsumed += v.getPrice(v, op, parameter)
}
if op >= opcode.PUSHBYTES1 && op <= opcode.PUSHBYTES75 { if op >= opcode.PUSHBYTES1 && op <= opcode.PUSHBYTES75 {
v.estack.PushVal(parameter) v.estack.PushVal(parameter)
return return

View file

@ -62,6 +62,40 @@ func TestRegisterInteropGetter(t *testing.T) {
assert.Equal(t, currRegistered+1, len(v.getInterop)) assert.Equal(t, currRegistered+1, len(v.getInterop))
} }
func TestVM_SetPriceGetter(t *testing.T) {
v := New()
prog := []byte{
byte(opcode.PUSH4), byte(opcode.PUSH2),
byte(opcode.PUSHDATA1), 0x01, 0x01,
byte(opcode.PUSHDATA1), 0x02, 0xCA, 0xFE,
byte(opcode.PUSH4), byte(opcode.RET),
}
t.Run("no price getter", func(t *testing.T) {
v.Load(prog)
runVM(t, v)
require.EqualValues(t, 0, v.GasConsumed())
})
v.SetPriceGetter(func(_ *VM, op opcode.Opcode, p []byte) util.Fixed8 {
if op == opcode.PUSH4 {
return 1
} else if op == opcode.PUSHDATA1 && bytes.Equal(p, []byte{0xCA, 0xFE}) {
return 7
}
return 0
})
t.Run("with price getter", func(t *testing.T) {
v.Load(prog)
runVM(t, v)
require.EqualValues(t, 9, v.gasConsumed)
})
}
func TestBytesToPublicKey(t *testing.T) { func TestBytesToPublicKey(t *testing.T) {
v := New() v := New()
cache := v.GetPublicKeys() cache := v.GetPublicKeys()