From c62071ace58585a68d1c6e9650af24969154e7f4 Mon Sep 17 00:00:00 2001 From: owen Date: Fri, 23 Jun 2023 15:08:25 -0400 Subject: [PATCH 01/17] impl feeHistory into gasprice package, add eth endpoint --- gasprice/feehistory.go | 106 ++++++++++++++++++++++++++++++++++++++++ gasprice/gasprice.go | 2 + jsonrpc/eth_endpoint.go | 28 +++++++++++ jsonrpc/types.go | 7 +++ 4 files changed, 143 insertions(+) create mode 100644 gasprice/feehistory.go diff --git a/gasprice/feehistory.go b/gasprice/feehistory.go new file mode 100644 index 0000000000..f8b773f552 --- /dev/null +++ b/gasprice/feehistory.go @@ -0,0 +1,106 @@ +package gasprice + +import ( + "errors" + "math/big" + "sort" +) + +var ( + ErrInvalidPercentile = errors.New("invalid percentile") + ErrRequestedRange = errors.New("requested range is too large") + ErrBlockCount = errors.New("blockCount must be greater than 0") + ErrBlockInfo = errors.New("could not find block info") +) + +const ( + maxBlockRequest = 1024 +) + +type txGasAndReward struct { + gasUsed *big.Int + reward *big.Int +} + +func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPercentiles []float64) (*uint64, *[]uint64, *[]float64, *[][]uint64, error) { + if blockCount < 1 { + return nil, nil, nil, nil, ErrBlockCount + } + if newestBlock > g.backend.Header().Number { + newestBlock = g.backend.Header().Number + } + if blockCount > maxBlockRequest { + blockCount = maxBlockRequest + } + if blockCount > newestBlock { + blockCount = newestBlock + } + for i, p := range rewardPercentiles { + if p < 0 || p > 100 { + return nil, nil, nil, nil, ErrInvalidPercentile + } + if i > 0 && p < rewardPercentiles[i-1] { + return nil, nil, nil, nil, ErrInvalidPercentile + } + } + + var ( + oldestBlock = newestBlock - blockCount + 1 + baseFeePerGas = make([]uint64, blockCount+1) + gasUsedRatio = make([]float64, blockCount) + reward = make([][]uint64, blockCount) + ) + if oldestBlock < 1 { + oldestBlock = 1 + } + + for i := oldestBlock; i < oldestBlock+blockCount; i++ { + block, ok := g.backend.GetBlockByNumber(i, false) + if !ok { + return nil, nil, nil, nil, ErrBlockInfo + } + baseFeePerGas[i-oldestBlock] = block.Header.BaseFee + gasUsedRatio[i-oldestBlock] = float64(block.Header.GasUsed) / float64(block.Header.GasLimit) + + if len(rewardPercentiles) == 0 { + //reward percentiles not requested, skip rest of this loop + continue + } + reward[i-oldestBlock] = make([]uint64, len(rewardPercentiles)) + if len(block.Transactions) == 0 { + for j := range reward[i-oldestBlock] { + reward[i-oldestBlock][j] = 0 + } + //no transactions in block, set rewards to 0 and move to next block + continue + } + + sorter := make([]*txGasAndReward, len(block.Transactions)) + for j, tx := range block.Transactions { + cost := tx.Cost() + sorter[j] = &txGasAndReward{ + gasUsed: cost.Sub(cost, tx.Value), + reward: tx.EffectiveTip(block.Header.BaseFee), + } + } + sort.Slice(sorter, func(i, j int) bool { + return sorter[i].reward.Cmp(sorter[j].reward) < 0 + }) + + var txIndex int + sumGasUsed := sorter[0].gasUsed.Uint64() + + // calculate reward for each percentile + for c, v := range rewardPercentiles { + thresholdGasUsed := uint64(float64(block.Header.GasUsed) * v / 100) + for sumGasUsed < thresholdGasUsed && txIndex < len(block.Transactions)-1 { + txIndex++ + sumGasUsed += sorter[txIndex].gasUsed.Uint64() + } + reward[i-oldestBlock][c] = sorter[txIndex].reward.Uint64() + } + } + + baseFeePerGas[blockCount] = g.backend.Header().BaseFee + return &oldestBlock, &baseFeePerGas, &gasUsedRatio, &reward, nil +} diff --git a/gasprice/gasprice.go b/gasprice/gasprice.go index a800a26002..66d26932bb 100644 --- a/gasprice/gasprice.go +++ b/gasprice/gasprice.go @@ -44,6 +44,7 @@ type Config struct { // Blockchain is the interface representing blockchain type Blockchain interface { + GetBlockByNumber(number uint64, full bool) (*types.Block, bool) GetBlockByHash(hash types.Hash, full bool) (*types.Block, bool) Header() *types.Header Config() *chain.Params @@ -53,6 +54,7 @@ type Blockchain interface { type GasStore interface { // MaxPriorityFeePerGas calculates the priority fee needed for transaction to be included in a block MaxPriorityFeePerGas() (*big.Int, error) + FeeHistory(blockCount uint64, newestBlock uint64, rewardPercentiles []float64) (*uint64, *[]uint64, *[]float64, *[][]uint64, error) } var _ GasStore = (*GasHelper)(nil) diff --git a/jsonrpc/eth_endpoint.go b/jsonrpc/eth_endpoint.go index cbea344587..f0889f2f50 100644 --- a/jsonrpc/eth_endpoint.go +++ b/jsonrpc/eth_endpoint.go @@ -802,3 +802,31 @@ func (e *Eth) MaxPriorityFeePerGas() (interface{}, error) { return argBigPtr(priorityFee), nil } +func (e *Eth) FeeHistory(blockCount uint64, newestBlock uint64, rewardPercentiles []float64) (interface{}, error) { + oldestBlock, baseFeePerGas, gasUsedRatio, reward, err := e.store.FeeHistory(blockCount, newestBlock, rewardPercentiles) + if err != nil { + return nil, err + } + //convert responses to argUint64 suitable for JSON marshalling + baseFeeSlice := make([]argUint64, len(*baseFeePerGas)) + for i, value := range *baseFeePerGas { + baseFeeSlice[i] = argUint64(value) + } + gasUsedSlice := make([]argUint64, len(*gasUsedRatio)) + for i, value := range *gasUsedRatio { + gasUsedSlice[i] = argUint64(value) + } + rewardSlice := make([][]argUint64, len(*reward)) + for i, value := range *reward { + rewardSlice[i] = make([]argUint64, len(value)) + for c := range value { + rewardSlice[i][c] = argUint64(value[c]) + } + } + return &feeHistory{ + OldestBlock: argUint64(*oldestBlock), + BaseFeePerGas: baseFeeSlice, + GasUsedRatio: gasUsedSlice, + Reward: rewardSlice, + }, nil +} diff --git a/jsonrpc/types.go b/jsonrpc/types.go index 2e2a5e3f57..d1d1ff126f 100644 --- a/jsonrpc/types.go +++ b/jsonrpc/types.go @@ -344,3 +344,10 @@ type progression struct { CurrentBlock argUint64 `json:"currentBlock"` HighestBlock argUint64 `json:"highestBlock"` } + +type feeHistory struct { + OldestBlock argUint64 `json:"oldestBlock"` + BaseFeePerGas []argUint64 `json:"baseFeePerGas"` + GasUsedRatio []argUint64 `json:"gasUsedRatio"` + Reward [][]argUint64 `json:"reward"` +} From 0fabf3b6c09bc5af03fd6eec0934a133ead738d4 Mon Sep 17 00:00:00 2001 From: owen Date: Fri, 23 Jun 2023 15:37:58 -0400 Subject: [PATCH 02/17] add goroutines to eth_endpoint --- jsonrpc/eth_endpoint.go | 82 +++++++++++++++++++++++++++++++---------- jsonrpc/types.go | 7 ---- 2 files changed, 63 insertions(+), 26 deletions(-) diff --git a/jsonrpc/eth_endpoint.go b/jsonrpc/eth_endpoint.go index f0889f2f50..f6ec5cca52 100644 --- a/jsonrpc/eth_endpoint.go +++ b/jsonrpc/eth_endpoint.go @@ -803,30 +803,74 @@ func (e *Eth) MaxPriorityFeePerGas() (interface{}, error) { return argBigPtr(priorityFee), nil } func (e *Eth) FeeHistory(blockCount uint64, newestBlock uint64, rewardPercentiles []float64) (interface{}, error) { + type feeHistoryResult struct { + OldestBlock argUint64 + BaseFeePerGas []argUint64 + GasUsedRatio []argUint64 + Reward [][]argUint64 + } + + // Retrieve oldestBlock, baseFeePerGas, gasUsedRatio, and reward synchronously oldestBlock, baseFeePerGas, gasUsedRatio, reward, err := e.store.FeeHistory(blockCount, newestBlock, rewardPercentiles) if err != nil { return nil, err } - //convert responses to argUint64 suitable for JSON marshalling - baseFeeSlice := make([]argUint64, len(*baseFeePerGas)) - for i, value := range *baseFeePerGas { - baseFeeSlice[i] = argUint64(value) + + // Create channels to receive the processed slices asynchronously + baseFeePerGasCh := make(chan []argUint64) + gasUsedRatioCh := make(chan []argUint64) + rewardCh := make(chan [][]argUint64) + + // Process baseFeePerGas asynchronously + go func() { + baseFeePerGasCh <- convertToArgUint64Slice(*baseFeePerGas) + }() + + // Process gasUsedRatio asynchronously + go func() { + gasUsedRatioCh <- convertFloat64SliceToArgUint64Slice(*gasUsedRatio) + }() + + // Process reward asynchronously + go func() { + rewardCh <- convertToArgUint64SliceSlice(*reward) + }() + + // Wait for the processed slices from goroutines + baseFeePerGasResult := <-baseFeePerGasCh + gasUsedRatioResult := <-gasUsedRatioCh + rewardResult := <-rewardCh + + result := &feeHistoryResult{ + OldestBlock: argUint64(*oldestBlock), + BaseFeePerGas: baseFeePerGasResult, + GasUsedRatio: gasUsedRatioResult, + Reward: rewardResult, } - gasUsedSlice := make([]argUint64, len(*gasUsedRatio)) - for i, value := range *gasUsedRatio { - gasUsedSlice[i] = argUint64(value) + + return result, nil +} + +func convertToArgUint64Slice(slice []uint64) []argUint64 { + argSlice := make([]argUint64, len(slice)) + for i, value := range slice { + argSlice[i] = argUint64(value) } - rewardSlice := make([][]argUint64, len(*reward)) - for i, value := range *reward { - rewardSlice[i] = make([]argUint64, len(value)) - for c := range value { - rewardSlice[i][c] = argUint64(value[c]) - } + return argSlice +} + +func convertToArgUint64SliceSlice(slice [][]uint64) [][]argUint64 { + argSlice := make([][]argUint64, len(slice)) + for i, value := range slice { + argSlice[i] = convertToArgUint64Slice(value) } - return &feeHistory{ - OldestBlock: argUint64(*oldestBlock), - BaseFeePerGas: baseFeeSlice, - GasUsedRatio: gasUsedSlice, - Reward: rewardSlice, - }, nil + return argSlice +} + +func convertFloat64SliceToArgUint64Slice(slice []float64) []argUint64 { + argSlice := make([]argUint64, len(slice)) + for i, value := range slice { + argSlice[i] = argUint64(value) + } + return argSlice } diff --git a/jsonrpc/types.go b/jsonrpc/types.go index d1d1ff126f..2e2a5e3f57 100644 --- a/jsonrpc/types.go +++ b/jsonrpc/types.go @@ -344,10 +344,3 @@ type progression struct { CurrentBlock argUint64 `json:"currentBlock"` HighestBlock argUint64 `json:"highestBlock"` } - -type feeHistory struct { - OldestBlock argUint64 `json:"oldestBlock"` - BaseFeePerGas []argUint64 `json:"baseFeePerGas"` - GasUsedRatio []argUint64 `json:"gasUsedRatio"` - Reward [][]argUint64 `json:"reward"` -} From eacf98c6d7f9c88bcd0c75715207e0acb6269c0b Mon Sep 17 00:00:00 2001 From: owen Date: Fri, 23 Jun 2023 17:05:13 -0400 Subject: [PATCH 03/17] init tests, clean --- gasprice/feehistory.go | 1 - gasprice/feehistory_test.go | 111 ++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 gasprice/feehistory_test.go diff --git a/gasprice/feehistory.go b/gasprice/feehistory.go index f8b773f552..2ed22dcf48 100644 --- a/gasprice/feehistory.go +++ b/gasprice/feehistory.go @@ -8,7 +8,6 @@ import ( var ( ErrInvalidPercentile = errors.New("invalid percentile") - ErrRequestedRange = errors.New("requested range is too large") ErrBlockCount = errors.New("blockCount must be greater than 0") ErrBlockInfo = errors.New("could not find block info") ) diff --git a/gasprice/feehistory_test.go b/gasprice/feehistory_test.go new file mode 100644 index 0000000000..dd7ffd76e4 --- /dev/null +++ b/gasprice/feehistory_test.go @@ -0,0 +1,111 @@ +package gasprice + +import ( + "math/big" + "testing" + + "github.com/0xPolygon/polygon-edge/types" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + // "github.com/umbracle/ethgo" +) + +func TestGasHelper_FeeHistory(t *testing.T) { + t.Parallel() + + var cases = []struct { + Name string + ExpectedOldestBlock uint64 + ExpectedBaseFeePerGas []uint64 + ExpectedGasUsedRatio []float64 + ExpectedRewards [][]uint64 + BlockRange uint64 + NewestBlock uint64 + RewardPercentiles []float64 + Error bool + GetBackend func() Blockchain + }{ + { + Name: "Block does not exist", + Error: true, + BlockRange: 10, + NewestBlock: 30, + RewardPercentiles: []float64{15, 20}, + GetBackend: func() Blockchain { + header := &types.Header{ + Number: 0, + Hash: types.StringToHash("some header"), + } + backend := new(backendMock) + backend.On("Header").Return(header) + backend.On("GetBlockByNumber", mock.Anything, true).Return(&types.Block{}, false) + + return backend + }, + }, + { + Name: "Block Range < 1", + Error: true, + BlockRange: 0, + NewestBlock: 30, + RewardPercentiles: []float64{15, 20}, + GetBackend: func() Blockchain { + backend := createTestBlocks(t, 30) + createTestTxs(t, backend, 3, 200) + + return backend + }, + }, + { + Name: "Invalid rewardPercentile", + Error: true, + BlockRange: 10, + NewestBlock: 30, + RewardPercentiles: []float64{50, 15}, + GetBackend: func() Blockchain { + backend := createTestBlocks(t, 50) + createTestTxs(t, backend, 1, 200) + + return backend + }, + }, + } + + for _, tc := range cases { + tc := tc + t.Run(tc.Name, func(t *testing.T) { + t.Parallel() + + backend := tc.GetBackend() + gasHelper := NewGasHelper(DefaultGasHelperConfig, backend) + oldestBlock, baseFeePerGas, gasUsedRatio, rewards, err := gasHelper.FeeHistory(tc.BlockRange, tc.NewestBlock, tc.RewardPercentiles) + + if tc.Error { + require.Error(t, err) + } else { + require.NoError(t, err) + require.True(t, oldestBlock == &tc.ExpectedOldestBlock) + require.True(t, baseFeePerGas == &tc.ExpectedBaseFeePerGas) + require.True(t, gasUsedRatio == &tc.ExpectedGasUsedRatio) + require.True(t, rewards == &tc.ExpectedRewards) + } + }) + } +} + +var _ Blockchain = (*backendMock)(nil) + +func (b *backendMock) GetBlockByNumber(n uint64, full bool) (*types.Block, bool) { + if len(b.blocks) == 0 { + args := b.Called(n, full) + + return args.Get(0).(*types.Block), args.Get(1).(bool) //nolint:forcetypeassert + } + + block, exists := b.blocks[Uint64ToHash(n)] + + return block, exists +} +func Uint64ToHash(n uint64) types.Hash { + return types.BytesToHash(big.NewInt(int64(n)).Bytes()) +} From 1f45aca3725a2714bd01fa431c57f942b46ff2c5 Mon Sep 17 00:00:00 2001 From: owen Date: Mon, 26 Jun 2023 17:20:38 -0400 Subject: [PATCH 04/17] lru caching --- gasprice/feehistory.go | 36 ++++++++++++++++++++++++++++++++++++ gasprice/gasprice.go | 9 +++++++++ 2 files changed, 45 insertions(+) diff --git a/gasprice/feehistory.go b/gasprice/feehistory.go index 2ed22dcf48..300ecff451 100644 --- a/gasprice/feehistory.go +++ b/gasprice/feehistory.go @@ -1,7 +1,10 @@ package gasprice import ( + "encoding/binary" "errors" + "log" + "math" "math/big" "sort" ) @@ -16,6 +19,18 @@ const ( maxBlockRequest = 1024 ) +type cacheKey struct { + number uint64 + percentiles string +} + +// processedFees contains the results of a processed block. +type processedFees struct { + reward []uint64 + baseFee uint64 + gasUsedRatio float64 +} + type txGasAndReward struct { gasUsed *big.Int reward *big.Int @@ -52,12 +67,27 @@ func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPerc if oldestBlock < 1 { oldestBlock = 1 } + percentileKey := make([]byte, 8*len(rewardPercentiles)) + for i, p := range rewardPercentiles { + binary.LittleEndian.PutUint64(percentileKey[i*8:(i+1)*8], math.Float64bits(p)) + } for i := oldestBlock; i < oldestBlock+blockCount; i++ { + + cacheKey := cacheKey{number: i, percentiles: string(percentileKey)} + //cache is hit, load from cache and continue to next block + if p, ok := g.historyCache.Get(cacheKey); ok { + log.Println("cache hit", p) + baseFeePerGas[i-oldestBlock] = p.(*processedFees).baseFee + gasUsedRatio[i-oldestBlock] = p.(*processedFees).gasUsedRatio + reward[i-oldestBlock] = p.(*processedFees).reward + continue + } block, ok := g.backend.GetBlockByNumber(i, false) if !ok { return nil, nil, nil, nil, ErrBlockInfo } + baseFeePerGas[i-oldestBlock] = block.Header.BaseFee gasUsedRatio[i-oldestBlock] = float64(block.Header.GasUsed) / float64(block.Header.GasLimit) @@ -98,6 +128,12 @@ func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPerc } reward[i-oldestBlock][c] = sorter[txIndex].reward.Uint64() } + blockFees := &processedFees{ + reward: reward[i-oldestBlock], + baseFee: block.Header.BaseFee, + gasUsedRatio: gasUsedRatio[i-oldestBlock], + } + g.historyCache.Add(cacheKey, blockFees) } baseFeePerGas[blockCount] = g.backend.Header().BaseFee diff --git a/gasprice/gasprice.go b/gasprice/gasprice.go index 66d26932bb..ce06ed459f 100644 --- a/gasprice/gasprice.go +++ b/gasprice/gasprice.go @@ -9,6 +9,7 @@ import ( "github.com/0xPolygon/polygon-edge/chain" "github.com/0xPolygon/polygon-edge/crypto" "github.com/0xPolygon/polygon-edge/types" + lru "github.com/hashicorp/golang-lru" "github.com/umbracle/ethgo" ) @@ -80,6 +81,8 @@ type GasHelper struct { lastHeaderHash types.Hash lock sync.Mutex + + historyCache *lru.Cache } // NewGasHelper is the constructor function for GasHelper struct @@ -89,6 +92,11 @@ func NewGasHelper(config *Config, backend Blockchain) *GasHelper { pricePercentile = 100 } + cache, err := lru.New(100) + if err != nil { + panic(err) + } + return &GasHelper{ numOfBlocksToCheck: config.NumOfBlocksToCheck, pricePercentile: pricePercentile, @@ -97,6 +105,7 @@ func NewGasHelper(config *Config, backend Blockchain) *GasHelper { lastPrice: config.LastPrice, maxPrice: config.MaxPrice, backend: backend, + historyCache: cache, } } From 8f41449ded8cb4b0115334104e6cfd16824e22a1 Mon Sep 17 00:00:00 2001 From: owen Date: Tue, 27 Jun 2023 11:53:20 -0400 Subject: [PATCH 05/17] feedback changes --- gasprice/feehistory.go | 19 +++++++++++-------- gasprice/feehistory_test.go | 7 ++++--- gasprice/gasprice.go | 6 +++--- gasprice/gasprice_test.go | 3 ++- server/server.go | 5 ++++- 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/gasprice/feehistory.go b/gasprice/feehistory.go index 300ecff451..158b1ba540 100644 --- a/gasprice/feehistory.go +++ b/gasprice/feehistory.go @@ -3,7 +3,6 @@ package gasprice import ( "encoding/binary" "errors" - "log" "math" "math/big" "sort" @@ -12,7 +11,7 @@ import ( var ( ErrInvalidPercentile = errors.New("invalid percentile") ErrBlockCount = errors.New("blockCount must be greater than 0") - ErrBlockInfo = errors.New("could not find block info") + ErrBlockNotFound = errors.New("could not find block") ) const ( @@ -49,6 +48,7 @@ func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPerc if blockCount > newestBlock { blockCount = newestBlock } + for i, p := range rewardPercentiles { if p < 0 || p > 100 { return nil, nil, nil, nil, ErrInvalidPercentile @@ -72,20 +72,23 @@ func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPerc binary.LittleEndian.PutUint64(percentileKey[i*8:(i+1)*8], math.Float64bits(p)) } - for i := oldestBlock; i < oldestBlock+blockCount; i++ { + for i := oldestBlock; i <= newestBlock; i++ { cacheKey := cacheKey{number: i, percentiles: string(percentileKey)} //cache is hit, load from cache and continue to next block if p, ok := g.historyCache.Get(cacheKey); ok { - log.Println("cache hit", p) - baseFeePerGas[i-oldestBlock] = p.(*processedFees).baseFee - gasUsedRatio[i-oldestBlock] = p.(*processedFees).gasUsedRatio - reward[i-oldestBlock] = p.(*processedFees).reward + processedFee, isOk := p.(*processedFees) + if !isOk { + return nil, nil, nil, nil, errors.New("could not convert catched processed fee") + } + baseFeePerGas[i-oldestBlock] = processedFee.baseFee + gasUsedRatio[i-oldestBlock] = processedFee.gasUsedRatio + reward[i-oldestBlock] = processedFee.reward continue } block, ok := g.backend.GetBlockByNumber(i, false) if !ok { - return nil, nil, nil, nil, ErrBlockInfo + return nil, nil, nil, nil, ErrBlockNotFound } baseFeePerGas[i-oldestBlock] = block.Header.BaseFee diff --git a/gasprice/feehistory_test.go b/gasprice/feehistory_test.go index dd7ffd76e4..46ace3dd54 100644 --- a/gasprice/feehistory_test.go +++ b/gasprice/feehistory_test.go @@ -77,7 +77,8 @@ func TestGasHelper_FeeHistory(t *testing.T) { t.Parallel() backend := tc.GetBackend() - gasHelper := NewGasHelper(DefaultGasHelperConfig, backend) + gasHelper, err := NewGasHelper(DefaultGasHelperConfig, backend) + require.NoError(t, err) oldestBlock, baseFeePerGas, gasUsedRatio, rewards, err := gasHelper.FeeHistory(tc.BlockRange, tc.NewestBlock, tc.RewardPercentiles) if tc.Error { @@ -102,10 +103,10 @@ func (b *backendMock) GetBlockByNumber(n uint64, full bool) (*types.Block, bool) return args.Get(0).(*types.Block), args.Get(1).(bool) //nolint:forcetypeassert } - block, exists := b.blocks[Uint64ToHash(n)] + block, exists := b.blocks[uint64ToHash(n)] return block, exists } -func Uint64ToHash(n uint64) types.Hash { +func uint64ToHash(n uint64) types.Hash { return types.BytesToHash(big.NewInt(int64(n)).Bytes()) } diff --git a/gasprice/gasprice.go b/gasprice/gasprice.go index ce06ed459f..6427fec423 100644 --- a/gasprice/gasprice.go +++ b/gasprice/gasprice.go @@ -86,7 +86,7 @@ type GasHelper struct { } // NewGasHelper is the constructor function for GasHelper struct -func NewGasHelper(config *Config, backend Blockchain) *GasHelper { +func NewGasHelper(config *Config, backend Blockchain) (*GasHelper, error) { pricePercentile := config.PricePercentile if pricePercentile > 100 { pricePercentile = 100 @@ -94,7 +94,7 @@ func NewGasHelper(config *Config, backend Blockchain) *GasHelper { cache, err := lru.New(100) if err != nil { - panic(err) + return nil, err } return &GasHelper{ @@ -106,7 +106,7 @@ func NewGasHelper(config *Config, backend Blockchain) *GasHelper { maxPrice: config.MaxPrice, backend: backend, historyCache: cache, - } + }, nil } // MaxPriorityFeePerGas calculates the priority fee needed for transaction to be included in a block diff --git a/gasprice/gasprice_test.go b/gasprice/gasprice_test.go index f4ff7bbde3..dfbb737ecd 100644 --- a/gasprice/gasprice_test.go +++ b/gasprice/gasprice_test.go @@ -154,7 +154,8 @@ func TestGasHelper_MaxPriorityFeePerGas(t *testing.T) { t.Parallel() backend := tc.GetBackend() - gasHelper := NewGasHelper(DefaultGasHelperConfig, backend) + gasHelper, err := NewGasHelper(DefaultGasHelperConfig, backend) + require.NoError(t, err) price, err := gasHelper.MaxPriorityFeePerGas() if tc.Error { diff --git a/server/server.go b/server/server.go index c1872955e3..4081c52877 100644 --- a/server/server.go +++ b/server/server.go @@ -342,7 +342,10 @@ func NewServer(config *Config) (*Server, error) { } // here we can provide some other configuration - m.gasHelper = gasprice.NewGasHelper(gasprice.DefaultGasHelperConfig, m.blockchain) + m.gasHelper, err = gasprice.NewGasHelper(gasprice.DefaultGasHelperConfig, m.blockchain) + if err != nil { + return nil, err + } m.executor.GetHash = m.blockchain.GetHashHelper From d2b9029fcfcea046e9695920281338237605efa3 Mon Sep 17 00:00:00 2001 From: owen Date: Tue, 27 Jun 2023 12:45:25 -0400 Subject: [PATCH 06/17] linting --- gasprice/feehistory.go | 22 ++++++++++++++++++++-- gasprice/gasprice.go | 2 +- jsonrpc/eth_endpoint.go | 3 +++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/gasprice/feehistory.go b/gasprice/feehistory.go index 158b1ba540..ca73bdee0e 100644 --- a/gasprice/feehistory.go +++ b/gasprice/feehistory.go @@ -35,16 +35,22 @@ type txGasAndReward struct { reward *big.Int } -func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPercentiles []float64) (*uint64, *[]uint64, *[]float64, *[][]uint64, error) { +func (g *GasHelper) FeeHistory( + blockCount uint64, newestBlock uint64, rewardPercentiles []float64) ( + *uint64, *[]uint64, *[]float64, *[][]uint64, error) { + if blockCount < 1 { return nil, nil, nil, nil, ErrBlockCount } + if newestBlock > g.backend.Header().Number { newestBlock = g.backend.Header().Number } + if blockCount > maxBlockRequest { blockCount = maxBlockRequest } + if blockCount > newestBlock { blockCount = newestBlock } @@ -53,6 +59,7 @@ func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPerc if p < 0 || p > 100 { return nil, nil, nil, nil, ErrInvalidPercentile } + if i > 0 && p < rewardPercentiles[i-1] { return nil, nil, nil, nil, ErrInvalidPercentile } @@ -64,16 +71,17 @@ func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPerc gasUsedRatio = make([]float64, blockCount) reward = make([][]uint64, blockCount) ) + if oldestBlock < 1 { oldestBlock = 1 } + percentileKey := make([]byte, 8*len(rewardPercentiles)) for i, p := range rewardPercentiles { binary.LittleEndian.PutUint64(percentileKey[i*8:(i+1)*8], math.Float64bits(p)) } for i := oldestBlock; i <= newestBlock; i++ { - cacheKey := cacheKey{number: i, percentiles: string(percentileKey)} //cache is hit, load from cache and continue to next block if p, ok := g.historyCache.Get(cacheKey); ok { @@ -81,11 +89,14 @@ func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPerc if !isOk { return nil, nil, nil, nil, errors.New("could not convert catched processed fee") } + baseFeePerGas[i-oldestBlock] = processedFee.baseFee gasUsedRatio[i-oldestBlock] = processedFee.gasUsedRatio reward[i-oldestBlock] = processedFee.reward + continue } + block, ok := g.backend.GetBlockByNumber(i, false) if !ok { return nil, nil, nil, nil, ErrBlockNotFound @@ -98,6 +109,7 @@ func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPerc //reward percentiles not requested, skip rest of this loop continue } + reward[i-oldestBlock] = make([]uint64, len(rewardPercentiles)) if len(block.Transactions) == 0 { for j := range reward[i-oldestBlock] { @@ -108,6 +120,7 @@ func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPerc } sorter := make([]*txGasAndReward, len(block.Transactions)) + for j, tx := range block.Transactions { cost := tx.Cost() sorter[j] = &txGasAndReward{ @@ -115,11 +128,13 @@ func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPerc reward: tx.EffectiveTip(block.Header.BaseFee), } } + sort.Slice(sorter, func(i, j int) bool { return sorter[i].reward.Cmp(sorter[j].reward) < 0 }) var txIndex int + sumGasUsed := sorter[0].gasUsed.Uint64() // calculate reward for each percentile @@ -129,8 +144,10 @@ func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPerc txIndex++ sumGasUsed += sorter[txIndex].gasUsed.Uint64() } + reward[i-oldestBlock][c] = sorter[txIndex].reward.Uint64() } + blockFees := &processedFees{ reward: reward[i-oldestBlock], baseFee: block.Header.BaseFee, @@ -140,5 +157,6 @@ func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPerc } baseFeePerGas[blockCount] = g.backend.Header().BaseFee + return &oldestBlock, &baseFeePerGas, &gasUsedRatio, &reward, nil } diff --git a/gasprice/gasprice.go b/gasprice/gasprice.go index 6427fec423..070342710b 100644 --- a/gasprice/gasprice.go +++ b/gasprice/gasprice.go @@ -55,7 +55,7 @@ type Blockchain interface { type GasStore interface { // MaxPriorityFeePerGas calculates the priority fee needed for transaction to be included in a block MaxPriorityFeePerGas() (*big.Int, error) - FeeHistory(blockCount uint64, newestBlock uint64, rewardPercentiles []float64) (*uint64, *[]uint64, *[]float64, *[][]uint64, error) + FeeHistory(uint64, uint64, []float64) (*uint64, *[]uint64, *[]float64, *[][]uint64, error) } var _ GasStore = (*GasHelper)(nil) diff --git a/jsonrpc/eth_endpoint.go b/jsonrpc/eth_endpoint.go index f6ec5cca52..33b3d4ceca 100644 --- a/jsonrpc/eth_endpoint.go +++ b/jsonrpc/eth_endpoint.go @@ -856,6 +856,7 @@ func convertToArgUint64Slice(slice []uint64) []argUint64 { for i, value := range slice { argSlice[i] = argUint64(value) } + return argSlice } @@ -864,6 +865,7 @@ func convertToArgUint64SliceSlice(slice [][]uint64) [][]argUint64 { for i, value := range slice { argSlice[i] = convertToArgUint64Slice(value) } + return argSlice } @@ -872,5 +874,6 @@ func convertFloat64SliceToArgUint64Slice(slice []float64) []argUint64 { for i, value := range slice { argSlice[i] = argUint64(value) } + return argSlice } From 04cf6e6a6cb5b6de5467acc174ba1cc584ae9b68 Mon Sep 17 00:00:00 2001 From: owen Date: Tue, 27 Jun 2023 12:59:44 -0400 Subject: [PATCH 07/17] linting final --- gasprice/feehistory.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/gasprice/feehistory.go b/gasprice/feehistory.go index ca73bdee0e..a295e2a9cf 100644 --- a/gasprice/feehistory.go +++ b/gasprice/feehistory.go @@ -35,10 +35,8 @@ type txGasAndReward struct { reward *big.Int } -func (g *GasHelper) FeeHistory( - blockCount uint64, newestBlock uint64, rewardPercentiles []float64) ( +func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPercentiles []float64) ( *uint64, *[]uint64, *[]float64, *[][]uint64, error) { - if blockCount < 1 { return nil, nil, nil, nil, ErrBlockCount } From cd6a31ffcbad95ea4da72f356135724d40f1f9f1 Mon Sep 17 00:00:00 2001 From: owen Date: Tue, 27 Jun 2023 13:43:44 -0400 Subject: [PATCH 08/17] block does not exist test fix --- gasprice/feehistory_test.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/gasprice/feehistory_test.go b/gasprice/feehistory_test.go index 46ace3dd54..b838df71ce 100644 --- a/gasprice/feehistory_test.go +++ b/gasprice/feehistory_test.go @@ -1,13 +1,13 @@ package gasprice import ( + "errors" "math/big" "testing" "github.com/0xPolygon/polygon-edge/types" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - // "github.com/umbracle/ethgo" ) func TestGasHelper_FeeHistory(t *testing.T) { @@ -32,13 +32,8 @@ func TestGasHelper_FeeHistory(t *testing.T) { NewestBlock: 30, RewardPercentiles: []float64{15, 20}, GetBackend: func() Blockchain { - header := &types.Header{ - Number: 0, - Hash: types.StringToHash("some header"), - } backend := new(backendMock) - backend.On("Header").Return(header) - backend.On("GetBlockByNumber", mock.Anything, true).Return(&types.Block{}, false) + backend.On("GetBlockByNumber", mock.Anything, true).Return(errors.New("block not found")) return backend }, From f25784ee7574ee866d05764456805db3e36f6cc7 Mon Sep 17 00:00:00 2001 From: owen Date: Tue, 27 Jun 2023 13:53:05 -0400 Subject: [PATCH 09/17] we fix again --- gasprice/feehistory_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/gasprice/feehistory_test.go b/gasprice/feehistory_test.go index b838df71ce..101a1b4e04 100644 --- a/gasprice/feehistory_test.go +++ b/gasprice/feehistory_test.go @@ -32,7 +32,12 @@ func TestGasHelper_FeeHistory(t *testing.T) { NewestBlock: 30, RewardPercentiles: []float64{15, 20}, GetBackend: func() Blockchain { + header := &types.Header{ + Number: 0, + Hash: types.StringToHash("some header"), + } backend := new(backendMock) + backend.On("Header").Return(header) backend.On("GetBlockByNumber", mock.Anything, true).Return(errors.New("block not found")) return backend @@ -56,7 +61,7 @@ func TestGasHelper_FeeHistory(t *testing.T) { Error: true, BlockRange: 10, NewestBlock: 30, - RewardPercentiles: []float64{50, 15}, + RewardPercentiles: []float64{101, 0}, GetBackend: func() Blockchain { backend := createTestBlocks(t, 50) createTestTxs(t, backend, 1, 200) From 193e8b01cb28cec2e8b7a534db34b7b1bb72f95d Mon Sep 17 00:00:00 2001 From: owen Date: Tue, 27 Jun 2023 14:14:26 -0400 Subject: [PATCH 10/17] test fix --- gasprice/feehistory.go | 2 +- gasprice/feehistory_test.go | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/gasprice/feehistory.go b/gasprice/feehistory.go index a295e2a9cf..0887937d12 100644 --- a/gasprice/feehistory.go +++ b/gasprice/feehistory.go @@ -95,7 +95,7 @@ func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPerc continue } - block, ok := g.backend.GetBlockByNumber(i, false) + block, ok := g.backend.GetBlockByNumber(i, true) if !ok { return nil, nil, nil, nil, ErrBlockNotFound } diff --git a/gasprice/feehistory_test.go b/gasprice/feehistory_test.go index 101a1b4e04..bb5e275fcb 100644 --- a/gasprice/feehistory_test.go +++ b/gasprice/feehistory_test.go @@ -1,7 +1,6 @@ package gasprice import ( - "errors" "math/big" "testing" @@ -38,7 +37,7 @@ func TestGasHelper_FeeHistory(t *testing.T) { } backend := new(backendMock) backend.On("Header").Return(header) - backend.On("GetBlockByNumber", mock.Anything, true).Return(errors.New("block not found")) + backend.On("GetBlockByNumber", mock.Anything, mock.Anything).Return(nil, false) return backend }, @@ -48,7 +47,7 @@ func TestGasHelper_FeeHistory(t *testing.T) { Error: true, BlockRange: 0, NewestBlock: 30, - RewardPercentiles: []float64{15, 20}, + RewardPercentiles: []float64{101, 0}, GetBackend: func() Blockchain { backend := createTestBlocks(t, 30) createTestTxs(t, backend, 3, 200) @@ -61,7 +60,7 @@ func TestGasHelper_FeeHistory(t *testing.T) { Error: true, BlockRange: 10, NewestBlock: 30, - RewardPercentiles: []float64{101, 0}, + RewardPercentiles: []float64{50, 15}, GetBackend: func() Blockchain { backend := createTestBlocks(t, 50) createTestTxs(t, backend, 1, 200) From bb051d231c13904cbea731bf5dffa7aa256a6b59 Mon Sep 17 00:00:00 2001 From: owen Date: Tue, 27 Jun 2023 14:15:20 -0400 Subject: [PATCH 11/17] . --- gasprice/feehistory_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gasprice/feehistory_test.go b/gasprice/feehistory_test.go index bb5e275fcb..c9303805e5 100644 --- a/gasprice/feehistory_test.go +++ b/gasprice/feehistory_test.go @@ -47,7 +47,7 @@ func TestGasHelper_FeeHistory(t *testing.T) { Error: true, BlockRange: 0, NewestBlock: 30, - RewardPercentiles: []float64{101, 0}, + RewardPercentiles: []float64{10, 15}, GetBackend: func() Blockchain { backend := createTestBlocks(t, 30) createTestTxs(t, backend, 3, 200) @@ -60,7 +60,7 @@ func TestGasHelper_FeeHistory(t *testing.T) { Error: true, BlockRange: 10, NewestBlock: 30, - RewardPercentiles: []float64{50, 15}, + RewardPercentiles: []float64{101, 0}, GetBackend: func() Blockchain { backend := createTestBlocks(t, 50) createTestTxs(t, backend, 1, 200) From 631188a6ce29e676716150f953f1b88eed08c7b7 Mon Sep 17 00:00:00 2001 From: owen Date: Tue, 27 Jun 2023 15:33:47 -0400 Subject: [PATCH 12/17] fix GetBlockByNumber mock --- gasprice/feehistory_test.go | 10 +++++----- gasprice/gasprice_test.go | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/gasprice/feehistory_test.go b/gasprice/feehistory_test.go index c9303805e5..2f408d6c26 100644 --- a/gasprice/feehistory_test.go +++ b/gasprice/feehistory_test.go @@ -32,12 +32,12 @@ func TestGasHelper_FeeHistory(t *testing.T) { RewardPercentiles: []float64{15, 20}, GetBackend: func() Blockchain { header := &types.Header{ - Number: 0, + Number: 1, Hash: types.StringToHash("some header"), } backend := new(backendMock) backend.On("Header").Return(header) - backend.On("GetBlockByNumber", mock.Anything, mock.Anything).Return(nil, false) + backend.On("GetBlockByNumber", mock.Anything, mock.Anything).Return(&types.Block{}, false) return backend }, @@ -95,14 +95,14 @@ func TestGasHelper_FeeHistory(t *testing.T) { var _ Blockchain = (*backendMock)(nil) -func (b *backendMock) GetBlockByNumber(n uint64, full bool) (*types.Block, bool) { +func (b *backendMock) GetBlockByNumber(number uint64, full bool) (*types.Block, bool) { if len(b.blocks) == 0 { - args := b.Called(n, full) + args := b.Called(number, full) return args.Get(0).(*types.Block), args.Get(1).(bool) //nolint:forcetypeassert } - block, exists := b.blocks[uint64ToHash(n)] + block, exists := b.blocksByNumber[number] return block, exists } diff --git a/gasprice/gasprice_test.go b/gasprice/gasprice_test.go index dfbb737ecd..dbb99e6ab3 100644 --- a/gasprice/gasprice_test.go +++ b/gasprice/gasprice_test.go @@ -238,7 +238,8 @@ var _ Blockchain = (*backendMock)(nil) type backendMock struct { mock.Mock - blocks map[types.Hash]*types.Block + blocks map[types.Hash]*types.Block + blocksByNumber map[uint64]*types.Block } func (b *backendMock) Header() *types.Header { From 4b0313b5cb7b005c5c48e93dbb6ebc816188294e Mon Sep 17 00:00:00 2001 From: owen Date: Tue, 27 Jun 2023 15:37:18 -0400 Subject: [PATCH 13/17] rm unused helper --- gasprice/feehistory_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/gasprice/feehistory_test.go b/gasprice/feehistory_test.go index 2f408d6c26..ba992eabbf 100644 --- a/gasprice/feehistory_test.go +++ b/gasprice/feehistory_test.go @@ -1,7 +1,6 @@ package gasprice import ( - "math/big" "testing" "github.com/0xPolygon/polygon-edge/types" @@ -106,6 +105,3 @@ func (b *backendMock) GetBlockByNumber(number uint64, full bool) (*types.Block, return block, exists } -func uint64ToHash(n uint64) types.Hash { - return types.BytesToHash(big.NewInt(int64(n)).Bytes()) -} From 89f4ba3c455d67873b7a692866418175991a1f37 Mon Sep 17 00:00:00 2001 From: owen Date: Wed, 28 Jun 2023 12:09:36 -0400 Subject: [PATCH 14/17] feedback changes --- gasprice/feehistory.go | 19 ++++++++++------ gasprice/feehistory_test.go | 31 +++++++++++++++++++++----- gasprice/gasprice.go | 3 ++- gasprice/gasprice_test.go | 5 +++-- jsonrpc/eth_endpoint.go | 43 +++++-------------------------------- jsonrpc/types.go | 34 +++++++++++++++++++++++++++++ 6 files changed, 82 insertions(+), 53 deletions(-) diff --git a/gasprice/feehistory.go b/gasprice/feehistory.go index 0887937d12..6960dc3844 100644 --- a/gasprice/feehistory.go +++ b/gasprice/feehistory.go @@ -36,9 +36,9 @@ type txGasAndReward struct { } func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPercentiles []float64) ( - *uint64, *[]uint64, *[]float64, *[][]uint64, error) { + uint64, []uint64, []float64, [][]uint64, error) { if blockCount < 1 { - return nil, nil, nil, nil, ErrBlockCount + return 0, nil, nil, nil, ErrBlockCount } if newestBlock > g.backend.Header().Number { @@ -55,11 +55,11 @@ func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPerc for i, p := range rewardPercentiles { if p < 0 || p > 100 { - return nil, nil, nil, nil, ErrInvalidPercentile + return 0, nil, nil, nil, ErrInvalidPercentile } if i > 0 && p < rewardPercentiles[i-1] { - return nil, nil, nil, nil, ErrInvalidPercentile + return 0, nil, nil, nil, ErrInvalidPercentile } } @@ -85,7 +85,7 @@ func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPerc if p, ok := g.historyCache.Get(cacheKey); ok { processedFee, isOk := p.(*processedFees) if !isOk { - return nil, nil, nil, nil, errors.New("could not convert catched processed fee") + return 0, nil, nil, nil, errors.New("could not convert catched processed fee") } baseFeePerGas[i-oldestBlock] = processedFee.baseFee @@ -97,12 +97,17 @@ func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPerc block, ok := g.backend.GetBlockByNumber(i, true) if !ok { - return nil, nil, nil, nil, ErrBlockNotFound + return 0, nil, nil, nil, ErrBlockNotFound } baseFeePerGas[i-oldestBlock] = block.Header.BaseFee gasUsedRatio[i-oldestBlock] = float64(block.Header.GasUsed) / float64(block.Header.GasLimit) + if math.IsNaN(gasUsedRatio[i-oldestBlock]) { + //gasUsedRatio is NaN, set to 0 + gasUsedRatio[i-oldestBlock] = 0 + } + if len(rewardPercentiles) == 0 { //reward percentiles not requested, skip rest of this loop continue @@ -156,5 +161,5 @@ func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPerc baseFeePerGas[blockCount] = g.backend.Header().BaseFee - return &oldestBlock, &baseFeePerGas, &gasUsedRatio, &reward, nil + return oldestBlock, baseFeePerGas, gasUsedRatio, reward, nil } diff --git a/gasprice/feehistory_test.go b/gasprice/feehistory_test.go index ba992eabbf..17ebe56a9b 100644 --- a/gasprice/feehistory_test.go +++ b/gasprice/feehistory_test.go @@ -49,7 +49,6 @@ func TestGasHelper_FeeHistory(t *testing.T) { RewardPercentiles: []float64{10, 15}, GetBackend: func() Blockchain { backend := createTestBlocks(t, 30) - createTestTxs(t, backend, 3, 200) return backend }, @@ -64,6 +63,28 @@ func TestGasHelper_FeeHistory(t *testing.T) { backend := createTestBlocks(t, 50) createTestTxs(t, backend, 1, 200) + return backend + }, + }, + { + Name: "rewardPercentile not set", + BlockRange: 10, + NewestBlock: 30, + RewardPercentiles: []float64{}, + ExpectedOldestBlock: 21, + ExpectedBaseFeePerGas: []uint64{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + ExpectedGasUsedRatio: []float64{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + ExpectedRewards: [][]uint64{ + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + }, + GetBackend: func() Blockchain { + backend := createTestBlocks(t, 30) + createTestTxs(t, backend, 5, 500) + return backend }, }, @@ -83,10 +104,10 @@ func TestGasHelper_FeeHistory(t *testing.T) { require.Error(t, err) } else { require.NoError(t, err) - require.True(t, oldestBlock == &tc.ExpectedOldestBlock) - require.True(t, baseFeePerGas == &tc.ExpectedBaseFeePerGas) - require.True(t, gasUsedRatio == &tc.ExpectedGasUsedRatio) - require.True(t, rewards == &tc.ExpectedRewards) + require.Equal(t, tc.ExpectedOldestBlock, oldestBlock) + require.Equal(t, tc.ExpectedBaseFeePerGas, baseFeePerGas) + require.Equal(t, tc.ExpectedGasUsedRatio, gasUsedRatio) + require.Equal(t, tc.ExpectedRewards, rewards) } }) } diff --git a/gasprice/gasprice.go b/gasprice/gasprice.go index 070342710b..080add4a8b 100644 --- a/gasprice/gasprice.go +++ b/gasprice/gasprice.go @@ -55,7 +55,8 @@ type Blockchain interface { type GasStore interface { // MaxPriorityFeePerGas calculates the priority fee needed for transaction to be included in a block MaxPriorityFeePerGas() (*big.Int, error) - FeeHistory(uint64, uint64, []float64) (*uint64, *[]uint64, *[]float64, *[][]uint64, error) + // FeeHistory returns the collection of historical gas information + FeeHistory(uint64, uint64, []float64) (uint64, []uint64, []float64, [][]uint64, error) } var _ GasStore = (*GasHelper)(nil) diff --git a/gasprice/gasprice_test.go b/gasprice/gasprice_test.go index dbb99e6ab3..2be07e3be3 100644 --- a/gasprice/gasprice_test.go +++ b/gasprice/gasprice_test.go @@ -171,7 +171,7 @@ func TestGasHelper_MaxPriorityFeePerGas(t *testing.T) { func createTestBlocks(t *testing.T, numOfBlocks int) *backendMock { t.Helper() - backend := &backendMock{blocks: make(map[types.Hash]*types.Block)} + backend := &backendMock{blocks: make(map[types.Hash]*types.Block), blocksByNumber: make(map[uint64]*types.Block)} genesis := &types.Block{ Header: &types.Header{ Number: 0, @@ -181,6 +181,7 @@ func createTestBlocks(t *testing.T, numOfBlocks int) *backendMock { }, } backend.blocks[genesis.Hash()] = genesis + backend.blocksByNumber[genesis.Number()] = genesis currentBlock := genesis @@ -193,7 +194,7 @@ func createTestBlocks(t *testing.T, numOfBlocks int) *backendMock { ParentHash: currentBlock.Hash(), }, } - + backend.blocksByNumber[block.Number()] = block backend.blocks[block.Hash()] = block currentBlock = block } diff --git a/jsonrpc/eth_endpoint.go b/jsonrpc/eth_endpoint.go index 33b3d4ceca..19236cd127 100644 --- a/jsonrpc/eth_endpoint.go +++ b/jsonrpc/eth_endpoint.go @@ -802,14 +802,8 @@ func (e *Eth) MaxPriorityFeePerGas() (interface{}, error) { return argBigPtr(priorityFee), nil } -func (e *Eth) FeeHistory(blockCount uint64, newestBlock uint64, rewardPercentiles []float64) (interface{}, error) { - type feeHistoryResult struct { - OldestBlock argUint64 - BaseFeePerGas []argUint64 - GasUsedRatio []argUint64 - Reward [][]argUint64 - } +func (e *Eth) FeeHistory(blockCount uint64, newestBlock uint64, rewardPercentiles []float64) (interface{}, error) { // Retrieve oldestBlock, baseFeePerGas, gasUsedRatio, and reward synchronously oldestBlock, baseFeePerGas, gasUsedRatio, reward, err := e.store.FeeHistory(blockCount, newestBlock, rewardPercentiles) if err != nil { @@ -823,17 +817,17 @@ func (e *Eth) FeeHistory(blockCount uint64, newestBlock uint64, rewardPercentile // Process baseFeePerGas asynchronously go func() { - baseFeePerGasCh <- convertToArgUint64Slice(*baseFeePerGas) + baseFeePerGasCh <- convertToArgUint64Slice(baseFeePerGas) }() // Process gasUsedRatio asynchronously go func() { - gasUsedRatioCh <- convertFloat64SliceToArgUint64Slice(*gasUsedRatio) + gasUsedRatioCh <- convertFloat64SliceToArgUint64Slice(gasUsedRatio) }() // Process reward asynchronously go func() { - rewardCh <- convertToArgUint64SliceSlice(*reward) + rewardCh <- convertToArgUint64SliceSlice(reward) }() // Wait for the processed slices from goroutines @@ -842,7 +836,7 @@ func (e *Eth) FeeHistory(blockCount uint64, newestBlock uint64, rewardPercentile rewardResult := <-rewardCh result := &feeHistoryResult{ - OldestBlock: argUint64(*oldestBlock), + OldestBlock: *argUintPtr(oldestBlock), BaseFeePerGas: baseFeePerGasResult, GasUsedRatio: gasUsedRatioResult, Reward: rewardResult, @@ -850,30 +844,3 @@ func (e *Eth) FeeHistory(blockCount uint64, newestBlock uint64, rewardPercentile return result, nil } - -func convertToArgUint64Slice(slice []uint64) []argUint64 { - argSlice := make([]argUint64, len(slice)) - for i, value := range slice { - argSlice[i] = argUint64(value) - } - - return argSlice -} - -func convertToArgUint64SliceSlice(slice [][]uint64) [][]argUint64 { - argSlice := make([][]argUint64, len(slice)) - for i, value := range slice { - argSlice[i] = convertToArgUint64Slice(value) - } - - return argSlice -} - -func convertFloat64SliceToArgUint64Slice(slice []float64) []argUint64 { - argSlice := make([]argUint64, len(slice)) - for i, value := range slice { - argSlice[i] = argUint64(value) - } - - return argSlice -} diff --git a/jsonrpc/types.go b/jsonrpc/types.go index 2e2a5e3f57..b87714a990 100644 --- a/jsonrpc/types.go +++ b/jsonrpc/types.go @@ -344,3 +344,37 @@ type progression struct { CurrentBlock argUint64 `json:"currentBlock"` HighestBlock argUint64 `json:"highestBlock"` } + +type feeHistoryResult struct { + OldestBlock argUint64 + BaseFeePerGas []argUint64 + GasUsedRatio []argUint64 + Reward [][]argUint64 +} + +func convertToArgUint64Slice(slice []uint64) []argUint64 { + argSlice := make([]argUint64, len(slice)) + for i, value := range slice { + argSlice[i] = argUint64(value) + } + + return argSlice +} + +func convertToArgUint64SliceSlice(slice [][]uint64) [][]argUint64 { + argSlice := make([][]argUint64, len(slice)) + for i, value := range slice { + argSlice[i] = convertToArgUint64Slice(value) + } + + return argSlice +} + +func convertFloat64SliceToArgUint64Slice(slice []float64) []argUint64 { + argSlice := make([]argUint64, len(slice)) + for i, value := range slice { + argSlice[i] = argUint64(value) + } + + return argSlice +} From 66f12bec41f1c24ac6a6618319b5526ef5b799c9 Mon Sep 17 00:00:00 2001 From: owen Date: Wed, 28 Jun 2023 13:48:53 -0400 Subject: [PATCH 15/17] happy tests --- gasprice/feehistory_test.go | 158 +++++++++++++++++++++++++++++++++++- gasprice/gasprice_test.go | 1 + 2 files changed, 155 insertions(+), 4 deletions(-) diff --git a/gasprice/feehistory_test.go b/gasprice/feehistory_test.go index 17ebe56a9b..1182160dc4 100644 --- a/gasprice/feehistory_test.go +++ b/gasprice/feehistory_test.go @@ -1,11 +1,17 @@ package gasprice import ( + "math/rand" "testing" + "time" + "github.com/0xPolygon/polygon-edge/chain" + "github.com/0xPolygon/polygon-edge/crypto" + "github.com/0xPolygon/polygon-edge/helper/tests" "github.com/0xPolygon/polygon-edge/types" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "github.com/umbracle/ethgo" ) func TestGasHelper_FeeHistory(t *testing.T) { @@ -42,7 +48,7 @@ func TestGasHelper_FeeHistory(t *testing.T) { }, }, { - Name: "Block Range < 1", + Name: "blockRange < 1", Error: true, BlockRange: 0, NewestBlock: 30, @@ -68,12 +74,49 @@ func TestGasHelper_FeeHistory(t *testing.T) { }, { Name: "rewardPercentile not set", - BlockRange: 10, + BlockRange: 5, NewestBlock: 30, RewardPercentiles: []float64{}, - ExpectedOldestBlock: 21, + ExpectedOldestBlock: 26, ExpectedBaseFeePerGas: []uint64{ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + chain.GenesisBaseFee, + chain.GenesisBaseFee, + chain.GenesisBaseFee, + chain.GenesisBaseFee, + chain.GenesisBaseFee, + chain.GenesisBaseFee, + }, + ExpectedGasUsedRatio: []float64{ + 0, 0, 0, 0, 0, + }, + ExpectedRewards: [][]uint64{ + nil, nil, nil, nil, nil, + }, + GetBackend: func() Blockchain { + backend := createTestBlocks(t, 30) + createTestTxs(t, backend, 5, 500) + + return backend + }, + }, + { + Name: "blockRange > newestBlock", + BlockRange: 20, + NewestBlock: 10, + RewardPercentiles: []float64{}, + ExpectedOldestBlock: 1, + ExpectedBaseFeePerGas: []uint64{ + chain.GenesisBaseFee, + chain.GenesisBaseFee, + chain.GenesisBaseFee, + chain.GenesisBaseFee, + chain.GenesisBaseFee, + chain.GenesisBaseFee, + chain.GenesisBaseFee, + chain.GenesisBaseFee, + chain.GenesisBaseFee, + chain.GenesisBaseFee, + chain.GenesisBaseFee, }, ExpectedGasUsedRatio: []float64{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -85,6 +128,113 @@ func TestGasHelper_FeeHistory(t *testing.T) { backend := createTestBlocks(t, 30) createTestTxs(t, backend, 5, 500) + return backend + }, + }, + { + Name: "blockRange == newestBlock", + BlockRange: 10, + NewestBlock: 10, + RewardPercentiles: []float64{}, + ExpectedOldestBlock: 1, + ExpectedBaseFeePerGas: []uint64{ + chain.GenesisBaseFee, chain.GenesisBaseFee, chain.GenesisBaseFee, + chain.GenesisBaseFee, chain.GenesisBaseFee, chain.GenesisBaseFee, + chain.GenesisBaseFee, chain.GenesisBaseFee, chain.GenesisBaseFee, + chain.GenesisBaseFee, chain.GenesisBaseFee, + }, + ExpectedGasUsedRatio: []float64{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + ExpectedRewards: [][]uint64{ + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + }, + GetBackend: func() Blockchain { + backend := createTestBlocks(t, 30) + createTestTxs(t, backend, 5, 500) + + return backend + }, + }, + { + Name: "rewardPercentile requested", + BlockRange: 5, + NewestBlock: 10, + RewardPercentiles: []float64{10, 25}, + ExpectedOldestBlock: 6, + ExpectedBaseFeePerGas: []uint64{ + chain.GenesisBaseFee, + chain.GenesisBaseFee, + chain.GenesisBaseFee, + chain.GenesisBaseFee, + chain.GenesisBaseFee, + chain.GenesisBaseFee, + }, + ExpectedGasUsedRatio: []float64{ + 0, 0, 0, 0, 0, + }, + ExpectedRewards: [][]uint64{ + {0x2e90edd000, 0x2e90edd000}, + {0x2e90edd000, 0x2e90edd000}, + {0x2e90edd000, 0x2e90edd000}, + {0x2e90edd000, 0x2e90edd000}, + {0x2e90edd000, 0x2e90edd000}, + }, + GetBackend: func() Blockchain { + backend := createTestBlocks(t, 10) + rand.Seed(time.Now().UTC().UnixNano()) + + senderKey, sender := tests.GenerateKeyAndAddr(t) + + for _, b := range backend.blocksByNumber { + signer := crypto.NewSigner(backend.Config().Forks.At(b.Number()), + uint64(backend.Config().ChainID)) + + b.Transactions = make([]*types.Transaction, 3) + b.Header.Miner = sender.Bytes() + + for i := 0; i < 3; i++ { + tx := &types.Transaction{ + From: sender, + Value: ethgo.Ether(1), + To: &types.ZeroAddress, + Type: types.DynamicFeeTx, + GasTipCap: ethgo.Gwei(uint64(200)), + GasFeeCap: ethgo.Gwei(uint64(200 + 200)), + } + + tx, err := signer.SignTx(tx, senderKey) + require.NoError(t, err) + b.Transactions[i] = tx + } + } + + return backend + }, + }, + { + Name: "BaseFeePerGas sanity check", + BlockRange: 5, + NewestBlock: 10, + RewardPercentiles: []float64{}, + ExpectedOldestBlock: 6, + ExpectedBaseFeePerGas: []uint64{ + chain.GenesisBaseFee, + chain.GenesisBaseFee, + chain.GenesisBaseFee, + chain.GenesisBaseFee, + chain.GenesisBaseFee, + chain.GenesisBaseFee, + }, + ExpectedGasUsedRatio: []float64{ + 0, 0, 0, 0, 0, + }, + ExpectedRewards: [][]uint64{ + nil, nil, nil, nil, nil, + }, + GetBackend: func() Blockchain { + backend := createTestBlocks(t, 10) + return backend }, }, diff --git a/gasprice/gasprice_test.go b/gasprice/gasprice_test.go index 2be07e3be3..387e48c5e6 100644 --- a/gasprice/gasprice_test.go +++ b/gasprice/gasprice_test.go @@ -192,6 +192,7 @@ func createTestBlocks(t *testing.T, numOfBlocks int) *backendMock { Hash: types.BytesToHash([]byte(fmt.Sprintf("Block %d", i))), Miner: types.ZeroAddress.Bytes(), ParentHash: currentBlock.Hash(), + BaseFee: chain.GenesisBaseFee, }, } backend.blocksByNumber[block.Number()] = block From f4acfa2360fb5048fe345f8479ac342156cbafbf Mon Sep 17 00:00:00 2001 From: owen Date: Fri, 30 Jun 2023 13:57:38 -0400 Subject: [PATCH 16/17] use FeeHistoryReturn --- gasprice/feehistory.go | 22 ++++++++++++++-------- gasprice/feehistory_test.go | 10 +++++----- gasprice/gasprice.go | 2 +- jsonrpc/eth_endpoint.go | 10 +++++----- 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/gasprice/feehistory.go b/gasprice/feehistory.go index 6960dc3844..f6589c71a6 100644 --- a/gasprice/feehistory.go +++ b/gasprice/feehistory.go @@ -35,10 +35,16 @@ type txGasAndReward struct { reward *big.Int } -func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPercentiles []float64) ( - uint64, []uint64, []float64, [][]uint64, error) { +type FeeHistoryReturn struct { + OldestBlock uint64 + BaseFeePerGas []uint64 + GasUsedRatio []float64 + Reward [][]uint64 +} + +func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPercentiles []float64) (*FeeHistoryReturn, error) { if blockCount < 1 { - return 0, nil, nil, nil, ErrBlockCount + return &FeeHistoryReturn{0, nil, nil, nil}, ErrBlockCount } if newestBlock > g.backend.Header().Number { @@ -55,11 +61,11 @@ func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPerc for i, p := range rewardPercentiles { if p < 0 || p > 100 { - return 0, nil, nil, nil, ErrInvalidPercentile + return &FeeHistoryReturn{0, nil, nil, nil}, ErrInvalidPercentile } if i > 0 && p < rewardPercentiles[i-1] { - return 0, nil, nil, nil, ErrInvalidPercentile + return &FeeHistoryReturn{0, nil, nil, nil}, ErrInvalidPercentile } } @@ -85,7 +91,7 @@ func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPerc if p, ok := g.historyCache.Get(cacheKey); ok { processedFee, isOk := p.(*processedFees) if !isOk { - return 0, nil, nil, nil, errors.New("could not convert catched processed fee") + return &FeeHistoryReturn{0, nil, nil, nil}, errors.New("could not convert catched processed fee") } baseFeePerGas[i-oldestBlock] = processedFee.baseFee @@ -97,7 +103,7 @@ func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPerc block, ok := g.backend.GetBlockByNumber(i, true) if !ok { - return 0, nil, nil, nil, ErrBlockNotFound + return &FeeHistoryReturn{0, nil, nil, nil}, ErrBlockNotFound } baseFeePerGas[i-oldestBlock] = block.Header.BaseFee @@ -161,5 +167,5 @@ func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPerc baseFeePerGas[blockCount] = g.backend.Header().BaseFee - return oldestBlock, baseFeePerGas, gasUsedRatio, reward, nil + return &FeeHistoryReturn{oldestBlock, baseFeePerGas, gasUsedRatio, reward}, nil } diff --git a/gasprice/feehistory_test.go b/gasprice/feehistory_test.go index 1182160dc4..124869c9e2 100644 --- a/gasprice/feehistory_test.go +++ b/gasprice/feehistory_test.go @@ -248,16 +248,16 @@ func TestGasHelper_FeeHistory(t *testing.T) { backend := tc.GetBackend() gasHelper, err := NewGasHelper(DefaultGasHelperConfig, backend) require.NoError(t, err) - oldestBlock, baseFeePerGas, gasUsedRatio, rewards, err := gasHelper.FeeHistory(tc.BlockRange, tc.NewestBlock, tc.RewardPercentiles) + history, err := gasHelper.FeeHistory(tc.BlockRange, tc.NewestBlock, tc.RewardPercentiles) if tc.Error { require.Error(t, err) } else { require.NoError(t, err) - require.Equal(t, tc.ExpectedOldestBlock, oldestBlock) - require.Equal(t, tc.ExpectedBaseFeePerGas, baseFeePerGas) - require.Equal(t, tc.ExpectedGasUsedRatio, gasUsedRatio) - require.Equal(t, tc.ExpectedRewards, rewards) + require.Equal(t, tc.ExpectedOldestBlock, history.OldestBlock) + require.Equal(t, tc.ExpectedBaseFeePerGas, history.BaseFeePerGas) + require.Equal(t, tc.ExpectedGasUsedRatio, history.GasUsedRatio) + require.Equal(t, tc.ExpectedRewards, history.Reward) } }) } diff --git a/gasprice/gasprice.go b/gasprice/gasprice.go index 080add4a8b..ed6f3680b6 100644 --- a/gasprice/gasprice.go +++ b/gasprice/gasprice.go @@ -56,7 +56,7 @@ type GasStore interface { // MaxPriorityFeePerGas calculates the priority fee needed for transaction to be included in a block MaxPriorityFeePerGas() (*big.Int, error) // FeeHistory returns the collection of historical gas information - FeeHistory(uint64, uint64, []float64) (uint64, []uint64, []float64, [][]uint64, error) + FeeHistory(uint64, uint64, []float64) (*FeeHistoryReturn, error) } var _ GasStore = (*GasHelper)(nil) diff --git a/jsonrpc/eth_endpoint.go b/jsonrpc/eth_endpoint.go index 19236cd127..d8f510db44 100644 --- a/jsonrpc/eth_endpoint.go +++ b/jsonrpc/eth_endpoint.go @@ -805,7 +805,7 @@ func (e *Eth) MaxPriorityFeePerGas() (interface{}, error) { func (e *Eth) FeeHistory(blockCount uint64, newestBlock uint64, rewardPercentiles []float64) (interface{}, error) { // Retrieve oldestBlock, baseFeePerGas, gasUsedRatio, and reward synchronously - oldestBlock, baseFeePerGas, gasUsedRatio, reward, err := e.store.FeeHistory(blockCount, newestBlock, rewardPercentiles) + history, err := e.store.FeeHistory(blockCount, newestBlock, rewardPercentiles) if err != nil { return nil, err } @@ -817,17 +817,17 @@ func (e *Eth) FeeHistory(blockCount uint64, newestBlock uint64, rewardPercentile // Process baseFeePerGas asynchronously go func() { - baseFeePerGasCh <- convertToArgUint64Slice(baseFeePerGas) + baseFeePerGasCh <- convertToArgUint64Slice(history.BaseFeePerGas) }() // Process gasUsedRatio asynchronously go func() { - gasUsedRatioCh <- convertFloat64SliceToArgUint64Slice(gasUsedRatio) + gasUsedRatioCh <- convertFloat64SliceToArgUint64Slice(history.GasUsedRatio) }() // Process reward asynchronously go func() { - rewardCh <- convertToArgUint64SliceSlice(reward) + rewardCh <- convertToArgUint64SliceSlice(history.Reward) }() // Wait for the processed slices from goroutines @@ -836,7 +836,7 @@ func (e *Eth) FeeHistory(blockCount uint64, newestBlock uint64, rewardPercentile rewardResult := <-rewardCh result := &feeHistoryResult{ - OldestBlock: *argUintPtr(oldestBlock), + OldestBlock: *argUintPtr(history.OldestBlock), BaseFeePerGas: baseFeePerGasResult, GasUsedRatio: gasUsedRatioResult, Reward: rewardResult, From 17522f8a8a6a1be0dc68bae79d2de611da40b369 Mon Sep 17 00:00:00 2001 From: owen Date: Mon, 3 Jul 2023 11:13:38 -0400 Subject: [PATCH 17/17] lint fix --- gasprice/feehistory.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gasprice/feehistory.go b/gasprice/feehistory.go index f6589c71a6..89b941b3f9 100644 --- a/gasprice/feehistory.go +++ b/gasprice/feehistory.go @@ -42,7 +42,8 @@ type FeeHistoryReturn struct { Reward [][]uint64 } -func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPercentiles []float64) (*FeeHistoryReturn, error) { +func (g *GasHelper) FeeHistory(blockCount uint64, newestBlock uint64, rewardPercentiles []float64) ( + *FeeHistoryReturn, error) { if blockCount < 1 { return &FeeHistoryReturn{0, nil, nil, nil}, ErrBlockCount }