neoneo-go/pkg/core/interop/context.go
Anna Shaleva 6a4e312eac core: move GetPrice from core to interop
We have additional logic for getting BaseExecFee policy value. This
logic should be moved to interop context instead of being in Policer,
because Policer is just an interface over Policy contract.

After moving this logic to interop context, we need to use it to define
BaseExecFee instead of (Policer).BaseExecFee. Thus, moving
(*Blockchain).GetPrice to (*Context).GetPrice is necessary.
2021-02-05 11:36:32 +03:00

231 lines
6.7 KiB
Go

package interop
import (
"errors"
"fmt"
"sort"
"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/dao"
"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"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
"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/stackitem"
"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
Container crypto.Verifiable
Natives []Contract
Trigger trigger.Type
Block *block.Block
Tx *transaction.Transaction
DAO *dao.Cached
Notifications []state.NotificationEvent
Log *zap.Logger
VM *vm.VM
Functions [][]Function
getContract func(dao.DAO, util.Uint160) (*state.Contract, error)
}
// NewContext returns new interop context.
func NewContext(trigger trigger.Type, bc blockchainer.Blockchainer, d dao.DAO,
getContract func(dao.DAO, util.Uint160) (*state.Contract, error), natives []Contract,
block *block.Block, tx *transaction.Transaction, log *zap.Logger) *Context {
dao := dao.NewCached(d)
nes := make([]state.NotificationEvent, 0)
return &Context{
Chain: bc,
Natives: natives,
Trigger: trigger,
Block: block,
Tx: tx,
DAO: dao,
Notifications: nes,
Log: log,
// Functions is a slice of slices of interops sorted by ID.
Functions: [][]Function{},
getContract: getContract,
}
}
// Function binds function name, id with the function itself and price,
// it's supposed to be inited once for all interopContexts, so it doesn't use
// vm.InteropFuncPrice directly.
type Function struct {
ID uint32
Name string
Func func(*Context) error
// ParamCount is a number of function parameters.
ParamCount int
Price int64
// RequiredFlags is a set of flags which must be set during script invocations.
// Default value is NoneFlag i.e. no flags are required.
RequiredFlags callflag.CallFlag
}
// Method is a signature for a native method.
type Method = func(ic *Context, args []stackitem.Item) stackitem.Item
// MethodAndPrice is a native-contract method descriptor.
type MethodAndPrice struct {
Func Method
MD *manifest.Method
Price int64
RequiredFlags callflag.CallFlag
}
// Contract is an interface for all native contracts.
type Contract interface {
Initialize(*Context) error
Metadata() *ContractMD
OnPersist(*Context) error
PostPersist(*Context) error
}
// ContractMD represents native contract instance.
type ContractMD struct {
Manifest manifest.Manifest
Name string
ContractID int32
NEF nef.File
Hash util.Uint160
Methods map[MethodAndArgCount]MethodAndPrice
}
// MethodAndArgCount represents method's signature.
type MethodAndArgCount struct {
Name string
ArgCount int
}
// NewContractMD returns Contract with the specified list of methods.
func NewContractMD(name string, id int32) *ContractMD {
c := &ContractMD{
Name: name,
ContractID: id,
Methods: make(map[MethodAndArgCount]MethodAndPrice),
}
// NEF is now stored in contract state and affects state dump.
// Therefore values are taken from C# node.
c.NEF.Header.Compiler = "neo-core-v3.0"
c.NEF.Header.Magic = nef.Magic
c.NEF.Script = state.CreateNativeContractScript(id)
c.NEF.Checksum = c.NEF.CalculateChecksum()
c.Hash = state.CreateContractHash(util.Uint160{}, c.NEF.Checksum, name)
c.Manifest = *manifest.DefaultManifest(name)
return c
}
// AddMethod adds new method to a native contract.
func (c *ContractMD) AddMethod(md *MethodAndPrice, desc *manifest.Method) {
md.MD = desc
desc.Safe = md.RequiredFlags&(callflag.All^callflag.ReadOnly) == 0
index := sort.Search(len(c.Manifest.ABI.Methods), func(i int) bool {
md := c.Manifest.ABI.Methods[i]
if md.Name != desc.Name {
return md.Name >= desc.Name
}
return len(md.Parameters) > len(desc.Parameters)
})
c.Manifest.ABI.Methods = append(c.Manifest.ABI.Methods, manifest.Method{})
copy(c.Manifest.ABI.Methods[index+1:], c.Manifest.ABI.Methods[index:])
c.Manifest.ABI.Methods[index] = *desc
key := MethodAndArgCount{
Name: desc.Name,
ArgCount: len(desc.Parameters),
}
c.Methods[key] = *md
}
// GetMethod returns method `name` with specified number of parameters.
func (c *ContractMD) GetMethod(name string, paramCount int) (MethodAndPrice, bool) {
key := MethodAndArgCount{
Name: name,
ArgCount: paramCount,
}
mp, ok := c.Methods[key]
return mp, ok
}
// AddEvent adds new event to a native contract.
func (c *ContractMD) AddEvent(name string, ps ...manifest.Parameter) {
c.Manifest.ABI.Events = append(c.Manifest.ABI.Events, manifest.Event{
Name: name,
Parameters: ps,
})
}
// Sort sorts interop functions by id.
func Sort(fs []Function) {
sort.Slice(fs, func(i, j int) bool { return fs[i].ID < fs[j].ID })
}
// GetContract returns contract by its hash in current interop context.
func (ic *Context) GetContract(hash util.Uint160) (*state.Contract, error) {
return ic.getContract(ic.DAO, hash)
}
// GetFunction returns metadata for interop with the specified id.
func (ic *Context) GetFunction(id uint32) *Function {
for _, slice := range ic.Functions {
n := sort.Search(len(slice), func(i int) bool {
return slice[i].ID >= id
})
if n < len(slice) && slice[n].ID == id {
return &slice[n]
}
}
return nil
}
// BaseExecFee represents factor to multiply syscall prices with.
func (ic *Context) BaseExecFee() int64 {
if ic.Chain == nil || (ic.Block != nil && ic.Block.Index == 0) {
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)
if f == nil {
return errors.New("syscall not found")
}
cf := ic.VM.Context().GetCallFlags()
if !cf.Has(f.RequiredFlags) {
return fmt.Errorf("missing call flags: %05b vs %05b", cf, f.RequiredFlags)
}
if !ic.VM.AddGas(f.Price * ic.BaseExecFee()) {
return errors.New("insufficient amount of gas")
}
return f.Func(ic)
}
// SpawnVM spawns new VM with the specified gas limit and set context.VM field.
func (ic *Context) SpawnVM() *vm.VM {
v := vm.NewWithTrigger(ic.Trigger)
v.GasLimit = -1
v.SyscallHandler = ic.SyscallHandler
ic.VM = v
return v
}