Skip to content

Commit

Permalink
- allowed gas station to work with float values for better accuracy w…
Browse files Browse the repository at this point in the history
…hen deciding the gas price value
  • Loading branch information
iulianpascalau committed Feb 5, 2025
1 parent 59e4ee6 commit 8d74d8c
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 52 deletions.
2 changes: 1 addition & 1 deletion clients/gasManagement/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package gasManagement
import "github.com/multiversx/mx-bridge-eth-go/core"

// GetLatestGasPrice -
func (gs *gasStation) GetLatestGasPrice() int {
func (gs *gasStation) GetLatestGasPrice() float64 {
gs.mut.RLock()
defer gs.mut.RUnlock()

Expand Down
32 changes: 17 additions & 15 deletions clients/gasManagement/gasStation.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,15 @@ type gasStation struct {
maximumFetchRetries int
log logger.Logger
httpClient HTTPClient
maximumGasPrice int
maximumGasPrice float64
cancel func()
gasPriceSelector core.EthGasPriceSelector
loopStatus *atomic.Flag
gasPriceMultiplier *big.Int
minGasPriceValue *big.Int
gasPriceMultiplier float64
minGasPriceValue float64

mut sync.RWMutex
latestGasPrice int
latestGasPrice float64
fetchRetries int
}

Expand All @@ -69,11 +69,11 @@ func NewGasStation(args ArgsGasStation) (*gasStation, error) {
requestRetryDelay: args.RequestRetryDelay,
maximumFetchRetries: args.MaximumFetchRetries,
httpClient: http.DefaultClient,
maximumGasPrice: args.MaximumGasPrice,
maximumGasPrice: float64(args.MaximumGasPrice),
gasPriceSelector: args.GasPriceSelector,
loopStatus: &atomic.Flag{},
gasPriceMultiplier: big.NewInt(int64(args.GasPriceMultiplier)),
minGasPriceValue: big.NewInt(minGasPriceValue),
gasPriceMultiplier: float64(args.GasPriceMultiplier),
minGasPriceValue: float64(minGasPriceValue),
latestGasPrice: -1,
fetchRetries: 0,
}
Expand Down Expand Up @@ -169,11 +169,11 @@ func (gs *gasStation) doRequest(ctx context.Context) error {
gs.latestGasPrice = -1
switch gs.gasPriceSelector {
case core.EthFastGasPrice:
_, err = fmt.Sscanf(response.Result.FastGasPrice, "%d", &gs.latestGasPrice)
_, err = fmt.Sscanf(response.Result.FastGasPrice, "%f", &gs.latestGasPrice)
case core.EthProposeGasPrice:
_, err = fmt.Sscanf(response.Result.ProposeGasPrice, "%d", &gs.latestGasPrice)
_, err = fmt.Sscanf(response.Result.ProposeGasPrice, "%f", &gs.latestGasPrice)
case core.EthSafeGasPrice:
_, err = fmt.Sscanf(response.Result.SafeGasPrice, "%d", &gs.latestGasPrice)
_, err = fmt.Sscanf(response.Result.SafeGasPrice, "%f", &gs.latestGasPrice)
default:
err = fmt.Errorf("%w: %q", ErrInvalidGasPriceSelector, gs.gasPriceSelector)
}
Expand Down Expand Up @@ -219,15 +219,17 @@ func (gs *gasStation) GetCurrentGasPrice() (*big.Int, error) {
}

if gs.latestGasPrice > gs.maximumGasPrice {
return big.NewInt(0), fmt.Errorf("%w maximum value: %d, fetched value: %d, gas price selector: %s",
return big.NewInt(0), fmt.Errorf("%w maximum value: %0.3f, fetched value: %0.3f, gas price selector: %s",
ErrGasPriceIsHigherThanTheMaximumSet, gs.maximumGasPrice, gs.latestGasPrice, gs.gasPriceSelector)
}

result := big.NewInt(int64(gs.latestGasPrice))
if result.Cmp(gs.minGasPriceValue) < 0 {
result.Set(gs.minGasPriceValue)
result := gs.latestGasPrice
if result < gs.minGasPriceValue {
result = gs.minGasPriceValue
}
return result.Mul(result, gs.gasPriceMultiplier), nil

result = result * gs.gasPriceMultiplier
return big.NewInt(0).SetInt64(int64(result)), nil
}

// Close will stop any started go routines
Expand Down
120 changes: 84 additions & 36 deletions clients/gasManagement/gasStation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ func TestGasStation_InvalidJsonResponse(t *testing.T) {

time.Sleep(time.Millisecond * 500)
assert.False(t, gs.loopStatus.IsSet())
assert.Equal(t, gs.GetLatestGasPrice(), -1)
assert.Equal(t, float64(-1), gs.GetLatestGasPrice())
gasPrice, err := gs.GetCurrentGasPrice()
assert.Equal(t, big.NewInt(0), gasPrice)
assert.Equal(t, ErrLatestGasPricesWereNotFetched, err)
Expand Down Expand Up @@ -189,10 +189,10 @@ func TestGasStation_GoodResponseShouldSave(t *testing.T) {

time.Sleep(time.Millisecond * 500)
assert.False(t, gs.loopStatus.IsSet())
var expectedPrice = -1
_, err = fmt.Sscanf(gsResponse.Result.SafeGasPrice, "%d", &expectedPrice)
var expectedPrice = float64(-1)
_, err = fmt.Sscanf(gsResponse.Result.SafeGasPrice, "%f", &expectedPrice)
require.Nil(t, err)
assert.Equal(t, gs.GetLatestGasPrice(), expectedPrice)
assert.Equal(t, expectedPrice, gs.GetLatestGasPrice())
}

func TestGasStation_RetryMechanism_FailsFirstRequests(t *testing.T) {
Expand Down Expand Up @@ -228,33 +228,33 @@ func TestGasStation_RetryMechanism_FailsFirstRequests(t *testing.T) {
chanNok <- struct{}{}
time.Sleep(time.Millisecond * 100)
assert.True(t, gs.loopStatus.IsSet())
assert.Equal(t, -1, gs.GetLatestGasPrice())
assert.Equal(t, float64(-1), gs.GetLatestGasPrice())
gasPrice, err := gs.GetCurrentGasPrice()
assert.Equal(t, big.NewInt(0), gasPrice)
assert.Equal(t, ErrLatestGasPricesWereNotFetched, err)

chanNok <- struct{}{}
time.Sleep(time.Millisecond * 100)
assert.True(t, gs.loopStatus.IsSet())
assert.Equal(t, -1, gs.GetLatestGasPrice())
assert.Equal(t, float64(-1), gs.GetLatestGasPrice())
gasPrice, err = gs.GetCurrentGasPrice()
assert.Equal(t, big.NewInt(0), gasPrice)
assert.Equal(t, ErrLatestGasPricesWereNotFetched, err)

chanNok <- struct{}{}
time.Sleep(time.Millisecond * 100)
assert.True(t, gs.loopStatus.IsSet())
assert.Equal(t, -1, gs.GetLatestGasPrice())
assert.Equal(t, float64(-1), gs.GetLatestGasPrice())
gasPrice, err = gs.GetCurrentGasPrice()
assert.Equal(t, big.NewInt(0), gasPrice)
assert.Equal(t, ErrLatestGasPricesWereNotFetched, err)

chanOk <- struct{}{} // response is now ok
time.Sleep(time.Millisecond * 100)
assert.True(t, gs.loopStatus.IsSet())
assert.Equal(t, 81, gs.GetLatestGasPrice())
assert.Equal(t, float64(81), gs.GetLatestGasPrice())
gasPrice, err = gs.GetCurrentGasPrice()
assert.Equal(t, big.NewInt(int64(gs.GetLatestGasPrice()*args.GasPriceMultiplier)), gasPrice)
assert.Equal(t, big.NewInt(int64(gs.GetLatestGasPrice()*float64(args.GasPriceMultiplier))), gasPrice)
assert.Nil(t, err)
_ = gs.Close()

Expand Down Expand Up @@ -300,9 +300,9 @@ func TestGasStation_RetryMechanism_IntermittentFails(t *testing.T) {
time.Sleep(time.Millisecond * 100)

assert.True(t, gs.loopStatus.IsSet())
assert.Equal(t, 81, gs.GetLatestGasPrice())
assert.Equal(t, float64(81), gs.GetLatestGasPrice())
gasPrice, errGet := gs.GetCurrentGasPrice()
assert.Equal(t, big.NewInt(int64(gs.GetLatestGasPrice()*args.GasPriceMultiplier)), gasPrice)
assert.Equal(t, big.NewInt(int64(gs.GetLatestGasPrice()*float64(args.GasPriceMultiplier))), gasPrice)
assert.Nil(t, errGet)
}

Expand All @@ -312,12 +312,50 @@ func TestGasStation_RetryMechanism_IntermittentFails(t *testing.T) {
assert.False(t, gs.loopStatus.IsSet())
}

func TestGasStation_GetCurrentGasPrice(t *testing.T) {
func TestGasStation_GetCurrentGasPriceShouldWork(t *testing.T) {
t.Parallel()

gsResponse := createMockGasStationResponse()
args := createMockArgsGasStation()
gasPriceMultiplier := big.NewInt(int64(args.GasPriceMultiplier))
t.Run("should work with int values", func(t *testing.T) {
gsResponse := createMockGasStationResponse()
args := createMockArgsGasStation()

expectedFast := big.NewInt(83000000000)
expectedPropose := big.NewInt(82000000000)
expectedSafe := big.NewInt(81000000000)

testGetCurrentGasPrice(t, gsResponse, args, expectedFast, expectedPropose, expectedSafe)
})
t.Run("should work with float64 values and no trim", func(t *testing.T) {
gsResponse := createMockGasStationResponseWithFloatValues()
args := createMockArgsGasStation()

expectedFast := big.NewInt(1460784306)
expectedPropose := big.NewInt(1327985733)
expectedSafe := big.NewInt(1289774824)

testGetCurrentGasPrice(t, gsResponse, args, expectedFast, expectedPropose, expectedSafe)
})
t.Run("should work with float64 values with trim", func(t *testing.T) {
gsResponse := createMockGasStationResponseWithFloatValues()
args := createMockArgsGasStation()
args.GasPriceMultiplier = 10000

expectedFast := big.NewInt(14607)
expectedPropose := big.NewInt(13279)
expectedSafe := big.NewInt(12897)

testGetCurrentGasPrice(t, gsResponse, args, expectedFast, expectedPropose, expectedSafe)
})
}

func testGetCurrentGasPrice(
t *testing.T,
gsResponse gasStationResponse,
args ArgsGasStation,
expectedFast *big.Int,
expectedPropose *big.Int,
expectedSafe *big.Int,
) {
// synchronize the process loop & the testing go routine with an unbuffered channel
chanOk := make(chan struct{})
httpServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
Expand All @@ -343,36 +381,21 @@ func TestGasStation_GetCurrentGasPrice(t *testing.T) {
time.Sleep(time.Millisecond * 100)
price, err := gs.GetCurrentGasPrice()
require.Nil(t, err)
expectedPrice := -1
_, err = fmt.Sscanf(gsResponse.Result.FastGasPrice, "%d", &expectedPrice)
require.Nil(t, err)
assert.NotEqual(t, expectedPrice, -1)
expected := big.NewInt(0).Mul(big.NewInt(int64(expectedPrice)), gasPriceMultiplier)
assert.Equal(t, expected, price)
assert.Equal(t, expectedFast, price)

gs.SetSelector(core.EthProposeGasPrice)
chanOk <- struct{}{}
time.Sleep(time.Millisecond * 100)
price, err = gs.GetCurrentGasPrice()
require.Nil(t, err)
expectedPrice = -1
_, err = fmt.Sscanf(gsResponse.Result.ProposeGasPrice, "%d", &expectedPrice)
require.Nil(t, err)
assert.NotEqual(t, expectedPrice, -1)
expected = big.NewInt(0).Mul(big.NewInt(int64(expectedPrice)), gasPriceMultiplier)
assert.Equal(t, expected, price)
assert.Equal(t, expectedPropose, price)

gs.SetSelector(core.EthSafeGasPrice)
chanOk <- struct{}{}
time.Sleep(time.Millisecond * 100)
price, err = gs.GetCurrentGasPrice()
require.Nil(t, err)
expectedPrice = -1
_, err = fmt.Sscanf(gsResponse.Result.SafeGasPrice, "%d", &expectedPrice)
require.Nil(t, err)
assert.NotEqual(t, expectedPrice, -1)
expected = big.NewInt(0).Mul(big.NewInt(int64(expectedPrice)), gasPriceMultiplier)
assert.Equal(t, expected, price)
assert.Equal(t, expectedSafe, price)

gs.SetSelector("invalid")
chanOk <- struct{}{}
Expand Down Expand Up @@ -410,7 +433,10 @@ func TestGasStation_GetCurrentGasPriceExceededMaximum(t *testing.T) {
assert.True(t, gs.loopStatus.IsSet())

price, err := gs.GetCurrentGasPrice()
require.True(t, errors.Is(err, ErrGasPriceIsHigherThanTheMaximumSet))
require.ErrorIs(t, err, ErrGasPriceIsHigherThanTheMaximumSet)
require.Contains(t, err.Error(), "set maximum value: 100.000")
require.Contains(t, err.Error(), "fetched value: 101.000")
require.Contains(t, err.Error(), "gas price selector: SafeGasPrice")
assert.Equal(t, big.NewInt(0), price)
_ = gs.Close()
}
Expand All @@ -433,14 +459,14 @@ func TestGasStation_GetCurrentGasPriceBelowMin(t *testing.T) {

gs, err := NewGasStation(args)
require.Nil(t, err)
expectedPrice := big.NewInt(0).Mul(gs.minGasPriceValue, gs.gasPriceMultiplier)
expectedPrice := gs.minGasPriceValue * gs.gasPriceMultiplier

time.Sleep(time.Second * 2)
assert.True(t, gs.loopStatus.IsSet())

price, err := gs.GetCurrentGasPrice()
require.Nil(t, err)
assert.Equal(t, expectedPrice, price)
assert.Equal(t, big.NewInt(0).SetInt64(int64(expectedPrice)), price)
_ = gs.Close()
}

Expand All @@ -465,3 +491,25 @@ func createMockGasStationResponse() gasStationResponse {
},
}
}

func createMockGasStationResponseWithFloatValues() gasStationResponse {
return gasStationResponse{
Status: "1",
Message: "OK-Missing/Invalid API Key, rate limit of 1/5sec applied",
Result: struct {
LastBlock string `json:"LastBlock"`
SafeGasPrice string `json:"SafeGasPrice"`
ProposeGasPrice string `json:"ProposeGasPrice"`
FastGasPrice string `json:"FastGasPrice"`
SuggestBaseFee string `json:"suggestBaseFee"`
GasUsedRatio string `json:"gasUsedRatio"`
}{
LastBlock: "21779373",
SafeGasPrice: "1.289774824",
ProposeGasPrice: "1.327985733",
FastGasPrice: "1.460784306",
SuggestBaseFee: "1.289774824",
GasUsedRatio: "0.339906295527105,0.348258876646649,0.41887262757803,0.316123823179043,0.659216773979187",
},
}
}

0 comments on commit 8d74d8c

Please sign in to comment.