Skip to content

feat(state/core_accessor): add fee estimator #4087

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

Closed
wants to merge 4 commits into from

Conversation

vgonkivs
Copy link
Member

@vgonkivs vgonkivs commented Feb 4, 2025

Implemenation of the ADR-13
This PR enables third-party estimation for the submit transactions in the node(besides PFB). By default, fee estimation relies on the consensus node to which the node is connected. But the user can now provide a non-default endpoint, so the node will query gas price and gas from it. It can be provided via cli(using "core.estimator.address"). The address will be added to the node's config as FeeEstimatorAddress.
Additionally, the user can now provide a maxGasPrice for every submit tx(via max.gas.price flag)- the max price that the user is willing to pay for the transaction. The transaction will not be submitted in case when estimated gas price exceeds the maxGasPrice(default: minGasPrice*100)

@vgonkivs vgonkivs added area:state Related to fetching state and state execution kind:feat Attached to feature PRs labels Feb 4, 2025
@vgonkivs vgonkivs self-assigned this Feb 4, 2025
@vgonkivs vgonkivs force-pushed the fee_estimator branch 2 times, most recently from 90f851c to 7c01a6f Compare February 4, 2025 15:07
@codecov-commenter
Copy link

codecov-commenter commented Feb 13, 2025

Codecov Report

Attention: Patch coverage is 68.23529% with 54 lines in your changes missing coverage. Please review.

Project coverage is 44.97%. Comparing base (2469e7a) to head (539f554).
Report is 451 commits behind head on main.

Files with missing lines Patch % Lines
state/estimator.go 66.66% 19 Missing and 6 partials ⚠️
state/tx_config.go 54.83% 14 Missing ⚠️
state/core_access.go 55.00% 6 Missing and 3 partials ⚠️
nodebuilder/core/flags.go 66.66% 3 Missing and 1 partial ⚠️
nodebuilder/state/cmd/state.go 90.47% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4087      +/-   ##
==========================================
+ Coverage   44.83%   44.97%   +0.14%     
==========================================
  Files         265      310      +45     
  Lines       14620    22834    +8214     
==========================================
+ Hits         6555    10270    +3715     
- Misses       7313    11472    +4159     
- Partials      752     1092     +340     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@vgonkivs vgonkivs marked this pull request as ready for review February 13, 2025 16:06
Copy link
Member

@renaynay renaynay left a comment

Choose a reason for hiding this comment

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

The biggest comment rn is just that core_access is super messy 😭 Quite hard to read. Is there anything that can be done to make the code more readable?

func SanitizeAddr(addr string) (string, error) {
original := addr
// NormalizeAddress extracts the host and port, removing unsupported schemes.
func NormalizeAddress(addr string) string {
Copy link
Member

Choose a reason for hiding this comment

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

why does SanitizeAddr not work for this?

Copy link
Member Author

Choose a reason for hiding this comment

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

it additionally cuts the port.
addr = strings.Split(addr, ":")[0]

@@ -23,13 +24,14 @@ func coreAccessor(
fraudServ libfraud.Service[*header.ExtendedHeader],
network p2p.Network,
client *grpc.ClientConn,
address core.EstimatorAddress,
Copy link
Member

Choose a reason for hiding this comment

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

can't you just grab the core cfg there and take from cfg directly instead of supplying individual value to fx?

Copy link
Member Author

Choose a reason for hiding this comment

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

We are using core.EstimatorAddress only from the core config:

type Config struct {
	IP   string
	Port string
	TLSEnabled bool
	XTokenPath string
	FeeEstimatorAddress EstimatorAddress
}


log = logging.Logger("state")
ErrInvalidAmount = errors.New("state: amount must be greater than zero")
errGasPriceExceedsLimit = errors.New("state: estimated gasPrice exceeds max gasPrice")
Copy link
Member

Choose a reason for hiding this comment

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

why unexported?

Copy link
Member Author

Choose a reason for hiding this comment

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

Made exported

Comment on lines 77 to 128
if e.estimatorConn != nil && e.estimatorConn.GetState() != connectivity.Shutdown {
gasPrice, gas, err := signer.QueryGasUsedAndPrice(ctx, e.estimatorConn, priority.toApp(), rawTx)
if err == nil {
return gasPrice, gas, nil
}
log.Warn("failed to query gas used and price from the estimator endpoint.", "err", err)
}

if e.defaultClientConn == nil || e.defaultClientConn.GetState() == connectivity.Shutdown {
return 0, 0, errors.New("connection with the consensus node is dropped")
}

gasPrice, gas, err := signer.QueryGasUsedAndPrice(ctx, e.defaultClientConn, priority.toApp(), rawTx)
if err != nil {
log.Warn("state: failed to query gas used and price from the default endpoint", "err", err)
}
Copy link
Member

Choose a reason for hiding this comment

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

you can unify these to clean it up and just determine which conn to used based on if estimator conn exists.

What err will be returned if you pass a conn where connectivity shutdown to the signer.QueryGasUsedAndPrice? If it's readable (conn is closed) then we cna just return that unwrapped

Copy link
Member Author

Choose a reason for hiding this comment

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

simplified

@@ -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()
Copy link
Member

Choose a reason for hiding this comment

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

Start function could use some cleanup (better organisation w.r.t spacing + order)

@Wondertan
Copy link
Member

Did we tested this manually? Like run a node with an estimator submitting a real blob?

@vgonkivs
Copy link
Member Author

vgonkivs commented Mar 6, 2025

Submit blobs is using a linear formula to calculate gas. I tested it with Transfer

@Wondertan
Copy link
Member

Oh, i see, didnt know that, but you made it with Transfer - to confirm?

@vgonkivs
Copy link
Member Author

vgonkivs commented Mar 7, 2025

It worked fine last month when I was testing it iirc. I'm getting an error from the estimation endpoint now. Trying to clarify with the app team.

@Wondertan
Copy link
Member

Ok, let's retest it.

BTW, are we sure that estimation is only for Transfers and not blobs? Seems weird that we make this issue a priority if its only for transfers

@vgonkivs
Copy link
Member Author

vgonkivs commented Mar 7, 2025

BTW, are we sure that estimation is only for Transfers and not blobs?

Yes, as per ADR blobs should be estimated using formula. All other types of the Txs have to use estimation service

@vgonkivs vgonkivs requested a review from cmwaters March 7, 2025 13:22
@vgonkivs
Copy link
Member Author

vgonkivs commented Mar 7, 2025

Tested on Trasfer transaction with numia-mocha public endpoint.

Copy link
Member

@Wondertan Wondertan left a comment

Choose a reason for hiding this comment

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

Skipping review of semantical nits.
If it works - it works

err = ca.estimator.Start(ctx)
if err != nil {
log.Warn("state: failed to connect to estimator endpoint", "err", err)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

should we return err here? if no - comment why it's ok to skip it.

Copy link
Member

Choose a reason for hiding this comment

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

we should actually return an error here bc the only time err is returned from estimator.Start is if grpc conn fails to be created which is an actual err.

Copy link
Contributor

Choose a reason for hiding this comment

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

Done

err = ca.estimator.Start(ctx)
if err != nil {
log.Warn("state: failed to connect to estimator endpoint", "err", err)
}
Copy link
Member

Choose a reason for hiding this comment

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

we should actually return an error here bc the only time err is returned from estimator.Start is if grpc conn fails to be created which is an actual err.

msg sdktypes.Msg,
) (float64, uint64, error) {
if cfg.GasPrice() != DefaultGasPrice && cfg.GasLimit() != 0 {
return cfg.GasPrice(), cfg.GasLimit(), nil
Copy link
Member

Choose a reason for hiding this comment

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

so if both GasPrice and GasLimit are set in the config, GasPrice sanity check (checking if it exceeds the MaxGasPrice) can be bypassed? Is the idea here that if the user sets gas themselves that they wouldn't set a max gas price, and if they set a max gas price, that means they probably didn't specify gas and wants the node to calculate for them (within some upper bound)?

We should document this behaviour here

Copy link
Member

Choose a reason for hiding this comment

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

comments throughout this function would be nice.

Copy link
Contributor

Choose a reason for hiding this comment

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

Done. Not sure about "comments throughout this function", I don't see a good reason for that, added Go-doc only.

priority TxPriority,
rawTx []byte,
) (gasPrice float64, gas uint64, err error) {
conns := []*grpc.ClientConn{e.estimatorConn, e.defaultClientConn}
Copy link
Member

Choose a reason for hiding this comment

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

So the idea here is that there will /can be an estimator service that will perform this function, but if not, the default validator you're using to submit txs will also be able to estimate.

Copy link
Contributor

Choose a reason for hiding this comment

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

From what I understand your comment is just informational, nothing to change, right?

@cristaloleg
Copy link
Contributor

Closing in favour of #4168

Thank you @vgonkivs for your time and effort 🫡

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:state Related to fetching state and state execution kind:feat Attached to feature PRs
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants