Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8598a72
Track and index the latest Flow fees surge factor
m-Peter Aug 22, 2025
7484cbc
Multiply eth_gasPrice with the latest surge factor
m-Peter Aug 22, 2025
453c678
Update tx submission logic to take into account the surge factor changes
m-Peter Aug 25, 2025
60fc001
Multiply eth_maxPriorityFeePerGas with the latest surge factor
m-Peter Aug 25, 2025
5561275
Move gas price calculation to FeeParameters
m-Peter Aug 25, 2025
557d5f4
Update eth_feeHistory to take into account changes to Flow fees surge…
m-Peter Aug 26, 2025
a09baeb
Logging and gas calculation improvements
m-Peter Aug 26, 2025
407106e
Track Flow fees params updates in its own subscriber service
m-Peter Aug 28, 2025
edfa90c
Merge branch 'mpeter/track-surge-factor' into mpeter/surge-factor-for…
m-Peter Aug 29, 2025
d395389
Use evmEventFilter method instead of blocksFilter for EVM event subsc…
m-Peter Aug 29, 2025
52a3ac5
Merge remote-tracking branch 'origin/main' into mpeter/surge-factor-f…
m-Peter Sep 10, 2025
0f26f75
Bootstrap surge factor using the network's current value
m-Peter Sep 10, 2025
0150b56
Merge remote-tracking branch 'origin/main' into mpeter/surge-factor-f…
m-Peter Sep 11, 2025
16d7682
Allow manual image build
turbolent Sep 11, 2025
2f59024
remove input
turbolent Sep 12, 2025
2c164b4
Merge branch 'main' into bastian/manual-image-build
m-Peter Sep 12, 2025
fee80d0
Merge pull request #879 from onflow/bastian/manual-image-build
m-Peter Sep 12, 2025
5bfb526
Merge remote-tracking branch 'origin/mpeter/poc-index-finalized-block…
m-Peter Sep 15, 2025
bc8ebbb
Merge remote-tracking branch 'origin/main' into mpeter/surge-factor-f…
m-Peter Sep 15, 2025
e163682
Improve eth_feeHistory block reward calculation
m-Peter Sep 15, 2025
4cb6bb7
Fallback to default fee parameters in case of error
m-Peter Sep 15, 2025
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
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
tags:
- '*'
workflow_dispatch:

env:
DOCKER_IMAGE_URL: ${{ vars.REPO_DOCKER_IMAGE_URL }}
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ generate:
mockery --dir=storage --name=BlockIndexer --output=storage/mocks
mockery --dir=storage --name=ReceiptIndexer --output=storage/mocks
mockery --dir=storage --name=TransactionIndexer --output=storage/mocks
mockery --dir=storage --name=AccountIndexer --output=storage/mocks
mockery --dir=storage --name=TraceIndexer --output=storage/mocks
mockery --dir=storage --name=FeeParametersIndexer --output=storage/mocks
mockery --all --dir=services/traces --output=services/traces/mocks
mockery --all --dir=services/ingestion --output=services/ingestion/mocks
mockery --dir=models --name=Engine --output=models/mocks
Expand Down
38 changes: 34 additions & 4 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ type BlockChainAPI struct {
blocks storage.BlockIndexer
transactions storage.TransactionIndexer
receipts storage.ReceiptIndexer
feeParameters storage.FeeParametersIndexer
indexingResumedHeight uint64
rateLimiter RateLimiter
collector metrics.Collector
Expand All @@ -97,6 +98,7 @@ func NewBlockChainAPI(
blocks storage.BlockIndexer,
transactions storage.TransactionIndexer,
receipts storage.ReceiptIndexer,
feeParameters storage.FeeParametersIndexer,
rateLimiter RateLimiter,
collector metrics.Collector,
indexingResumedHeight uint64,
Expand All @@ -108,6 +110,7 @@ func NewBlockChainAPI(
blocks: blocks,
transactions: transactions,
receipts: receipts,
feeParameters: feeParameters,
indexingResumedHeight: indexingResumedHeight,
rateLimiter: rateLimiter,
collector: collector,
Expand Down Expand Up @@ -179,7 +182,13 @@ func (b *BlockChainAPI) SendRawTransaction(
return common.Hash{}, err
}

id, err := b.evm.SendRawTransaction(ctx, input)
feeParams, err := b.feeParameters.Get()
if err != nil {
b.logger.Warn().Err(err).Msg("fee parameters unavailable; falling back to base gas price")
feeParams = models.DefaultFeeParameters()
}

id, err := b.evm.SendRawTransaction(ctx, input, feeParams)
if err != nil {
return handleError[common.Hash](err, l, b.collector)
}
Expand Down Expand Up @@ -814,8 +823,17 @@ func (b *BlockChainAPI) FeeHistory(
maxCount := min(uint64(blockCount), lastBlockNumber)

blockRewards := make([]*hexutil.Big, len(rewardPercentiles))
gasPrice := b.config.GasPrice

feeParams, err := b.feeParameters.Get()
if err != nil {
b.logger.Warn().Err(err).Msg("fee parameters unavailable; falling back to base gas price")
} else {
gasPrice = feeParams.CalculateGasPrice(b.config.GasPrice)
}

for i := range rewardPercentiles {
blockRewards[i] = (*hexutil.Big)(b.config.GasPrice)
blockRewards[i] = (*hexutil.Big)(gasPrice)
}

for i := maxCount; i >= uint64(1); i-- {
Expand Down Expand Up @@ -1014,7 +1032,13 @@ func (b *BlockChainAPI) Coinbase(ctx context.Context) (common.Address, error) {

// GasPrice returns a suggestion for a gas price for legacy transactions.
func (b *BlockChainAPI) GasPrice(ctx context.Context) (*hexutil.Big, error) {
return (*hexutil.Big)(b.config.GasPrice), nil
feeParams, err := b.feeParameters.Get()
if err != nil {
b.logger.Warn().Err(err).Msg("fee parameters unavailable; falling back to base gas price")
return (*hexutil.Big)(b.config.GasPrice), nil
}
gasPrice := feeParams.CalculateGasPrice(b.config.GasPrice)
return (*hexutil.Big)(gasPrice), nil
}

// GetUncleCountByBlockHash returns number of uncles in the block for the given block hash
Expand Down Expand Up @@ -1055,7 +1079,13 @@ func (b *BlockChainAPI) GetUncleByBlockNumberAndIndex(

// MaxPriorityFeePerGas returns a suggestion for a gas tip cap for dynamic fee transactions.
func (b *BlockChainAPI) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.Big, error) {
return (*hexutil.Big)(b.config.GasPrice), nil
feeParams, err := b.feeParameters.Get()
if err != nil {
b.logger.Warn().Err(err).Msg("fee parameters unavailable; falling back to base gas price")
return (*hexutil.Big)(b.config.GasPrice), nil
}
gasPrice := feeParams.CalculateGasPrice(b.config.GasPrice)
return (*hexutil.Big)(gasPrice), nil
}

// Mining returns true if client is actively mining new blocks.
Expand Down
88 changes: 74 additions & 14 deletions bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package bootstrap

import (
"context"
_ "embed"
"errors"
"fmt"
"math"
"time"

pebbleDB "github.com/cockroachdb/pebble"
gethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/onflow/cadence"
"github.com/onflow/flow-go-sdk/access"
"github.com/onflow/flow-go-sdk/access/grpc"
"github.com/onflow/flow-go/fvm/environment"
Expand Down Expand Up @@ -50,14 +52,20 @@ const (
DefaultResourceExhaustedMaxRetryDelay = 30 * time.Second
)

var (
//go:embed cadence/get_fees_surge_factor.cdc
getFeesSurgeFactor []byte
)

type Storages struct {
Storage *pebble.Storage
Registers *pebble.RegisterStorage
Blocks storage.BlockIndexer
Transactions storage.TransactionIndexer
Receipts storage.ReceiptIndexer
Traces storage.TraceIndexer
EventsHash *pebble.EventsHash
Storage *pebble.Storage
Registers *pebble.RegisterStorage
Blocks storage.BlockIndexer
Transactions storage.TransactionIndexer
Receipts storage.ReceiptIndexer
Traces storage.TraceIndexer
FeeParameters storage.FeeParametersIndexer
EventsHash *pebble.EventsHash
}

type Publishers struct {
Expand Down Expand Up @@ -205,16 +213,25 @@ func (b *Bootstrap) StartEventIngestion(ctx context.Context) error {
ValidateResults: true,
}

feeParamsSubscriber := ingestion.NewFeeParamsEventSubscriber(
b.logger,
b.client,
chainID,
nextCadenceHeight,
)

// initialize event ingestion engine
b.events = ingestion.NewEventIngestionEngine(
subscriber,
feeParamsSubscriber,
blocksProvider,
b.storages.Storage,
b.storages.Registers,
b.storages.Blocks,
b.storages.Receipts,
b.storages.Transactions,
b.storages.Traces,
b.storages.FeeParameters,
b.publishers.Block,
b.publishers.Logs,
b.logger,
Expand Down Expand Up @@ -338,6 +355,7 @@ func (b *Bootstrap) StartAPIServer(ctx context.Context) error {
b.storages.Blocks,
b.storages.Transactions,
b.storages.Receipts,
b.storages.FeeParameters,
rateLimiter,
b.collector,
indexingResumedHeight,
Expand Down Expand Up @@ -680,6 +698,15 @@ func setupStorage(
// // TODO(JanezP): verify storage account owner is correct
// }

feeParameters := pebble.NewFeeParameters(store)
currentFeeParams, err := getNetworkFeeParams(context.Background(), config, client, logger)
if err != nil {
return nil, nil, fmt.Errorf("failed to fetch current fees surge factor: %w", err)
}
if err := feeParameters.Store(currentFeeParams, batch); err != nil {
return nil, nil, fmt.Errorf("failed to bootstrap fee parameters: %w", err)
}

if batch.Count() > 0 {
err = batch.Commit(pebbleDB.Sync)
if err != nil {
Expand All @@ -688,13 +715,14 @@ func setupStorage(
}

return db, &Storages{
Storage: store,
Blocks: blocks,
Registers: registerStore,
Transactions: pebble.NewTransactions(store),
Receipts: pebble.NewReceipts(store),
Traces: pebble.NewTraces(store),
EventsHash: eventsHash,
Storage: store,
Blocks: blocks,
Registers: registerStore,
Transactions: pebble.NewTransactions(store),
Receipts: pebble.NewReceipts(store),
Traces: pebble.NewTraces(store),
FeeParameters: feeParameters,
EventsHash: eventsHash,
}, nil
}

Expand Down Expand Up @@ -817,3 +845,35 @@ func (m *metricsWrapper) Stop() {
m.stopFN()
<-m.Done()
}

// getNetworkFeeParams returns the network's current Flow fees parameters
func getNetworkFeeParams(
ctx context.Context,
config config.Config,
client *requester.CrossSporkClient,
logger zerolog.Logger,
) (*models.FeeParameters, error) {
val, err := client.ExecuteScriptAtLatestBlock(
ctx,
requester.ReplaceAddresses(getFeesSurgeFactor, config.FlowNetworkID),
nil,
)
if err != nil {
return nil, err
}

// sanity check, should never occur
surgeFactor, ok := val.(cadence.UFix64)
if !ok {
return nil, fmt.Errorf("failed to convert surgeFactor %v to UFix64, got type: %T", val, val)
}

logger.Debug().
Uint64("surge-factor", uint64(surgeFactor)).
Msg("get current surge factor executed")

feeParameters := models.DefaultFeeParameters()
feeParameters.SurgeFactor = surgeFactor

return feeParameters, nil
}
5 changes: 5 additions & 0 deletions bootstrap/cadence/get_fees_surge_factor.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import FlowFees

access(all) fun main(): UFix64 {
return FlowFees.getFeeParameters().surgeFactor
}
45 changes: 43 additions & 2 deletions models/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ import (
)

const (
BlockExecutedQualifiedIdentifier = string(events.EventTypeBlockExecuted)
TransactionExecutedQualifiedIdentifier = string(events.EventTypeTransactionExecuted)
BlockExecutedQualifiedIdentifier = string(events.EventTypeBlockExecuted)
TransactionExecutedQualifiedIdentifier = string(events.EventTypeTransactionExecuted)
FeeParametersChangedQualifiedIdentifier = "FlowFees.FeeParametersChanged"
)

// isBlockExecutedEvent checks whether the given event contains block executed data.
Expand All @@ -33,6 +34,15 @@ func isTransactionExecutedEvent(event cadence.Event) bool {
return event.EventType.QualifiedIdentifier == TransactionExecutedQualifiedIdentifier
}

// isFeeParametersChangedEvent checks whether the given event contains updates
// to Flow fees parameters.
func isFeeParametersChangedEvent(event cadence.Event) bool {
if event.EventType == nil {
return false
}
return event.EventType.QualifiedIdentifier == FeeParametersChangedQualifiedIdentifier
}

// CadenceEvents contains Flow emitted events containing one or zero evm block executed event,
// and multiple or zero evm transaction events.
type CadenceEvents struct {
Expand Down Expand Up @@ -254,3 +264,34 @@ func NewBlockEventsError(err error) BlockEvents {
Err: err,
}
}

type FeeParamsEvents struct {
FeeParameters *FeeParameters // updates to Flow fees parameters
Err error
}

func NewFeeParamsEvents(events flow.BlockEvents) *FeeParamsEvents {
for _, event := range events.Events {
val := event.Value
if isFeeParametersChangedEvent(val) {
feeParameters, err := decodeFeeParametersChangedEvent(val)
return &FeeParamsEvents{
FeeParameters: feeParameters,
Err: err,
}
}
}

return &FeeParamsEvents{
Err: fmt.Errorf(
"could not find any %s events",
FeeParametersChangedQualifiedIdentifier,
),
}
}

func NewFeeParamsEventsError(err error) *FeeParamsEvents {
return &FeeParamsEvents{
Err: err,
}
}
64 changes: 64 additions & 0 deletions models/fee_parameters.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package models

import (
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/rlp"
"github.com/onflow/cadence"
)

const feeParamsPrecision = 100_000_000

var surgeFactorScale = big.NewInt(feeParamsPrecision)

func DefaultFeeParameters() *FeeParameters {
return &FeeParameters{
SurgeFactor: cadence.UFix64(feeParamsPrecision),
InclusionEffortCost: cadence.UFix64(feeParamsPrecision),
ExecutionEffortCost: cadence.UFix64(feeParamsPrecision),
}
}

type FeeParameters struct {
SurgeFactor cadence.UFix64 `cadence:"surgeFactor"`
InclusionEffortCost cadence.UFix64 `cadence:"inclusionEffortCost"`
ExecutionEffortCost cadence.UFix64 `cadence:"executionEffortCost"`
}

func (f *FeeParameters) ToBytes() ([]byte, error) {
return rlp.EncodeToBytes(f)
}

func (f *FeeParameters) CalculateGasPrice(currentGasPrice *big.Int) *big.Int {
if currentGasPrice == nil {
return new(big.Int) // zero
}

// gasPrice = (currentGasPrice * surgeFactor) / feeParamsPrecision
surgeFactor := new(big.Int).SetUint64(uint64(f.SurgeFactor))
gasPrice := new(big.Int).Mul(currentGasPrice, surgeFactor)
return new(big.Int).Quo(gasPrice, surgeFactorScale)
}

func NewFeeParametersFromBytes(data []byte) (*FeeParameters, error) {
feeParameters := &FeeParameters{}
if err := rlp.DecodeBytes(data, feeParameters); err != nil {
return nil, err
}

return feeParameters, nil
}

func decodeFeeParametersChangedEvent(event cadence.Event) (*FeeParameters, error) {
feeParameters := &FeeParameters{}
if err := cadence.DecodeFields(event, feeParameters); err != nil {
return nil, fmt.Errorf(
"failed to Cadence-decode FlowFees.FeeParametersChanged event [%s]: %w",
event.String(),
err,
)
}

return feeParameters, nil
}
Loading