github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/services/oracle/request.go (about) 1 package oracle 2 3 import ( 4 "context" 5 "errors" 6 "mime" 7 "net/http" 8 "net/url" 9 "time" 10 11 "github.com/nspcc-dev/neo-go/pkg/core/state" 12 "github.com/nspcc-dev/neo-go/pkg/core/storage" 13 "github.com/nspcc-dev/neo-go/pkg/core/transaction" 14 "github.com/nspcc-dev/neo-go/pkg/crypto/keys" 15 "github.com/nspcc-dev/neo-go/pkg/services/oracle/neofs" 16 "go.uber.org/zap" 17 ) 18 19 const defaultMaxConcurrentRequests = 10 20 21 type request struct { 22 ID uint64 23 Req *state.OracleRequest 24 } 25 26 func (o *Oracle) runRequestWorker() { 27 for { 28 select { 29 case <-o.close: 30 return 31 case req := <-o.requestCh: 32 acc := o.getAccount() 33 if acc == nil { 34 continue 35 } 36 err := o.processRequest(acc.PrivateKey(), req) 37 if err != nil { 38 o.Log.Debug("can't process request", zap.Uint64("id", req.ID), zap.Error(err)) 39 } 40 } 41 } 42 } 43 44 // RemoveRequests removes all data associated with requests 45 // which have been processed by oracle contract. 46 func (o *Oracle) RemoveRequests(ids []uint64) { 47 o.respMtx.Lock() 48 defer o.respMtx.Unlock() 49 if !o.running { 50 for _, id := range ids { 51 delete(o.pending, id) 52 } 53 } else { 54 for _, id := range ids { 55 delete(o.responses, id) 56 } 57 } 58 } 59 60 // AddRequests saves all requests in-fly for further processing. 61 func (o *Oracle) AddRequests(reqs map[uint64]*state.OracleRequest) { 62 if len(reqs) == 0 { 63 return 64 } 65 66 o.respMtx.Lock() 67 if !o.running { 68 for id, r := range reqs { 69 o.pending[id] = r 70 } 71 o.respMtx.Unlock() 72 return 73 } 74 o.respMtx.Unlock() 75 76 select { 77 case o.requestMap <- reqs: 78 default: 79 select { 80 case old := <-o.requestMap: 81 for id, r := range old { 82 reqs[id] = r 83 } 84 default: 85 } 86 o.requestMap <- reqs 87 } 88 } 89 90 // ProcessRequestsInternal processes the provided requests synchronously. 91 func (o *Oracle) ProcessRequestsInternal(reqs map[uint64]*state.OracleRequest) { 92 acc := o.getAccount() 93 if acc == nil { 94 return 95 } 96 97 // Process actual requests. 98 for id, req := range reqs { 99 if err := o.processRequest(acc.PrivateKey(), request{ID: id, Req: req}); err != nil { 100 o.Log.Debug("can't process request", zap.Error(err)) 101 } 102 } 103 } 104 105 func (o *Oracle) processRequest(priv *keys.PrivateKey, req request) error { 106 if req.Req == nil { 107 o.processFailedRequest(priv, req) 108 return nil 109 } 110 111 incTx := o.getResponse(req.ID, true) 112 if incTx == nil { 113 return nil 114 } 115 resp := &transaction.OracleResponse{ID: req.ID, Code: transaction.Success} 116 u, err := url.ParseRequestURI(req.Req.URL) 117 if err != nil { 118 o.Log.Warn("malformed oracle request", zap.String("url", req.Req.URL), zap.Error(err)) 119 resp.Code = transaction.ProtocolNotSupported 120 } else { 121 switch u.Scheme { 122 case "https": 123 httpReq, err := http.NewRequest("GET", req.Req.URL, nil) 124 if err != nil { 125 o.Log.Warn("failed to create http request", zap.String("url", req.Req.URL), zap.Error(err)) 126 resp.Code = transaction.Error 127 break 128 } 129 httpReq.Header.Set("User-Agent", "NeoOracleService/3.0") 130 httpReq.Header.Set("Content-Type", "application/json") 131 r, err := o.Client.Do(httpReq) 132 if err != nil { 133 if errors.Is(err, ErrRestrictedRedirect) { 134 resp.Code = transaction.Forbidden 135 } else { 136 resp.Code = transaction.Error 137 } 138 o.Log.Warn("oracle request failed", zap.String("url", req.Req.URL), zap.Error(err), zap.Stringer("code", resp.Code)) 139 break 140 } 141 defer r.Body.Close() 142 switch r.StatusCode { 143 case http.StatusOK: 144 if !checkMediaType(r.Header.Get("Content-Type"), o.MainCfg.AllowedContentTypes) { 145 resp.Code = transaction.ContentTypeNotSupported 146 break 147 } 148 149 resp.Result, resp.Code = o.readResponse(r.Body, req.Req.URL) 150 case http.StatusForbidden: 151 resp.Code = transaction.Forbidden 152 case http.StatusNotFound: 153 resp.Code = transaction.NotFound 154 case http.StatusRequestTimeout: 155 resp.Code = transaction.Timeout 156 default: 157 resp.Code = transaction.Error 158 } 159 case neofs.URIScheme: 160 ctx, cancel := context.WithTimeout(context.Background(), o.MainCfg.NeoFS.Timeout) 161 defer cancel() 162 index := (int(req.ID) + incTx.attempts) % len(o.MainCfg.NeoFS.Nodes) 163 rc, err := neofs.Get(ctx, priv, u, o.MainCfg.NeoFS.Nodes[index]) 164 if err != nil { 165 resp.Code = transaction.Error 166 o.Log.Warn("failed to perform oracle request", zap.String("url", req.Req.URL), zap.Error(err)) 167 if rc != nil { 168 rc.Close() // intentionally skip the closing error, make it unified with Oracle `https` protocol. 169 } 170 break 171 } 172 resp.Result, resp.Code = o.readResponse(rc, req.Req.URL) 173 rc.Close() // intentionally skip the closing error, make it unified with Oracle `https` protocol. 174 default: 175 resp.Code = transaction.ProtocolNotSupported 176 o.Log.Warn("unknown oracle request scheme", zap.String("url", req.Req.URL)) 177 } 178 } 179 if resp.Code == transaction.Success { 180 resp.Result, err = filterRequest(resp.Result, req.Req) 181 if err != nil { 182 o.Log.Warn("oracle filter failed", zap.Uint64("request", req.ID), zap.Error(err)) 183 resp.Code = transaction.Error 184 } 185 } 186 o.Log.Debug("oracle request processed", zap.String("url", req.Req.URL), zap.Int("code", int(resp.Code)), zap.String("result", string(resp.Result))) 187 188 currentHeight := o.Chain.BlockHeight() 189 vubInc := o.Chain.GetConfig().MaxValidUntilBlockIncrement 190 _, h, err := o.Chain.GetTransaction(req.Req.OriginalTxID) 191 if err != nil { 192 if !errors.Is(err, storage.ErrKeyNotFound) { 193 return err 194 } 195 // The only reason tx can be not found is that it hasn't been persisted from DAO yet. 196 h = currentHeight 197 } 198 h += vubInc // Main tx is only valid for RequestHeight + ValidUntilBlock. 199 tx, err := o.CreateResponseTx(int64(req.Req.GasForResponse), h, resp) 200 if err != nil { 201 return err 202 } 203 for h <= currentHeight { // Backup tx must be valid in any event. 204 h += vubInc 205 } 206 backupTx, err := o.CreateResponseTx(int64(req.Req.GasForResponse), h, &transaction.OracleResponse{ 207 ID: req.ID, 208 Code: transaction.ConsensusUnreachable, 209 }) 210 if err != nil { 211 return err 212 } 213 214 incTx.Lock() 215 incTx.request = req.Req 216 incTx.tx = tx 217 incTx.backupTx = backupTx 218 incTx.reverifyTx(o.Network) 219 220 txSig := priv.SignHashable(uint32(o.Network), tx) 221 incTx.addResponse(priv.PublicKey(), txSig, false) 222 223 backupSig := priv.SignHashable(uint32(o.Network), backupTx) 224 incTx.addResponse(priv.PublicKey(), backupSig, true) 225 226 readyTx, ready := incTx.finalize(o.getOracleNodes(), false) 227 if ready { 228 ready = !incTx.isSent 229 incTx.isSent = true 230 } 231 incTx.time = time.Now() 232 incTx.attempts++ 233 incTx.Unlock() 234 235 o.ResponseHandler.SendResponse(priv, resp, txSig) 236 if ready { 237 o.sendTx(readyTx) 238 } 239 return nil 240 } 241 242 func (o *Oracle) processFailedRequest(priv *keys.PrivateKey, req request) { 243 // Request is being processed again. 244 incTx := o.getResponse(req.ID, false) 245 if incTx == nil { 246 // Request was processed by other oracle nodes. 247 return 248 } else if incTx.isSent { 249 // Tx was sent but not yet persisted. Try to pool it again. 250 o.sendTx(incTx.tx) 251 return 252 } 253 254 // Don't process request again, fallback to backup tx. 255 incTx.Lock() 256 readyTx, ready := incTx.finalize(o.getOracleNodes(), true) 257 if ready { 258 ready = !incTx.isSent 259 incTx.isSent = true 260 } 261 incTx.time = time.Now() 262 incTx.attempts++ 263 txSig := incTx.backupSigs[string(priv.PublicKey().Bytes())].sig 264 incTx.Unlock() 265 266 o.ResponseHandler.SendResponse(priv, getFailedResponse(req.ID), txSig) 267 if ready { 268 o.sendTx(readyTx) 269 } 270 } 271 272 func checkMediaType(hdr string, allowed []string) bool { 273 if len(allowed) == 0 { 274 return true 275 } 276 277 typ, _, err := mime.ParseMediaType(hdr) 278 if err != nil { 279 return false 280 } 281 282 for _, ct := range allowed { 283 if ct == typ { 284 return true 285 } 286 } 287 return false 288 }