Skip to content

Commit

Permalink
feat: add fee estimator
Browse files Browse the repository at this point in the history
  • Loading branch information
vgonkivs committed Feb 4, 2025
1 parent daf1162 commit 62acad7
Show file tree
Hide file tree
Showing 14 changed files with 258 additions and 61 deletions.
2 changes: 2 additions & 0 deletions api/docgen/examples.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ func init() {
state.WithKeyName("my_celes_key"),
state.WithSignerAddress("celestia1pjcmwj8w6hyr2c4wehakc5g8cfs36aysgucx66"),
state.WithFeeGranterAddress("celestia1hakc56ax66ypjcmwj8w6hyr2c4g8cfs3wesguc"),
state.WithMaxGasPrice(state.DefaultMaxGasPrice),
state.WithTxPriority(1),
))
}

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c
github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b
github.com/benbjohnson/clock v1.3.5
github.com/celestiaorg/celestia-app/v3 v3.3.0-arabica
github.com/celestiaorg/celestia-app/v3 v3.3.0
github.com/celestiaorg/go-fraud v0.2.1
github.com/celestiaorg/go-header v0.6.4
github.com/celestiaorg/go-libp2p-messenger v0.2.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,8 @@ github.com/celestiaorg/boxo v0.0.0-20241118122411-70a650316c3b h1:M9X7s1WJ/7Ju84
github.com/celestiaorg/boxo v0.0.0-20241118122411-70a650316c3b/go.mod h1:OpUrJtGmZZktUqJvPOtmP8wSfEFcdF/55d3PNCcYLwc=
github.com/celestiaorg/celestia-app/v3 v3.3.0-arabica h1:MQHm5NvWt5cQl5YHpTHvuGb55/1uWQg3SNypkVt5rhU=
github.com/celestiaorg/celestia-app/v3 v3.3.0-arabica/go.mod h1:MKhiQgATDdLouzC5KvXDAnDpEgIXyD0MNiq0ChrWFco=
github.com/celestiaorg/celestia-app/v3 v3.3.0 h1:NW/Sx5EZiGUrBIqojoqgKYFmVHsb3R7u2hWdOxZV+wI=
github.com/celestiaorg/celestia-app/v3 v3.3.0/go.mod h1:MKhiQgATDdLouzC5KvXDAnDpEgIXyD0MNiq0ChrWFco=
github.com/celestiaorg/celestia-core v1.45.0-tm-v0.34.35 h1:T21AhezjcByAlWDHmiVbpg743Uqk/dqBzJkQsAnbQf8=
github.com/celestiaorg/celestia-core v1.45.0-tm-v0.34.35/go.mod h1:fQ46s1hYFTGFBsHsuGsbxDZ720ZPQow5Iyqw+yErZSo=
github.com/celestiaorg/cosmos-sdk v1.27.0-sdk-v0.46.16 h1:qxWiGrDEcg4FzVTpIXU/v3wjP7q1Lz4AMhSBBRABInU=
Expand Down
14 changes: 10 additions & 4 deletions libs/utils/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,20 @@ import (

var ErrInvalidIP = errors.New("invalid IP address or hostname given")

// SanitizeAddr trims leading protocol scheme and port from the given
// IP address or hostname if present.
func SanitizeAddr(addr string) (string, error) {
original := addr
// NormalizeAddress extracts the host and port, removing unsupported schemes.
func NormalizeAddress(addr string) string {
addr = strings.TrimPrefix(addr, "http://")
addr = strings.TrimPrefix(addr, "https://")
addr = strings.TrimPrefix(addr, "tcp://")
addr = strings.TrimSuffix(addr, "/")
return addr
}

// SanitizeAddr trims leading protocol scheme and port from the given
// IP address or hostname if present.
func SanitizeAddr(addr string) (string, error) {
original := addr
addr = NormalizeAddress(addr)
addr = strings.Split(addr, ":")[0]
if addr == "" {
return "", fmt.Errorf("%w: %s", ErrInvalidIP, original)
Expand Down
6 changes: 6 additions & 0 deletions nodebuilder/core/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const (

var MetricsEnabled bool

type EstimatorAddress string

// Config combines all configuration fields for managing the relationship with a Core node.
type Config struct {
IP string
Expand All @@ -24,6 +26,8 @@ type Config struct {
// The JSON file should have a key-value pair where the key is "x-token" and the value is the authentication token.
// If left empty, the client will not include the X-Token in its requests.
XTokenPath string
// FeeEstimatorAddress specifies a third-party endpoint that will be used to calculate the gas price and gas.
FeeEstimatorAddress EstimatorAddress
}

// DefaultConfig returns default configuration for managing the
Expand Down Expand Up @@ -54,6 +58,8 @@ func (cfg *Config) Validate() error {
if err != nil {
return fmt.Errorf("nodebuilder/core: invalid grpc port: %s", err.Error())
}
pasedAddr := utils.NormalizeAddress(string(cfg.FeeEstimatorAddress))
cfg.FeeEstimatorAddress = EstimatorAddress(pasedAddr)
return nil
}

Expand Down
23 changes: 18 additions & 5 deletions nodebuilder/core/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import (
)

var (
coreIPFlag = "core.ip"
corePortFlag = "core.port"
coreGRPCFlag = "core.grpc.port"
coreTLS = "core.tls"
coreXTokenPathFlag = "core.xtoken.path" //nolint:gosec
coreIPFlag = "core.ip"
corePortFlag = "core.port"
coreGRPCFlag = "core.grpc.port"
coreTLS = "core.tls"
coreXTokenPathFlag = "core.xtoken.path" //nolint:gosec
coreEstimatorAddressFlag = "core.estimator.address"
)

// Flags gives a set of hardcoded Core flags.
Expand Down Expand Up @@ -50,6 +51,13 @@ func Flags() *flag.FlagSet {
"NOTE: the path is parsed only if coreTLS enabled."+
"If left empty, the client will not include the X-Token in its requests.",
)
flags.String(
coreEstimatorAddressFlag,
"",
"specifies the endpoint of the third-party service that should be used to calculate"+
"the gas price and gas. Format: <address>:<port>. Default connection to the consensus node will be used if "+
"left empty.",
)
return flags
}

Expand Down Expand Up @@ -88,5 +96,10 @@ func ParseFlags(
}
}
cfg.IP = coreIP

if cmd.Flag(coreEstimatorAddressFlag).Changed {
addr := cmd.Flag(coreEstimatorAddressFlag).Value.String()
cfg.FeeEstimatorAddress = EstimatorAddress(addr)
}
return cfg.Validate()
}
24 changes: 24 additions & 0 deletions nodebuilder/state/cmd/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmd
import (
"fmt"
"strconv"
"strings"

"cosmossdk.io/math"
"github.com/spf13/cobra"
Expand All @@ -18,6 +19,8 @@ var (
gasPrice float64
feeGranterAddress string
amount uint64
txPriority int
maxGasPrice float64
)

func init() {
Expand Down Expand Up @@ -460,6 +463,25 @@ func ApplyFlags(cmds ...*cobra.Command) {
"Note: The granter should be provided as a Bech32 address.\n"+
"Example: celestiaxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
)

// add additional flags for all submit transactions besides submit blobs.
if !strings.Contains(cmd.Name(), "blob") {
cmd.PersistentFlags().Float64Var(
&maxGasPrice,
"max.gas.price",
state.DefaultMaxGasPrice,
"Specifies max gas price for the tx submission.",
)
cmd.PersistentFlags().IntVar(
&txPriority,
"tx.priority",
state.TxPriorityMedium,
"Specifies tx priority. Should be set in range:"+
"1. TxPriorityLow;\n"+
"2. TxPriorityMedium;\n"+
"3. TxPriorityHigh.\nDefault: TxPriorityMedium",
)
}
}
}

Expand All @@ -470,5 +492,7 @@ func GetTxConfig() *state.TxConfig {
state.WithKeyName(keyName),
state.WithSignerAddress(signer),
state.WithFeeGranterAddress(feeGranterAddress),
state.WithMaxGasPrice(maxGasPrice),
state.WithTxPriority(txPriority),
)
}
4 changes: 3 additions & 1 deletion nodebuilder/state/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/celestiaorg/go-header/sync"

"github.com/celestiaorg/celestia-node/header"
"github.com/celestiaorg/celestia-node/nodebuilder/core"
modfraud "github.com/celestiaorg/celestia-node/nodebuilder/fraud"
"github.com/celestiaorg/celestia-node/nodebuilder/p2p"
"github.com/celestiaorg/celestia-node/share/eds/byzantine"
Expand All @@ -23,13 +24,14 @@ func coreAccessor(
fraudServ libfraud.Service[*header.ExtendedHeader],
network p2p.Network,
client *grpc.ClientConn,
address core.EstimatorAddress,
) (
*state.CoreAccessor,
Module,
*modfraud.ServiceBreaker[*state.CoreAccessor, *header.ExtendedHeader],
error,
) {
ca, err := state.NewCoreAccessor(keyring, string(keyname), sync, client, network.String())
ca, err := state.NewCoreAccessor(keyring, string(keyname), sync, client, network.String(), string(address))

sBreaker := &modfraud.ServiceBreaker[*state.CoreAccessor, *header.ExtendedHeader]{
Service: ca,
Expand Down
1 change: 1 addition & 0 deletions nodebuilder/state/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func ConstructModule(tp node.Type, cfg *Config, coreCfg *core.Config) fx.Option
cfgErr := cfg.Validate()
baseComponents := fx.Options(
fx.Supply(*cfg),
fx.Supply(coreCfg.FeeEstimatorAddress),
fx.Error(cfgErr),
fx.Provide(func(ks keystore.Keystore) (keyring.Keyring, AccountName, error) {
return Keyring(*cfg, ks)
Expand Down
45 changes: 28 additions & 17 deletions state/core_access.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ type CoreAccessor struct {
coreConn *grpc.ClientConn
network string

estimator *estimator
// these fields are mutatable and thus need to be protected by a mutex
lock sync.Mutex
lastPayForBlob int64
Expand All @@ -83,6 +84,7 @@ func NewCoreAccessor(
getter libhead.Head[*header.ExtendedHeader],
conn *grpc.ClientConn,
network string,
estimatorAddress string,
) (*CoreAccessor, error) {
// create verifier
prt := merkle.DefaultProofRuntime()
Expand All @@ -106,6 +108,7 @@ func NewCoreAccessor(
prt: prt,
coreConn: conn,
network: network,
estimator: &estimator{estimatorAddress: estimatorAddress, clientConn: conn},
}
return ca, nil
}
Expand Down Expand Up @@ -136,6 +139,7 @@ func (ca *CoreAccessor) Start(ctx context.Context) error {
if err != nil {
return fmt.Errorf("querying minimum gas price: %w", err)
}
ca.estimator.connect()
return nil
}

Expand Down Expand Up @@ -176,7 +180,7 @@ func (ca *CoreAccessor) SubmitPayForBlob(
for i, blob := range libBlobs {
blobSizes[i] = uint32(len(blob.Data()))
}
gas = estimateGasForBlobs(blobSizes)
gas = ca.estimator.estimateGasForBlobs(blobSizes)
}

gasPrice := cfg.GasPrice()
Expand Down Expand Up @@ -574,22 +578,6 @@ func (ca *CoreAccessor) submitMsg(
}

txConfig := make([]user.TxOption, 0)
gas := cfg.GasLimit()

if gas == 0 {
gas, err = estimateGas(ctx, client, msg)
if err != nil {
return nil, fmt.Errorf("estimating gas: %w", err)
}
}

gasPrice := cfg.GasPrice()
if gasPrice == DefaultGasPrice {
gasPrice = ca.minGasPrice
}

txConfig = append(txConfig, user.SetGasLimitAndGasPrice(gas, gasPrice))

if cfg.FeeGranterAddress() != "" {
granter, err := parseAccAddressFromString(cfg.FeeGranterAddress())
if err != nil {
Expand All @@ -598,6 +586,29 @@ func (ca *CoreAccessor) submitMsg(
txConfig = append(txConfig, user.SetFeeGranter(granter))
}

if cfg.GasLimit() == 0 || cfg.GasPrice() == DefaultGasPrice {
gasPrice, gas, err := ca.estimator.estimateGas(ctx, client, cfg.priority, msg)
if err != nil {
return nil, err
}

if cfg.GasLimit() == 0 {
cfg.gas = gas
}
if cfg.GasPrice() == DefaultGasPrice {
migGasPrice := ca.getMinGasPrice()
if gasPrice < migGasPrice {
gasPrice = migGasPrice
}
cfg.gasPrice = gasPrice
cfg.isGasPriceSet = true
}
}

if cfg.maxGasPrice < cfg.GasPrice() {
return nil, fmt.Errorf("state: estimated gasPrice exceeds max gasPrice")
}
txConfig = append(txConfig, user.SetGasLimitAndGasPrice(cfg.GasLimit(), cfg.GasPrice()))
resp, err := client.SubmitTx(ctx, []sdktypes.Msg{msg}, txConfig...)
return convertToSdkTxResponse(resp), err
}
Expand Down
2 changes: 1 addition & 1 deletion state/core_access_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ func buildAccessor(t *testing.T) (*CoreAccessor, []string) {

conn, err := grpc.NewClient(grpcAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err)
ca, err := NewCoreAccessor(cctx.Keyring, accounts[0].Name, nil, conn, chainID)
ca, err := NewCoreAccessor(cctx.Keyring, accounts[0].Name, nil, conn, chainID, "")
require.NoError(t, err)
return ca, getNames(accounts)
}
Expand Down
Loading

0 comments on commit 62acad7

Please sign in to comment.