Skip to content

Commit

Permalink
check for used addresses
Browse files Browse the repository at this point in the history
  • Loading branch information
buck54321 committed Feb 3, 2024
1 parent adc5d54 commit e18fc2b
Show file tree
Hide file tree
Showing 27 changed files with 250 additions and 45 deletions.
12 changes: 12 additions & 0 deletions client/asset/bch/spv.go
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,18 @@ func (w *bchSPVWallet) RemovePeer(addr string) error {
return w.peerManager.RemovePeer(addr)
}

func (w *bchSPVWallet) TotalReceivedForAddr(btcAddr btcutil.Address, minConf int32) (btcutil.Amount, error) {
bchAddr, err := dexbch.BTCAddrToBCHAddr(btcAddr, w.btcParams)
if err != nil {
return 0, err
}
amt, err := w.Wallet.TotalReceivedForAddr(bchAddr, 0)
if err != nil {
return 0, err
}
return btcutil.Amount(amt), nil
}

// secretSource is used to locate keys and redemption scripts while signing a
// transaction. secretSource satisfies the txauthor.SecretsSource interface.
type secretSource struct {
Expand Down
8 changes: 7 additions & 1 deletion client/asset/btc/btc.go
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,7 @@ var _ asset.Authenticator = (*ExchangeWalletFullNode)(nil)
var _ asset.Authenticator = (*ExchangeWalletAccelerator)(nil)
var _ asset.AddressReturner = (*baseWallet)(nil)
var _ asset.WalletHistorian = (*ExchangeWalletSPV)(nil)
var _ asset.NewAddresser = (*baseWallet)(nil)

// RecoveryCfg is the information that is transferred from the old wallet
// to the new one when the wallet is recovered.
Expand Down Expand Up @@ -4256,6 +4257,11 @@ func (btc *baseWallet) NewAddress() (string, error) {
return btc.DepositAddress()
}

// AddressUsed checks if a wallet address has been used.
func (btc *baseWallet) AddressUsed(addrStr string) (bool, error) {
return btc.node.addressUsed(addrStr)
}

// EstimateRegistrationTxFee returns an estimate for the tx fee needed to
// pay the registration fee using the provided feeRate.
func (btc *baseWallet) EstimateRegistrationTxFee(feeRate uint64) uint64 {
Expand Down Expand Up @@ -5271,7 +5277,7 @@ func (btc *intermediaryWallet) checkPendingTxs(tip uint64) {
}
}

btc.addTxToHistory(asset.Receive, txHash, toSatoshi(tx.Amount), fee, nil, nil, true)
btc.addTxToHistory(asset.Receive, txHash, toSatoshi(tx.Amount), fee, nil, &tx.Address, true)
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions client/asset/btc/electrum_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -1159,3 +1159,11 @@ func (ew *electrumWallet) findOutputSpender(ctx context.Context, txHash *chainha

return nil, 0, nil // caller should check msgTx (internal method)
}

func (ew *electrumWallet) addressUsed(addrStr string) (bool, error) {
txs, err := ew.wallet.GetAddressHistory(ew.ctx, addrStr)
if err != nil {
return false, fmt.Errorf("error getting address history: %w", err)
}
return len(txs) > 0, nil
}
72 changes: 41 additions & 31 deletions client/asset/btc/rpcclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,37 +29,38 @@ import (
)

const (
methodGetBalances = "getbalances"
methodGetBalance = "getbalance"
methodListUnspent = "listunspent"
methodLockUnspent = "lockunspent"
methodListLockUnspent = "listlockunspent"
methodChangeAddress = "getrawchangeaddress"
methodNewAddress = "getnewaddress"
methodSignTx = "signrawtransactionwithwallet"
methodSignTxLegacy = "signrawtransaction"
methodUnlock = "walletpassphrase"
methodLock = "walletlock"
methodPrivKeyForAddress = "dumpprivkey"
methodGetTransaction = "gettransaction"
methodSendToAddress = "sendtoaddress"
methodSetTxFee = "settxfee"
methodGetWalletInfo = "getwalletinfo"
methodGetAddressInfo = "getaddressinfo"
methodListDescriptors = "listdescriptors"
methodValidateAddress = "validateaddress"
methodEstimateSmartFee = "estimatesmartfee"
methodSendRawTransaction = "sendrawtransaction"
methodGetTxOut = "gettxout"
methodGetBlock = "getblock"
methodGetBlockHash = "getblockhash"
methodGetBestBlockHash = "getbestblockhash"
methodGetRawMempool = "getrawmempool"
methodGetRawTransaction = "getrawtransaction"
methodGetBlockHeader = "getblockheader"
methodGetNetworkInfo = "getnetworkinfo"
methodGetBlockchainInfo = "getblockchaininfo"
methodFundRawTransaction = "fundrawtransaction"
methodGetBalances = "getbalances"
methodGetBalance = "getbalance"
methodListUnspent = "listunspent"
methodLockUnspent = "lockunspent"
methodListLockUnspent = "listlockunspent"
methodChangeAddress = "getrawchangeaddress"
methodNewAddress = "getnewaddress"
methodSignTx = "signrawtransactionwithwallet"
methodSignTxLegacy = "signrawtransaction"
methodUnlock = "walletpassphrase"
methodLock = "walletlock"
methodPrivKeyForAddress = "dumpprivkey"
methodGetTransaction = "gettransaction"
methodSendToAddress = "sendtoaddress"
methodSetTxFee = "settxfee"
methodGetWalletInfo = "getwalletinfo"
methodGetAddressInfo = "getaddressinfo"
methodListDescriptors = "listdescriptors"
methodValidateAddress = "validateaddress"
methodEstimateSmartFee = "estimatesmartfee"
methodSendRawTransaction = "sendrawtransaction"
methodGetTxOut = "gettxout"
methodGetBlock = "getblock"
methodGetBlockHash = "getblockhash"
methodGetBestBlockHash = "getbestblockhash"
methodGetRawMempool = "getrawmempool"
methodGetRawTransaction = "getrawtransaction"
methodGetBlockHeader = "getblockheader"
methodGetNetworkInfo = "getnetworkinfo"
methodGetBlockchainInfo = "getblockchaininfo"
methodFundRawTransaction = "fundrawtransaction"
methodGetReceivedByAddress = "getreceivedbyaddress"
)

// IsTxNotFoundErr will return true if the error indicates that the requested
Expand Down Expand Up @@ -1133,6 +1134,15 @@ func SearchBlockForRedemptions(
return
}

func (wc *rpcClient) addressUsed(addr string) (bool, error) {
var recv float64
const minConf = 0
if err := wc.call(methodGetReceivedByAddress, []any{addr, minConf}, &recv); err != nil {
return false, err
}
return recv != 0, nil
}

// call is used internally to marshal parameters and send requests to the RPC
// server via (*rpcclient.Client).RawRequest. If thing is non-nil, the result
// will be marshaled into thing.
Expand Down
4 changes: 4 additions & 0 deletions client/asset/btc/spv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,10 @@ func (c *tBtcWallet) RemovePeer(string) error {
return nil
}

func (c *tBtcWallet) TotalReceivedForAddr(addr btcutil.Address, minConf int32) (btcutil.Amount, error) {
return 0, nil
}

type tNeutrinoClient struct {
*testData
}
Expand Down
15 changes: 15 additions & 0 deletions client/asset/btc/spv_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ type BTCWallet interface {
AddPeer(string) error
RemovePeer(string) error
ListSinceBlock(start, end, syncHeight int32) ([]btcjson.ListTransactionsResult, error)
TotalReceivedForAddr(addr btcutil.Address, minConf int32) (btcutil.Amount, error)
}

type XCWalletAccount struct {
Expand Down Expand Up @@ -1651,6 +1652,20 @@ func (w *spvWallet) getWalletTransaction(txHash *chainhash.Hash) (*GetTransactio
*/
}

func (w *spvWallet) addressUsed(addrStr string) (bool, error) {
addr, err := w.decodeAddr(addrStr, w.chainParams)
if err != nil {
return false, fmt.Errorf("error decoding address: %w", err)
}

const minConfs = 0
amt, err := w.wallet.TotalReceivedForAddr(addr, minConfs)
if err != nil {
return false, fmt.Errorf("error getting address received: %v", err)
}
return amt != 0, nil
}

func confirms(txHeight, curHeight int32) int32 {
switch {
case txHeight == -1, txHeight > curHeight:
Expand Down
1 change: 1 addition & 0 deletions client/asset/btc/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type Wallet interface {
ownsAddress(addr btcutil.Address) (bool, error) // this should probably just take a string
getWalletTransaction(txHash *chainhash.Hash) (*GetTransactionResult, error)
reconfigure(walletCfg *asset.WalletConfig, currentAddress string) (restartRequired bool, err error)
addressUsed(addr string) (bool, error)
}

type txLister interface {
Expand Down
6 changes: 6 additions & 0 deletions client/asset/dcr/dcr.go
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,7 @@ var _ asset.TxFeeEstimator = (*ExchangeWallet)(nil)
var _ asset.Bonder = (*ExchangeWallet)(nil)
var _ asset.Authenticator = (*ExchangeWallet)(nil)
var _ asset.TicketBuyer = (*ExchangeWallet)(nil)
var _ asset.NewAddresser = (*ExchangeWallet)(nil)

type block struct {
height int64
Expand Down Expand Up @@ -4005,6 +4006,11 @@ func (dcr *ExchangeWallet) NewAddress() (string, error) {
return dcr.DepositAddress()
}

// AddressUsed checks if a wallet address has been used.
func (dcr *ExchangeWallet) AddressUsed(addrStr string) (bool, error) {
return dcr.wallet.AddressUsed(dcr.ctx, addrStr)
}

// Unlock unlocks the exchange wallet.
func (dcr *ExchangeWallet) Unlock(pw []byte) error {
// Older SPV wallet potentially need an upgrade while we have a password.
Expand Down
4 changes: 4 additions & 0 deletions client/asset/dcr/dcr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,10 @@ func (c *tRPCClient) SetTxFee(ctx context.Context, fee dcrutil.Amount) error {
return nil
}

func (c *tRPCClient) GetReceivedByAddressMinConf(ctx context.Context, address stdaddr.Address, minConfs int) (dcrutil.Amount, error) {
return 0, nil
}

func (c *tRPCClient) ListSinceBlock(ctx context.Context, hash *chainhash.Hash) (*walletjson.ListSinceBlockResult, error) {
return nil, nil
}
Expand Down
14 changes: 14 additions & 0 deletions client/asset/dcr/rpcwallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ type rpcClient interface {
SetVoteChoice(ctx context.Context, agendaID, choiceID string) error
SetTxFee(ctx context.Context, fee dcrutil.Amount) error
ListSinceBlock(ctx context.Context, hash *chainhash.Hash) (*walletjson.ListSinceBlockResult, error)
GetReceivedByAddressMinConf(ctx context.Context, address stdaddr.Address, minConfs int) (dcrutil.Amount, error)
}

// newRPCWallet creates an rpcClient and uses it to construct a new instance
Expand Down Expand Up @@ -1149,6 +1150,19 @@ func (w *rpcWallet) SetTxFee(ctx context.Context, feePerKB dcrutil.Amount) error
return w.rpcClient.SetTxFee(ctx, feePerKB)
}

func (w *rpcWallet) AddressUsed(ctx context.Context, addrStr string) (bool, error) {
addr, err := stdaddr.DecodeAddress(addrStr, w.chainParams)
if err != nil {
return false, err
}
const minConf = 0
recv, err := w.rpcClient.GetReceivedByAddressMinConf(ctx, addr, minConf)
if err != nil {
return false, err
}
return recv != 0, nil
}

// anylist is a list of RPC parameters to be converted to []json.RawMessage and
// sent via nodeRawRequest.
type anylist []any
Expand Down
14 changes: 14 additions & 0 deletions client/asset/dcr/spv.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ type dcrWallet interface {
SetRelayFee(relayFee dcrutil.Amount)
GetTicketInfo(ctx context.Context, hash *chainhash.Hash) (*wallet.TicketSummary, *wire.BlockHeader, error)
ListSinceBlock(ctx context.Context, start, end, syncHeight int32) ([]walletjson.ListTransactionsResult, error)
TotalReceivedForAddr(ctx context.Context, addr stdaddr.Address, minConf int32) (dcrutil.Amount, error)
vspclient.Wallet
// TODO: Rescan and DiscoverActiveAddresses can be used for a Rescanner.
}
Expand Down Expand Up @@ -1304,6 +1305,19 @@ func (w *spvWallet) SetTxFee(_ context.Context, feePerKB dcrutil.Amount) error {
return nil
}

func (w *spvWallet) AddressUsed(ctx context.Context, addrStr string) (bool, error) {
addr, err := stdaddr.DecodeAddress(addrStr, w.chainParams)
if err != nil {
return false, err
}
const minConf = 0
recv, err := w.TotalReceivedForAddr(ctx, addr, minConf)
if err != nil {
return false, err
}
return recv != 0, nil
}

// cacheBlock caches a block for future use. The block has a lastAccess stamp
// added, and will be discarded if not accessed again within 2 hours.
func (w *spvWallet) cacheBlock(block *wire.MsgBlock) {
Expand Down
4 changes: 4 additions & 0 deletions client/asset/dcr/spv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,10 @@ func (w *tDcrWallet) ListSinceBlock(ctx context.Context, start, end, syncHeight
return nil, nil
}

func (w *tDcrWallet) TotalReceivedForAddr(ctx context.Context, addr stdaddr.Address, minConf int32) (dcrutil.Amount, error) {
return 0, nil
}

func tNewSpvWallet() (*spvWallet, *tDcrWallet) {
dcrw := &tDcrWallet{
blockHeader: make(map[chainhash.Hash]*wire.BlockHeader),
Expand Down
1 change: 1 addition & 0 deletions client/asset/dcr/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ type Wallet interface {
SetTxFee(ctx context.Context, feePerKB dcrutil.Amount) error
StakeInfo(ctx context.Context) (*wallet.StakeInfoData, error)
Reconfigure(ctx context.Context, cfg *asset.WalletConfig, net dex.Network, currentAddress string) (restart bool, err error)
AddressUsed(ctx context.Context, addrStr string) (bool, error)
}

// WalletTransaction is a pared down version of walletjson.GetTransactionResult.
Expand Down
1 change: 1 addition & 0 deletions client/asset/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,7 @@ type Sweeper interface {
// NewAddresser is a wallet that can generate new deposit addresses.
type NewAddresser interface {
NewAddress() (string, error)
AddressUsed(string) (bool, error)
}

// AddressReturner is a wallet that allows recycling of unused redemption or refund
Expand Down
12 changes: 12 additions & 0 deletions client/asset/ltc/spv.go
Original file line number Diff line number Diff line change
Expand Up @@ -836,6 +836,18 @@ func (w *ltcSPVWallet) RemovePeer(addr string) error {
return w.peerManager.RemovePeer(addr)
}

func (w *ltcSPVWallet) TotalReceivedForAddr(btcAddr btcutil.Address, minConf int32) (btcutil.Amount, error) {
ltcAddr, err := w.addrBTC2LTC(btcAddr)
if err != nil {
return 0, err
}
amt, err := w.Wallet.TotalReceivedForAddr(ltcAddr, 0)
if err != nil {
return 0, err
}
return btcutil.Amount(amt), nil
}

// secretSource is used to locate keys and redemption scripts while signing a
// transaction. secretSource satisfies the txauthor.SecretsSource interface.
type secretSource struct {
Expand Down
6 changes: 6 additions & 0 deletions client/asset/zec/transparent_rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,3 +365,9 @@ func syncStatus(c rpcCaller) (*btc.SyncStatus, error) {
Syncing: chainInfo.Syncing(),
}, nil
}

func getReceivedByAddress(c rpcCaller, addrStr string) (recv uint64, _ error) {
const minConf = 0
const inZats = true
return recv, c.CallRPC("getreceivedbyaddress", []any{addrStr, minConf, inZats}, &recv)
}
8 changes: 8 additions & 0 deletions client/asset/zec/zec.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ type zecWallet struct {

var _ asset.FeeRater = (*zecWallet)(nil)
var _ asset.Wallet = (*zecWallet)(nil)
var _ asset.NewAddresser = (*zecWallet)(nil)

// DRAFT TODO: Implement LiveReconfigurer
// var _ asset.LiveReconfigurer = (*zecWallet)(nil)
Expand Down Expand Up @@ -1573,6 +1574,13 @@ func (w *zecWallet) NewAddress() (string, error) {
return w.DepositAddress()
}

// AddressUsed checks if a wallet address has been used.
func (w *zecWallet) AddressUsed(addrStr string) (bool, error) {
// TODO: Resolve with new unified address encoding in https://github.com/decred/dcrdex/pull/2675
recv, err := getReceivedByAddress(w, addrStr)
return recv != 0, err
}

// DEPRECATED
func (w *zecWallet) EstimateRegistrationTxFee(feeRate uint64) uint64 {
return math.MaxUint64
Expand Down
15 changes: 15 additions & 0 deletions client/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -3791,6 +3791,21 @@ func (c *Core) NewDepositAddress(assetID uint32) (string, error) {
return addr, nil
}

// AddressUsed checks whether an address for a NewAddresser has been used.
func (c *Core) AddressUsed(assetID uint32, addr string) (bool, error) {
w, exists := c.wallet(assetID)
if !exists {
return false, newError(missingWalletErr, "no wallet found for %s", unbip(assetID))
}

na, ok := w.Wallet.(asset.NewAddresser)
if !ok {
return false, errors.New("wallet is not a NewAddresser")
}

return na.AddressUsed(addr)
}

// AutoWalletConfig attempts to load setting from a wallet package's
// asset.WalletInfo.DefaultConfigPath. If settings are not found, an empty map
// is returned.
Expand Down
4 changes: 4 additions & 0 deletions client/core/core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -923,6 +923,10 @@ func (w *TXCWallet) NewAddress() (string, error) {
return "", w.addrErr
}

func (w *TXCWallet) AddressUsed(addr string) (bool, error) {
return false, nil
}

func (w *TXCWallet) Unlock(pw []byte) error {
return w.unlockErr
}
Expand Down

0 comments on commit e18fc2b

Please sign in to comment.