github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/services/oracle/oracle.go (about) 1 package oracle 2 3 import ( 4 "bytes" 5 "errors" 6 "net/http" 7 "sync" 8 "time" 9 10 "github.com/nspcc-dev/neo-go/pkg/config" 11 "github.com/nspcc-dev/neo-go/pkg/config/netmode" 12 "github.com/nspcc-dev/neo-go/pkg/core/block" 13 "github.com/nspcc-dev/neo-go/pkg/core/interop" 14 "github.com/nspcc-dev/neo-go/pkg/core/state" 15 "github.com/nspcc-dev/neo-go/pkg/core/transaction" 16 "github.com/nspcc-dev/neo-go/pkg/crypto/keys" 17 "github.com/nspcc-dev/neo-go/pkg/services/oracle/broadcaster" 18 "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" 19 "github.com/nspcc-dev/neo-go/pkg/util" 20 "github.com/nspcc-dev/neo-go/pkg/wallet" 21 "go.uber.org/zap" 22 ) 23 24 type ( 25 // Ledger is an interface to Blockchain sufficient for Oracle. 26 Ledger interface { 27 BlockHeight() uint32 28 FeePerByte() int64 29 GetBaseExecFee() int64 30 GetConfig() config.Blockchain 31 GetMaxVerificationGAS() int64 32 GetTestVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) (*interop.Context, error) 33 GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error) 34 } 35 36 // Oracle represents an oracle module capable of talking 37 // with the external world. 38 Oracle struct { 39 Config 40 41 // Native Oracle contract related information that may be updated on Oracle contract 42 // update. 43 oracleInfoLock sync.RWMutex 44 oracleResponse []byte 45 oracleScript []byte 46 verifyOffset int 47 48 // accMtx protects account and oracle nodes. 49 accMtx sync.RWMutex 50 currAccount *wallet.Account 51 oracleNodes keys.PublicKeys 52 oracleSignContract []byte 53 54 close chan struct{} 55 done chan struct{} 56 requestCh chan request 57 requestMap chan map[uint64]*state.OracleRequest 58 59 // respMtx protects responses and pending maps. 60 respMtx sync.RWMutex 61 // running is false until Run() is invoked. 62 running bool 63 // pending contains requests for not yet started service. 64 pending map[uint64]*state.OracleRequest 65 // responses contains active not completely processed requests. 66 responses map[uint64]*incompleteTx 67 // removed contains ids of requests which won't be processed further due to expiration. 68 removed map[uint64]bool 69 70 wallet *wallet.Wallet 71 } 72 73 // Config contains oracle module parameters. 74 Config struct { 75 Log *zap.Logger 76 Network netmode.Magic 77 MainCfg config.OracleConfiguration 78 Client HTTPClient 79 Chain Ledger 80 ResponseHandler Broadcaster 81 OnTransaction TxCallback 82 } 83 84 // HTTPClient is an interface capable of doing oracle requests. 85 HTTPClient interface { 86 Do(*http.Request) (*http.Response, error) 87 } 88 89 // Broadcaster broadcasts oracle responses. 90 Broadcaster interface { 91 SendResponse(priv *keys.PrivateKey, resp *transaction.OracleResponse, txSig []byte) 92 Run() 93 Shutdown() 94 } 95 96 // TxCallback executes on new transactions when they are ready to be pooled. 97 TxCallback = func(tx *transaction.Transaction) error 98 ) 99 100 const ( 101 // defaultRequestTimeout is the default request timeout. 102 defaultRequestTimeout = time.Second * 5 103 104 // defaultMaxTaskTimeout is the default timeout for the request to be dropped if it can't be processed. 105 defaultMaxTaskTimeout = time.Hour 106 107 // defaultRefreshInterval is the default timeout for the failed request to be reprocessed. 108 defaultRefreshInterval = time.Minute * 3 109 110 // maxRedirections is the number of allowed redirections for Oracle HTTPS request. 111 maxRedirections = 2 112 ) 113 114 // ErrRestrictedRedirect is returned when redirection to forbidden address occurs 115 // during Oracle response creation. 116 var ErrRestrictedRedirect = errors.New("oracle request redirection error") 117 118 // NewOracle returns new oracle instance. 119 func NewOracle(cfg Config) (*Oracle, error) { 120 o := &Oracle{ 121 Config: cfg, 122 123 close: make(chan struct{}), 124 done: make(chan struct{}), 125 requestMap: make(chan map[uint64]*state.OracleRequest, 1), 126 pending: make(map[uint64]*state.OracleRequest), 127 responses: make(map[uint64]*incompleteTx), 128 removed: make(map[uint64]bool), 129 } 130 if o.MainCfg.RequestTimeout == 0 { 131 o.MainCfg.RequestTimeout = defaultRequestTimeout 132 } 133 if o.MainCfg.NeoFS.Timeout == 0 { 134 o.MainCfg.NeoFS.Timeout = defaultRequestTimeout 135 } 136 if o.MainCfg.MaxConcurrentRequests == 0 { 137 o.MainCfg.MaxConcurrentRequests = defaultMaxConcurrentRequests 138 } 139 o.requestCh = make(chan request, o.MainCfg.MaxConcurrentRequests) 140 if o.MainCfg.MaxTaskTimeout == 0 { 141 o.MainCfg.MaxTaskTimeout = defaultMaxTaskTimeout 142 } 143 if o.MainCfg.RefreshInterval == 0 { 144 o.MainCfg.RefreshInterval = defaultRefreshInterval 145 } 146 147 var err error 148 w := cfg.MainCfg.UnlockWallet 149 if o.wallet, err = wallet.NewWalletFromFile(w.Path); err != nil { 150 return nil, err 151 } 152 153 haveAccount := false 154 for _, acc := range o.wallet.Accounts { 155 if err := acc.Decrypt(w.Password, o.wallet.Scrypt); err == nil { 156 haveAccount = true 157 break 158 } 159 } 160 if !haveAccount { 161 return nil, errors.New("no wallet account could be unlocked") 162 } 163 164 if o.ResponseHandler == nil { 165 o.ResponseHandler = broadcaster.New(cfg.MainCfg, cfg.Log) 166 } 167 if o.OnTransaction == nil { 168 o.OnTransaction = func(*transaction.Transaction) error { return nil } 169 } 170 if o.Client == nil { 171 o.Client = getDefaultClient(o.MainCfg) 172 } 173 return o, nil 174 } 175 176 // Name returns service name. 177 func (o *Oracle) Name() string { 178 return "oracle" 179 } 180 181 // Shutdown shutdowns Oracle. It can only be called once, subsequent calls 182 // to Shutdown on the same instance are no-op. The instance that was stopped can 183 // not be started again by calling Start (use a new instance if needed). 184 func (o *Oracle) Shutdown() { 185 o.respMtx.Lock() 186 defer o.respMtx.Unlock() 187 if !o.running { 188 return 189 } 190 o.Log.Info("stopping oracle service") 191 o.running = false 192 close(o.close) 193 o.ResponseHandler.Shutdown() 194 <-o.done 195 o.wallet.Close() 196 _ = o.Log.Sync() 197 } 198 199 // Start runs the oracle service in a separate goroutine. 200 // The Oracle only starts once, subsequent calls to Start are no-op. 201 func (o *Oracle) Start() { 202 o.respMtx.Lock() 203 if o.running { 204 o.respMtx.Unlock() 205 return 206 } 207 o.Log.Info("starting oracle service") 208 go o.start() 209 } 210 211 // IsAuthorized returns whether Oracle service currently is authorized to collect 212 // signatures. It returns true iff designated Oracle node's account provided to 213 // the Oracle service in decrypted state. 214 func (o *Oracle) IsAuthorized() bool { 215 return o.getAccount() != nil 216 } 217 218 func (o *Oracle) start() { 219 o.requestMap <- o.pending // Guaranteed to not block, only AddRequests sends to it. 220 o.pending = nil 221 o.running = true 222 o.respMtx.Unlock() 223 224 for i := 0; i < o.MainCfg.MaxConcurrentRequests; i++ { 225 go o.runRequestWorker() 226 } 227 go o.ResponseHandler.Run() 228 229 tick := time.NewTicker(o.MainCfg.RefreshInterval) 230 main: 231 for { 232 select { 233 case <-o.close: 234 break main 235 case <-tick.C: 236 var reprocess []uint64 237 o.respMtx.Lock() 238 o.removed = make(map[uint64]bool) 239 for id, incTx := range o.responses { 240 incTx.RLock() 241 since := time.Since(incTx.time) 242 if since > o.MainCfg.MaxTaskTimeout { 243 o.removed[id] = true 244 } else if since > o.MainCfg.RefreshInterval { 245 reprocess = append(reprocess, id) 246 } 247 incTx.RUnlock() 248 } 249 for id := range o.removed { 250 delete(o.responses, id) 251 } 252 o.respMtx.Unlock() 253 254 for _, id := range reprocess { 255 o.requestCh <- request{ID: id} 256 } 257 case reqs := <-o.requestMap: 258 for id, req := range reqs { 259 o.requestCh <- request{ 260 ID: id, 261 Req: req, 262 } 263 } 264 } 265 } 266 tick.Stop() 267 drain: 268 for { 269 select { 270 case <-o.requestMap: 271 default: 272 break drain 273 } 274 } 275 close(o.requestMap) 276 close(o.done) 277 } 278 279 // UpdateNativeContract updates native oracle contract info for tx verification. 280 func (o *Oracle) UpdateNativeContract(script, resp []byte, h util.Uint160, verifyOffset int) { 281 o.oracleInfoLock.Lock() 282 defer o.oracleInfoLock.Unlock() 283 284 o.oracleScript = bytes.Clone(script) 285 o.oracleResponse = bytes.Clone(resp) 286 o.verifyOffset = verifyOffset 287 } 288 289 func (o *Oracle) sendTx(tx *transaction.Transaction) { 290 if err := o.OnTransaction(tx); err != nil { 291 o.Log.Error("can't pool oracle tx", 292 zap.String("hash", tx.Hash().StringLE()), 293 zap.Error(err)) 294 } 295 }