Skip to content

Commit

Permalink
reconfig bug. CoinNotFoundError return value bug
Browse files Browse the repository at this point in the history
  • Loading branch information
buck54321 committed Oct 21, 2021
1 parent 350557b commit d316157
Show file tree
Hide file tree
Showing 14 changed files with 158 additions and 72 deletions.
1 change: 1 addition & 0 deletions client/asset/btc/btc.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ var (
spvWalletDefinition,
rpcWalletDefinition,
},
LegacyWalletIndex: 1,
}
)

Expand Down
58 changes: 43 additions & 15 deletions client/asset/btc/spv.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ func logNeutrino(netDir string) error {
rotatorMtx.Lock()
defer rotatorMtx.Unlock()
if loggerCount > 0 {
loggerCount++
return nil
}

Expand Down Expand Up @@ -477,10 +478,19 @@ func (w *spvWallet) syncStatus() (*syncStatus, error) {
return nil, err
}

target := w.syncHeight()
currentHeight := blk.Height
synced := w.wallet.ChainSynced()
// Sometimes the wallet doesn't report the chain as synced right away.
// Seems to be a bug.
if !synced && target > 0 && target == currentHeight {
synced = true
}

return &syncStatus{
Target: w.syncHeight(),
Height: blk.Height,
Syncing: !w.wallet.ChainSynced(),
Target: target,
Height: currentHeight,
Syncing: !synced,
}, nil
}

Expand Down Expand Up @@ -677,7 +687,8 @@ func (w *spvWallet) sendToAddress(address string, value, feeRate uint64, subtrac
}

func (w *spvWallet) sendWithSubtract(pkScript []byte, value, feeRate uint64) (*chainhash.Hash, error) {
const unfundedTxSize = dexbtc.MinimumTxOverhead + 2*dexbtc.P2WPKHOutputSize
txOutSize := dexbtc.TxOutOverhead + uint64(len(pkScript)) // send-to address
var unfundedTxSize uint64 = dexbtc.MinimumTxOverhead + dexbtc.P2WPKHOutputSize /* change */ + txOutSize

unspents, err := w.listUnspent()
if err != nil {
Expand All @@ -689,7 +700,9 @@ func (w *spvWallet) sendWithSubtract(pkScript []byte, value, feeRate uint64) (*c
return nil, fmt.Errorf("error converting unspent outputs: %w", err)
}

enough := func(inputsSize, inputsVal uint64) bool {
// With sendWithSubtract, fees are subtracted from the sent amount, so we
// target an input sum, not an output value. Makes the math easy.
enough := func(_, inputsVal uint64) bool {
return inputsVal >= value
}

Expand All @@ -699,8 +712,16 @@ func (w *spvWallet) sendWithSubtract(pkScript []byte, value, feeRate uint64) (*c
}

fees := (unfundedTxSize + uint64(inputsSize)) * feeRate
if fees > sum {
send := value - fees
extra := sum - send

switch {
case fees > sum:
return nil, fmt.Errorf("fees > sum")
case fees > value:
return nil, fmt.Errorf("fees > value")
case send > sum:
return nil, fmt.Errorf("send > sum")
}

tx := wire.NewMsgTx(wire.TxVersion)
Expand All @@ -710,9 +731,6 @@ func (w *spvWallet) sendWithSubtract(pkScript []byte, value, feeRate uint64) (*c
tx.AddTxIn(txIn)
}

send := value - fees
extra := sum - send

change := extra - fees
changeAddr, err := w.changeAddress()
if err != nil {
Expand Down Expand Up @@ -918,6 +936,11 @@ func (w *spvWallet) connect(ctx context.Context, wg *sync.WaitGroup) error {
// *Rescan supplied with a QuitChan-type RescanOption.
// Actually, should use btcwallet.Wallet.NtfnServer ?

// notes := make(<-chan interface{})
// if w.chainClient != nil {
// notes = w.chainClient.Notifications()
// }

// Nanny for the caches checkpoints and txBlocks caches.
wg.Add(1)
go func() {
Expand All @@ -926,6 +949,7 @@ func (w *spvWallet) connect(ctx context.Context, wg *sync.WaitGroup) error {
defer w.stop()

ticker := time.NewTicker(time.Minute * 20)
defer ticker.Stop()
expiration := time.Hour * 2
for {
select {
Expand All @@ -945,6 +969,8 @@ func (w *spvWallet) connect(ctx context.Context, wg *sync.WaitGroup) error {
}
}
w.checkpointMtx.Unlock()
// case note := <-notes:
// fmt.Printf("--Notification received: %T: %+v \n", note, note)
case <-ctx.Done():
return
}
Expand Down Expand Up @@ -1290,14 +1316,10 @@ func (w *spvWallet) getTxOut(txHash *chainhash.Hash, vout uint32, pkScript []byt
return nil, 0, err
}

if utxo == nil || utxo.spend != nil {
if utxo == nil || utxo.spend != nil || utxo.blockHash == nil {
return nil, 0, nil
}

if utxo.blockHash == nil {
return nil, 0, fmt.Errorf("output %s:%v not found", txHash, vout)
}

tip, err := w.cl.BestBlock()
if err != nil {
return nil, 0, fmt.Errorf("BestBlock error: %v", err)
Expand Down Expand Up @@ -1335,11 +1357,12 @@ search:
if err != nil {
return nil, fmt.Errorf("error getting block hash for height %d: %w", height, err)
}
res.checkpoint = *blockHash
matched, err := w.matchPkScript(blockHash, [][]byte{pkScript})
if err != nil {
return nil, fmt.Errorf("matchPkScript error: %w", err)
}

res.checkpoint = *blockHash
if !matched {
continue search
}
Expand Down Expand Up @@ -1401,6 +1424,11 @@ func (w *spvWallet) matchPkScript(blockHash *chainhash.Hash, scripts [][]byte) (
if err != nil {
return false, fmt.Errorf("GetCFilter error: %w", err)
}

if filter.N() == 0 {
return false, fmt.Errorf("unexpected empty filter for %s", blockHash)
}

var filterKey [gcs.KeySize]byte
copy(filterKey[:], blockHash[:gcs.KeySize])

Expand Down
5 changes: 4 additions & 1 deletion client/asset/btc/spv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"time"

"decred.org/dcrdex/dex"
"decred.org/dcrdex/dex/encode"
dexbtc "decred.org/dcrdex/dex/networks/btc"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/btcjson"
Expand Down Expand Up @@ -319,7 +320,9 @@ func (c *tNeutrinoClient) GetBlockHeader(blkHash *chainhash.Hash) (*wire.BlockHe
func (c *tNeutrinoClient) GetCFilter(blockHash chainhash.Hash, filterType wire.FilterType, options ...neutrino.QueryOption) (*gcs.Filter, error) {
var key [gcs.KeySize]byte
copy(key[:], blockHash.CloneBytes()[:])
return gcs.BuildGCSFilter(builder.DefaultP, builder.DefaultM, key, c.getCFilterScripts[blockHash])
scripts := c.getCFilterScripts[blockHash]
scripts = append(scripts, encode.RandomBytes(10))
return gcs.BuildGCSFilter(builder.DefaultP, builder.DefaultM, key, scripts)
}

func (c *tNeutrinoClient) GetBlock(blockHash chainhash.Hash, options ...neutrino.QueryOption) (*btcutil.Block, error) {
Expand Down
4 changes: 4 additions & 0 deletions client/asset/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ type WalletInfo struct {
// be the initial form offered to the user for configuration, with others
// available to select.
AvailableWallets []*WalletDefinition `json:"availablewallets"`
// LegacyWalletIndex should be set for assets that existed before wallets
// were typed. The index should point to the WalletDefinition that should
// be assumed when the type is provided as an empty string.
LegacyWalletIndex int `json:"emptyidx"`
// UnitInfo is the information about unit names and conversion factors for
// the asset.
UnitInfo dex.UnitInfo `json:"unitinfo"`
Expand Down
86 changes: 67 additions & 19 deletions client/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -1797,27 +1797,15 @@ func (c *Core) CreateWallet(appPW, walletPW []byte, form *WalletForm) error {
// derived deterministically from the app seed and a random password. The
// password is returned for encrypting and storing.
func (c *Core) createSeededWallet(assetID uint32, crypter encrypt.Crypter, form *WalletForm) ([]byte, error) {
creds := c.creds()
if creds == nil {
return nil, errors.New("no v2 credentials stored")
}

appSeed, err := crypter.Decrypt(creds.EncSeed)
seed, pw, err := c.assetSeedAndPass(assetID, crypter)
if err != nil {
return nil, fmt.Errorf("app seed decryption error: %w", err)
return nil, err
}

c.log.Infof("Initializing a built-in %s wallet", unbip(assetID))

b := make([]byte, len(appSeed)+4)
copy(b, appSeed)
binary.BigEndian.PutUint32(b[len(appSeed):], assetID)

seed := blake256.Sum256(b)
pw := encode.RandomBytes(32)
if err = asset.CreateWallet(assetID, &asset.CreateWalletParams{
Type: form.Type,
Seed: seed[:],
Seed: seed,
Pass: pw,
Settings: form.Config,
DataDir: c.assetDataDirectory(assetID),
Expand All @@ -1830,6 +1818,26 @@ func (c *Core) createSeededWallet(assetID uint32, crypter encrypt.Crypter, form
return pw, nil
}

func (c *Core) assetSeedAndPass(assetID uint32, crypter encrypt.Crypter) (seed, pass []byte, err error) {
creds := c.creds()
if creds == nil {
return nil, nil, errors.New("no v2 credentials stored")
}

appSeed, err := crypter.Decrypt(creds.EncSeed)
if err != nil {
return nil, nil, fmt.Errorf("app seed decryption error: %w", err)
}

b := make([]byte, len(appSeed)+4)
copy(b, appSeed)
binary.BigEndian.PutUint32(b[len(appSeed):], assetID)

s := blake256.Sum256(b)
p := blake256.Sum256(seed)
return s[:], p[:], nil
}

// assetDataDirectory is a directory for a wallet to use for local storage.
func (c *Core) assetDataDirectory(assetID uint32) string {
return filepath.Join(filepath.Dir(c.cfg.DBPath), "assetdb", unbip(assetID))
Expand Down Expand Up @@ -2026,7 +2034,7 @@ func (c *Core) ReconfigureWallet(appPW, newWalletPW []byte, form *WalletForm) er
assetID, unbip(assetID))
}
seeded := oldWallet.Info().Seeded
if seeded && len(newWalletPW) > 0 {
if seeded && newWalletPW != nil {
return newError(passwordErr, "cannot set a password on a built-in wallet")
}
oldDepositAddr := oldWallet.currentDepositAddress()
Expand All @@ -2039,10 +2047,23 @@ func (c *Core) ReconfigureWallet(appPW, newWalletPW []byte, form *WalletForm) er
Address: oldDepositAddr,
}

fmt.Printf("--ReconfigureWallet.0 %+v \n", walletDef)
var restartOnFail bool

defer func() {
if restartOnFail {
if _, err := c.connectWallet(oldWallet); err != nil {
c.log.Errorf("Failed to reconnect wallet after a failed reconfiguration attempt: %v", err)
}
}
}()

oldDef, err := walletDefinition(assetID, oldWallet.walletType)
if err != nil {
return fmt.Errorf("failed to locate old wallet definition: %v", err)
}

if walletDef.Seeded {
if len(newWalletPW) > 0 {
if newWalletPW != nil {
return newError(passwordErr, "cannot set a password on a seeded wallet")
}

Expand All @@ -2056,8 +2077,27 @@ func (c *Core) ReconfigureWallet(appPW, newWalletPW []byte, form *WalletForm) er
if err != nil {
return newError(createWalletErr, "error creating new %q-type %s wallet: %v", form.Type, unbip(assetID), err)
}
// return newError(existenceCheckErr, "cannot reconfigure wallet that doesn't exist")
} else if oldDef.Seeded {
_, newWalletPW, err = c.assetSeedAndPass(assetID, crypter)
if err != nil {
return newError(authErr, "error retrieving wallet password: %v", err)
}
}

if oldWallet.connected() {
oldDef, err := walletDefinition(assetID, oldWallet.walletType)
// Error can be normal if the wallet was created before wallet types
// were a thing. Just assume this is an old wallet and therefore not
// seeded.
if err == nil && oldDef.Seeded {
oldWallet.Disconnect()
restartOnFail = true
}
}
} else if newWalletPW == nil && oldDef.Seeded {
// If we're switching from a seeded wallet and no password was provided,
// use empty string = wallet not encrypted.
newWalletPW = []byte{}
}

// Reload the wallet with the new settings.
Expand Down Expand Up @@ -2159,6 +2199,8 @@ func (c *Core) ReconfigureWallet(appPW, newWalletPW []byte, form *WalletForm) er
c.wallets[assetID] = wallet
c.walletMtx.Unlock()

restartOnFail = false

if oldWallet.connected() {
// NOTE: Cannot lock the wallet backend because it may be the same as
// the one just connected.
Expand Down Expand Up @@ -6235,6 +6277,12 @@ func walletDefinition(assetID uint32, walletType string) (*asset.WalletDefinitio
if err != nil {
return nil, newError(assetSupportErr, "asset.Info error: %v", err)
}
if walletType == "" {
if len(winfo.AvailableWallets) <= winfo.LegacyWalletIndex {
return nil, fmt.Errorf("legacy wallet index out of range")
}
return winfo.AvailableWallets[winfo.LegacyWalletIndex], nil
}
var walletDef *asset.WalletDefinition
for _, def := range winfo.AvailableWallets {
if def.Type == walletType {
Expand Down
6 changes: 4 additions & 2 deletions client/core/trade.go
Original file line number Diff line number Diff line change
Expand Up @@ -2029,7 +2029,7 @@ func (t *trackedTrade) processAuditMsg(msgID uint64, audit *msgjson.Audit) error
err := t.auditContract(match, audit.CoinID, audit.Contract, audit.TxData)
if err != nil {
contractID := coinIDString(t.wallets.toAsset.ID, audit.CoinID)
t.dc.log.Error("Failed to audit contract coin %v (%s) for match %s: %v",
t.dc.log.Errorf("Failed to audit contract coin %v (%s) for match %s: %v",
contractID, t.wallets.toAsset.Symbol, match, err)
// Don't revoke in case server sends a revised audit request before
// the match is revoked.
Expand Down Expand Up @@ -2075,6 +2075,7 @@ func (t *trackedTrade) searchAuditInfo(match *matchTracker, coinID []byte, contr
var auditInfo *asset.AuditInfo
var tries int
contractID, contractSymb := coinIDString(t.wallets.toAsset.ID, coinID), t.wallets.toAsset.Symbol
tLastWarning := time.Now()
t.latencyQ.Wait(&wait.Waiter{
Expiration: time.Now().Add(24 * time.Hour), // effectively forever
TryFunc: func() bool {
Expand All @@ -2093,7 +2094,8 @@ func (t *trackedTrade) searchAuditInfo(match *matchTracker, coinID []byte, contr
"Check your internet and wallet connections!", contractID, contractSymb))
return wait.DontTryAgain
}
if tries > 0 && tries%12 == 0 {
if time.Since(tLastWarning) > 30*time.Minute {
tLastWarning = time.Now()
subject, detail := t.formatDetails(TopicAuditTrouble, contractID, contractSymb, match)
t.notify(newOrderNote(TopicAuditTrouble, subject, detail, db.WarningLevel, t.coreOrder()))
}
Expand Down
14 changes: 7 additions & 7 deletions client/webserver/live_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1099,6 +1099,7 @@ func (c *TCore) walletState(assetID uint32) *core.WalletState {
Balance: c.balances[assetID],
Units: winfos[assetID].UnitInfo.AtomicUnit,
Encrypted: true,
Synced: true,
}
}

Expand Down Expand Up @@ -1224,13 +1225,12 @@ func (c *TCore) SupportedAssets() map[uint32]*core.SupportedAsset {
c.mtx.RLock()
defer c.mtx.RUnlock()
return map[uint32]*core.SupportedAsset{
0: mkSupportedAsset("btc", c.wallets[0], c.balances[0]),
42: mkSupportedAsset("dcr", c.wallets[42], c.balances[42]),
2: mkSupportedAsset("ltc", c.wallets[2], c.balances[2]),
22: mkSupportedAsset("mona", c.wallets[22], c.balances[22]),
3: mkSupportedAsset("doge", c.wallets[3], c.balances[3]),
28: mkSupportedAsset("vtc", c.wallets[28], c.balances[28]),
141: mkSupportedAsset("kmd", c.wallets[141], c.balances[141]),
0: mkSupportedAsset("btc", c.wallets[0], c.balances[0]),
42: mkSupportedAsset("dcr", c.wallets[42], c.balances[42]),
2: mkSupportedAsset("ltc", c.wallets[2], c.balances[2]),
22: mkSupportedAsset("mona", c.wallets[22], c.balances[22]),
3: mkSupportedAsset("doge", c.wallets[3], c.balances[3]),
28: mkSupportedAsset("vtc", c.wallets[28], c.balances[28]),
}
}

Expand Down
2 changes: 1 addition & 1 deletion client/webserver/site/src/html/wallets.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@
</form>

{{- /* RECONFIGURE WALLET */ -}}
<form class="card bg1 border1 pb-3 d-hide mt-3" id="walletReconfig" autocomplete="off">
<form class="card bg1 border1 pb-3 d-hide mt-3" id="reconfigForm" autocomplete="off">
<div class="bg2 px-2 py-1 text-center position-relative fs18">
[[[Reconfigure]]]
<img id="recfgAssetLogo" class="micro-icon mx-1">
Expand Down
Loading

0 comments on commit d316157

Please sign in to comment.