Skip to content

Commit

Permalink
Merge branch 'release/galactica' into feat/hex-values
Browse files Browse the repository at this point in the history
  • Loading branch information
freemanzMrojo authored Feb 25, 2025
2 parents 97a557a + 83e0035 commit 24f3b0b
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 24 deletions.
29 changes: 28 additions & 1 deletion api/doc/thor.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -763,7 +763,7 @@ paths:
description: |
Retrieve historical fee data, including the gas used ratio per block.
Will return only fees for accessible blocks.
Max accessible blocks = BestBlock - api-backtrace-limit-flag .
Max accessible blocks = BestBlock - api-backtrace-limit-flag.
api-backtrace-limit defaults to 1000 blocks.
parameters:
- $ref: '#/components/parameters/BlockCountInQuery'
Expand All @@ -782,6 +782,21 @@ paths:
schema:
type: string
example: 'Invalid revision'

/fees/priority:
get:
tags:
- Fees
summary: Suggest a priority fee for a transaction to be included in a block
description: |
This endpoint allows you to estimate the priority fee for a transaction to be included in a block.
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/GetFeesPriorityResponse'

components:
schemas:
Expand Down Expand Up @@ -1280,6 +1295,18 @@ components:
- 0.75
- 0.22

GetFeesPriorityResponse:
type: object
title: GetFeesPriorityResponse
allOf:
- properties:
maxPriorityFeePerGas:
type: string
format: hex
description: |
The suggested maximum priority fee per gas as an hexadecimal string.
example: '0x98'

TxMeta:
title: TxMeta
type: object
Expand Down
119 changes: 97 additions & 22 deletions api/fees/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,47 @@
package fees

import (
"container/heap"
"math/big"

"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/vechain/thor/v2/cache"
"github.com/vechain/thor/v2/chain"
"github.com/vechain/thor/v2/thor"
"github.com/vechain/thor/v2/tx"
)

const (
priorityNumberOfTxsPerBlock = 3
priorityPercentile = 60
)

// minPriorityHeap is a min-heap of priority fee values.
type minPriorityHeap []*big.Int

func (h minPriorityHeap) Len() int { return len(h) }
func (h minPriorityHeap) Less(i, j int) bool { return h[i].Cmp(h[j]) < 0 }
func (h minPriorityHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }

func (h *minPriorityHeap) Push(x interface{}) {
*h = append(*h, x.(*big.Int))
}

func (h *minPriorityHeap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[0 : n-1]
return x
}

type FeeCacheEntry struct {
parentBlockID thor.Bytes32
baseFee *hexutil.Big
gasUsedRatio float64
parentBlockID thor.Bytes32
priorityFees *minPriorityHeap
}

type FeesData struct {
repo *chain.Repository
cache *cache.PrioCache
Expand All @@ -38,38 +66,85 @@ func getBaseFee(baseFee *big.Int) *hexutil.Big {
return (*hexutil.Big)(big.NewInt(0))
}

// resolveRange resolves the base fees and gas used ratios for the given block range.
// resolveRange resolves the base fees, gas used ratios and priority fees for the given block range.
// Assumes that the boundaries (newest block - block count) are correct and validated beforehand.
func (fd *FeesData) resolveRange(newestBlockSummary *chain.BlockSummary, blockCount uint32) (thor.Bytes32, []*hexutil.Big, []float64, error) {
func (fd *FeesData) resolveRange(newestBlockSummary *chain.BlockSummary, blockCount uint32) (thor.Bytes32, []*hexutil.Big, []float64, *minPriorityHeap, error) {
newestBlockID := newestBlockSummary.Header.ID()

baseFees := make([]*hexutil.Big, blockCount)
gasUsedRatios := make([]float64, blockCount)
priorityFees := &minPriorityHeap{}
heap.Init(priorityFees)

var oldestBlockID thor.Bytes32
for i := blockCount; i > 0; i-- {
oldestBlockID = newestBlockID
fees, _, found := fd.cache.Get(newestBlockID)
if !found {
// retrieve from db + retro-populate cache
blockSummary, err := fd.repo.GetBlockSummary(newestBlockID)
if err != nil {
return thor.Bytes32{}, nil, nil, err
}

header := blockSummary.Header
fees = &FeeCacheEntry{
baseFee: getBaseFee(header.BaseFee()),
gasUsedRatio: float64(header.GasUsed()) / float64(header.GasLimit()),
parentBlockID: header.ParentID(),
}
fd.cache.Set(header.ID(), fees, float64(header.Number()))
fees, err := fd.getOrLoadFees(newestBlockID)
if err != nil {
return thor.Bytes32{}, nil, nil, nil, err
}
baseFees[i-1] = fees.(*FeeCacheEntry).baseFee
gasUsedRatios[i-1] = fees.(*FeeCacheEntry).gasUsedRatio
baseFees[i-1] = fees.baseFee
gasUsedRatios[i-1] = fees.gasUsedRatio
fd.updatePriorityFees(priorityFees, fees.priorityFees, priorityNumberOfTxsPerBlock*int(blockCount))

newestBlockID = fees.parentBlockID
}

return oldestBlockID, baseFees, gasUsedRatios, priorityFees, nil
}

func (fd *FeesData) getOrLoadFees(blockID thor.Bytes32) (*FeeCacheEntry, error) {
fees, _, found := fd.cache.Get(blockID)
if found {
return fees.(*FeeCacheEntry), nil
}

block, err := fd.repo.GetBlock(blockID)
if err != nil {
return nil, err
}

header := block.Header()
transactions := block.Transactions()

blockPriorityFees := &minPriorityHeap{}
heap.Init(blockPriorityFees)

for _, tx := range transactions {
maxPriorityFeePerGas := fd.effectiveMaxPriorityFeePerGas(tx, header.BaseFee())
fd.updatePriorityFees(blockPriorityFees, &minPriorityHeap{maxPriorityFeePerGas}, priorityNumberOfTxsPerBlock)
}

fees = &FeeCacheEntry{
baseFee: getBaseFee(header.BaseFee()),
gasUsedRatio: float64(header.GasUsed()) / float64(header.GasLimit()),
parentBlockID: header.ParentID(),
priorityFees: blockPriorityFees,
}
fd.cache.Set(header.ID(), fees, float64(header.Number()))

return fees.(*FeeCacheEntry), nil
}

func (fd *FeesData) effectiveMaxPriorityFeePerGas(tx *tx.Transaction, baseFee *big.Int) *big.Int {
if baseFee == nil {
return tx.MaxPriorityFeePerGas()
}
maxFeePerGas := tx.MaxFeePerGas()
maxFeePerGas.Sub(maxFeePerGas, baseFee)

newestBlockID = fees.(*FeeCacheEntry).parentBlockID
maxPriorityFeePerGas := tx.MaxPriorityFeePerGas()
if maxPriorityFeePerGas.Cmp(maxFeePerGas) < 0 {
return maxPriorityFeePerGas
}
return maxPriorityFeePerGas
}

return oldestBlockID, baseFees, gasUsedRatios, nil
func (fd *FeesData) updatePriorityFees(priorityFees, newFees *minPriorityHeap, maxLen int) {
for _, fee := range *newFees {
heap.Push(priorityFees, fee)
if priorityFees.Len() > maxLen {
heap.Pop(priorityFees)
}
}
}
37 changes: 36 additions & 1 deletion api/fees/fees.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,24 @@ package fees

import (
"math"
"math/big"
"net/http"
"strconv"

"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/vechain/thor/v2/api/utils"
"github.com/vechain/thor/v2/bft"
"github.com/vechain/thor/v2/chain"
)

const priorityNumberOfBlocks = 20

var (
priorityMinPriorityFee = big.NewInt(2)
)

type Fees struct {
data *FeesData
bft bft.Committer
Expand Down Expand Up @@ -86,7 +94,7 @@ func (f *Fees) handleGetFeesHistory(w http.ResponseWriter, req *http.Request) er
return err
}

oldestBlockRevision, baseFees, gasUsedRatios, err := f.data.resolveRange(newestBlockSummary, blockCount)
oldestBlockRevision, baseFees, gasUsedRatios, _, err := f.data.resolveRange(newestBlockSummary, blockCount)
if err != nil {
return err
}
Expand All @@ -98,10 +106,37 @@ func (f *Fees) handleGetFeesHistory(w http.ResponseWriter, req *http.Request) er
})
}

func (f *Fees) handleGetPriority(w http.ResponseWriter, _ *http.Request) error {
bestBlockSummary := f.data.repo.BestBlockSummary()
blockCount := uint32(math.Min(float64(priorityNumberOfBlocks), float64(f.backtraceLimit)))
blockCount = uint32(math.Min(float64(blockCount), float64(bestBlockSummary.Header.Number()+1)))

_, _, _, priorityFees, err := f.data.resolveRange(bestBlockSummary, blockCount)
if err != nil {
return err
}

priorityFee := (*hexutil.Big)(priorityMinPriorityFee)
if priorityFees.Len() > 0 {
priorityFeeEntry := (*priorityFees)[(priorityFees.Len()-1)*priorityPercentile/100]
if priorityFeeEntry.Cmp(priorityMinPriorityFee) > 0 {
priorityFee = (*hexutil.Big)(priorityFeeEntry)
}
}

return utils.WriteJSON(w, &FeesPriority{
MaxPriorityFeePerGas: priorityFee,
})
}

func (f *Fees) Mount(root *mux.Router, pathPrefix string) {
sub := root.PathPrefix(pathPrefix).Subrouter()
sub.Path("/history").
Methods(http.MethodGet).
Name("GET /fees/history").
HandlerFunc(utils.WrapHandlerFunc(f.handleGetFeesHistory))
sub.Path("/priority").
Methods(http.MethodGet).
Name("GET /fees/priority").
HandlerFunc(utils.WrapHandlerFunc(f.handleGetPriority))
}
18 changes: 18 additions & 0 deletions api/fees/fees_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ func TestFeesFixedSizeSameAsBacktrace(t *testing.T) {
"getFeeHistoryMoreBlocksRequestedThanAvailable": getFeeHistoryMoreBlocksRequestedThanAvailable,
"getFeeHistoryBlock0": getFeeHistoryBlock0,
"getFeeHistoryBlockCount0": getFeeHistoryBlockCount0,
"getFeePriority": getFeePriority,
} {
t.Run(name, func(t *testing.T) {
tt(t, tclient, bestchain)
Expand Down Expand Up @@ -339,3 +340,20 @@ func getFeeHistoryBlockCount0(t *testing.T, tclient *thorclient.Client, bestchai
require.NotNil(t, res)
assert.Equal(t, "invalid blockCount, it should not be 0\n", string(res))
}

func getFeePriority(t *testing.T, tclient *thorclient.Client, bestchain *chain.Chain) {
res, statusCode, err := tclient.RawHTTPClient().RawHTTPGet("/fees/priority")
require.NoError(t, err)
require.Equal(t, 200, statusCode)
require.NotNil(t, res)
var feesPriority fees.FeesPriority
if err := json.Unmarshal(res, &feesPriority); err != nil {
t.Fatal(err)
}

expectedFeesPriority := fees.FeesPriority{
MaxPriorityFeePerGas: (*hexutil.Big)(big.NewInt(100)),
}

assert.Equal(t, expectedFeesPriority, feesPriority)
}
4 changes: 4 additions & 0 deletions api/fees/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@ type FeesHistory struct {
BaseFees []*hexutil.Big `json:"baseFees"`
GasUsedRatios []float64 `json:"gasUsedRatios"`
}

type FeesPriority struct {
MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"`
}

0 comments on commit 24f3b0b

Please sign in to comment.