From 53c04e09f8b7305a1b3f5589d20d5ccbf4f0579d Mon Sep 17 00:00:00 2001 From: Viacheslav Gonkivskyi Date: Tue, 4 Jun 2024 16:04:54 +0300 Subject: [PATCH] feat: add tx options struct --- state/tx_options.go | 159 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 state/tx_options.go diff --git a/state/tx_options.go b/state/tx_options.go new file mode 100644 index 0000000000..28568e9aad --- /dev/null +++ b/state/tx_options.go @@ -0,0 +1,159 @@ +package state + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "math" + + "github.com/celestiaorg/celestia-app/app" + sdktypes "github.com/cosmos/cosmos-sdk/types" + + "github.com/celestiaorg/celestia-app/pkg/user" + apptypes "github.com/celestiaorg/celestia-app/x/blob/types" +) + +// gasMultiplier is used to increase gas limit in case if tx has additional options. +const gasMultiplier = 1.1 + +var ErrNoGasProvided = errors.New("gas limit was not set") + +// TxOptions specifies additional options that will be applied to the Tx. +type TxOptions struct { + fee *Fee + GasLimit uint64 + + // Specifies the key that should be used to sign transactions. + // NOTE: This `Account` should be available in the `Keystore`. + Account string +} + +func DefaultTxOptions() TxOptions { + return TxOptions{ + fee: DefaultFee(), + GasLimit: 0, + Account: "", + } +} + +type jsonTxOptions struct { + Fee *Fee `json:"fee"` + GasLimit uint64 `json:"gasLimit"` + Account string `json:"account, omitempty"` +} + +func (options *TxOptions) UnmarshalJSON(data []byte) error { + var jsonOpts *jsonTxOptions + err := json.Unmarshal(data, jsonOpts) + if err != nil { + return fmt.Errorf("unmarshalling TxOptions:%w", err) + } + + options.fee.Amount = jsonOpts.Fee.Amount + options.fee.isSet = jsonOpts.Fee.isSet + options.GasLimit = jsonOpts.GasLimit + options.Account = jsonOpts.Account + return nil +} + +// SetFeeAmount sets fee for the transaction. +func (options *TxOptions) SetFeeAmount(amount int64) { + if amount >= 0 { + options.fee.Amount = amount + options.fee.isSet = true + } +} + +// getFee returns fee amount as a `user.TxOption` that can be applied to the transactions. +// It will calculate fee in case user haven't provided it. +// NOTE: it is very important to ensure that gas limit was set. +func (options *TxOptions) getFee(minGasPrice float64) (user.TxOption, error) { + if options.GasLimit == 0 { + return nil, ErrNoGasProvided + } + + if !options.fee.isSet { + options.fee.Amount = int64(math.Ceil(minGasPrice * float64(options.GasLimit))) + } + + fee := sdktypes.NewCoins(sdktypes.NewCoin(app.BondDenom, sdktypes.NewInt(options.fee.Amount))) + return user.SetFeeAmount(fee), nil +} + +// getGas returns a gas limit as a `user.TxOption` that can be applied to the transactions. +// GasLimit will be estimated in case it has not been set. +// NOTE: final result of the estimation will be multiplied by the `gasMultiplier`(1.1) to cover additional options of the Tx, +// as not all options are counted in the `EstimateGas` +func (options *TxOptions) getGas(ctx context.Context, client *user.TxClient, msg sdktypes.Msg, opts ...user.TxOption) (user.TxOption, error) { + if options.GasLimit == 0 { + // Gas estimation depends on the amount of bytes of the original TX, including all options(Fee, Granter, etc.), + // so, it is very important to pass ALL options that will be applied to the tx. + // 1utia as fee is ok at this point(as fee may not be calculated yet). + opts = append([]user.TxOption{user.SetFee(1)}, opts...) + gasLimit, err := client.EstimateGas(ctx, []sdktypes.Msg{msg}, opts...) + if err != nil { + return nil, fmt.Errorf("estimating gas: %w", err) + } + options.GasLimit = uint64(float64(gasLimit) * gasMultiplier) + } + return user.SetGasLimit(options.GasLimit), nil +} + +// getGasForBlobs returns a gas limit as a `user.TxOption` that can be applied to the `MsgPayForBlob` transactions. +// NOTE: final result of the estimation will be multiplied by the `gasMultiplier`(1.1) to cover additional options of the Tx. +func (options *TxOptions) getGasForBlobs(blobSizes []uint32) user.TxOption { + if options.GasLimit != 0 { + gasLimit := apptypes.DefaultEstimateGas(blobSizes) + options.GasLimit = uint64(float64(gasLimit) * gasMultiplier) + } + return user.SetGasLimit(options.GasLimit) +} + +type Fee struct { + Amount int64 + isSet bool +} + +func DefaultFee() *Fee { + return &Fee{ + Amount: -1, + } +} + +type jsonFee struct { + Amount int64 `json:"amount,omitempty"` + IsSet bool `json:"isSet,omitempty"` +} + +func (f *Fee) MarshalJSON() ([]byte, error) { + fee := jsonFee{ + Amount: f.Amount, + IsSet: f.isSet, + } + return json.Marshal(fee) +} + +func (f *Fee) UnmarshalJSON(data []byte) error { + var fee *jsonFee + err := json.Unmarshal(data, fee) + if err != nil { + return err + } + + f.Amount = fee.Amount + f.isSet = fee.IsSet + if !f.isSet { + f.Amount = -1 + } + return nil +} + +func parseAddressFromString(addrStr string) (Address, error) { + var address Address + err := address.UnmarshalJSON([]byte(addrStr)) + if err != nil { + return address, err + } + return address, nil +}