github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/native/oracle.go (about) 1 package native 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "errors" 7 "fmt" 8 "math" 9 "math/big" 10 "strings" 11 "sync/atomic" 12 13 "github.com/nspcc-dev/neo-go/pkg/config" 14 "github.com/nspcc-dev/neo-go/pkg/core/dao" 15 "github.com/nspcc-dev/neo-go/pkg/core/interop" 16 "github.com/nspcc-dev/neo-go/pkg/core/interop/contract" 17 "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" 18 "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" 19 "github.com/nspcc-dev/neo-go/pkg/core/state" 20 "github.com/nspcc-dev/neo-go/pkg/core/storage" 21 "github.com/nspcc-dev/neo-go/pkg/core/transaction" 22 "github.com/nspcc-dev/neo-go/pkg/crypto/hash" 23 "github.com/nspcc-dev/neo-go/pkg/crypto/keys" 24 "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" 25 "github.com/nspcc-dev/neo-go/pkg/smartcontract" 26 "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" 27 "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" 28 "github.com/nspcc-dev/neo-go/pkg/util" 29 "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" 30 ) 31 32 // Oracle represents Oracle native contract. 33 type Oracle struct { 34 interop.ContractMD 35 GAS *GAS 36 NEO *NEO 37 38 Desig *Designate 39 oracleScript []byte 40 41 // Module is an oracle module capable of talking with the external world. 42 Module atomic.Value 43 // newRequests contains new requests created during the current block. 44 newRequests map[uint64]*state.OracleRequest 45 } 46 47 type OracleCache struct { 48 requestPrice int64 49 } 50 51 // OracleService specifies oracle module interface. 52 type OracleService interface { 53 // AddRequests processes new requests. 54 AddRequests(map[uint64]*state.OracleRequest) 55 // RemoveRequests removes already processed requests. 56 RemoveRequests([]uint64) 57 // UpdateOracleNodes updates oracle nodes. 58 UpdateOracleNodes(keys.PublicKeys) 59 // UpdateNativeContract updates oracle contract native script and hash. 60 UpdateNativeContract([]byte, []byte, util.Uint160, int) 61 // Start runs oracle module. 62 Start() 63 // Shutdown shutdowns oracle module. 64 Shutdown() 65 } 66 67 const ( 68 oracleContractID = -9 69 maxURLLength = 256 70 maxFilterLength = 128 71 maxCallbackLength = 32 72 maxUserDataLength = 512 73 // maxRequestsCount is the maximum number of requests per URL. 74 maxRequestsCount = 256 75 76 // DefaultOracleRequestPrice is the default amount GAS needed for an oracle request. 77 DefaultOracleRequestPrice = 5000_0000 78 79 // MinimumResponseGas is the minimum response fee permitted for a request. 80 MinimumResponseGas = 10_000_000 81 ) 82 83 var ( 84 prefixRequestPrice = []byte{5} 85 prefixIDList = []byte{6} 86 prefixRequest = []byte{7} 87 prefixRequestID = []byte{9} 88 ) 89 90 // Various validation errors. 91 var ( 92 ErrBigArgument = errors.New("some of the arguments are invalid") 93 ErrInvalidWitness = errors.New("witness check failed") 94 ErrLowResponseGas = errors.New("not enough gas for response") 95 ErrNotEnoughGas = errors.New("gas limit exceeded") 96 ErrRequestNotFound = errors.New("oracle request not found") 97 ErrResponseNotFound = errors.New("oracle response not found") 98 ) 99 100 var ( 101 _ interop.Contract = (*Oracle)(nil) 102 _ dao.NativeContractCache = (*OracleCache)(nil) 103 ) 104 105 // Copy implements NativeContractCache interface. 106 func (c *OracleCache) Copy() dao.NativeContractCache { 107 cp := &OracleCache{} 108 copyOracleCache(c, cp) 109 return cp 110 } 111 112 func copyOracleCache(src, dst *OracleCache) { 113 *dst = *src 114 } 115 116 func newOracle() *Oracle { 117 o := &Oracle{ 118 ContractMD: *interop.NewContractMD(nativenames.Oracle, oracleContractID), 119 newRequests: make(map[uint64]*state.OracleRequest), 120 } 121 defer o.BuildHFSpecificMD(o.ActiveIn()) 122 123 o.oracleScript = CreateOracleResponseScript(o.Hash) 124 125 desc := newDescriptor("request", smartcontract.VoidType, 126 manifest.NewParameter("url", smartcontract.StringType), 127 manifest.NewParameter("filter", smartcontract.StringType), 128 manifest.NewParameter("callback", smartcontract.StringType), 129 manifest.NewParameter("userData", smartcontract.AnyType), 130 manifest.NewParameter("gasForResponse", smartcontract.IntegerType)) 131 md := newMethodAndPrice(o.request, 0, callflag.States|callflag.AllowNotify) 132 o.AddMethod(md, desc) 133 134 desc = newDescriptor("finish", smartcontract.VoidType) 135 md = newMethodAndPrice(o.finish, 0, callflag.States|callflag.AllowCall|callflag.AllowNotify) 136 o.AddMethod(md, desc) 137 138 desc = newDescriptor("verify", smartcontract.BoolType) 139 md = newMethodAndPrice(o.verify, 1<<15, callflag.NoneFlag) 140 o.AddMethod(md, desc) 141 142 eDesc := newEventDescriptor("OracleRequest", manifest.NewParameter("Id", smartcontract.IntegerType), 143 manifest.NewParameter("RequestContract", smartcontract.Hash160Type), 144 manifest.NewParameter("Url", smartcontract.StringType), 145 manifest.NewParameter("Filter", smartcontract.StringType)) 146 eMD := newEvent(eDesc) 147 o.AddEvent(eMD) 148 149 eDesc = newEventDescriptor("OracleResponse", manifest.NewParameter("Id", smartcontract.IntegerType), 150 manifest.NewParameter("OriginalTx", smartcontract.Hash256Type)) 151 eMD = newEvent(eDesc) 152 o.AddEvent(eMD) 153 154 desc = newDescriptor("getPrice", smartcontract.IntegerType) 155 md = newMethodAndPrice(o.getPrice, 1<<15, callflag.ReadStates) 156 o.AddMethod(md, desc) 157 158 desc = newDescriptor("setPrice", smartcontract.VoidType, 159 manifest.NewParameter("price", smartcontract.IntegerType)) 160 md = newMethodAndPrice(o.setPrice, 1<<15, callflag.States) 161 o.AddMethod(md, desc) 162 163 return o 164 } 165 166 // GetOracleResponseScript returns a script for the transaction with an oracle response. 167 func (o *Oracle) GetOracleResponseScript() []byte { 168 return bytes.Clone(o.oracleScript) 169 } 170 171 // OnPersist implements the Contract interface. 172 func (o *Oracle) OnPersist(ic *interop.Context) error { 173 return nil 174 } 175 176 // PostPersist represents `postPersist` method. 177 func (o *Oracle) PostPersist(ic *interop.Context) error { 178 p := o.getPriceInternal(ic.DAO) 179 180 var nodes keys.PublicKeys 181 var reward []big.Int 182 single := big.NewInt(p) 183 var removedIDs []uint64 184 185 orc, _ := o.Module.Load().(*OracleService) 186 for _, tx := range ic.Block.Transactions { 187 resp := getResponse(tx) 188 if resp == nil { 189 continue 190 } 191 reqKey := makeRequestKey(resp.ID) 192 req := new(state.OracleRequest) 193 if err := o.getConvertibleFromDAO(ic.DAO, reqKey, req); err != nil { 194 continue 195 } 196 ic.DAO.DeleteStorageItem(o.ID, reqKey) 197 if orc != nil && *orc != nil { 198 removedIDs = append(removedIDs, resp.ID) 199 } 200 201 idKey := makeIDListKey(req.URL) 202 idList := new(IDList) 203 if err := o.getConvertibleFromDAO(ic.DAO, idKey, idList); err != nil { 204 return err 205 } 206 if !idList.Remove(resp.ID) { 207 return errors.New("response ID wasn't found") 208 } 209 210 var err error 211 if len(*idList) == 0 { 212 ic.DAO.DeleteStorageItem(o.ID, idKey) 213 } else { 214 err = putConvertibleToDAO(o.ID, ic.DAO, idKey, idList) 215 } 216 if err != nil { 217 return err 218 } 219 220 if nodes == nil { 221 nodes, err = o.GetOracleNodes(ic.DAO) 222 if err != nil { 223 return err 224 } 225 reward = make([]big.Int, len(nodes)) 226 } 227 228 if len(reward) > 0 { 229 index := resp.ID % uint64(len(nodes)) 230 reward[index].Add(&reward[index], single) 231 } 232 } 233 for i := range reward { 234 o.GAS.mint(ic, nodes[i].GetScriptHash(), &reward[i], false) 235 } 236 237 if len(removedIDs) != 0 { 238 (*orc).RemoveRequests(removedIDs) 239 } 240 return o.updateCache(ic.DAO) 241 } 242 243 // Metadata returns contract metadata. 244 func (o *Oracle) Metadata() *interop.ContractMD { 245 return &o.ContractMD 246 } 247 248 // Initialize initializes an Oracle contract. 249 func (o *Oracle) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error { 250 switch hf { 251 case o.ActiveIn(): 252 setIntWithKey(o.ID, ic.DAO, prefixRequestID, 0) 253 setIntWithKey(o.ID, ic.DAO, prefixRequestPrice, DefaultOracleRequestPrice) 254 255 cache := &OracleCache{ 256 requestPrice: int64(DefaultOracleRequestPrice), 257 } 258 ic.DAO.SetCache(o.ID, cache) 259 default: 260 orc, _ := o.Module.Load().(*OracleService) 261 if orc != nil && *orc != nil { 262 md, ok := newMD.GetMethod(manifest.MethodVerify, -1) 263 if !ok { 264 panic(fmt.Errorf("%s method not found", manifest.MethodVerify)) 265 } 266 (*orc).UpdateNativeContract(newMD.NEF.Script, o.GetOracleResponseScript(), 267 o.Hash, md.MD.Offset) 268 } 269 } 270 271 return nil 272 } 273 274 func (o *Oracle) InitializeCache(blockHeight uint32, d *dao.Simple) error { 275 cache := &OracleCache{} 276 cache.requestPrice = getIntWithKey(o.ID, d, prefixRequestPrice) 277 d.SetCache(o.ID, cache) 278 return nil 279 } 280 281 // ActiveIn implements the Contract interface. 282 func (o *Oracle) ActiveIn() *config.Hardfork { 283 return nil 284 } 285 286 func getResponse(tx *transaction.Transaction) *transaction.OracleResponse { 287 for i := range tx.Attributes { 288 if tx.Attributes[i].Type == transaction.OracleResponseT { 289 return tx.Attributes[i].Value.(*transaction.OracleResponse) 290 } 291 } 292 return nil 293 } 294 295 func (o *Oracle) finish(ic *interop.Context, _ []stackitem.Item) stackitem.Item { 296 err := o.FinishInternal(ic) 297 if err != nil { 298 panic(err) 299 } 300 return stackitem.Null{} 301 } 302 303 // FinishInternal processes an oracle response. 304 func (o *Oracle) FinishInternal(ic *interop.Context) error { 305 if len(ic.VM.Istack()) != 2 { 306 return errors.New("Oracle.finish called from non-entry script") 307 } 308 if ic.Invocations[o.Hash] != 1 { 309 return errors.New("Oracle.finish called multiple times") 310 } 311 resp := getResponse(ic.Tx) 312 if resp == nil { 313 return ErrResponseNotFound 314 } 315 req, err := o.GetRequestInternal(ic.DAO, resp.ID) 316 if err != nil { 317 return ErrRequestNotFound 318 } 319 ic.AddNotification(o.Hash, "OracleResponse", stackitem.NewArray([]stackitem.Item{ 320 stackitem.Make(resp.ID), 321 stackitem.Make(req.OriginalTxID.BytesBE()), 322 })) 323 324 origTx, _, err := ic.DAO.GetTransaction(req.OriginalTxID) 325 if err != nil { 326 return ErrRequestNotFound 327 } 328 ic.UseSigners(origTx.Signers) 329 defer ic.UseSigners(nil) 330 331 userData, err := stackitem.Deserialize(req.UserData) 332 if err != nil { 333 return err 334 } 335 args := []stackitem.Item{ 336 stackitem.Make(req.URL), 337 userData, 338 stackitem.Make(resp.Code), 339 stackitem.Make(resp.Result), 340 } 341 cs, err := ic.GetContract(req.CallbackContract) 342 if err != nil { 343 return err 344 } 345 return contract.CallFromNative(ic, o.Hash, cs, req.CallbackMethod, args, false) 346 } 347 348 func (o *Oracle) request(ic *interop.Context, args []stackitem.Item) stackitem.Item { 349 url, err := stackitem.ToString(args[0]) 350 if err != nil { 351 panic(err) 352 } 353 var filter *string 354 _, ok := args[1].(stackitem.Null) 355 if !ok { 356 // Check UTF-8 validity. 357 str, err := stackitem.ToString(args[1]) 358 if err != nil { 359 panic(err) 360 } 361 filter = &str 362 } 363 cb, err := stackitem.ToString(args[2]) 364 if err != nil { 365 panic(err) 366 } 367 userData := args[3] 368 gas, err := args[4].TryInteger() 369 if err != nil { 370 panic(err) 371 } 372 if !ic.VM.AddGas(o.getPriceInternal(ic.DAO)) { 373 panic("insufficient gas") 374 } 375 if err := o.RequestInternal(ic, url, filter, cb, userData, gas); err != nil { 376 panic(err) 377 } 378 return stackitem.Null{} 379 } 380 381 // RequestInternal processes an oracle request. 382 func (o *Oracle) RequestInternal(ic *interop.Context, url string, filter *string, cb string, userData stackitem.Item, gas *big.Int) error { 383 if len(url) > maxURLLength || (filter != nil && len(*filter) > maxFilterLength) || len(cb) > maxCallbackLength || !gas.IsInt64() { 384 return ErrBigArgument 385 } 386 if gas.Int64() < MinimumResponseGas { 387 return ErrLowResponseGas 388 } 389 if strings.HasPrefix(cb, "_") { 390 return errors.New("disallowed callback method (starts with '_')") 391 } 392 393 if !ic.VM.AddGas(gas.Int64()) { 394 return ErrNotEnoughGas 395 } 396 callingHash := ic.VM.GetCallingScriptHash() 397 o.GAS.mint(ic, o.Hash, gas, false) 398 si := ic.DAO.GetStorageItem(o.ID, prefixRequestID) 399 itemID := bigint.FromBytes(si) 400 id := itemID.Uint64() 401 itemID.Add(itemID, intOne) 402 ic.DAO.PutBigInt(o.ID, prefixRequestID, itemID) 403 404 // Should be executed from the contract. 405 _, err := ic.GetContract(ic.VM.GetCallingScriptHash()) 406 if err != nil { 407 return err 408 } 409 410 data, err := ic.DAO.GetItemCtx().Serialize(userData, false) 411 if err != nil { 412 return err 413 } 414 if len(data) > maxUserDataLength { 415 return ErrBigArgument 416 } 417 data = bytes.Clone(data) // Serialization context will be used in PutRequestInternal again. 418 419 var filterNotif stackitem.Item 420 if filter != nil { 421 filterNotif = stackitem.Make(*filter) 422 } else { 423 filterNotif = stackitem.Null{} 424 } 425 ic.AddNotification(o.Hash, "OracleRequest", stackitem.NewArray([]stackitem.Item{ 426 stackitem.Make(id), 427 stackitem.Make(ic.VM.GetCallingScriptHash().BytesBE()), 428 stackitem.Make(url), 429 filterNotif, 430 })) 431 req := &state.OracleRequest{ 432 OriginalTxID: o.getOriginalTxID(ic.DAO, ic.Tx), 433 GasForResponse: gas.Uint64(), 434 URL: url, 435 Filter: filter, 436 CallbackContract: callingHash, 437 CallbackMethod: cb, 438 UserData: data, 439 } 440 return o.PutRequestInternal(id, req, ic.DAO) 441 } 442 443 // PutRequestInternal puts the oracle request with the specified id to d. 444 func (o *Oracle) PutRequestInternal(id uint64, req *state.OracleRequest, d *dao.Simple) error { 445 reqKey := makeRequestKey(id) 446 if err := putConvertibleToDAO(o.ID, d, reqKey, req); err != nil { 447 return err 448 } 449 orc, _ := o.Module.Load().(*OracleService) 450 if orc != nil && *orc != nil { 451 o.newRequests[id] = req 452 } 453 454 // Add request ID to the id list. 455 lst := new(IDList) 456 key := makeIDListKey(req.URL) 457 if err := o.getConvertibleFromDAO(d, key, lst); err != nil && !errors.Is(err, storage.ErrKeyNotFound) { 458 return err 459 } 460 if len(*lst) >= maxRequestsCount { 461 return fmt.Errorf("there are too many pending requests for %s url", req.URL) 462 } 463 *lst = append(*lst, id) 464 return putConvertibleToDAO(o.ID, d, key, lst) 465 } 466 467 // GetScriptHash returns script hash of oracle nodes. 468 func (o *Oracle) GetScriptHash(d *dao.Simple) (util.Uint160, error) { 469 return o.Desig.GetLastDesignatedHash(d, noderoles.Oracle) 470 } 471 472 // GetOracleNodes returns public keys of oracle nodes. 473 func (o *Oracle) GetOracleNodes(d *dao.Simple) (keys.PublicKeys, error) { 474 nodes, _, err := o.Desig.GetDesignatedByRole(d, noderoles.Oracle, math.MaxUint32) 475 return nodes, err 476 } 477 478 // GetRequestInternal returns the request by ID and key under which it is stored. 479 func (o *Oracle) GetRequestInternal(d *dao.Simple, id uint64) (*state.OracleRequest, error) { 480 key := makeRequestKey(id) 481 req := new(state.OracleRequest) 482 return req, o.getConvertibleFromDAO(d, key, req) 483 } 484 485 // GetIDListInternal returns the request by ID and key under which it is stored. 486 func (o *Oracle) GetIDListInternal(d *dao.Simple, url string) (*IDList, error) { 487 key := makeIDListKey(url) 488 idList := new(IDList) 489 return idList, o.getConvertibleFromDAO(d, key, idList) 490 } 491 492 func (o *Oracle) verify(ic *interop.Context, _ []stackitem.Item) stackitem.Item { 493 return stackitem.NewBool(ic.Tx.HasAttribute(transaction.OracleResponseT)) 494 } 495 496 func (o *Oracle) getPrice(ic *interop.Context, _ []stackitem.Item) stackitem.Item { 497 return stackitem.NewBigInteger(big.NewInt(o.getPriceInternal(ic.DAO))) 498 } 499 500 func (o *Oracle) getPriceInternal(d *dao.Simple) int64 { 501 cache := d.GetROCache(o.ID).(*OracleCache) 502 return cache.requestPrice 503 } 504 505 func (o *Oracle) setPrice(ic *interop.Context, args []stackitem.Item) stackitem.Item { 506 price := toBigInt(args[0]) 507 if price.Sign() <= 0 || !price.IsInt64() { 508 panic("invalid register price") 509 } 510 if !o.NEO.checkCommittee(ic) { 511 panic("invalid committee signature") 512 } 513 setIntWithKey(o.ID, ic.DAO, prefixRequestPrice, price.Int64()) 514 cache := ic.DAO.GetRWCache(o.ID).(*OracleCache) 515 cache.requestPrice = price.Int64() 516 return stackitem.Null{} 517 } 518 519 func (o *Oracle) getOriginalTxID(d *dao.Simple, tx *transaction.Transaction) util.Uint256 { 520 for i := range tx.Attributes { 521 if tx.Attributes[i].Type == transaction.OracleResponseT { 522 id := tx.Attributes[i].Value.(*transaction.OracleResponse).ID 523 req, _ := o.GetRequestInternal(d, id) 524 return req.OriginalTxID 525 } 526 } 527 return tx.Hash() 528 } 529 530 // GetRequests returns all requests which have not been finished yet. 531 func (o *Oracle) GetRequests(d *dao.Simple) (map[uint64]*state.OracleRequest, error) { 532 var reqs = make(map[uint64]*state.OracleRequest) 533 var err error 534 d.Seek(o.ID, storage.SeekRange{Prefix: prefixRequest}, func(k, v []byte) bool { 535 if len(k) != 8 { 536 err = errors.New("invalid request ID") 537 return false 538 } 539 req := new(state.OracleRequest) 540 err = stackitem.DeserializeConvertible(v, req) 541 if err != nil { 542 return false 543 } 544 id := binary.BigEndian.Uint64(k) 545 reqs[id] = req 546 return true 547 }) 548 if err != nil { 549 return nil, err 550 } 551 return reqs, nil 552 } 553 554 func makeRequestKey(id uint64) []byte { 555 k := make([]byte, 9) 556 k[0] = prefixRequest[0] 557 binary.BigEndian.PutUint64(k[1:], id) 558 return k 559 } 560 561 func makeIDListKey(url string) []byte { 562 return append(prefixIDList, hash.Hash160([]byte(url)).BytesBE()...) 563 } 564 565 func (o *Oracle) getConvertibleFromDAO(d *dao.Simple, key []byte, item stackitem.Convertible) error { 566 return getConvertibleFromDAO(o.ID, d, key, item) 567 } 568 569 // updateCache updates cached Oracle values if they've been changed. 570 func (o *Oracle) updateCache(d *dao.Simple) error { 571 orc, _ := o.Module.Load().(*OracleService) 572 if orc == nil || *orc == nil { 573 return nil 574 } 575 576 reqs := o.newRequests 577 o.newRequests = make(map[uint64]*state.OracleRequest) 578 for id := range reqs { 579 key := makeRequestKey(id) 580 if si := d.GetStorageItem(o.ID, key); si == nil { // tx has failed 581 delete(reqs, id) 582 } 583 } 584 (*orc).AddRequests(reqs) 585 return nil 586 } 587 588 // CreateOracleResponseScript returns a script that is used to create the native Oracle 589 // response. 590 func CreateOracleResponseScript(nativeOracleHash util.Uint160) []byte { 591 script, err := smartcontract.CreateCallScript(nativeOracleHash, "finish") 592 if err != nil { 593 panic(fmt.Errorf("failed to create Oracle response script: %w", err)) 594 } 595 return script 596 }