Skip to content

Commit

Permalink
core: Check can trade with new wallet
Browse files Browse the repository at this point in the history
If a user has active trades, they must be settled by a wallet that can
complete those trades. Disallow changing to a new wallet that cannot do
this.
  • Loading branch information
JoeGruffins committed May 12, 2021
1 parent ba59397 commit 3fa59cf
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 6 deletions.
9 changes: 9 additions & 0 deletions client/asset/bch/bch.go
Expand Up @@ -210,6 +210,15 @@ func (bch *BCHWallet) AuditContract(coinID, contract, txData dex.Bytes) (*asset.
return ai, nil
}

// RefundAddress extracts and returns the refund address from a contract.
func (bch *BCHWallet) RefundAddress(contract dex.Bytes) (string, error) {
addr, err := bch.ExchangeWallet.RefundAddress(contract)
if err != nil {
return "", err
}
return dexbch.RecodeCashAddress(addr, bch.Net())
}

// rawTxSigner signs the transaction using Bitcoin Cash's custom signature
// hash and signing algorithm.
func rawTxInSigner(btcTx *wire.MsgTx, idx int, subScript []byte, hashType txscript.SigHashType, btcKey *btcec.PrivateKey, val uint64) ([]byte, error) {
Expand Down
9 changes: 9 additions & 0 deletions client/asset/btc/btc.go
Expand Up @@ -1687,6 +1687,15 @@ func (btc *ExchangeWallet) AuditContract(coinID, contract, txData dex.Bytes) (*a
}, nil
}

// RefundAddress extracts and returns the refund address from a contract.
func (btc *ExchangeWallet) RefundAddress(contract dex.Bytes) (string, error) {
sender, _, _, _, err := dexbtc.ExtractSwapDetails(contract, btc.segwit, btc.chainParams)
if err != nil {
return "", fmt.Errorf("error extracting refund address: %w", err)
}
return sender.String(), nil
}

// LocktimeExpired returns true if the specified contract's locktime has
// expired, making it possible to issue a Refund.
func (btc *ExchangeWallet) LocktimeExpired(contract dex.Bytes) (bool, time.Time, error) {
Expand Down
9 changes: 9 additions & 0 deletions client/asset/dcr/dcr.go
Expand Up @@ -1631,6 +1631,15 @@ func (dcr *ExchangeWallet) AuditContract(coinID, contract, txData dex.Bytes) (*a
}, nil
}

// RefundAddress extracts and returns the refund address from a contract.
func (dcr *ExchangeWallet) RefundAddress(contract dex.Bytes) (string, error) {
sender, _, _, _, err := dexdcr.ExtractSwapDetails(contract, dcr.chainParams)
if err != nil {
return "", fmt.Errorf("error extracting refund address: %w", err)
}
return sender.String(), nil
}

// LocktimeExpired returns true if the specified contract's locktime has
// expired, making it possible to issue a Refund.
func (dcr *ExchangeWallet) LocktimeExpired(contract dex.Bytes) (bool, time.Time, error) {
Expand Down
2 changes: 2 additions & 0 deletions client/asset/interface.go
Expand Up @@ -180,6 +180,8 @@ type Wallet interface {
ValidateSecret(secret, secretHash []byte) bool
// SyncStatus is information about the blockchain sync status.
SyncStatus() (synced bool, progress float32, err error)
// RefundAddress extracts and returns the refund address from a contract.
RefundAddress(contract dex.Bytes) (string, error)
}

// Balance is categorized information about a wallet's balance.
Expand Down
72 changes: 72 additions & 0 deletions client/core/core.go
Expand Up @@ -1902,6 +1902,78 @@ func (c *Core) ReconfigureWallet(appPW, newWalletPW []byte, assetID uint32, cfg
return err
}

// If there are active trades, make sure they can be settled by the
// keys held within the new wallet.
sameWallet := func() error {
ownsAddr := func(addr string) error {
owns, err := wallet.OwnsAddress(addr)
if err != nil {
return err
}
if !owns {
return fmt.Errorf("new wallet does not own address found in active trades: %v", addr)
}
return nil
}
for _, dc := range c.dexConnections() {
maybeDifferentWallet := false
for _, trade := range dc.trackedTrades() {
if !trade.isActive() {
continue
}
waID := wallet.AssetID
// If the to asset, check if we own an
// contract.Address.
if trade.wallets.toAsset.ID == waID {
if err := ownsAddr(trade.Trade().SwapAddress()); err != nil {
return err
}
// Assume all trade addresses are
// owned by the new wallet if one is.
return nil
}
// If the from asset, check if we own a
// refund address for a match if any exist.
if trade.wallets.fromAsset.ID == waID {
for _, match := range trade.matches {
script := match.MetaData.Proof.Script
if len(script) == 0 {
continue
}
addr, err := wallet.RefundAddress(script)
if err != nil {
return err
}
if err := ownsAddr(addr); err != nil {
return err
}
// Assume all refund addresses
// are owned by the new
// wallet if one is.
return nil
}
// If we did not find a refund address,
// we cannot be sure that this is the
// same wallet.
//
// TODO: Implement a way to check that
// these accounts are the same or not.
maybeDifferentWallet = true
}
}
if maybeDifferentWallet {
return errors.New("unable to change wallets with active trades. " +
"trades with this wallet must match or be canceled in " +
"order to change settings")
}
}
return nil
}
if err := sameWallet(); err != nil {
wallet.Disconnect()
return newError(walletErr, "new wallet cannot be used with current active trades: %v", err)
}

// If newWalletPW is non-nil, update the wallet's password.
if newWalletPW != nil { // includes empty non-nil slice
err = c.setWalletPassword(wallet, newWalletPW, crypter)
Expand Down
49 changes: 43 additions & 6 deletions client/core/core_test.go
Expand Up @@ -530,14 +530,17 @@ type TXCWallet struct {
preSwap *asset.PreSwap
preRedeemForm *asset.PreRedeemForm
preRedeem *asset.PreRedeem
ownsAddress bool
ownsAddressErr error
}

func newTWallet(assetID uint32) (*xcWallet, *TXCWallet) {
w := &TXCWallet{
changeCoin: &tCoin{id: encode.RandomBytes(36)},
syncStatus: func() (synced bool, progress float32, err error) { return true, 1, nil },
confs: make(map[string]uint32),
confsErr: make(map[string]error),
changeCoin: &tCoin{id: encode.RandomBytes(36)},
syncStatus: func() (synced bool, progress float32, err error) { return true, 1, nil },
confs: make(map[string]uint32),
confsErr: make(map[string]error),
ownsAddress: true,
}
xcWallet := &xcWallet{
Wallet: w,
Expand All @@ -561,7 +564,7 @@ func (w *TXCWallet) Info() *asset.WalletInfo {
}

func (w *TXCWallet) OwnsAddress(address string) (bool, error) {
return true, nil
return w.ownsAddress, w.ownsAddressErr
}

func (w *TXCWallet) Connect(ctx context.Context) (*sync.WaitGroup, error) {
Expand Down Expand Up @@ -668,6 +671,10 @@ func (w *TXCWallet) AuditContract(coinID, contract, txData dex.Bytes) (*asset.Au
return w.auditInfo, w.auditErr
}

func (w *TXCWallet) RefundAddress(contract dex.Bytes) (string, error) {
return "", nil
}

func (w *TXCWallet) LocktimeExpired(contract dex.Bytes) (bool, time.Time, error) {
return true, time.Now().Add(-time.Minute), nil
}
Expand Down Expand Up @@ -5203,14 +5210,26 @@ func TestReconfigureWallet(t *testing.T) {

// For the last success, make sure that we also clear any related
// tickGovernors.
matchID := ordertest.RandomMatchID()
match := &matchTracker{
suspectSwap: true,
tickGovernor: time.NewTimer(time.Hour),
MetaMatch: db.MetaMatch{
MetaData: &db.MatchMetaData{
Proof: db.MatchProof{
Script: dex.Bytes{0},
},
},
UserMatch: &order.UserMatch{
MatchID: matchID,
},
},
}
tCore.conns[tDexHost].trades[order.OrderID{}] = &trackedTrade{
Order: &order.LimitOrder{
P: order.Prefix{
BaseAsset: assetID,
BaseAsset: assetID,
ServerTime: time.Now(),
},
},
wallets: &walletSet{
Expand All @@ -5222,7 +5241,25 @@ func TestReconfigureWallet(t *testing.T) {
matches: map[order.MatchID]*matchTracker{
{}: match,
},
metaData: &db.OrderMetaData{},
dc: rig.dc,
}

// Error checking if wallet owns address.
tXyzWallet.ownsAddressErr = tErr
err = tCore.ReconfigureWallet(tPW, nil, assetID, newSettings)
if !errorHasCode(err, walletErr) {
t.Fatalf("wrong error when expecting ownsAddress wallet error: %v", err)
}
tXyzWallet.ownsAddressErr = nil

// Wallet doesn't own address.
tXyzWallet.ownsAddress = false
err = tCore.ReconfigureWallet(tPW, nil, assetID, newSettings)
if !errorHasCode(err, walletErr) {
t.Fatalf("wrong error when expecting not owned wallet error: %v", err)
}
tXyzWallet.ownsAddress = true

// Success updating settings.
err = tCore.ReconfigureWallet(tPW, nil, assetID, newSettings)
Expand Down

0 comments on commit 3fa59cf

Please sign in to comment.