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 }