Skip to content

Conversation

vctt94
Copy link
Member

@vctt94 vctt94 commented Jun 9, 2022

closes #1609

@vctt94 vctt94 changed the title client: fix native btc wallet creation with fee limit smaller than 1s… client: fix native btc wallet creation with fee limit smaller than 1sat/B Jun 9, 2022
@chappjc
Copy link
Member

chappjc commented Jun 9, 2022

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
It shouldn't be possible to create a wallet that we can't open

@vctt94 vctt94 force-pushed the fix-native-btc-wallet-creation branch 2 times, most recently from ce12a91 to 6cc445e Compare June 13, 2022 16:47
@vctt94
Copy link
Member Author

vctt94 commented Jun 13, 2022

I agree it shouldn't be possible to create a wallet which can not be opened.

Now I am creating a method validateBtcWalletConf(walletCfg *WalletConfig) which I use to compare the fee rate limit, fallback fee rate, and conf target.

When the validation fails on newUnconnectedWallet, I set the default values to keep old behavior.

I also added two new params into WalletConfig struct, FeeRateLimitPerByte and FallbackFeeRatePerByte so we can pass them to the baseWallet, after creating a new baseWallet.

Comment on lines 917 to 918
if walletCfg.FeeRateLimitPerByte == 0 {
walletCfg.FeeRateLimitPerByte = cfg.DefaultFeeRateLimit
Copy link
Member

@chappjc chappjc Jun 15, 2022

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.

Copy link
Member Author

@vctt94 vctt94 Jun 15, 2022

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

Copy link
Member Author

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

Copy link
Member

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?

Copy link
Member Author

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,
			}
		}

Copy link
Member

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.

@vctt94 vctt94 force-pushed the fix-native-btc-wallet-creation branch from e185943 to 6dd64af Compare June 15, 2022 21:11
@chappjc chappjc added this to the 0.5 milestone Jun 17, 2022
Comment on lines -881 to -884
fallbackFeesPerByte := toSatoshi(walletCfg.FallbackFeeRate / 1000)
if fallbackFeesPerByte == 0 {
fallbackFeesPerByte = cfg.DefaultFallbackFee
Copy link
Member

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")
}

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member Author

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?

Copy link
Member

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.

Copy link
Member

@chappjc chappjc Jun 23, 2022

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 full CreateWallet 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.

Copy link
Member

@JoeGruffins JoeGruffins left a 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.

Comment on lines 890 to 906
err = fmt.Errorf("fallback fee rate limit is smaller than smallest unit: %v",
walletCfg.FallbackFeeRate)
Copy link
Member

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.

Copy link
Member

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.

Comment on lines 917 to 918
if walletCfg.FeeRateLimitPerByte == 0 {
walletCfg.FeeRateLimitPerByte = cfg.DefaultFeeRateLimit
Copy link
Member

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?

FeeRateLimitPerByte uint64
FallbackFeeRatePerByte uint64
RedeemConfTarget uint64 `ini:"redeemconftarget"`
ActivelyUsed bool `ini:"special:activelyUsed"` //injected by core
Copy link
Member

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

@vctt94 vctt94 force-pushed the fix-native-btc-wallet-creation branch from 995284b to 244b6b7 Compare June 21, 2022 14:59
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",
Copy link
Member

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?

@vctt94 vctt94 force-pushed the fix-native-btc-wallet-creation branch from ac3ddf4 to 0c4673a Compare June 22, 2022 14:08
Comment on lines 911 to 919
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
}
Copy link
Member

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.

Comment on lines 440 to 448
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
Copy link
Member

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.

@vctt94 vctt94 force-pushed the fix-native-btc-wallet-creation branch 3 times, most recently from a7d7690 to 01920cf Compare June 27, 2022 19:50
@@ -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{}
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
baseWallet := &baseWalletConfig{}
cfg := &baseWalletConfig{}

walletCfg.FeeRateLimit)
}
baseWallet.redeemConfTarget = walletCfg.RedeemConfTarget
if walletCfg.RedeemConfTarget == 0 {
Copy link
Member

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.

Comment on lines 449 to 450
// baseWalletConfig is the config params for the base wallet.
type baseWalletConfig struct {
Copy link
Member

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.

@@ -611,11 +623,8 @@ type baseWallet struct {
lastPeerCount uint32
peersChange func(uint32)
minNetworkVersion uint64
fallbackFeeRate uint64
feeRateLimit uint64
config *baseWalletConfig
Copy link
Member

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.

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",
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
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",

@vctt94 vctt94 force-pushed the fix-native-btc-wallet-creation branch 3 times, most recently from f883c1e to ce75e1f Compare June 28, 2022 15:11
@vctt94
Copy link
Member Author

vctt94 commented Jun 28, 2022

Thanks for the review buck, I addressed your comments.

}

func newUnconnectedWallet(cfg *BTCCloneCFG, walletCfg *WalletConfig) (*baseWallet, error) {
baseWalletConfig, err := readBaseWalletConfig(walletCfg)
Copy link
Member

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?

@vctt94 vctt94 force-pushed the fix-native-btc-wallet-creation branch from ce75e1f to d5232e6 Compare July 4, 2022 19:49
@buck54321 buck54321 merged commit 1961b68 into decred:master Jul 5, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

btc: creating native wallet with too-low fee limit wrecks the wallet
4 participants