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  }