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 }