-
Notifications
You must be signed in to change notification settings - Fork 127
client: fix native btc wallet creation with fee limit smaller than 1sat/B #1657
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
client: fix native btc wallet creation with fee limit smaller than 1sat/B #1657
Conversation
Hmm there a whole bunch of walletconf checks in unconnectedWallet that I figure should be shared. Maybe we make a shared function for the common stuff |
ce12a91
to
6cc445e
Compare
I agree it shouldn't be possible to create a wallet which can not be opened. Now I am creating a method When the validation fails on I also added two new params into WalletConfig struct, |
client/asset/btc/btc.go
Outdated
if walletCfg.FeeRateLimitPerByte == 0 { | ||
walletCfg.FeeRateLimitPerByte = cfg.DefaultFeeRateLimit |
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.
If the previous value, FallbackFeeRate
, failed validation, then validateBtcWalletConf
would have returned before it set walletCfg.FeeRateLimitPerByte
, which may have been valid, but it would go with the default instead.
I suggest were rework this a little, only applying the defaults to the fields that failed validation.
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 removed the early return from the validate method.
I also noticed this values being re-applied on error were actually doing nothing, as they would be overwritten by the forms input, so I removed them, as well
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.
oh, actually they need to be overwritten on error, otherwise tests fail.
Okay, undoing that
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.
If we want default values if values not set can we set those first and then do validate and error if that errors?
Isn't it correct to return error if values are wrong here? This happens when opening an existing wallet correct? We don't want to silently ignore what the user thinks they set, overwrite it and use some values they don't want?
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.
If we want default values if values not set can we set those first and then do validate and error if that errors?
What do you mean by set those value first? Right now the default values are populated from the configOpts
and if changing its value on the form, it will not be the default values anymore.
Isn't it correct to return error if values are wrong here? This happens when opening an existing wallet correct? We don't want to silently ignore what the user thinks they set, overwrite it and use some values they don't want?
In the UI an error will be thrown, when creating the wallet, but I agree with you about returning the error, the reason I am not returning it, is to keep the old behavior:
Right now on the walletTypeSPV test it passes an empty wallet config struct, so it will fail if we return the errors here
case walletTypeSPV:
w, err := newUnconnectedWallet(cfg, &WalletConfig{})
if err == nil {
wallet = &ExchangeWalletFullNode{w}
neutrinoClient := &tNeutrinoClient{data}
wallet.node = &spvWallet{
chainParams: &chaincfg.MainNetParams,
wallet: &tBtcWallet{data},
cl: neutrinoClient,
tipChan: make(chan *block, 1),
chainClient: nil,
acctNum: 0,
txBlocks: data.dbBlockForTx,
checkpoints: data.checkpoints,
log: cfg.Logger.SubLogger("SPV"),
loader: nil,
}
}
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 looks correct now. But I don't think we need the trace log as it will log as error later.
e185943
to
6dd64af
Compare
fallbackFeesPerByte := toSatoshi(walletCfg.FallbackFeeRate / 1000) | ||
if fallbackFeesPerByte == 0 { | ||
fallbackFeesPerByte = cfg.DefaultFallbackFee |
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.
Isn't the only thing we need to do is to check that
if walletCfg.FallbackFeeRate > 0 && walletCfg.FallbackFeeRate < 1000 {
return nil, fmt.Errorf("minimum fee rate is 1000 sats/kB")
}
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.
If we just check for FallbackFeeRate
it will still be possible to create a wallet with "invalid" values, for example:
someone tries to create a wallet with Highest acceptable fee rate less than the minimum value allowed (anything less than 1 sat/B), would create the wallet directories and db, but the form would throw an error making a wallet which can not be opened, like described in the issue.
Also, with toSatoshi()
It rounds for the minimum value possible, so we just need to check if it is not 0, considering we are not checking for a maximum value.
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.
Notice that in @buck54321's example code it permits 0, allowing the default to be applied elsewhere (caller).
The validator function can still check each of the settings that can result in an unopenable wallet.
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 it does not set values on error, it sets if values are not specified.
Thanks for clarifying chapp, I had misunderstood this. I fixed it now.
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.
Isn't the only thing we need to do is to check that
if walletCfg.FallbackFeeRate > 0 && walletCfg.FallbackFeeRate < 1000 { return nil, fmt.Errorf("minimum fee rate is 1000 sats/kB") }
I'm saying this entire PR should be just these three lines. You didn't need to touch WalletConfig
or create any new functions. We already had a pattern established for validating input values, We just weren't checking this condition.
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.
okay, but the problem is actually on the Create
method, which does not validate the spv wallet allowing it to create a wallet which can not be opened. Should I copy this validations over that method?
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 see what you mean. It wasn't failing on creation but failed on open, which tanked the creation process. I guess wallets need to basically guarantee that the settings given to Create
are valid.
We should also re-consider the sequence in CreateWallet
that allows this to happen. Should we somehow delete the wallet if the full CreateWallet
sequence doesn't complete? Or should we store the wallet (in db
and wallets
map) right after Create
, so users could have recovered by adjusting configuration settings.
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.
We should also re-consider the sequence in
CreateWallet
that allows this to happen. Should we somehow delete the wallet if the fullCreateWallet
sequence doesn't complete?
If in (*Core).CreateWallet
it creates the wallet fine but then opening it fails, I agree it would be ideal to "uncreate" the wallet. I think that can be handled if the wallet is a Recoverer
that allows it to be destroyed. We can follow-up with that I think. Ideally the Move
method, which started out as Destroy
in the draft if I recall correctly, would take an empty string to signal deletion instead of backup.
e.g.
// moveWalletData will move all wallet files to a backup directory, or delete them if empty string.
func (w *spvWallet) moveWalletData(backupDir string) error {
if backupDir == "" {
return os.RemoveAll(w.netDir)
}
...
...for later though.
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.
Solves the issue. I think should error more though.
client/asset/btc/btc.go
Outdated
err = fmt.Errorf("fallback fee rate limit is smaller than smallest unit: %v", | ||
walletCfg.FallbackFeeRate) |
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 think this log needs to print what the smallest unit is, not what I put in. I know what I put in.
Also you should return for every error I think and return nil
at the end if nothing errored, not set all to err
and return that at the end. This is what is normally done, and it helps future devs who may change this and expect returning on error to have happened.
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 is still not returning on error. It's not wrong, just not standard.
client/asset/btc/btc.go
Outdated
if walletCfg.FeeRateLimitPerByte == 0 { | ||
walletCfg.FeeRateLimitPerByte = cfg.DefaultFeeRateLimit |
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.
If we want default values if values not set can we set those first and then do validate and error if that errors?
Isn't it correct to return error if values are wrong here? This happens when opening an existing wallet correct? We don't want to silently ignore what the user thinks they set, overwrite it and use some values they don't want?
client/asset/btc/btc.go
Outdated
FeeRateLimitPerByte uint64 | ||
FallbackFeeRatePerByte uint64 | ||
RedeemConfTarget uint64 `ini:"redeemconftarget"` | ||
ActivelyUsed bool `ini:"special:activelyUsed"` //injected by core |
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.
nit: space missing in comment
995284b
to
244b6b7
Compare
client/asset/btc/btc.go
Outdated
fallbackFeesPerByte = cfg.DefaultFallbackFee | ||
walletCfg.FallbackFeeRatePerByte = toSatoshi(walletCfg.FallbackFeeRate / 1000) | ||
if walletCfg.FallbackFeeRatePerByte == 0 { | ||
err = fmt.Errorf("fallback fee rate limit is smaller than smallest unit(1 sat/byte): %v", |
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.
While true, the form has values of coin/kb. The error is also like this and in scientific notation. So maybe 1e-5 coin/kb
?
ac3ddf4
to
0c4673a
Compare
client/asset/btc/btc.go
Outdated
if walletCfg.FallbackFeeRate == 0 { | ||
walletCfg.FallbackFeeRate = float64(cfg.DefaultFallbackFee) * 1000 / 1e8 | ||
} | ||
if walletCfg.FeeRateLimit == 0 { | ||
walletCfg.FeeRateLimit = float64(cfg.DefaultFeeRateLimit) * 1000 / 1e8 | ||
} | ||
if walletCfg.RedeemConfTarget == 0 { | ||
walletCfg.RedeemConfTarget = defaultRedeemConfTarget | ||
} |
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.
If you're going to assign defaults here, you'll need want to do this in Create
also, no? Maybe just move it in to validateBtcWalletConf
.
client/asset/btc/btc.go
Outdated
UseSplitTx bool `ini:"txsplit"` | ||
FallbackFeeRate float64 `ini:"fallbackfee"` | ||
FeeRateLimit float64 `ini:"feeratelimit"` | ||
FeeRateLimitPerByte uint64 | ||
FallbackFeeRatePerByte uint64 | ||
RedeemConfTarget uint64 `ini:"redeemconftarget"` | ||
ActivelyUsed bool `ini:"special:activelyUsed"` // injected by core | ||
WalletName string `ini:"walletname"` // RPC | ||
Birthday uint64 `ini:"walletbirthday"` // SPV |
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.
Rather than WalletConfig
pulling double duty, I suggest having a second struct
type baseWalletConfig struct {
fallbackFeeRate uint64 // atoms/byte
feeRateLimit uint64 // atoms/byte
redeemConfTarget uint64
useSplitTx bool
}
and modifying validateBtcWalletConf
to be something like...
func readBaseWalletConfig(walletCfg *WalletConfig) (*baseWalletConfig, error)
Set the defaults for zeros inside. Then you can embed baseWalletConfig
in baseWallet
.
type baseWallet {
baseWalletConfig
...
}
and delete the existing fields.
a7d7690
to
01920cf
Compare
client/asset/btc/btc.go
Outdated
@@ -876,36 +885,48 @@ func newRPCWallet(requester RawRequesterWithContext, cfg *BTCCloneCFG, walletCon | |||
return &ExchangeWalletFullNode{btc}, nil | |||
} | |||
|
|||
func newUnconnectedWallet(cfg *BTCCloneCFG, walletCfg *WalletConfig) (*baseWallet, error) { | |||
func readBaseWalletConfig(walletCfg *WalletConfig) (*baseWalletConfig, error) { | |||
baseWallet := &baseWalletConfig{} |
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.
baseWallet := &baseWalletConfig{} | |
cfg := &baseWalletConfig{} |
client/asset/btc/btc.go
Outdated
walletCfg.FeeRateLimit) | ||
} | ||
baseWallet.redeemConfTarget = walletCfg.RedeemConfTarget | ||
if walletCfg.RedeemConfTarget == 0 { |
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 think this condition is impossible.
client/asset/btc/btc.go
Outdated
// baseWalletConfig is the config params for the base wallet. | ||
type baseWalletConfig struct { |
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.
Could you move this down to right above the baseWallet
definition.
client/asset/btc/btc.go
Outdated
@@ -611,11 +623,8 @@ type baseWallet struct { | |||
lastPeerCount uint32 | |||
peersChange func(uint32) | |||
minNetworkVersion uint64 | |||
fallbackFeeRate uint64 | |||
feeRateLimit uint64 | |||
config *baseWalletConfig |
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.
Could you please embed this so you don't have to add .config.
everywhere. It'll actually save me a bunch of conflicts in #1686 if you do.
client/asset/btc/btc.go
Outdated
fallbackFeesPerByte = cfg.DefaultFallbackFee | ||
baseWallet.fallbackFeeRate = toSatoshi(walletCfg.FallbackFeeRate / 1000) | ||
if baseWallet.fallbackFeeRate == 0 { | ||
return nil, fmt.Errorf("fallback fee rate limit is smaller than smallest unit(1e-5 coins/kB): %v", |
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.
return nil, fmt.Errorf("fallback fee rate limit is smaller than smallest unit(1e-5 coins/kB): %v", | |
return nil, fmt.Errorf("fallback fee rate limit is smaller than the minimum 1000 sats/byte: %v", |
f883c1e
to
ce75e1f
Compare
Thanks for the review buck, I addressed your comments. |
client/asset/btc/btc.go
Outdated
} | ||
|
||
func newUnconnectedWallet(cfg *BTCCloneCFG, walletCfg *WalletConfig) (*baseWallet, error) { | ||
baseWalletConfig, err := readBaseWalletConfig(walletCfg) |
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.
You're variable name is shadowing the type name. How about just baseCfg
?
…ewallet config params before creating the wallet
ce75e1f
to
d5232e6
Compare
closes #1609