Skip to content

fix: calculate gas price info #143

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions serve/filters/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,8 @@ func (f *Manager) subscribeToEvents() {
f.subscriptions.sendEvent(filterSubscription.NewHeadsEvent, newBlock.Block)

// Send an event to the `newGasPrice` subscription when creating a block with transactions
if len(newBlock.Block.Txs) > 0 {
f.subscriptions.sendEvent(filterSubscription.NewGasPriceEvent, newBlock.Block)
if len(newBlock.Results) > 0 {
f.subscriptions.sendEvent(filterSubscription.NewGasPriceEvent, newBlock.Results)
}

for _, txResult := range newBlock.Results {
Expand Down
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 {
block, ok := data.(*types.Block)
txResults, ok := data.([]*types.TxResult)
if !ok {
return fmt.Errorf("unable to cast txResult, %s", data)
}

gasPrices, err := methods.GetGasPricesByBlock(block)
gasPrices, err := methods.GetGasPricesByTxResults(txResults)
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions serve/filters/subscription/gas_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func TestGasPriceSubscription_WriteResponse(t *testing.T) {
var (
capturedWrite any

mockBlock = &types.Block{}
mockTxResults = []*types.TxResult{}
mockGasPrices = []*methods.GasPrice{}
)

Expand All @@ -36,7 +36,7 @@ func TestGasPriceSubscription_WriteResponse(t *testing.T) {
gasPriceSubscription := NewGasPriceSubscription(mockConn)

// Write the response
require.NoError(t, gasPriceSubscription.WriteResponse("", mockBlock))
require.NoError(t, gasPriceSubscription.WriteResponse("", mockTxResults))

// Make sure the captured data matches
require.NotNil(t, capturedWrite)
Expand Down
13 changes: 8 additions & 5 deletions serve/handlers/gas/gas.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package gas

import (
"fmt"
"math"
"strconv"

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

defer it.Close()

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

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

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

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

gasPrices, err := methods.GetGasPricesByBlocks(blocks)
gasPrices, err := methods.GetGasPricesByTxResults(txResults)
if err != nil {
return nil, err
}
Expand Down
20 changes: 11 additions & 9 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 blockIterator func(uint64, uint64) (storage.Iterator[*types.Block], error)
type txReverseIterator func(uint64, uint64, uint32, uint32) (storage.Iterator[*types.TxResult], error)

type mockStorage struct {
getLatestHeightFn getLatestHeight
blockIteratorFn blockIterator
getLatestHeightFn getLatestHeight
txReverseIteratorFn txReverseIterator
}

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

func (m *mockStorage) BlockIterator(
fromBlockNum,
toBlockNum uint64,
) (storage.Iterator[*types.Block], error) {
if m.blockIteratorFn != nil {
return m.blockIteratorFn(fromBlockNum, toBlockNum)
func (m *mockStorage) TxReverseIterator(
fromTxNum,
toTxNum uint64,
fromIndex,
toIndex uint32,
) (storage.Iterator[*types.TxResult], error) {
if m.txReverseIteratorFn != nil {
return m.txReverseIteratorFn(fromTxNum, toTxNum, fromIndex, toIndex)
}

return nil, nil
Expand Down
10 changes: 8 additions & 2 deletions serve/handlers/gas/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ type Storage interface {
// GetLatestHeight returns the latest block height from the storage
GetLatestHeight() (uint64, error)

// BlockIterator iterates over Blocks, limiting the results to be between the provided block numbers
BlockIterator(fromBlockNum, toBlockNum uint64) (storage.Iterator[*types.Block], error)
// TxIterator iterates over transactions, limiting the results to be between the provided block numbers
// and transaction indexes
TxReverseIterator(
fromBlockNum,
toBlockNum uint64,
fromTxIndex,
toTxIndex uint32,
) (storage.Iterator[*types.TxResult], error)
}
82 changes: 45 additions & 37 deletions serve/methods/gas.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,82 +9,90 @@ import (
)

type gasFeeTotalInfo struct {
Low int64
High int64
TotalAmount int64
Low float64
High float64
TotalAmount float64
TotalCount int64
}

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

return GetGasPricesByBlocks(blocks)
return GetGasPricesByTxResults(txResults)
}

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

for _, block := range blocks {
blockGasFeeInfo := calculateGasFeePerBlock(block)
txResultGasFeeInfo := calculateGasFeePerTxResults(txResults)

for denom, gasFeeInfo := range blockGasFeeInfo {
_, exists := gasFeeInfoMap[denom]
if !exists {
gasFeeInfoMap[denom] = &gasFeeTotalInfo{}
}
for denom, gasFeeInfo := range txResultGasFeeInfo {
_, exists := gasFeeInfoMap[denom]
if !exists {
gasFeeInfoMap[denom] = &gasFeeTotalInfo{}
}

err := modifyAggregatedInfo(gasFeeInfoMap[denom], gasFeeInfo)
if err != nil {
return nil, err
}
err := modifyAggregatedInfo(gasFeeInfoMap[denom], gasFeeInfo)
if err != nil {
return nil, err
}
}

return calculateGasPrices(gasFeeInfoMap), nil
}

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

for _, t := range block.Txs {
for _, t := range txResults {
if t.Response.IsErr() ||
t.Response.GasUsed == 0 ||
t.Height <= 0 {
continue
}

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

denom := stdTx.Fee.GasFee.Denom
amount := stdTx.Fee.GasFee.Amount
gasFeeAmount := stdTx.Fee.GasFee.Amount
GasUsedAmount := t.Response.GasUsed

// Calculate the gas price (gasFee / gasUsed)
gasPrice := float64(gasFeeAmount) / float64(GasUsedAmount)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The gas used has no influence on the gas price, actually. The chain takes everything the user specified in gasFee as part of the tx, even though the tx itself took less gas than what the user specified in GasUsedAmount

See:
gnolang/gno#3805


info := gasFeeInfo[denom]
if info == nil {
info = &gasFeeTotalInfo{}
gasFeeInfo[denom] = info
}

info.Low = minWithDefault(info.Low, amount)
info.High = max(info.High, amount)
info.TotalAmount += amount
info.Low = minWithDefault(info.Low, gasPrice)
info.High = max(info.High, gasPrice)
info.TotalAmount += gasPrice
info.TotalCount++
}

return gasFeeInfo
}

// modifyAggregatedInfo updates the aggregated gas fee statistics by merging the block's statistics.
func modifyAggregatedInfo(currentInfo, blockInfo *gasFeeTotalInfo) error {
// modifyAggregatedInfo updates the aggregated gas fee statistics by merging the txResult's statistics.
func modifyAggregatedInfo(currentInfo, txResultInfo *gasFeeTotalInfo) error {
if currentInfo == nil {
return fmt.Errorf("not initialized aggregated data")
}

currentInfo.Low = minWithDefault(currentInfo.Low, blockInfo.Low)
currentInfo.High = max(currentInfo.High, blockInfo.High)
currentInfo.TotalAmount += blockInfo.TotalAmount / blockInfo.TotalCount
currentInfo.Low = minWithDefault(currentInfo.Low, txResultInfo.Low)
currentInfo.High = max(currentInfo.High, txResultInfo.High)
currentInfo.TotalAmount += txResultInfo.TotalAmount / float64(txResultInfo.TotalCount)
currentInfo.TotalCount++

return nil
Expand All @@ -102,7 +110,7 @@ func calculateGasPrices(gasFeeInfoMap map[string]*gasFeeTotalInfo) []*GasPrice {
gasPrices = append(gasPrices, &GasPrice{
High: info.High,
Low: info.Low,
Average: info.TotalAmount / info.TotalCount,
Average: info.TotalAmount / float64(info.TotalCount),
Denom: denom,
})
}
Expand All @@ -112,7 +120,7 @@ func calculateGasPrices(gasFeeInfoMap map[string]*gasFeeTotalInfo) []*GasPrice {

// min calculates the smaller of two values, or returns the new value
// if the current value is uninitialized (0).
func minWithDefault(current, newValue int64) int64 {
func minWithDefault(current, newValue float64) float64 {
if current <= 0 {
return newValue
}
Expand Down
Loading
Loading