-
Notifications
You must be signed in to change notification settings - Fork 19
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
feat: add fee estimation #118
Changes from 1 commit
8d5afae
09cdf8a
f738fe7
c3ca3ba
0de53b5
5fdaa9e
58c9847
fd2b46f
bce3483
ab04295
fc82572
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -56,9 +56,28 @@ func NewWallet(cfg *config.BTCConfig) (*Client, error) { | |
return wallet, nil | ||
} | ||
|
||
// TODO make it dynamic | ||
func (c *Client) GetTxFee() uint64 { | ||
return uint64(c.Cfg.TxFee.ToUnit(btcutil.AmountSatoshi)) | ||
// GetTxFee returns tx fee according to its size | ||
// if tx size is zero, it returns the default tx | ||
// fee in config | ||
func (c *Client) GetTxFee(txSize uint64) uint64 { | ||
defaultFee := uint64(c.Cfg.TxFeeDefault.ToUnit(btcutil.AmountSatoshi)) | ||
if txSize == 0 { | ||
return defaultFee | ||
} | ||
feeRate, err := c.Client.EstimateFee(c.Cfg.TargetBlockNum) | ||
if err != nil { | ||
return defaultFee | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the failure condition on this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What resources does it need? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For example, when FeeEstimator is disabled in the BTC node, or when there are insufficient blocks observed |
||
} | ||
feeRateAmount, err := btcutil.NewAmount(feeRate) | ||
if err != nil { | ||
return defaultFee | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would this having an error mean that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, this err would mean |
||
} | ||
fee := uint64(feeRateAmount.ToUnit(btcutil.AmountSatoshi)) * txSize | ||
if fee > uint64(c.Cfg.TxFeeMax.ToUnit(btcutil.AmountSatoshi)) || | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can maybe create a helper method i.e. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. Thanks! |
||
fee < uint64(c.Cfg.TxFeeMax.ToUnit(btcutil.AmountSatoshi)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you use |
||
return defaultFee | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if the |
||
} | ||
return fee | ||
} | ||
|
||
func (c *Client) GetWalletName() string { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,7 +17,10 @@ type BTCConfig struct { | |
WalletName string `mapstructure:"wallet-name"` | ||
WalletCAFile string `mapstructure:"wallet-ca-file"` | ||
WalletLockTime int64 `mapstructure:"wallet-lock-time"` // time duration in which the wallet remains unlocked, in seconds | ||
TxFee btcutil.Amount `mapstructure:"tx-fee"` // BTC tx fee, in BTC | ||
TxFeeDefault btcutil.Amount `mapstructure:"tx-fee-default"` // default BTC tx fee, in BTC | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed. Thanks! |
||
TxFeeMin btcutil.Amount `mapstructure:"tx-fee-min"` // minimum tx fee, in BTC | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why would we want a minimum transaction fee? Personally I would always set this to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Having There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest we remove this attribute alltogether. Why would someone want to set the minimum fee into anything other than the minimum value possible (e.g. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest we remove this attribute alltogether. Why would someone want to set the minimum fee into anything other than the minimum value possible (e.g. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The estimation might return us an extremely low tx fee which is not reasonable. We set a reasonable tx fee here to prevent this case. It could be |
||
TxFeeMax btcutil.Amount `mapstructure:"tx-fee-max"` // maximum tx fee, in BTC | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe panic if |
||
TargetBlockNum int64 `mapstructure:"target-block-num"` // for tx fee estimation | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe add in the comment that this specifies a parameter into how soon we want the transaction to be included (e.g. 1 specifying next block). |
||
NetParams string `mapstructure:"net-params"` | ||
Username string `mapstructure:"username"` | ||
Password string `mapstructure:"password"` | ||
|
@@ -50,7 +53,10 @@ func (cfg *BTCConfig) Validate() error { | |
} | ||
|
||
func DefaultBTCConfig() BTCConfig { | ||
feeAmount, err := btcutil.NewAmount(0.00001) | ||
feeAmountDefault, err := btcutil.NewAmount(1e-5) | ||
feeAmountMin, err := btcutil.NewAmount(1e-6) | ||
feeAmountMax, err := btcutil.NewAmount(1e-3) | ||
|
||
if err != nil { | ||
panic(err) | ||
} | ||
|
@@ -63,7 +69,10 @@ func DefaultBTCConfig() BTCConfig { | |
WalletName: "default", | ||
WalletCAFile: defaultBtcWalletCAFile, | ||
WalletLockTime: 10, | ||
TxFee: feeAmount, | ||
TxFeeDefault: feeAmountDefault, | ||
TxFeeMin: feeAmountMin, | ||
TxFeeMax: feeAmountMax, | ||
TargetBlockNum: 1, | ||
NetParams: types.BtcSimnet.String(), | ||
Username: "rpcuser", | ||
Password: "rpcpass", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -114,6 +114,7 @@ func (rl *Relayer) ChainTwoTxAndSend( | |
tx1, recipient, err := rl.buildTxWithData( | ||
utxo, | ||
data1, | ||
false, | ||
) | ||
if err != nil { | ||
return nil, nil, err | ||
|
@@ -137,6 +138,7 @@ func (rl *Relayer) ChainTwoTxAndSend( | |
tx2, _, err := rl.buildTxWithData( | ||
changeUtxo, | ||
data2, | ||
false, | ||
) | ||
if err != nil { | ||
return nil, nil, err | ||
|
@@ -195,7 +197,7 @@ func (rl *Relayer) PickHighUTXO() (*types.UTXO, error) { | |
} | ||
|
||
// TODO: consider dust, reference: https://www.oreilly.com/library/view/mastering-bitcoin/9781491902639/ch08.html#tx_verification | ||
if uint64(amount.ToUnit(btcutil.AmountSatoshi)) < rl.GetTxFee()*2 { | ||
if uint64(amount.ToUnit(btcutil.AmountSatoshi)) < rl.GetTxFee(0)*2 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will return the default fee, without considering the transaction size. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this is to ensure that we have sufficient balance in this utxo as we are considering max fee as default There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then why don't we use the max fee config directly? This adds further complexity to the |
||
return nil, errors.New("insufficient fees") | ||
} | ||
|
||
|
@@ -219,6 +221,7 @@ func (rl *Relayer) PickHighUTXO() (*types.UTXO, error) { | |
func (rl *Relayer) buildTxWithData( | ||
utxo *types.UTXO, | ||
data []byte, | ||
forTxSize bool, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we add this parameter? We should always try to estimate the fee. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is used when calculating tx size. See here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why don't we always calculate the transaction size? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah I see what's happening. I suggest that we don't use this function again with the dubious flag, but just create a copy of the transaction we have already built and add a dummy change output. Then calculate the transaction size based on that. A cyclic dependency between the functions based on a flag is going to come bite us in the future (I had to spend some time reading the code to understand what was happening). |
||
) (*wire.MsgTx, btcutil.Address, error) { | ||
log.Logger.Debugf("Building a BTC tx using %v with data %x", utxo.TxID.String(), data) | ||
tx := wire.NewMsgTx(wire.TxVersion) | ||
|
@@ -248,9 +251,18 @@ func (rl *Relayer) buildTxWithData( | |
} | ||
// TODO | ||
// If this will become a dynamic calculation this might lead to a different fee being used in the log and the actual transaction. | ||
change := uint64(utxo.Amount.ToUnit(btcutil.AmountSatoshi)) - rl.GetTxFee() | ||
log.Logger.Debugf("balance of input: %v satoshi, tx fee: %v satoshi, output value: %v", | ||
int64(utxo.Amount.ToUnit(btcutil.AmountSatoshi)), rl.GetTxFee(), change) | ||
change := uint64(utxo.Amount.ToUnit(btcutil.AmountSatoshi)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If |
||
if !forTxSize { | ||
// to simulate building the tx and get tx size | ||
txSize, err := rl.getTxSize(utxo, data) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
txFee := rl.GetTxFee(txSize) | ||
change -= txFee | ||
log.Logger.Debugf("balance of input: %v satoshi, tx fee: %v satoshi, output value: %v", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This log appeared before but now it will only appear if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep. The log shouldn't appear when calculating tx size |
||
int64(utxo.Amount.ToUnit(btcutil.AmountSatoshi)), txFee, change) | ||
} | ||
tx.AddTxOut(wire.NewTxOut(int64(change), changeScript)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The above estimation uses a transaction without the change output and therefore it will have a lesser size leading to an estimation based on a lower-sized transaction than the one that is actually sent. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, the output is included. Only that the change will minus tx fee (we don't know the tx fee yet) when calculating tx size There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see |
||
|
||
// sign tx | ||
|
@@ -285,6 +297,16 @@ func (rl *Relayer) buildTxWithData( | |
return tx, changeAddr, nil | ||
} | ||
|
||
func (rl *Relayer) getTxSize(utxo *types.UTXO, data []byte) (uint64, error) { | ||
tx, _, err := rl.buildTxWithData(utxo, data, true) | ||
if err != nil { | ||
return 0, err | ||
} | ||
txSize := tx.SerializeSize() | ||
log.Logger.Debugf("tx size is %v", txSize) | ||
return uint64(txSize), nil | ||
} | ||
|
||
func (rl *Relayer) sendTxToBTC(tx *wire.MsgTx) (*chainhash.Hash, error) { | ||
log.Logger.Debugf("Sending tx %v to BTC", tx.TxHash().String()) | ||
ha, err := rl.SendRawTransaction(tx, true) | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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.
Should this be a panic condition? Someone calling with an empty transaction size.
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.
This will be called when finding utxos that have sufficient funds