-
Notifications
You must be signed in to change notification settings - Fork 2
CRE-468 Implement metering for write targets #54
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
Conversation
ebb60b4 to
986a9a2
Compare
| return nil, 0, fmt.Errorf("invalid gas spend limit format: %s", spendLimit) | ||
| } | ||
|
|
||
| // Multiply by 10^decimals to convert from ETH to wei |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since it's a generalized component we shouldn't mention chain-specific terms (ETH, wei) here. It's a conversion from native unit to small unit (or something like this)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mm, I agree. Should this conversion logic also live with the specific target strategy implementer? If so, we need to find a way to plumb that through
|
There are two options to get Transaction Cost:
|
…patible estimate check fixed
After discussing offline, I agree - 4ef7f71 |
| // Wrapper around the ChainWriter to get the transaction status | ||
| GetTransactionStatus(ctx context.Context, transactionID string) (commontypes.TransactionStatus, error) | ||
| // Wrapper around the ChainWriter to get the fee esimate | ||
| GetEstimateFee(ctx context.Context, contract string, method string, args any, toAddress string, meta *commontypes.TxMeta, val *big.Int) (commontypes.EstimateFee, error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So here signature should be
GetEstimateFee(ctx context.Context, report []byte, reportContext []byte, signatures [][]byte, request capabilities.CapabilityRequest) (commontypes.EstimateFee, error)since TargetStrategy handles building payload for CW
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
aren't all of these report []byte, reportContext []byte, signatures [][]byte, part of the capability request?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type SignedReport struct {
Report []byte
// Report context is appended to the report before signing by libOCR.
// It contains config digest + round/epoch/sequence numbers (currently 96 bytes).
// Has to be appended to the report before validating signatures.
Context []byte
// Always exactly F+1 signatures.
Signatures [][]byte
// Report ID defined in the workflow spec (2 bytes).
ID []byte
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, just checked other arguments are unused by TransmitReport, idk why they are declared there. I guess the only thing is needed is the capability request
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd recommend we stay consistent for this PR and then migrate all the signatures in one swoop
| } | ||
| errMsg := c.asEmittedError(ctx, &wt.WriteError{ | ||
| Code: uint32(TransmissionStateFatal), | ||
| Summary: "InsufficientFunds", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Summary: "InsufficientFunds",
based on writeTarget.checkGasEstimate there are other possible errors:
fee, err := c.targetStrategy.GetEstimateFee(ctx, report, reportContext, signatures, request)
if err != nil {
return nil, 0, fmt.Errorf("failed to get gas estimate: %w", err)
}
// Convert spend limit from chain currency to gas units
limitFloat, ok := new(big.Float).SetString(spendLimit)
if !ok {
return nil, 0, fmt.Errorf("invalid gas spend limit format: %s", spendLimit)
}failed to get gas estimate doesnt match Summary:"InsufficientFunds", also it's retryable, so code should be Failed, not Fatal.
I think all this errors should be handled differently
| // Get the transaction fee | ||
| fee, err := c.targetStrategy.GetTransactionFee(ctx, txID) | ||
| if err != nil { | ||
| return capabilities.CapabilityResponse{}, fmt.Errorf("failed to get transaction fee: %w", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, at this point TX was already sent, we collected the receipt and confirmed successful transmission state, but here we return err since we couldn't get transaction fee (and since receipt is already in DB the only case for that, - we lost db connection). Should we return err in this case? Maybe just log and return estimation value from above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, for the ST3 use cases, we can do that.
| return limit.Limit, nil | ||
| } | ||
| } | ||
| return "", fmt.Errorf("no gas spend limit found for chain %s", c.chainInfo.ChainID) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this error necessary? Maybe makes more sense just to be a debug log
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getGasSpendLimit is complex enough to demand an error in the response API - its up to the Execute author to handle it properly. Currently, Execute handles it by skipping the check against gas estimation.
|
|
||
| // Compare estimate with limit | ||
| if fee.Fee.Cmp(limit) > 0 { | ||
| return nil, 0, fmt.Errorf("estimated gas fee %s exceeds spend limit %s", fee.Fee.String(), limit.String()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is this limit specified? Would this be in the workflow? And if so does the workflow do any sort of check to ensure the user actually has those funds?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The engine works with the billing service and a hierarchy of user defined limits to determine what to pass to the capabilities. Once a limit is passed to the capability, we ask that capabilities do their best to estimate the cost of execution upfront and stay within that cost to prevent double spend in the workflow owner account.
| info.reportInfo.reportID = binary.BigEndian.Uint16(inputs.ID) | ||
|
|
||
| // Get gas spend limit first | ||
| spendLimit, err := c.getGasSpendLimit(request) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how is this spend limit enforced when sending a tx?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it's enforced, it's a rough estimate that doesn't consider that chain state can change/txm can bump tx prority fee.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not enforced; we can't block NOP keys on sending transactions.
| GetEstimateFee(ctx context.Context, report []byte, reportContext []byte, signatures [][]byte, request capabilities.CapabilityRequest) (commontypes.EstimateFee, error) | ||
| // GetTransactionFee retrieves the actual transaction fee in native currency from the transaction receipt. | ||
| // This method should be implemented by chain-specific services and handle the conversion of gas units to native currency. | ||
| GetTransactionFee(ctx context.Context, transactionID string) (decimal.Decimal, error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gas values are always returned as big.Int, I'm assuming this value will be created like this: decimal.New(val, -18)? Could be annoying converting it back to big.Int
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm, lets just wait for TargetStrategy
Description
CRE-468
Implement metering for the write targets. Will require chain specific implementations of Target Strategy to be updated for
GetEstimateFeeandGetTargetStrategy.A few things need to happen:
This PR cannot break current df workflows. I've removed the estimation check against limits to keep things operationally simple until we have a good reason to start supplying those limits.
Requires Dependencies
Resolves Dependencies