github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/interop/context.go (about)

     1  package interop
     2  
     3  import (
     4  	"context"
     5  	"encoding/binary"
     6  	"errors"
     7  	"fmt"
     8  	"sort"
     9  	"strings"
    10  
    11  	"github.com/nspcc-dev/neo-go/pkg/config"
    12  	"github.com/nspcc-dev/neo-go/pkg/core/block"
    13  	"github.com/nspcc-dev/neo-go/pkg/core/dao"
    14  	"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
    15  	"github.com/nspcc-dev/neo-go/pkg/core/state"
    16  	"github.com/nspcc-dev/neo-go/pkg/core/storage"
    17  	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
    18  	"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
    19  	"github.com/nspcc-dev/neo-go/pkg/io"
    20  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
    21  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
    22  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
    23  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
    24  	"github.com/nspcc-dev/neo-go/pkg/util"
    25  	"github.com/nspcc-dev/neo-go/pkg/vm"
    26  	"github.com/nspcc-dev/neo-go/pkg/vm/emit"
    27  	"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
    28  	"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
    29  	"go.uber.org/zap"
    30  )
    31  
    32  const (
    33  	// DefaultBaseExecFee specifies the default multiplier for opcode and syscall prices.
    34  	DefaultBaseExecFee = 30
    35  )
    36  
    37  // Ledger is the interface to Blockchain required for Context functionality.
    38  type Ledger interface {
    39  	BlockHeight() uint32
    40  	CurrentBlockHash() util.Uint256
    41  	GetBlock(hash util.Uint256) (*block.Block, error)
    42  	GetConfig() config.Blockchain
    43  	GetHeaderHash(uint32) util.Uint256
    44  }
    45  
    46  // Context represents context in which interops are executed.
    47  type Context struct {
    48  	Chain            Ledger
    49  	Container        hash.Hashable
    50  	Network          uint32
    51  	Hardforks        map[string]uint32
    52  	Natives          []Contract
    53  	Trigger          trigger.Type
    54  	Block            *block.Block
    55  	NonceData        [16]byte
    56  	Tx               *transaction.Transaction
    57  	DAO              *dao.Simple
    58  	Notifications    []state.NotificationEvent
    59  	Log              *zap.Logger
    60  	VM               *vm.VM
    61  	Functions        []Function
    62  	Invocations      map[util.Uint160]int
    63  	cancelFuncs      []context.CancelFunc
    64  	getContract      func(*dao.Simple, util.Uint160) (*state.Contract, error)
    65  	baseExecFee      int64
    66  	baseStorageFee   int64
    67  	loadToken        func(ic *Context, id int32) error
    68  	GetRandomCounter uint32
    69  	signers          []transaction.Signer
    70  }
    71  
    72  // NewContext returns new interop context.
    73  func NewContext(trigger trigger.Type, bc Ledger, d *dao.Simple, baseExecFee, baseStorageFee int64,
    74  	getContract func(*dao.Simple, util.Uint160) (*state.Contract, error), natives []Contract,
    75  	loadTokenFunc func(ic *Context, id int32) error,
    76  	block *block.Block, tx *transaction.Transaction, log *zap.Logger) *Context {
    77  	dao := d.GetPrivate()
    78  	cfg := bc.GetConfig().ProtocolConfiguration
    79  	return &Context{
    80  		Chain:          bc,
    81  		Network:        uint32(cfg.Magic),
    82  		Hardforks:      cfg.Hardforks,
    83  		Natives:        natives,
    84  		Trigger:        trigger,
    85  		Block:          block,
    86  		Tx:             tx,
    87  		DAO:            dao,
    88  		Log:            log,
    89  		Invocations:    make(map[util.Uint160]int),
    90  		getContract:    getContract,
    91  		baseExecFee:    baseExecFee,
    92  		baseStorageFee: baseStorageFee,
    93  		loadToken:      loadTokenFunc,
    94  	}
    95  }
    96  
    97  // InitNonceData initializes nonce to be used in `GetRandom` calculations.
    98  func (ic *Context) InitNonceData() {
    99  	if tx, ok := ic.Container.(*transaction.Transaction); ok {
   100  		copy(ic.NonceData[:], tx.Hash().BytesBE())
   101  	}
   102  	if ic.Block != nil {
   103  		nonce := ic.Block.Nonce
   104  		nonce ^= binary.LittleEndian.Uint64(ic.NonceData[:])
   105  		binary.LittleEndian.PutUint64(ic.NonceData[:], nonce)
   106  	}
   107  }
   108  
   109  // UseSigners allows overriding signers used in this context.
   110  func (ic *Context) UseSigners(s []transaction.Signer) {
   111  	ic.signers = s
   112  }
   113  
   114  // Signers returns signers witnessing the current execution context.
   115  func (ic *Context) Signers() []transaction.Signer {
   116  	if ic.signers != nil {
   117  		return ic.signers
   118  	}
   119  	if ic.Tx != nil {
   120  		return ic.Tx.Signers
   121  	}
   122  	return nil
   123  }
   124  
   125  // Function binds function name, id with the function itself and the price,
   126  // it's supposed to be inited once for all interopContexts, so it doesn't use
   127  // vm.InteropFuncPrice directly.
   128  type Function struct {
   129  	ID   uint32
   130  	Name string
   131  	Func func(*Context) error
   132  	// ParamCount is a number of function parameters.
   133  	ParamCount int
   134  	Price      int64
   135  	// RequiredFlags is a set of flags which must be set during script invocations.
   136  	// Default value is NoneFlag i.e. no flags are required.
   137  	RequiredFlags callflag.CallFlag
   138  }
   139  
   140  // Method is a signature for a native method.
   141  type Method = func(ic *Context, args []stackitem.Item) stackitem.Item
   142  
   143  // MethodAndPrice is a generic hardfork-independent native contract method descriptor.
   144  type MethodAndPrice struct {
   145  	HFSpecificMethodAndPrice
   146  	ActiveFrom *config.Hardfork
   147  }
   148  
   149  // HFSpecificMethodAndPrice is a hardfork-specific native contract method descriptor.
   150  type HFSpecificMethodAndPrice struct {
   151  	Func          Method
   152  	MD            *manifest.Method
   153  	CPUFee        int64
   154  	StorageFee    int64
   155  	SyscallOffset int
   156  	RequiredFlags callflag.CallFlag
   157  }
   158  
   159  // Event is a generic hardfork-independent native contract event descriptor.
   160  type Event struct {
   161  	HFSpecificEvent
   162  	ActiveFrom *config.Hardfork
   163  }
   164  
   165  // HFSpecificEvent is a hardfork-specific native contract event descriptor.
   166  type HFSpecificEvent struct {
   167  	MD *manifest.Event
   168  }
   169  
   170  // Contract is an interface for all native contracts.
   171  type Contract interface {
   172  	// Initialize performs native contract initialization on contract deploy or update.
   173  	// Active hardfork is passed as the second argument.
   174  	Initialize(*Context, *config.Hardfork, *HFSpecificContractMD) error
   175  	// ActiveIn returns the hardfork native contract is active starting from or nil in case
   176  	// it's always active.
   177  	ActiveIn() *config.Hardfork
   178  	// InitializeCache aimed to initialize contract's cache when the contract has
   179  	// been deployed, but in-memory cached data were lost due to the node reset.
   180  	// It should be called each time after node restart iff the contract was
   181  	// deployed and no Initialize method was called.
   182  	InitializeCache(blockHeight uint32, d *dao.Simple) error
   183  	// Metadata returns generic native contract metadata.
   184  	Metadata() *ContractMD
   185  	OnPersist(*Context) error
   186  	PostPersist(*Context) error
   187  }
   188  
   189  // ContractMD represents a generic hardfork-independent native contract instance.
   190  type ContractMD struct {
   191  	ID   int32
   192  	Hash util.Uint160
   193  	Name string
   194  	// methods is a generic set of contract methods with activation hardforks. Any HF-dependent part of included methods
   195  	// (offsets, in particular) must not be used, there's a mdCache field for that.
   196  	methods []MethodAndPrice
   197  	// events is a generic set of contract events with activation hardforks. Any HF-dependent part of events must not be
   198  	// used, there's a mdCache field for that.
   199  	events []Event
   200  	// ActiveHFs is a map of hardforks that contract should react to. Contract update should be called for active
   201  	// hardforks. Note, that unlike the C# implementation, this map doesn't include contract's activation hardfork.
   202  	// This map is being initialized on contract creation and used as a read-only, hence, not protected
   203  	// by mutex.
   204  	ActiveHFs map[config.Hardfork]struct{}
   205  
   206  	// mdCache contains hardfork-specific ready-to-use contract descriptors. This cache is initialized in the native
   207  	// contracts constructors, and acts as read-only during the whole node lifetime, thus not protected by mutex.
   208  	mdCache map[config.Hardfork]*HFSpecificContractMD
   209  
   210  	// onManifestConstruction is a callback for manifest finalization.
   211  	onManifestConstruction func(*manifest.Manifest)
   212  }
   213  
   214  // HFSpecificContractMD is a hardfork-specific native contract descriptor.
   215  type HFSpecificContractMD struct {
   216  	state.ContractBase
   217  	Methods []HFSpecificMethodAndPrice
   218  	Events  []HFSpecificEvent
   219  }
   220  
   221  // NewContractMD returns Contract with the specified fields set. onManifestConstruction callback every time
   222  // after hardfork-specific manifest creation and aimed to finalize the manifest.
   223  func NewContractMD(name string, id int32, onManifestConstruction ...func(*manifest.Manifest)) *ContractMD {
   224  	c := &ContractMD{Name: name}
   225  	if len(onManifestConstruction) != 0 {
   226  		c.onManifestConstruction = onManifestConstruction[0]
   227  	}
   228  
   229  	c.ID = id
   230  	c.Hash = state.CreateNativeContractHash(c.Name)
   231  	c.ActiveHFs = make(map[config.Hardfork]struct{})
   232  	c.mdCache = make(map[config.Hardfork]*HFSpecificContractMD)
   233  
   234  	return c
   235  }
   236  
   237  // HFSpecificContractMD returns hardfork-specific native contract metadata, i.e. with methods, events and script
   238  // corresponding to the specified hardfork. If hardfork is not specified, then default metadata will be returned
   239  // (methods, events and script that are always active). Calling this method for hardforks older than the contract
   240  // activation hardfork is a no-op.
   241  func (c *ContractMD) HFSpecificContractMD(hf *config.Hardfork) *HFSpecificContractMD {
   242  	var key config.Hardfork
   243  	if hf != nil {
   244  		key = *hf
   245  	}
   246  	md, ok := c.mdCache[key]
   247  	if !ok {
   248  		panic(fmt.Errorf("native contract descriptor cache is not initialized: contract %s, hardfork %s", c.Hash.StringLE(), key))
   249  	}
   250  	if md == nil {
   251  		panic(fmt.Errorf("native contract descriptor cache is nil: contract %s, hardfork %s", c.Hash.StringLE(), key))
   252  	}
   253  	return md
   254  }
   255  
   256  // BuildHFSpecificMD generates and caches contract's descriptor for every known hardfork.
   257  func (c *ContractMD) BuildHFSpecificMD(activeIn *config.Hardfork) {
   258  	var start config.Hardfork
   259  	if activeIn != nil {
   260  		start = *activeIn
   261  	}
   262  
   263  	for _, hf := range append([]config.Hardfork{config.HFDefault}, config.Hardforks...) {
   264  		switch {
   265  		case hf.Cmp(start) < 0:
   266  			continue
   267  		case hf.Cmp(start) == 0:
   268  			c.buildHFSpecificMD(hf)
   269  		default:
   270  			if _, ok := c.ActiveHFs[hf]; !ok {
   271  				// Intentionally omit HFSpecificContractMD structure copying since mdCache is read-only.
   272  				c.mdCache[hf] = c.mdCache[hf.Prev()]
   273  				continue
   274  			}
   275  			c.buildHFSpecificMD(hf)
   276  		}
   277  	}
   278  }
   279  
   280  // buildHFSpecificMD builds hardfork-specific contract descriptor that includes methods and events active starting from
   281  // the specified hardfork or older. It also updates cache with the received value.
   282  func (c *ContractMD) buildHFSpecificMD(hf config.Hardfork) {
   283  	var (
   284  		abiMethods = make([]manifest.Method, 0, len(c.methods))
   285  		methods    = make([]HFSpecificMethodAndPrice, 0, len(c.methods))
   286  		abiEvents  = make([]manifest.Event, 0, len(c.events))
   287  		events     = make([]HFSpecificEvent, 0, len(c.events))
   288  	)
   289  	w := io.NewBufBinWriter()
   290  	for i := range c.methods {
   291  		m := c.methods[i]
   292  		if !(m.ActiveFrom == nil || (hf != config.HFDefault && (*m.ActiveFrom).Cmp(hf) >= 0)) {
   293  			continue
   294  		}
   295  
   296  		// Perform method descriptor copy to support independent HF-based offset update.
   297  		md := *m.MD
   298  		m.MD = &md
   299  		m.MD.Offset = w.Len()
   300  
   301  		emit.Int(w.BinWriter, 0)
   302  		m.SyscallOffset = w.Len()
   303  		emit.Syscall(w.BinWriter, interopnames.SystemContractCallNative)
   304  		emit.Opcodes(w.BinWriter, opcode.RET)
   305  
   306  		abiMethods = append(abiMethods, *m.MD)
   307  		methods = append(methods, m.HFSpecificMethodAndPrice)
   308  	}
   309  	if w.Err != nil {
   310  		panic(fmt.Errorf("can't create native contract script: %w", w.Err))
   311  	}
   312  	for i := range c.events {
   313  		e := c.events[i]
   314  		if !(e.ActiveFrom == nil || (hf != config.HFDefault && (*e.ActiveFrom).Cmp(hf) >= 0)) {
   315  			continue
   316  		}
   317  
   318  		abiEvents = append(abiEvents, *e.MD)
   319  		events = append(events, e.HFSpecificEvent)
   320  	}
   321  
   322  	// NEF is now stored in the contract state and affects state dump.
   323  	// Therefore, values are taken from C# node.
   324  	nf := nef.File{
   325  		Header: nef.Header{
   326  			Magic:    nef.Magic,
   327  			Compiler: "neo-core-v3.0",
   328  		},
   329  		Tokens: []nef.MethodToken{}, // avoid `nil` result during JSON marshalling,
   330  		Script: w.Bytes(),
   331  	}
   332  	nf.Checksum = nf.CalculateChecksum()
   333  	m := manifest.DefaultManifest(c.Name)
   334  	m.ABI.Methods = abiMethods
   335  	m.ABI.Events = abiEvents
   336  	if c.onManifestConstruction != nil {
   337  		c.onManifestConstruction(m)
   338  	}
   339  	md := &HFSpecificContractMD{
   340  		ContractBase: state.ContractBase{
   341  			ID:       c.ID,
   342  			Hash:     c.Hash,
   343  			NEF:      nf,
   344  			Manifest: *m,
   345  		},
   346  		Methods: methods,
   347  		Events:  events,
   348  	}
   349  
   350  	c.mdCache[hf] = md
   351  }
   352  
   353  // AddMethod adds a new method to a native contract.
   354  func (c *ContractMD) AddMethod(md *MethodAndPrice, desc *manifest.Method) {
   355  	md.MD = desc
   356  	desc.Safe = md.RequiredFlags&(callflag.All^callflag.ReadOnly) == 0
   357  
   358  	index := sort.Search(len(c.methods), func(i int) bool {
   359  		md := c.methods[i].MD
   360  		if md.Name != desc.Name {
   361  			return md.Name >= desc.Name
   362  		}
   363  		return len(md.Parameters) > len(desc.Parameters)
   364  	})
   365  	c.methods = append(c.methods, MethodAndPrice{})
   366  	copy(c.methods[index+1:], c.methods[index:])
   367  	c.methods[index] = *md
   368  
   369  	if md.ActiveFrom != nil {
   370  		c.ActiveHFs[*md.ActiveFrom] = struct{}{}
   371  	}
   372  }
   373  
   374  // GetMethodByOffset returns method with the provided offset.
   375  // Offset is offset of `System.Contract.CallNative` syscall.
   376  func (c *HFSpecificContractMD) GetMethodByOffset(offset int) (HFSpecificMethodAndPrice, bool) {
   377  	for k := range c.Methods {
   378  		if c.Methods[k].SyscallOffset == offset {
   379  			return c.Methods[k], true
   380  		}
   381  	}
   382  	return HFSpecificMethodAndPrice{}, false
   383  }
   384  
   385  // GetMethod returns method `name` with the specified number of parameters.
   386  func (c *HFSpecificContractMD) GetMethod(name string, paramCount int) (HFSpecificMethodAndPrice, bool) {
   387  	index := sort.Search(len(c.Methods), func(i int) bool {
   388  		md := c.Methods[i]
   389  		res := strings.Compare(name, md.MD.Name)
   390  		switch res {
   391  		case -1, 1:
   392  			return res == -1
   393  		default:
   394  			return paramCount <= len(md.MD.Parameters)
   395  		}
   396  	})
   397  	if index < len(c.Methods) {
   398  		md := c.Methods[index]
   399  		if md.MD.Name == name && (paramCount == -1 || len(md.MD.Parameters) == paramCount) {
   400  			return md, true
   401  		}
   402  	}
   403  	return HFSpecificMethodAndPrice{}, false
   404  }
   405  
   406  // AddEvent adds a new event to the native contract.
   407  func (c *ContractMD) AddEvent(md Event) {
   408  	c.events = append(c.events, md)
   409  
   410  	if md.ActiveFrom != nil {
   411  		c.ActiveHFs[*md.ActiveFrom] = struct{}{}
   412  	}
   413  }
   414  
   415  // Sort sorts interop functions by id.
   416  func Sort(fs []Function) {
   417  	sort.Slice(fs, func(i, j int) bool { return fs[i].ID < fs[j].ID })
   418  }
   419  
   420  // GetContract returns a contract by its hash in the current interop context.
   421  func (ic *Context) GetContract(hash util.Uint160) (*state.Contract, error) {
   422  	return ic.getContract(ic.DAO, hash)
   423  }
   424  
   425  // GetFunction returns metadata for interop with the specified id.
   426  func (ic *Context) GetFunction(id uint32) *Function {
   427  	n := sort.Search(len(ic.Functions), func(i int) bool {
   428  		return ic.Functions[i].ID >= id
   429  	})
   430  	if n < len(ic.Functions) && ic.Functions[n].ID == id {
   431  		return &ic.Functions[n]
   432  	}
   433  	return nil
   434  }
   435  
   436  // BaseExecFee represents factor to multiply syscall prices with.
   437  func (ic *Context) BaseExecFee() int64 {
   438  	return ic.baseExecFee
   439  }
   440  
   441  // BaseStorageFee represents price for storing one byte of data in the contract storage.
   442  func (ic *Context) BaseStorageFee() int64 {
   443  	return ic.baseStorageFee
   444  }
   445  
   446  // LoadToken wraps externally provided load-token loading function providing it with context,
   447  // this function can then be easily used by VM.
   448  func (ic *Context) LoadToken(id int32) error {
   449  	return ic.loadToken(ic, id)
   450  }
   451  
   452  // SyscallHandler handles syscall with id.
   453  func (ic *Context) SyscallHandler(_ *vm.VM, id uint32) error {
   454  	f := ic.GetFunction(id)
   455  	if f == nil {
   456  		return errors.New("syscall not found")
   457  	}
   458  	cf := ic.VM.Context().GetCallFlags()
   459  	if !cf.Has(f.RequiredFlags) {
   460  		return fmt.Errorf("missing call flags: %05b vs %05b", cf, f.RequiredFlags)
   461  	}
   462  	if !ic.VM.AddGas(f.Price * ic.BaseExecFee()) {
   463  		return errors.New("insufficient amount of gas")
   464  	}
   465  	return f.Func(ic)
   466  }
   467  
   468  // SpawnVM spawns a new VM with the specified gas limit and set context.VM field.
   469  func (ic *Context) SpawnVM() *vm.VM {
   470  	v := vm.NewWithTrigger(ic.Trigger)
   471  	ic.initVM(v)
   472  	return v
   473  }
   474  
   475  func (ic *Context) initVM(v *vm.VM) {
   476  	v.LoadToken = ic.LoadToken
   477  	v.GasLimit = -1
   478  	v.SyscallHandler = ic.SyscallHandler
   479  	v.SetPriceGetter(ic.GetPrice)
   480  	ic.VM = v
   481  }
   482  
   483  // ReuseVM resets given VM and allows to reuse it in the current context.
   484  func (ic *Context) ReuseVM(v *vm.VM) {
   485  	v.Reset(ic.Trigger)
   486  	ic.initVM(v)
   487  }
   488  
   489  // RegisterCancelFunc adds the given function to the list of functions to be called after the VM
   490  // finishes script execution.
   491  func (ic *Context) RegisterCancelFunc(f context.CancelFunc) {
   492  	if f != nil {
   493  		ic.cancelFuncs = append(ic.cancelFuncs, f)
   494  	}
   495  }
   496  
   497  // Finalize calls all registered cancel functions to release the occupied resources.
   498  func (ic *Context) Finalize() {
   499  	for _, f := range ic.cancelFuncs {
   500  		f()
   501  	}
   502  	ic.cancelFuncs = nil
   503  }
   504  
   505  // Exec executes loaded VM script and calls registered finalizers to release the occupied resources.
   506  func (ic *Context) Exec() error {
   507  	defer ic.Finalize()
   508  	return ic.VM.Run()
   509  }
   510  
   511  // BlockHeight returns the latest persisted and stored block height/index.
   512  // Persisting block index is not taken into account. If Context's block is set,
   513  // then BlockHeight calculations relies on persisting block index.
   514  func (ic *Context) BlockHeight() uint32 {
   515  	if ic.Block != nil {
   516  		return ic.Block.Index - 1 // Persisting block is not yet stored.
   517  	}
   518  	return ic.Chain.BlockHeight()
   519  }
   520  
   521  // CurrentBlockHash returns current block hash got from Context's block if it's set.
   522  func (ic *Context) CurrentBlockHash() util.Uint256 {
   523  	if ic.Block != nil {
   524  		return ic.Chain.GetHeaderHash(ic.Block.Index - 1) // Persisting block is not yet stored.
   525  	}
   526  	return ic.Chain.CurrentBlockHash()
   527  }
   528  
   529  // GetBlock returns block if it exists and available at the current Context's height.
   530  func (ic *Context) GetBlock(hash util.Uint256) (*block.Block, error) {
   531  	block, err := ic.Chain.GetBlock(hash)
   532  	if err != nil {
   533  		return nil, err
   534  	}
   535  	if block.Index > ic.BlockHeight() { // persisting block is not reachable.
   536  		return nil, storage.ErrKeyNotFound
   537  	}
   538  	return block, nil
   539  }
   540  
   541  // IsHardforkEnabled tells whether specified hard-fork enabled at the current context height.
   542  func (ic *Context) IsHardforkEnabled(hf config.Hardfork) bool {
   543  	height, ok := ic.Hardforks[hf.String()]
   544  	if ok {
   545  		return (ic.BlockHeight() + 1) >= height // persisting block should be taken into account.
   546  	}
   547  	// Completely rely on proper hardforks initialisation made by core.NewBlockchain.
   548  	return false
   549  }
   550  
   551  // IsHardforkActivation denotes whether current block height is the height of
   552  // specified hardfork activation.
   553  func (ic *Context) IsHardforkActivation(hf config.Hardfork) bool {
   554  	// Completely rely on proper hardforks initialisation made by core.NewBlockchain.
   555  	height, ok := ic.Hardforks[hf.String()]
   556  	return ok && ic.Block.Index == height
   557  }
   558  
   559  // AddNotification creates notification event and appends it to the notification list.
   560  func (ic *Context) AddNotification(hash util.Uint160, name string, item *stackitem.Array) {
   561  	ic.Notifications = append(ic.Notifications, state.NotificationEvent{
   562  		ScriptHash: hash,
   563  		Name:       name,
   564  		Item:       item,
   565  	})
   566  }