github.com/ethersphere/bee/v2@v2.2.0/pkg/settlement/swap/priceoracle/priceoracle.go (about)

     1  // Copyright 2021 The Swarm Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package priceoracle
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"io"
    11  	"math/big"
    12  	"time"
    13  
    14  	"github.com/ethereum/go-ethereum/accounts/abi"
    15  	"github.com/ethereum/go-ethereum/common"
    16  	"github.com/ethersphere/bee/v2/pkg/log"
    17  	"github.com/ethersphere/bee/v2/pkg/transaction"
    18  	"github.com/ethersphere/bee/v2/pkg/util/abiutil"
    19  	"github.com/ethersphere/go-price-oracle-abi/priceoracleabi"
    20  )
    21  
    22  // loggerName is the tree path name of the logger for this package.
    23  const loggerName = "priceoracle"
    24  
    25  var (
    26  	errDecodeABI = errors.New("could not decode abi data")
    27  )
    28  
    29  type service struct {
    30  	logger             log.Logger
    31  	priceOracleAddress common.Address
    32  	transactionService transaction.Service
    33  	exchangeRate       *big.Int
    34  	deduction          *big.Int
    35  	timeDivisor        int64
    36  	quitC              chan struct{}
    37  }
    38  
    39  type Service interface {
    40  	io.Closer
    41  	// CurrentRates returns the current value of exchange rate and deduction
    42  	// according to the latest information from oracle
    43  	CurrentRates() (exchangeRate *big.Int, deduction *big.Int, err error)
    44  	// GetPrice retrieves latest available information from oracle
    45  	GetPrice(ctx context.Context) (*big.Int, *big.Int, error)
    46  	Start()
    47  }
    48  
    49  var (
    50  	priceOracleABI = abiutil.MustParseABI(priceoracleabi.PriceOracleABIv0_2_0)
    51  )
    52  
    53  func New(logger log.Logger, priceOracleAddress common.Address, transactionService transaction.Service, timeDivisor int64) Service {
    54  	return &service{
    55  		logger:             logger.WithName(loggerName).Register(),
    56  		priceOracleAddress: priceOracleAddress,
    57  		transactionService: transactionService,
    58  		exchangeRate:       big.NewInt(0),
    59  		deduction:          nil,
    60  		quitC:              make(chan struct{}),
    61  		timeDivisor:        timeDivisor,
    62  	}
    63  }
    64  
    65  func (s *service) Start() {
    66  	loggerV1 := s.logger.V(1).Register()
    67  
    68  	ctx, cancel := context.WithCancel(context.Background())
    69  	go func() {
    70  		defer cancel()
    71  		<-s.quitC
    72  	}()
    73  
    74  	go func() {
    75  		defer cancel()
    76  		for {
    77  			exchangeRate, deduction, err := s.GetPrice(ctx)
    78  			if err != nil {
    79  				s.logger.Error(err, "could not get price")
    80  			} else {
    81  				loggerV1.Debug("updated exchange rate and deduction", "new_exchange_rate", exchangeRate, "new_deduction", deduction)
    82  				s.exchangeRate = exchangeRate
    83  				s.deduction = deduction
    84  			}
    85  
    86  			ts := time.Now().Unix()
    87  
    88  			// We poll the oracle in every timestamp divisible by constant 300 (timeDivisor)
    89  			// in order to get latest version approximately at the same time on all nodes
    90  			// and to minimize polling frequency
    91  			// If the node gets newer information than what was applicable at last polling point at startup
    92  			// this minimizes the negative scenario to less than 5 minutes
    93  			// during which cheques can not be sent / accepted because of the asymmetric information
    94  			timeUntilNextPoll := time.Duration(s.timeDivisor-ts%s.timeDivisor) * time.Second
    95  
    96  			select {
    97  			case <-s.quitC:
    98  				return
    99  			case <-time.After(timeUntilNextPoll):
   100  			}
   101  		}
   102  	}()
   103  }
   104  
   105  func (s *service) GetPrice(ctx context.Context) (*big.Int, *big.Int, error) {
   106  	callData, err := priceOracleABI.Pack("getPrice")
   107  	if err != nil {
   108  		return nil, nil, err
   109  	}
   110  	result, err := s.transactionService.Call(ctx, &transaction.TxRequest{
   111  		To:   &s.priceOracleAddress,
   112  		Data: callData,
   113  	})
   114  	if err != nil {
   115  		return nil, nil, err
   116  	}
   117  
   118  	results, err := priceOracleABI.Unpack("getPrice", result)
   119  	if err != nil {
   120  		return nil, nil, err
   121  	}
   122  
   123  	if len(results) != 2 {
   124  		return nil, nil, errDecodeABI
   125  	}
   126  
   127  	exchangeRate, ok := abi.ConvertType(results[0], new(big.Int)).(*big.Int)
   128  	if !ok || exchangeRate == nil {
   129  		return nil, nil, errDecodeABI
   130  	}
   131  
   132  	deduction, ok := abi.ConvertType(results[1], new(big.Int)).(*big.Int)
   133  	if !ok || deduction == nil {
   134  		return nil, nil, errDecodeABI
   135  	}
   136  
   137  	return exchangeRate, deduction, nil
   138  }
   139  
   140  func (s *service) CurrentRates() (exchangeRate, deduction *big.Int, err error) {
   141  	if s.exchangeRate.Cmp(big.NewInt(0)) == 0 {
   142  		return nil, nil, errors.New("exchange rate not yet available")
   143  	}
   144  	if s.deduction == nil {
   145  		return nil, nil, errors.New("deduction amount not yet available")
   146  	}
   147  	return s.exchangeRate, s.deduction, nil
   148  }
   149  
   150  func (s *service) Close() error {
   151  	close(s.quitC)
   152  	return nil
   153  }