Skip to content

Commit

Permalink
feat: calculate gas per block
Browse files Browse the repository at this point in the history
  • Loading branch information
jinoosss committed Nov 27, 2024
1 parent ba73018 commit e512a29
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 76 deletions.
4 changes: 2 additions & 2 deletions serve/filters/subscription/gas.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ func (b *GasPriceSubscription) GetType() events.Type {
}

func (b *GasPriceSubscription) WriteResponse(id string, data any) error {
txResults, ok := data.([]*types.TxResult)
block, ok := data.(*types.Block)
if !ok {
return fmt.Errorf("unable to cast txResult, %s", data)
}

gasPrices, err := methods.GetGasPricesByTxResults(txResults)
gasPrices, err := methods.GetGasPricesByBlock(block)
if err != nil {
return err
}
Expand Down
13 changes: 5 additions & 8 deletions serve/handlers/gas/gas.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package gas

import (
"fmt"
"math"
"strconv"

"github.com/gnolang/gno/tm2/pkg/bft/types"
Expand Down Expand Up @@ -58,34 +57,32 @@ func (h *Handler) GetGasPriceHandler(
func (h *Handler) getGasPriceBy(fromBlockNum, toBlockNum uint64) ([]*methods.GasPrice, error) {
it, err := h.
storage.
TxIterator(
BlockIterator(
fromBlockNum,
toBlockNum,
0,
math.MaxUint32,
)
if err != nil {
return nil, gqlerror.Wrap(err)
}

defer it.Close()

txs := make([]*types.TxResult, 0)
blocks := make([]*types.Block, 0)

for {
if !it.Next() {
break
}

tx, itErr := it.Value()
block, itErr := it.Value()
if itErr != nil {
return nil, err
}

txs = append(txs, tx)
blocks = append(blocks, block)
}

gasPrices, err := methods.GetGasPricesByTxResults(txs)
gasPrices, err := methods.GetGasPricesByBlocks(blocks)
if err != nil {
return nil, err
}
Expand Down
3 changes: 1 addition & 2 deletions serve/handlers/gas/gas_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"github.com/stretchr/testify/require"
)

func TestGetTx_InvalidParams(t *testing.T) {
func TestGetGasPriceHandler_InvalidParams(t *testing.T) {
t.Parallel()

testTable := []struct {
Expand All @@ -35,7 +35,6 @@ func TestGetTx_InvalidParams(t *testing.T) {
assert.Nil(t, response)

require.NotNil(t, err)

assert.Equal(t, spec.InvalidParamsErrorCode, err.Code)
})
}
Expand Down
14 changes: 6 additions & 8 deletions serve/handlers/gas/mocks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import (

type getLatestHeight func() (uint64, error)

type txIterator func(uint64, uint64, uint32, uint32) (storage.Iterator[*types.TxResult], error)
type blockIterator func(uint64, uint64) (storage.Iterator[*types.Block], error)

type mockStorage struct {
getLatestHeightFn getLatestHeight
txIteratorFn txIterator
blockIteratorFn blockIterator
}

func (m *mockStorage) GetLatestHeight() (uint64, error) {
Expand All @@ -22,14 +22,12 @@ func (m *mockStorage) GetLatestHeight() (uint64, error) {
return 0, nil
}

func (m *mockStorage) TxIterator(
func (m *mockStorage) BlockIterator(
fromBlockNum,
toBlockNum uint64,
fromTxIndex,
toTxIndex uint32,
) (storage.Iterator[*types.TxResult], error) {
if m.txIteratorFn != nil {
return m.txIteratorFn(fromBlockNum, toBlockNum, fromTxIndex, toTxIndex)
) (storage.Iterator[*types.Block], error) {
if m.blockIteratorFn != nil {
return m.blockIteratorFn(fromBlockNum, toBlockNum)
}

return nil, nil
Expand Down
11 changes: 3 additions & 8 deletions serve/handlers/gas/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,9 @@ import (
)

type Storage interface {
// GetTx returns specified tx from permanent storage
// GetLatestHeight returns the latest block height from the storage
GetLatestHeight() (uint64, error)

// GetTxByHash fetches the tx using the transaction hash
TxIterator(
fromBlockNum,
toBlockNum uint64,
fromTxIndex,
toTxIndex uint32,
) (storage.Iterator[*types.TxResult], error)
// BlockIterator iterates over Blocks, limiting the results to be between the provided block numbers
BlockIterator(fromBlockNum, toBlockNum uint64) (storage.Iterator[*types.Block], error)
}
105 changes: 81 additions & 24 deletions serve/methods/gas.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,50 +13,107 @@ type gasFeeTotalInfo struct {
TotalCount int64
}

func GetGasPricesByTxResults(txs []*types.TxResult) ([]*GasPrice, error) {
// GetGasPricesByBlock calculates the gas price statistics (low, high, average)
// for a single block.
func GetGasPricesByBlock(block *types.Block) ([]*GasPrice, error) {
blocks := []*types.Block{block}

return GetGasPricesByBlocks(blocks)
}

// GetGasPricesByBlocks calculates the gas price statistics (low, high, average)
// for multiple blocks.
func GetGasPricesByBlocks(blocks []*types.Block) ([]*GasPrice, error) {
gasFeeInfoMap := make(map[string]*gasFeeTotalInfo)

for _, t := range txs {
for _, block := range blocks {
blockGasFeeInfo := calculateGasFeePerBlock(block)

for denom, gasFeeInfo := range blockGasFeeInfo {
currentGasFeeInfo := gasFeeInfoMap[denom]
gasFeeInfoMap[denom] = calculateGasFee(currentGasFeeInfo, gasFeeInfo)
}
}

return calculateGasPrices(gasFeeInfoMap), nil
}

// calculateGasFeePerBlock processes all transactions in a single block to compute
// gas fee statistics (low, high, total amount, total count) for each gas fee denomination.
func calculateGasFeePerBlock(block *types.Block) map[string]*gasFeeTotalInfo {
gasFeeInfo := make(map[string]*gasFeeTotalInfo)

for _, t := range block.Txs {
var stdTx std.Tx
if err := amino.Unmarshal(t.Tx, &stdTx); err != nil {
if err := amino.Unmarshal(t, &stdTx); err != nil {
continue
}

gasFeeDenom := stdTx.Fee.GasFee.Denom
gasFeeAmount := stdTx.Fee.GasFee.Amount
denom := stdTx.Fee.GasFee.Denom
amount := stdTx.Fee.GasFee.Amount

if _, exists := gasFeeInfoMap[gasFeeDenom]; !exists {
gasFeeInfoMap[gasFeeDenom] = &gasFeeTotalInfo{}
info := gasFeeInfo[denom]
if info == nil {
info = &gasFeeTotalInfo{}
gasFeeInfo[denom] = info
}

if gasFeeInfoMap[gasFeeDenom].Low == 0 || gasFeeInfoMap[gasFeeDenom].Low > gasFeeAmount {
gasFeeInfoMap[gasFeeDenom].Low = gasFeeAmount
}
info.Low = min(info.Low, amount)
info.High = max(info.High, amount)
info.TotalAmount += amount
info.TotalCount++
}

if gasFeeInfoMap[gasFeeDenom].High == 0 || gasFeeInfoMap[gasFeeDenom].High < gasFeeAmount {
gasFeeInfoMap[gasFeeDenom].High = gasFeeAmount
}
return gasFeeInfo
}

gasFeeInfoMap[gasFeeDenom].TotalAmount += gasFeeAmount
gasFeeInfoMap[gasFeeDenom].TotalCount++
// calculateGasFee merges the gas fee statistics from a block into the global statistics.
func calculateGasFee(currentInfo *gasFeeTotalInfo, blockInfo *gasFeeTotalInfo) *gasFeeTotalInfo {

Check failure on line 71 in serve/methods/gas.go

View workflow job for this annotation

GitHub Actions / Go Linter / lint

paramTypeCombine: func(currentInfo *gasFeeTotalInfo, blockInfo *gasFeeTotalInfo) *gasFeeTotalInfo could be replaced with func(currentInfo, blockInfo *gasFeeTotalInfo) *gasFeeTotalInfo (gocritic)
if currentInfo == nil {
currentInfo = &gasFeeTotalInfo{}
}

gasPrices := make([]*GasPrice, 0)
currentInfo.Low = min(currentInfo.Low, blockInfo.Low)
currentInfo.High = max(currentInfo.High, blockInfo.High)
currentInfo.TotalAmount += blockInfo.TotalAmount / blockInfo.TotalCount
currentInfo.TotalCount++

return currentInfo
}

// calculateGasPrices generates the final gas price statistics (low, high, average)
func calculateGasPrices(gasFeeInfoMap map[string]*gasFeeTotalInfo) []*GasPrice {
gasPrices := make([]*GasPrice, 0, len(gasFeeInfoMap))

for denom, gasFeeInfo := range gasFeeInfoMap {
if gasFeeInfo.TotalCount == 0 {
for denom, info := range gasFeeInfoMap {
if info.TotalCount == 0 {
continue
}

average := gasFeeInfo.TotalAmount / gasFeeInfo.TotalCount

gasPrices = append(gasPrices, &GasPrice{
High: gasFeeInfo.High,
Low: gasFeeInfo.Low,
Average: average,
High: info.High,
Low: info.Low,
Average: info.TotalAmount / info.TotalCount,
Denom: denom,
})
}

return gasPrices, nil
return gasPrices
}

// min calculates the smaller of two values, or returns the new value
// if the current value is uninitialized (0).
func min(current, newValue int64) int64 {

Check failure on line 106 in serve/methods/gas.go

View workflow job for this annotation

GitHub Actions / Go Linter / lint

redefines-builtin-id: redefinition of the built-in function min (revive)

Check failure on line 106 in serve/methods/gas.go

View workflow job for this annotation

GitHub Actions / Go Linter / lint

builtinShadowDecl: shadowing of predeclared identifier: min (gocritic)

Check failure on line 106 in serve/methods/gas.go

View workflow job for this annotation

GitHub Actions / Go Linter / lint

function min has same name as predeclared identifier (predeclared)
if current == 0 || newValue < current {
return newValue
}
return current

Check failure on line 110 in serve/methods/gas.go

View workflow job for this annotation

GitHub Actions / Go Linter / lint

return with no blank line before (nlreturn)

Check failure on line 110 in serve/methods/gas.go

View workflow job for this annotation

GitHub Actions / Go Linter / lint

return statements should not be cuddled if block has more than two lines (wsl)
}

// max calculates the larger of two values.
func max(current, newValue int64) int64 {

Check failure on line 114 in serve/methods/gas.go

View workflow job for this annotation

GitHub Actions / Go Linter / lint

redefines-builtin-id: redefinition of the built-in function max (revive)

Check failure on line 114 in serve/methods/gas.go

View workflow job for this annotation

GitHub Actions / Go Linter / lint

builtinShadowDecl: shadowing of predeclared identifier: max (gocritic)

Check failure on line 114 in serve/methods/gas.go

View workflow job for this annotation

GitHub Actions / Go Linter / lint

function max has same name as predeclared identifier (predeclared)
if newValue > current {
return newValue
}
return current

Check failure on line 118 in serve/methods/gas.go

View workflow job for this annotation

GitHub Actions / Go Linter / lint

return with no blank line before (nlreturn)
}
Loading

0 comments on commit e512a29

Please sign in to comment.