From c4c264322288be1df1f68b1e0e8f99bb037dad38 Mon Sep 17 00:00:00 2001 From: Philemon Ukane Date: Thu, 18 Jan 2024 17:44:55 +0100 Subject: [PATCH 1/6] add ExchangeWalletCustom Signed-off-by: Philemon Ukane --- client/asset/btc/btc.go | 260 +++++++++++++---------- client/asset/btc/btc_test.go | 4 +- client/asset/btc/electrum.go | 16 +- client/asset/btc/electrum_client.go | 78 +++---- client/asset/btc/electrum_client_test.go | 10 +- client/asset/btc/rpcclient.go | 142 ++++++------- client/asset/btc/spv_test.go | 26 +-- client/asset/btc/spv_wrapper.go | 199 ++++++++++++----- client/asset/btc/wallet.go | 70 +++--- client/asset/ltc/ltc.go | 39 ++-- 10 files changed, 479 insertions(+), 365 deletions(-) diff --git a/client/asset/btc/btc.go b/client/asset/btc/btc.go index c1a50142b5..09904a01c0 100644 --- a/client/asset/btc/btc.go +++ b/client/asset/btc/btc.go @@ -633,26 +633,32 @@ func (d *Driver) MinLotSize(maxFeeRate uint64) uint64 { return dexbtc.MinLotSize(maxFeeRate, true) } -type CustomSPVWalletConstructor func(settings map[string]string, params *chaincfg.Params) (BTCWallet, error) +type CustomWallet interface { + Wallet + TxFeeEstimator + TipRedemptionWallet +} + +type CustomWalletConstructor func(settings map[string]string, params *chaincfg.Params) (CustomWallet, error) -// customSPVWalletConstructors are functions for setting up custom -// implementations of the BTCWallet interface that may be used by the -// ExchangeWalletSPV instead of the default spv implementation. -var customSPVWalletConstructors = map[string]CustomSPVWalletConstructor{} +// customWalletConstructors are functions for setting up CustomWallet +// implementations that may be used by the ExchangeWalletCustom instead of the +// default spv implementation. +var customWalletConstructors = map[string]CustomWalletConstructor{} -// RegisterCustomSPVWallet registers a function that should be used in creating -// a BTCWallet implementation that the ExchangeWalletSPV will use in place of +// RegisterCustomWallet registers a function that should be used in creating a +// CustomWallet implementation that ExchangeWalletCustom will use in place of // the default spv wallet implementation. External consumers can use this -// function to provide alternative BTCWallet implementations, and must do so -// before attempting to create an ExchangeWalletSPV instance of this type. It'll -// panic if callers try to register a wallet twice. -func RegisterCustomSPVWallet(constructor CustomSPVWalletConstructor, def *asset.WalletDefinition) { +// function to provide alternative CustomWallet implementations, and must do so +// before attempting to create an ExchangeWalletCustom instance of this type. +// It'll panic if callers try to register a wallet twice. +func RegisterCustomWallet(constructor CustomWalletConstructor, def *asset.WalletDefinition) { for _, availableWallets := range WalletInfo.AvailableWallets { if def.Type == availableWallets.Type { panic(fmt.Sprintf("wallet type (%q) is already registered", def.Type)) } } - customSPVWalletConstructors[def.Type] = constructor + customWalletConstructors[def.Type] = constructor WalletInfo.AvailableWallets = append(WalletInfo.AvailableWallets, def) } @@ -860,8 +866,8 @@ func (w *baseWallet) apiFeeFallback() bool { type intermediaryWallet struct { *baseWallet - txFeeEstimator txFeeEstimator - tipRedeemer tipRedemptionWallet + txFeeEstimator TxFeeEstimator + tipRedeemer TipRedemptionWallet syncingTxHistory atomic.Bool } @@ -891,6 +897,13 @@ type ExchangeWalletAccelerator struct { *ExchangeWalletFullNode } +// ExchangeWalletCustom is an external wallet that implements the Wallet, +// TxFeeEstimator and TipRedemptionWallet interface. +type ExchangeWalletCustom struct { + *intermediaryWallet + *authAddOn +} + // Check that wallets satisfy their supported interfaces. var _ asset.Wallet = (*intermediaryWallet)(nil) var _ asset.Accelerator = (*ExchangeWalletAccelerator)(nil) @@ -906,6 +919,7 @@ var _ asset.Bonder = (*baseWallet)(nil) var _ asset.Authenticator = (*ExchangeWalletSPV)(nil) var _ asset.Authenticator = (*ExchangeWalletFullNode)(nil) var _ asset.Authenticator = (*ExchangeWalletAccelerator)(nil) +var _ asset.Authenticator = (*ExchangeWalletCustom)(nil) var _ asset.AddressReturner = (*baseWallet)(nil) var _ asset.WalletHistorian = (*ExchangeWalletSPV)(nil) @@ -1108,22 +1122,11 @@ func NewWallet(cfg *asset.WalletConfig, logger dex.Logger, net dex.Network) (ass cloneCFG.MinElectrumVersion = *ver return ElectrumWallet(cloneCFG) default: - makeCustomWallet, ok := customSPVWalletConstructors[cfg.Type] + makeCustomWallet, ok := customWalletConstructors[cfg.Type] if !ok { return nil, fmt.Errorf("unknown wallet type %q", cfg.Type) } - - // Create custom wallet first and return early if we encounter any - // error. - btcWallet, err := makeCustomWallet(cfg.Settings, cloneCFG.ChainParams) - if err != nil { - return nil, fmt.Errorf("btc custom wallet setup error: %v", err) - } - - walletConstructor := func(_ string, _ *WalletConfig, _ *chaincfg.Params, _ dex.Logger) BTCWallet { - return btcWallet - } - return OpenSPVWallet(cloneCFG, walletConstructor) + return OpenCustomWallet(cloneCFG, makeCustomWallet) } } @@ -1361,6 +1364,43 @@ func noLocalFeeRate(ctx context.Context, rr RawRequester, u uint64) (uint64, err return 0, errors.New("no local fee rate estimate possible") } +// OpenCustomWallet opens a custom wallet. +func OpenCustomWallet(cfg *BTCCloneCFG, walletConstructor CustomWalletConstructor) (*ExchangeWalletCustom, error) { + walletCfg := new(WalletConfig) + err := config.Unmapify(cfg.WalletCFG.Settings, walletCfg) + if err != nil { + return nil, err + } + + // SPV wallets without a FeeEstimator will default to any enabled external + // fee estimator. + if cfg.FeeEstimator == nil { + cfg.FeeEstimator = noLocalFeeRate + } + + btc, err := newUnconnectedWallet(cfg, walletCfg) + if err != nil { + return nil, err + } + + customWallet, err := walletConstructor(cfg.WalletCFG.Settings, cfg.ChainParams) + if err != nil { + return nil, err + } + btc.setNode(customWallet) + + w := &ExchangeWalletCustom{ + intermediaryWallet: &intermediaryWallet{ + baseWallet: btc, + txFeeEstimator: customWallet, + tipRedeemer: customWallet, + }, + authAddOn: &authAddOn{customWallet}, + } + w.prepareRedemptionFinder() + return w, nil +} + // OpenSPVWallet opens the previously created native SPV wallet. func OpenSPVWallet(cfg *BTCCloneCFG, walletConstructor BTCWalletConstructor) (*ExchangeWalletSPV, error) { walletCfg := new(WalletConfig) @@ -1418,13 +1458,13 @@ func (btc *baseWallet) setNode(node Wallet) { return orderEnough(val, lots, maxFeeRate, btc.initTxSizeBase, btc.initTxSize, btc.segwit, reportChange) }, func() ([]*ListUnspentResult, error) { // list - return node.listUnspent() + return node.ListUnspent() }, func(unlock bool, ops []*Output) error { // lock - return node.lockUnspent(unlock, ops) + return node.LockUnspent(unlock, ops) }, func() ([]*RPCOutpoint, error) { // listLocked - return node.listLockUnspent() + return node.ListLockUnspent() }, func(txHash *chainhash.Hash, vout uint32) (*wire.TxOut, error) { txRaw, _, err := btc.rawWalletTx(txHash) @@ -1451,16 +1491,16 @@ func (btc *baseWallet) setNode(node Wallet) { func (btc *intermediaryWallet) prepareRedemptionFinder() { btc.rf = NewRedemptionFinder( btc.log, - btc.tipRedeemer.getWalletTransaction, - btc.tipRedeemer.getBlockHeight, - btc.tipRedeemer.getBlock, - btc.tipRedeemer.getBlockHeader, + btc.tipRedeemer.GetWalletTransaction, + btc.tipRedeemer.GetBlockHeight, + btc.tipRedeemer.GetBlock, + btc.tipRedeemer.GetBlockHeader, btc.hashTx, btc.deserializeTx, - btc.tipRedeemer.getBestBlockHeight, - btc.tipRedeemer.searchBlockForRedemptions, - btc.tipRedeemer.getBlockHash, - btc.tipRedeemer.findRedemptionsInMempool, + btc.tipRedeemer.GetBestBlockHeight, + btc.tipRedeemer.SearchBlockForRedemptions, + btc.tipRedeemer.GetBlockHash, + btc.tipRedeemer.FindRedemptionsInMempool, ) } @@ -1515,7 +1555,7 @@ func (btc *baseWallet) findExistingAddressBasedTxHistoryDB() (string, error) { func (btc *baseWallet) startTxHistoryDB(ctx context.Context) (*sync.WaitGroup, error) { var dbPath string - fingerPrint, err := btc.node.fingerprint() + fingerPrint, err := btc.node.Fingerprint() if err == nil && fingerPrint != "" { dbPath = btc.txHistoryDBPath(fingerPrint) } @@ -1584,12 +1624,12 @@ func (btc *baseWallet) startTxHistoryDB(ctx context.Context) (*sync.WaitGroup, e func (btc *baseWallet) connect(ctx context.Context) (*sync.WaitGroup, error) { btc.ctx = ctx var wg sync.WaitGroup - if err := btc.node.connect(ctx, &wg); err != nil { + if err := btc.node.Connect(ctx, &wg); err != nil { return nil, err } // Initialize the best block. - bestBlockHdr, err := btc.node.getBestBlockHeader() + bestBlockHdr, err := btc.node.GetBestBlockHeader() if err != nil { return nil, fmt.Errorf("error initializing best block for %s: %w", btc.symbol, err) } @@ -1667,7 +1707,7 @@ func (btc *intermediaryWallet) Connect(ctx context.Context) (*sync.WaitGroup, er // Reconfigure attempts to reconfigure the wallet. func (btc *baseWallet) Reconfigure(ctx context.Context, cfg *asset.WalletConfig, currentAddress string) (restart bool, err error) { // See what the node says. - restart, err = btc.node.reconfigure(cfg, currentAddress) + restart, err = btc.node.Reconfigure(cfg, currentAddress) if err != nil { return false, err } @@ -1728,13 +1768,13 @@ func (r *GetBlockchainInfoResult) Syncing() bool { // SyncStatus is information about the blockchain sync status. func (btc *baseWallet) SyncStatus() (*asset.SyncStatus, error) { - ss, err := btc.node.syncStatus() + ss, err := btc.node.SyncStatus() if err != nil { return nil, err } ss.StartingBlocks = uint64(atomic.LoadInt64(&btc.tipAtConnect)) if ss.Synced { - numPeers, err := btc.node.peerCount() + numPeers, err := btc.node.PeerCount() if err != nil { return nil, err } @@ -1750,7 +1790,7 @@ func (btc *baseWallet) OwnsDepositAddress(address string) (bool, error) { if err != nil { return false, err } - return btc.node.ownsAddress(addr) + return btc.node.OwnsAddress(addr) } func (btc *baseWallet) balance() (*asset.Balance, error) { @@ -1764,7 +1804,7 @@ func (btc *baseWallet) balance() (*asset.Balance, error) { if btc.useLegacyBalance { return btc.legacyBalance() } - balances, err := btc.node.balances() + balances, err := btc.node.Balances() if err != nil { return nil, err } @@ -2576,7 +2616,7 @@ func (btc *baseWallet) submitMultiSplitTx(fundingCoins asset.Coins, spents []*Ou var success bool defer func() { if !success { - btc.node.lockUnspent(true, spents) + btc.node.LockUnspent(true, spents) } }() @@ -2591,7 +2631,7 @@ func (btc *baseWallet) submitMultiSplitTx(fundingCoins asset.Coins, spents []*Ou outputAddresses := make([]btcutil.Address, len(orders)) for i, req := range requiredForOrders { - outputAddr, err := btc.node.externalAddress() + outputAddr, err := btc.node.ExternalAddress() if err != nil { return nil, 0, err } @@ -2603,7 +2643,7 @@ func (btc *baseWallet) submitMultiSplitTx(fundingCoins asset.Coins, spents []*Ou baseTx.AddTxOut(wire.NewTxOut(int64(req), script)) } - changeAddr, err := btc.node.changeAddress() + changeAddr, err := btc.node.ChangeAddress() if err != nil { return nil, 0, err } @@ -2627,7 +2667,7 @@ func (btc *baseWallet) submitMultiSplitTx(fundingCoins asset.Coins, spents []*Ou } } btc.cm.LockUTXOs(locks) - btc.node.lockUnspent(false, ops) + btc.node.LockUnspent(false, ops) var totalOut uint64 for _, txOut := range tx.TxOut { @@ -2761,7 +2801,7 @@ func (btc *baseWallet) fundMultiWithSplit(keep, maxLock uint64, values []*asset. } btc.cm.LockUTXOs(locks) - btc.node.lockUnspent(false, spents) + btc.node.LockUnspent(false, spents) return coins, redeemScripts, splitFees, nil } @@ -2779,7 +2819,7 @@ func (btc *baseWallet) fundMulti(maxLock uint64, values []*asset.MultiOrderValue } if len(coins) == len(values) || !allowSplit { btc.cm.LockOutputsMap(fundingCoins) - btc.node.lockUnspent(false, spents) + btc.node.LockUnspent(false, spents) return coins, redeemScripts, 0, nil } @@ -2814,7 +2854,7 @@ func (btc *baseWallet) split(value uint64, lots uint64, outputs []*Output, input return } btc.cm.LockOutputsMap(fundingCoins) - err = btc.node.lockUnspent(false, outputs) + err = btc.node.LockUnspent(false, outputs) if err != nil { btc.log.Errorf("error locking unspent outputs: %v", err) } @@ -2842,7 +2882,7 @@ func (btc *baseWallet) split(value uint64, lots uint64, outputs []*Output, input return coins, false, 0, nil // err==nil records and locks the provided fundingCoins in defer } - addr, err := btc.node.externalAddress() + addr, err := btc.node.ExternalAddress() if err != nil { return nil, false, 0, fmt.Errorf("error creating split transaction address: %w", err) } @@ -2861,7 +2901,7 @@ func (btc *baseWallet) split(value uint64, lots uint64, outputs []*Output, input baseTx.AddTxOut(wire.NewTxOut(int64(reqFunds), splitScript)) if extraOutput > 0 { - addr, err := btc.node.changeAddress() + addr, err := btc.node.ChangeAddress() if err != nil { return nil, false, 0, fmt.Errorf("error creating split transaction address: %w", err) } @@ -2873,7 +2913,7 @@ func (btc *baseWallet) split(value uint64, lots uint64, outputs []*Output, input } // Grab a change address. - changeAddr, err := btc.node.changeAddress() + changeAddr, err := btc.node.ChangeAddress() if err != nil { return nil, false, 0, fmt.Errorf("error creating change address: %w", err) } @@ -2958,7 +2998,7 @@ func (btc *baseWallet) rawWalletTx(hash *chainhash.Hash) ([]byte, uint32, error) // fallback to getWalletTransaction } - tx, err := btc.node.getWalletTransaction(hash) + tx, err := btc.node.GetWalletTransaction(hash) if err != nil { return nil, 0, err } @@ -2981,18 +3021,18 @@ type authAddOn struct { // the password for the underlying bitcoind wallet which will also be unlocked. // It implements asset.authenticator. func (a *authAddOn) Unlock(pw []byte) error { - return a.w.walletUnlock(pw) + return a.w.WalletUnlock(pw) } // Lock locks the underlying bitcoind wallet. It implements asset.authenticator. func (a *authAddOn) Lock() error { - return a.w.walletLock() + return a.w.WalletLock() } // Locked will be true if the wallet is currently locked. It implements // asset.authenticator. func (a *authAddOn) Locked() bool { - return a.w.locked() + return a.w.Locked() } func (btc *baseWallet) addInputsToTx(tx *wire.MsgTx, coins asset.Coins) (uint64, []OutPoint, error) { @@ -3028,7 +3068,7 @@ func (btc *baseWallet) fundedTx(coins asset.Coins) (*wire.MsgTx, uint64, []OutPo // lookupWalletTxOutput looks up the value of a transaction output that is // spandable by this wallet, and creates an output. func (btc *baseWallet) lookupWalletTxOutput(txHash *chainhash.Hash, vout uint32) (*Output, error) { - getTxResult, err := btc.node.getWalletTransaction(txHash) + getTxResult, err := btc.node.GetWalletTransaction(txHash) if err != nil { return nil, err } @@ -3056,7 +3096,7 @@ func (btc *baseWallet) getTransactions(coins []dex.Bytes) ([]*GetTransactionResu if err != nil { return nil, err } - getTxRes, err := btc.node.getWalletTransaction(txHash) + getTxRes, err := btc.node.GetWalletTransaction(txHash) if err != nil { return nil, err } @@ -3074,7 +3114,7 @@ func (btc *baseWallet) getTxFee(tx *wire.MsgTx) (uint64, error) { } for _, txIn := range tx.TxIn { - prevTx, err := btc.node.getWalletTransaction(&txIn.PreviousOutPoint.Hash) + prevTx, err := btc.node.GetWalletTransaction(&txIn.PreviousOutPoint.Hash) if err != nil { return 0, err } @@ -3144,7 +3184,7 @@ func (btc *baseWallet) additionalFeesRequired(txs []*GetTransactionResult, newFe // changeCanBeAccelerated returns nil if the change can be accelerated, // otherwise it returns an error containing the reason why it cannot. func (btc *baseWallet) changeCanBeAccelerated(change *Output, remainingSwaps bool) error { - lockedUtxos, err := btc.node.listLockUnspent() + lockedUtxos, err := btc.node.ListLockUnspent() if err != nil { return err } @@ -3160,7 +3200,7 @@ func (btc *baseWallet) changeCanBeAccelerated(change *Output, remainingSwaps boo } } - utxos, err := btc.node.listUnspent() + utxos, err := btc.node.ListUnspent() if err != nil { return err } @@ -3254,7 +3294,7 @@ func (btc *baseWallet) signedAccelerationTx(previousTxs []*GetTransactionResult, return makeError(err) } - addr, err := btc.node.externalAddress() + addr, err := btc.node.ExternalAddress() if err != nil { return makeError(fmt.Errorf("error creating change address: %w", err)) } @@ -3351,7 +3391,7 @@ func accelerateOrder(btc *baseWallet, swapCoins, accelerationCoins []dex.Bytes, // changeCanBeAccelerated would have returned an error since this means // that the change was locked by another order. if requiredForRemainingSwaps > 0 { - err = btc.node.lockUnspent(false, []*Output{newChange}) + err = btc.node.LockUnspent(false, []*Output{newChange}) if err != nil { // The transaction is already broadcasted, so don't fail now. btc.log.Errorf("failed to lock change output: %v", err) @@ -3793,7 +3833,7 @@ func (btc *baseWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, ui } // Grab a change address. - changeAddr, err := btc.node.changeAddress() + changeAddr, err := btc.node.ChangeAddress() if err != nil { return nil, nil, 0, fmt.Errorf("error creating change address: %w", err) } @@ -3857,7 +3897,7 @@ func (btc *baseWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, ui if change != nil && swaps.LockChange { // Lock the change output btc.log.Debugf("locking change coin %s", change) - err = btc.node.lockUnspent(false, []*Output{change}) + err = btc.node.LockUnspent(false, []*Output{change}) if err != nil { // The swap transaction is already broadcasted, so don't fail now. btc.log.Errorf("failed to lock change output: %v", err) @@ -3961,7 +4001,7 @@ func (btc *baseWallet) Redeem(form *asset.RedeemForm) ([]dex.Bytes, asset.Coin, } // Send the funds back to the exchange wallet. - redeemAddr, err := btc.node.externalAddress() + redeemAddr, err := btc.node.ExternalAddress() if err != nil { return nil, nil, 0, fmt.Errorf("error getting new address from the wallet: %w", err) } @@ -4063,7 +4103,7 @@ func (btc *baseWallet) SignMessage(coin asset.Coin, msg dex.Bytes) (pubkeys, sig if utxo == nil { return nil, nil, fmt.Errorf("no utxo found for %s", op) } - privKey, err := btc.node.privKeyForAddress(utxo.Address) + privKey, err := btc.node.PrivKeyForAddress(utxo.Address) if err != nil { return nil, nil, err } @@ -4107,7 +4147,7 @@ func (btc *baseWallet) AuditContract(coinID, contract, txData dex.Bytes, rebroad if len(txData) == 0 { // Fall back to gettxout, but we won't have the tx to rebroadcast. pkScript, _ := btc.scriptHashScript(contract) // pkScript and since time are unused if full node - txOut, _, err = btc.node.getTxOut(txHash, vout, pkScript, time.Now().Add(-ContractSearchLimit)) + txOut, _, err = btc.node.GetTxOut(txHash, vout, pkScript, time.Now().Add(-ContractSearchLimit)) if err != nil || txOut == nil { return nil, fmt.Errorf("error finding unspent contract: %s:%d : %w", txHash, vout, err) } @@ -4162,7 +4202,7 @@ func (btc *baseWallet) AuditContract(coinID, contract, txData dex.Bytes, rebroad // and does not affect the audit result. if rebroadcast && tx != nil { go func() { - if hashSent, err := btc.node.sendRawTransaction(tx); err != nil { + if hashSent, err := btc.node.SendRawTransaction(tx); err != nil { btc.log.Debugf("Rebroadcasting counterparty contract %v (THIS MAY BE NORMAL): %v", txHash, err) } else if !hashSent.IsEqual(txHash) { btc.log.Errorf("Counterparty contract %v was rebroadcast as %v!", txHash, hashSent) @@ -4188,7 +4228,7 @@ func (btc *baseWallet) AuditContract(coinID, contract, txData dex.Bytes, rebroad // LockTimeExpired returns true if the specified locktime has expired, making it // possible to refund the locked coins. func (btc *baseWallet) LockTimeExpired(_ context.Context, lockTime time.Time) (bool, error) { - medianTime, err := btc.node.medianTime() // TODO: pass ctx + medianTime, err := btc.node.MedianTime() // TODO: pass ctx if err != nil { return false, fmt.Errorf("error getting median time: %w", err) } @@ -4250,7 +4290,7 @@ func (btc *baseWallet) Refund(coinID, contract dex.Bytes, feeRate uint64) (dex.B // the wallet tx though and used for the spender search, while not passing // a script here to ensure no attempt is made to find the output without // a limited startTime. - utxo, _, err := btc.node.getTxOut(txHash, vout, pkScript, time.Time{}) + utxo, _, err := btc.node.GetTxOut(txHash, vout, pkScript, time.Time{}) if err != nil { return nil, fmt.Errorf("error finding unspent contract: %w", err) } @@ -4316,7 +4356,7 @@ func (btc *baseWallet) refundTx(txHash *chainhash.Hash, vout uint32, contract de return nil, fmt.Errorf("refund tx not worth the fees") } if refundAddr == nil { - refundAddr, err = btc.node.externalAddress() + refundAddr, err = btc.node.ExternalAddress() if err != nil { return nil, fmt.Errorf("error getting new address from the wallet: %w", err) } @@ -4361,7 +4401,7 @@ func (btc *baseWallet) refundTx(txHash *chainhash.Hash, vout uint32, contract de // DepositAddress returns an address for depositing funds into the // exchange wallet. func (btc *baseWallet) DepositAddress() (string, error) { - addr, err := btc.node.externalAddress() + addr, err := btc.node.ExternalAddress() if err != nil { return "", err } @@ -4369,14 +4409,14 @@ func (btc *baseWallet) DepositAddress() (string, error) { if err != nil { return "", err } - if btc.node.locked() { + if btc.node.Locked() { return addrStr, nil } // If the wallet is unlocked, be extra cautious and ensure the wallet gave // us an address for which we can retrieve the private keys, regardless of // what ownsAddress would say. - priv, err := btc.node.privKeyForAddress(addrStr) + priv, err := btc.node.PrivKeyForAddress(addrStr) if err != nil { return "", fmt.Errorf("private key unavailable for address %v: %w", addrStr, err) } @@ -4473,7 +4513,7 @@ func (btc *baseWallet) SendTransaction(rawTx []byte) ([]byte, error) { return nil, err } - txHash, err := btc.node.sendRawTransaction(msgTx) + txHash, err := btc.node.SendRawTransaction(msgTx) if err != nil { return nil, err } @@ -4530,7 +4570,7 @@ func (btc *baseWallet) send(address string, val uint64, feeRate uint64, subtract } fundedTx.AddTxOut(wire.NewTxOut(int64(toSend), pay2script)) - changeAddr, err := btc.node.changeAddress() + changeAddr, err := btc.node.ChangeAddress() if err != nil { return nil, 0, 0, fmt.Errorf("error creating change address: %w", err) } @@ -4579,7 +4619,7 @@ func (btc *baseWallet) SwapConfirmations(_ context.Context, id dex.Bytes, contra if err != nil { return 0, false, err } - return btc.node.swapConfirmations(txHash, vout, pkScript, startTime) + return btc.node.SwapConfirmations(txHash, vout, pkScript, startTime) } // RegFeeConfirmations gets the number of confirmations for the specified output @@ -4595,7 +4635,7 @@ func (btc *baseWallet) RegFeeConfirmations(_ context.Context, id dex.Bytes) (con } func (btc *baseWallet) checkPeers() { - numPeers, err := btc.node.peerCount() + numPeers, err := btc.node.PeerCount() if err != nil { prevPeer := atomic.SwapUint32(&btc.lastPeerCount, 0) if prevPeer != 0 { @@ -4653,7 +4693,7 @@ func (btc *intermediaryWallet) watchBlocks(ctx context.Context) { // Poll for the block. If the wallet offers tip reports, delay reporting // the tip to give the wallet a moment to request and scan block data. case <-ticker.C: - newTipHdr, err := btc.node.getBestBlockHeader() + newTipHdr, err := btc.node.GetBestBlockHeader() if err != nil { btc.log.Errorf("failed to get best block header from %s node: %v", btc.symbol, err) continue @@ -4690,7 +4730,7 @@ func (btc *intermediaryWallet) watchBlocks(ctx context.Context) { queuedBlock.queue.Stop() } blockAllowance := walletBlockAllowance - syncStatus, err := btc.node.syncStatus() + syncStatus, err := btc.node.SyncStatus() if err != nil { btc.log.Errorf("Error retrieving sync status before queuing polled block: %v", err) } else if !syncStatus.Synced { @@ -4773,7 +4813,7 @@ func (btc *baseWallet) signTxAndAddChange(baseTx *wire.MsgTx, addr btcutil.Addre // Sign the transaction to get an initial size estimate and calculate whether // a change output would be dust. sigCycles := 1 - msgTx, err := btc.node.signTx(baseTx) + msgTx, err := btc.node.SignTx(baseTx) if err != nil { return makeErr("signing error: %v, raw tx: %x", err, btc.wireBytes(baseTx)) } @@ -4818,7 +4858,7 @@ func (btc *baseWallet) signTxAndAddChange(baseTx *wire.MsgTx, addr btcutil.Addre for { // Sign the transaction with the change output and compute new size. sigCycles++ - msgTx, err = btc.node.signTx(baseTx) + msgTx, err = btc.node.SignTx(baseTx) if err != nil { return makeErr("signing error: %v, raw tx: %x", err, btc.wireBytes(baseTx)) } @@ -4876,7 +4916,7 @@ func (btc *baseWallet) signTxAndAddChange(baseTx *wire.MsgTx, addr btcutil.Addre } func (btc *baseWallet) broadcastTx(signedTx *wire.MsgTx) (*chainhash.Hash, error) { - txHash, err := btc.node.sendRawTransaction(signedTx) + txHash, err := btc.node.SendRawTransaction(signedTx) if err != nil { return nil, fmt.Errorf("sendrawtx error: %v, raw tx: %x", err, btc.wireBytes(signedTx)) } @@ -4896,7 +4936,7 @@ func (btc *baseWallet) createSig(tx *wire.MsgTx, idx int, pkScript []byte, addr return nil, nil, err } - privKey, err := btc.node.privKeyForAddress(addrStr) + privKey, err := btc.node.PrivKeyForAddress(addrStr) if err != nil { return nil, nil, err } @@ -4918,7 +4958,7 @@ func (btc *baseWallet) createWitnessSig(tx *wire.MsgTx, idx int, pkScript []byte if err != nil { return nil, nil, err } - privKey, err := btc.node.privKeyForAddress(addrStr) + privKey, err := btc.node.PrivKeyForAddress(addrStr) if err != nil { return nil, nil, err } @@ -4971,7 +5011,7 @@ func (btc *intermediaryWallet) EstimateSendTxFee(address string, sendAmount, fee tx := wire.NewMsgTx(btc.txVersion()) tx.AddTxOut(wireOP) - fee, err = btc.txFeeEstimator.estimateSendTxFee(tx, btc.feeRateWithFallback(feeRate), subtract) + fee, err = btc.txFeeEstimator.EstimateSendTxFee(tx, btc.feeRateWithFallback(feeRate), subtract) if err != nil { return 0, false, err } @@ -5124,7 +5164,7 @@ func (btc *baseWallet) MakeBondTx(ver uint16, amt, feeRate uint64, lockTime time return nil, nil, fmt.Errorf("failed to add inputs to bond tx: %w", err) } - changeAddr, err := btc.node.changeAddress() + changeAddr, err := btc.node.ChangeAddress() if err != nil { return nil, nil, fmt.Errorf("error creating change address: %w", err) } @@ -5219,7 +5259,7 @@ func (btc *baseWallet) makeBondRefundTxV0(txid *chainhash.Hash, vout uint32, amt } // Add the refund output. - redeemAddr, err := btc.node.externalAddress() + redeemAddr, err := btc.node.ExternalAddress() if err != nil { return nil, fmt.Errorf("error creating change address: %w", err) } @@ -5281,7 +5321,7 @@ func (btc *baseWallet) RefundBond(ctx context.Context, ver uint16, coinID, scrip return nil, err } - _, err = btc.node.sendRawTransaction(msgTx) + _, err = btc.node.SendRawTransaction(msgTx) if err != nil { return nil, fmt.Errorf("error sending refund bond transaction: %w", err) } @@ -5361,7 +5401,7 @@ func (btc *baseWallet) FindBond(_ context.Context, coinID []byte, _ time.Time) ( // If the bond was funded by this wallet or had a change output paying // to this wallet, it should be found here. - tx, err := btc.node.getWalletTransaction(txHash) + tx, err := btc.node.GetWalletTransaction(txHash) if err != nil { return nil, fmt.Errorf("did not find the bond output %v:%d", txHash, vout) } @@ -5387,7 +5427,7 @@ func (btc *intermediaryWallet) FindBond( // If the bond was funded by this wallet or had a change output paying // to this wallet, it should be found here. - tx, err := btc.node.getWalletTransaction(txHash) + tx, err := btc.node.GetWalletTransaction(txHash) if err == nil { msgTx, err := btc.deserializeTx(tx.Bytes) if err != nil { @@ -5403,7 +5443,7 @@ func (btc *intermediaryWallet) FindBond( // restored from seed. This is not a problem. However, we are unable to // use filters because we don't know any output scripts. Brute force // finding the transaction. - bestBlockHdr, err := btc.node.getBestBlockHeader() + bestBlockHdr, err := btc.node.GetBestBlockHeader() if err != nil { return nil, fmt.Errorf("unable to get best hash: %v", err) } @@ -5420,7 +5460,7 @@ out: if err := ctx.Err(); err != nil { return nil, fmt.Errorf("bond search stopped: %w", err) } - blk, err = btc.tipRedeemer.getBlock(*blockHash) + blk, err = btc.tipRedeemer.GetBlock(*blockHash) if err != nil { return nil, fmt.Errorf("error retrieving block %s: %w", blockHash, err) } @@ -5727,7 +5767,7 @@ func (btc *baseWallet) idUnknownTx(tx *ListTransactionsResult) (*asset.WalletTra } addr := addrs[0] - owns, err := btc.node.ownsAddress(addr) + owns, err := btc.node.OwnsAddress(addr) if err != nil { btc.log.Errorf("ownsAddress error: %w", err) return false @@ -5771,7 +5811,7 @@ func (btc *baseWallet) idUnknownTx(tx *ListTransactionsResult) (*asset.WalletTra } addr := addrs[0] - owns, err := btc.node.ownsAddress(addr) + owns, err := btc.node.OwnsAddress(addr) if err != nil { btc.log.Errorf("ownsAddress error: %w", err) continue @@ -5799,7 +5839,7 @@ func (btc *baseWallet) idUnknownTx(tx *ListTransactionsResult) (*asset.WalletTra } addr := addrs[0] - owns, err := btc.node.ownsAddress(addr) + owns, err := btc.node.OwnsAddress(addr) if err != nil { btc.log.Errorf("ownsAddress error: %w", err) continue @@ -5853,7 +5893,7 @@ func (btc *baseWallet) addUnknownTransactionsToHistory(tip uint64) { blockToQuery = tip - blockQueryBuffer } - txs, err := btc.node.listTransactionsSinceBlock(int32(blockToQuery)) + txs, err := btc.node.ListTransactionsSinceBlock(int32(blockToQuery)) if err != nil { btc.log.Errorf("Error listing transactions since block %d: %v", blockToQuery, err) return @@ -5941,7 +5981,7 @@ func (btc *intermediaryWallet) syncTxHistory(tip uint64) { return } - gtr, err := btc.node.getWalletTransaction(&txHash) + gtr, err := btc.node.GetWalletTransaction(&txHash) if errors.Is(err, asset.CoinNotFoundError) { err = txHistoryDB.RemoveTx(txHash.String()) if err == nil || errors.Is(err, asset.CoinNotFoundError) { @@ -5967,7 +6007,7 @@ func (btc *intermediaryWallet) syncTxHistory(tip uint64) { btc.log.Errorf("Error decoding block hash %s: %v", gtr.BlockHash, err) return } - blockHeight, err := btc.tipRedeemer.getBlockHeight(blockHash) + blockHeight, err := btc.tipRedeemer.GetBlockHeight(blockHash) if err != nil { btc.log.Errorf("Error getting block height for %s: %v", blockHash, err) return @@ -6048,7 +6088,7 @@ func (btc *intermediaryWallet) WalletTransaction(ctx context.Context, txID strin if err != nil { return nil, fmt.Errorf("error decoding txid %s: %w", txID, err) } - gtr, err := btc.node.getWalletTransaction(txHash) + gtr, err := btc.node.GetWalletTransaction(txHash) if err != nil { return nil, fmt.Errorf("error getting transaction %s: %w", txID, err) } @@ -6059,7 +6099,7 @@ func (btc *intermediaryWallet) WalletTransaction(ctx context.Context, txID strin if err != nil { return nil, fmt.Errorf("error decoding block hash %s: %w", gtr.BlockHash, err) } - height, err := btc.tipRedeemer.getBlockHeight(blockHash) + height, err := btc.tipRedeemer.GetBlockHeight(blockHash) if err != nil { return nil, fmt.Errorf("error getting block height for %s: %w", blockHash, err) } @@ -6107,7 +6147,7 @@ func (btc *intermediaryWallet) TxHistory(n int, refID *string, past bool) ([]*as // lockedSats is the total value of locked outputs, as locked with LockUnspent. func (btc *baseWallet) lockedSats() (uint64, error) { - lockedOutpoints, err := btc.node.listLockUnspent() + lockedOutpoints, err := btc.node.ListLockUnspent() if err != nil { return 0, err } @@ -6123,7 +6163,7 @@ func (btc *baseWallet) lockedSats() (uint64, error) { sum += utxo.Amount continue } - tx, err := btc.node.getWalletTransaction(txHash) + tx, err := btc.node.GetWalletTransaction(txHash) if err != nil { return 0, err } @@ -6151,7 +6191,7 @@ func (btc *baseWallet) wireBytes(tx *wire.MsgTx) []byte { // GetBestBlockHeight is exported for use by clone wallets. Not part of the // asset.Wallet interface. func (btc *baseWallet) GetBestBlockHeight() (int32, error) { - return btc.node.getBestBlockHeight() + return btc.node.GetBestBlockHeight() } // Convert the BTC value to satoshi. @@ -6360,7 +6400,7 @@ func (btc *baseWallet) ConfirmRedemption(coinID dex.Bytes, redemption *asset.Red return nil, err } - utxo, _, err := btc.node.getTxOut(swapHash, vout, pkScript, time.Now().Add(-ContractSearchLimit)) + utxo, _, err := btc.node.GetTxOut(swapHash, vout, pkScript, time.Now().Add(-ContractSearchLimit)) if err != nil { return nil, fmt.Errorf("error finding unspent contract %s with swap hash %v vout %d: %w", redemption.Spends.Coin.ID(), swapHash, vout, err) } diff --git a/client/asset/btc/btc_test.go b/client/asset/btc/btc_test.go index b7d27b3404..8099bb516b 100644 --- a/client/asset/btc/btc_test.go +++ b/client/asset/btc/btc_test.go @@ -719,7 +719,7 @@ func tNewWallet(segwit bool, walletType string) (*intermediaryWallet, *testData, panic(err.Error()) } // Initialize the best block. - bestHash, err := wallet.node.getBestBlockHash() + bestHash, err := wallet.node.GetBestBlockHash() if err != nil { shutdown() os.RemoveAll(dataDir) @@ -5745,7 +5745,7 @@ type tReconfigurer struct { err error } -func (r *tReconfigurer) reconfigure(walletCfg *asset.WalletConfig, currentAddress string) (restartRequired bool, err error) { +func (r *tReconfigurer) Reconfigure(walletCfg *asset.WalletConfig, currentAddress string) (restartRequired bool, err error) { return r.restart, r.err } diff --git a/client/asset/btc/electrum.go b/client/asset/btc/electrum.go index feb11f239d..466b0bdf2b 100644 --- a/client/asset/btc/electrum.go +++ b/client/asset/btc/electrum.go @@ -342,7 +342,7 @@ func (btc *ExchangeWalletElectrum) watchBlocks(ctx context.Context) { defer ticker.Stop() bestBlock := func() (*BlockVector, error) { - hdr, err := btc.node.getBestBlockHeader() + hdr, err := btc.node.GetBestBlockHeader() if err != nil { return nil, fmt.Errorf("getBestBlockHeader: %v", err) } @@ -367,7 +367,7 @@ func (btc *ExchangeWalletElectrum) watchBlocks(ctx context.Context) { // only comparing heights instead of hashes, which means we might // not notice a reorg to a block at the same height, which is // unimportant because of how electrum searches for transactions. - ss, err := btc.node.syncStatus() + ss, err := btc.node.SyncStatus() if err != nil { btc.log.Errorf("failed to get sync status: %w", err) continue @@ -442,7 +442,7 @@ func (btc *ExchangeWalletElectrum) syncTxHistory(tip uint64) { return } - gtr, err := btc.node.getWalletTransaction(&txHash) + gtr, err := btc.node.GetWalletTransaction(&txHash) if errors.Is(err, asset.CoinNotFoundError) { err = txHistoryDB.RemoveTx(txHash.String()) if err == nil || errors.Is(err, asset.CoinNotFoundError) { @@ -463,9 +463,9 @@ func (btc *ExchangeWalletElectrum) syncTxHistory(tip uint64) { var updated bool if gtr.BlockHash != "" { - bestHeight, err := btc.node.getBestBlockHeight() + bestHeight, err := btc.node.GetBestBlockHeight() if err != nil { - btc.log.Errorf("getBestBlockHeader: %v", err) + btc.log.Errorf("GetBestBlockHeader: %v", err) return } // TODO: Just get the block height with the header. @@ -558,16 +558,16 @@ func (btc *ExchangeWalletElectrum) WalletTransaction(ctx context.Context, txID s return nil, fmt.Errorf("error decoding txid %s: %w", txID, err) } - gtr, err := btc.node.getWalletTransaction(txHash) + gtr, err := btc.node.GetWalletTransaction(txHash) if err != nil { return nil, fmt.Errorf("error getting transaction %s: %w", txID, err) } var blockHeight uint32 if gtr.BlockHash != "" { - bestHeight, err := btc.node.getBestBlockHeight() + bestHeight, err := btc.node.GetBestBlockHeight() if err != nil { - return nil, fmt.Errorf("getBestBlockHeader: %v", err) + return nil, fmt.Errorf("GetBestBlockHeader: %v", err) } // TODO: Just get the block height with the header. blockHeight := bestHeight - int32(gtr.Confirmations) + 1 diff --git a/client/asset/btc/electrum_client.go b/client/asset/btc/electrum_client.go index 2d266ae5da..536f8977b8 100644 --- a/client/asset/btc/electrum_client.go +++ b/client/asset/btc/electrum_client.go @@ -206,7 +206,7 @@ func (ew *electrumWallet) connInfo(ctx context.Context, host string) (addr strin } // part of btc.Wallet interface -func (ew *electrumWallet) connect(ctx context.Context, wg *sync.WaitGroup) error { +func (ew *electrumWallet) Connect(ctx context.Context, wg *sync.WaitGroup) error { // Helper to get a host:port string and connection options for a host name. connInfo := func(host string) (addr string, srvOpts *electrum.ConnectOpts, err error) { addr, tlsConfig, err := ew.connInfo(ctx, host) @@ -260,7 +260,7 @@ func (ew *electrumWallet) connect(ctx context.Context, wg *sync.WaitGroup) error // This wallet may not be "protected", in which case we omit the password // from the requests. Detect this now and flag the wallet as unlocked. - _ = ew.walletUnlock([]byte{}) + _ = ew.WalletUnlock([]byte{}) // Start a goroutine to keep the chain client alive and on the same // ElectrumX server as the external Electrum wallet if possible. @@ -343,7 +343,7 @@ func (ew *electrumWallet) connect(ctx context.Context, wg *sync.WaitGroup) error return err } -func (ew *electrumWallet) reconfigure(cfg *asset.WalletConfig, currentAddress string) (restartRequired bool, err error) { +func (ew *electrumWallet) Reconfigure(cfg *asset.WalletConfig, currentAddress string) (restartRequired bool, err error) { // electrumWallet only handles walletTypeElectrum. if cfg.Type != walletTypeElectrum { restartRequired = true @@ -363,7 +363,7 @@ func (ew *electrumWallet) reconfigure(cfg *asset.WalletConfig, currentAddress st } // part of btc.Wallet interface -func (ew *electrumWallet) sendRawTransaction(tx *wire.MsgTx) (*chainhash.Hash, error) { +func (ew *electrumWallet) SendRawTransaction(tx *wire.MsgTx) (*chainhash.Hash, error) { b, err := serializeMsgTx(tx) if err != nil { return nil, err @@ -389,7 +389,7 @@ func (ew *electrumWallet) sendRawTransaction(tx *wire.MsgTx) (*chainhash.Hash, e prevOut := txIn.PreviousOutPoint ops[i] = &Output{Pt: NewOutPoint(&prevOut.Hash, prevOut.Index)} } - if err = ew.lockUnspent(true, ops); err != nil { + if err = ew.LockUnspent(true, ops); err != nil { ew.log.Errorf("Failed to unlock spent UTXOs: %v", err) } return hash, nil @@ -431,7 +431,7 @@ func (ew *electrumWallet) outputIsSpent(ctx context.Context, txHash *chainhash.H } // part of btc.Wallet interface -func (ew *electrumWallet) getTxOut(txHash *chainhash.Hash, vout uint32, _ []byte, _ time.Time) (*wire.TxOut, uint32, error) { +func (ew *electrumWallet) GetTxOut(txHash *chainhash.Hash, vout uint32, _ []byte, _ time.Time) (*wire.TxOut, uint32, error) { return ew.getTxOutput(ew.ctx, txHash, vout) } @@ -489,8 +489,8 @@ func (ew *electrumWallet) getBlockHeaderByHeight(ctx context.Context, height int } // part of btc.Wallet interface -func (ew *electrumWallet) medianTime() (time.Time, error) { - chainHeight, err := ew.getBestBlockHeight() +func (ew *electrumWallet) MedianTime() (time.Time, error) { + chainHeight, err := ew.GetBestBlockHeight() if err != nil { return time.Time{}, err } @@ -554,7 +554,7 @@ func (ew *electrumWallet) calcMedianTime(ctx context.Context, height int64) (tim } // part of btc.Wallet interface -func (ew *electrumWallet) getBlockHash(height int64) (*chainhash.Hash, error) { +func (ew *electrumWallet) GetBlockHash(height int64) (*chainhash.Hash, error) { hdr, err := ew.getBlockHeaderByHeight(ew.ctx, height) if err != nil { return nil, err @@ -564,16 +564,16 @@ func (ew *electrumWallet) getBlockHash(height int64) (*chainhash.Hash, error) { } // part of btc.Wallet interface -func (ew *electrumWallet) getBestBlockHash() (*chainhash.Hash, error) { +func (ew *electrumWallet) GetBestBlockHash() (*chainhash.Hash, error) { inf, err := ew.wallet.GetInfo(ew.ctx) if err != nil { return nil, err } - return ew.getBlockHash(inf.SyncHeight) + return ew.GetBlockHash(inf.SyncHeight) } // part of btc.Wallet interface -func (ew *electrumWallet) getBestBlockHeight() (int32, error) { +func (ew *electrumWallet) GetBestBlockHeight() (int32, error) { inf, err := ew.wallet.GetInfo(ew.ctx) if err != nil { return 0, err @@ -582,7 +582,7 @@ func (ew *electrumWallet) getBestBlockHeight() (int32, error) { } // part of btc.Wallet interface -func (ew *electrumWallet) getBestBlockHeader() (*BlockHeader, error) { +func (ew *electrumWallet) GetBestBlockHeader() (*BlockHeader, error) { inf, err := ew.wallet.GetInfo(ew.ctx) if err != nil { return nil, err @@ -604,7 +604,7 @@ func (ew *electrumWallet) getBestBlockHeader() (*BlockHeader, error) { } // part of btc.Wallet interface -func (ew *electrumWallet) balances() (*GetBalancesResult, error) { +func (ew *electrumWallet) Balances() (*GetBalancesResult, error) { eBal, err := ew.wallet.GetBalance(ew.ctx) if err != nil { return nil, err @@ -623,12 +623,12 @@ func (ew *electrumWallet) balances() (*GetBalancesResult, error) { } // part of btc.Wallet interface -func (ew *electrumWallet) listUnspent() ([]*ListUnspentResult, error) { +func (ew *electrumWallet) ListUnspent() ([]*ListUnspentResult, error) { eUnspent, err := ew.wallet.ListUnspent(ew.ctx) if err != nil { return nil, err } - chainHeight, err := ew.getBestBlockHeight() + chainHeight, err := ew.GetBestBlockHeight() if err != nil { return nil, err } @@ -698,7 +698,7 @@ func (ew *electrumWallet) listUnspent() ([]*ListUnspentResult, error) { } // part of btc.Wallet interface -func (ew *electrumWallet) lockUnspent(unlock bool, ops []*Output) error { +func (ew *electrumWallet) LockUnspent(unlock bool, ops []*Output) error { eUnspent, err := ew.wallet.ListUnspent(ew.ctx) if err != nil { return err @@ -767,13 +767,13 @@ func (ew *electrumWallet) listLockedOutpoints() []*RPCOutpoint { } // part of btc.Wallet interface -func (ew *electrumWallet) listLockUnspent() ([]*RPCOutpoint, error) { +func (ew *electrumWallet) ListLockUnspent() ([]*RPCOutpoint, error) { return ew.listLockedOutpoints(), nil } -// externalAddress creates a fresh address beyond the default gap limit, so it +// ExternalAddress creates a fresh address beyond the default gap limit, so it // should be used immediately. Part of btc.Wallet interface. -func (ew *electrumWallet) externalAddress() (btcutil.Address, error) { +func (ew *electrumWallet) ExternalAddress() (btcutil.Address, error) { addr, err := ew.wallet.GetUnusedAddress(ew.ctx) if err != nil { return nil, err @@ -781,14 +781,14 @@ func (ew *electrumWallet) externalAddress() (btcutil.Address, error) { return ew.decodeAddr(addr, ew.chainParams) } -// changeAddress creates a fresh address beyond the default gap limit, so it +// ChangeAddress creates a fresh address beyond the default gap limit, so it // should be used immediately. Part of btc.Wallet interface. -func (ew *electrumWallet) changeAddress() (btcutil.Address, error) { - return ew.externalAddress() // sadly, cannot request internal addresses +func (ew *electrumWallet) ChangeAddress() (btcutil.Address, error) { + return ew.ExternalAddress() // sadly, cannot request internal addresses } // part of btc.Wallet interface -func (ew *electrumWallet) signTx(inTx *wire.MsgTx) (*wire.MsgTx, error) { +func (ew *electrumWallet) SignTx(inTx *wire.MsgTx) (*wire.MsgTx, error) { // If the wallet's signtransaction RPC ever has a problem with the PSBT, we // could attempt to sign the transaction ourselves by pulling the inputs' // private keys and using txscript manually, but this can vary greatly @@ -819,7 +819,7 @@ type pubKeyer interface { } // part of btc.Wallet interface -func (ew *electrumWallet) privKeyForAddress(addr string) (*btcec.PrivateKey, error) { +func (ew *electrumWallet) PrivKeyForAddress(addr string) (*btcec.PrivateKey, error) { addrDec, err := ew.decodeAddr(addr, ew.chainParams) if err != nil { return nil, err @@ -885,8 +885,8 @@ func (ew *electrumWallet) testPass(pw []byte) error { return nil } -// walletLock locks the wallet. Part of the btc.Wallet interface. -func (ew *electrumWallet) walletLock() error { +// WalletLock locks the wallet. Part of the btc.Wallet interface. +func (ew *electrumWallet) WalletLock() error { ew.pwMtx.Lock() defer ew.pwMtx.Unlock() if ew.pw == "" && ew.unlocked { @@ -900,9 +900,9 @@ func (ew *electrumWallet) walletLock() error { return nil } -// locked indicates if the wallet has been unlocked. Part of the btc.Wallet +// Locked indicates if the wallet has been unlocked. Part of the btc.Wallet // interface. -func (ew *electrumWallet) locked() bool { +func (ew *electrumWallet) Locked() bool { ew.pwMtx.RLock() defer ew.pwMtx.RUnlock() return !ew.unlocked @@ -915,10 +915,10 @@ func (ew *electrumWallet) walletPass() string { return pw } -// walletUnlock attempts to unlock the wallet with the provided password. On +// WalletUnlock attempts to unlock the wallet with the provided password. On // success, the password is stored and may be accessed via pass or walletPass. // Part of the btc.Wallet interface. -func (ew *electrumWallet) walletUnlock(pw []byte) error { +func (ew *electrumWallet) WalletUnlock(pw []byte) error { if err := ew.testPass(pw); err != nil { return err } @@ -929,7 +929,7 @@ func (ew *electrumWallet) walletUnlock(pw []byte) error { } // part of the btc.Wallet interface -func (ew *electrumWallet) peerCount() (uint32, error) { +func (ew *electrumWallet) PeerCount() (uint32, error) { if ew.chain() == nil { // must work prior to resetChain return 0, nil } @@ -948,7 +948,7 @@ func (ew *electrumWallet) peerCount() (uint32, error) { } // part of the btc.Wallet interface -func (ew *electrumWallet) ownsAddress(addr btcutil.Address) (bool, error) { +func (ew *electrumWallet) OwnsAddress(addr btcutil.Address) (bool, error) { addrStr, err := ew.stringAddr(addr, ew.chainParams) if err != nil { return false, err @@ -964,7 +964,7 @@ func (ew *electrumWallet) ownsAddress(addr btcutil.Address) (bool, error) { } // part of the btc.Wallet interface -func (ew *electrumWallet) syncStatus() (*asset.SyncStatus, error) { +func (ew *electrumWallet) SyncStatus() (*asset.SyncStatus, error) { info, err := ew.wallet.GetInfo(ew.ctx) if err != nil { return nil, err @@ -977,8 +977,8 @@ func (ew *electrumWallet) syncStatus() (*asset.SyncStatus, error) { } // part of the btc.Wallet interface -func (ew *electrumWallet) listTransactionsSinceBlock(blockHeight int32) ([]*ListTransactionsResult, error) { - bestHeight, err := ew.getBestBlockHeight() +func (ew *electrumWallet) ListTransactionsSinceBlock(blockHeight int32) ([]*ListTransactionsResult, error) { + bestHeight, err := ew.GetBestBlockHeight() if err != nil { return nil, fmt.Errorf("error getting best block: %v", err) } @@ -1044,7 +1044,7 @@ func (ew *electrumWallet) checkWalletTx(txid string) ([]byte, uint32, error) { } // part of the walletTxChecker interface -func (ew *electrumWallet) getWalletTransaction(txHash *chainhash.Hash) (*GetTransactionResult, error) { +func (ew *electrumWallet) GetWalletTransaction(txHash *chainhash.Hash) (*GetTransactionResult, error) { // Try the wallet first. If it is not a wallet transaction or if it is // confirmed, fall back to the chain method to get the block info and time // fields. @@ -1078,12 +1078,12 @@ func (ew *electrumWallet) getWalletTransaction(txHash *chainhash.Hash) (*GetTran }, nil } -func (ew *electrumWallet) fingerprint() (string, error) { +func (ew *electrumWallet) Fingerprint() (string, error) { return "", fmt.Errorf("fingerprint not implemented") } // part of the walletTxChecker interface -func (ew *electrumWallet) swapConfirmations(txHash *chainhash.Hash, vout uint32, contract []byte, startTime time.Time) (confs uint32, spent bool, err error) { +func (ew *electrumWallet) SwapConfirmations(txHash *chainhash.Hash, vout uint32, contract []byte, startTime time.Time) (confs uint32, spent bool, err error) { // To determine if it is spent, we need the address of the output. var pkScript []byte txid := txHash.String() diff --git a/client/asset/btc/electrum_client_test.go b/client/asset/btc/electrum_client_test.go index 3a5d55f3a1..6d8c798fac 100644 --- a/client/asset/btc/electrum_client_test.go +++ b/client/asset/btc/electrum_client_test.go @@ -37,7 +37,7 @@ func Test_electrumWallet(t *testing.T) { segwit: true, }) var wg sync.WaitGroup - err := ew.connect(ctx, &wg) + err := ew.Connect(ctx, &wg) if err != nil { t.Fatal(err) } @@ -74,7 +74,7 @@ func Test_electrumWallet(t *testing.T) { } // gettxout - first the spent output - fundTxOut, fundConfs, err := ew.getTxOut(fundHash, fundVout, nil, time.Time{}) + fundTxOut, fundConfs, err := ew.GetTxOut(fundHash, fundVout, nil, time.Time{}) if err != nil { t.Fatal(err) } @@ -88,7 +88,7 @@ func Test_electrumWallet(t *testing.T) { fundVal = 20000 fundPkScript, _ = hex.DecodeString("76a914b3e0f80ce29ac48793de504ae9aa0b6579dda29a88ac") fundAddr = "mwv4kWRXkc2w42823FU9SJ16cvZH8Aobke" - fundTxOut, fundConfs, err = ew.getTxOut(fundHash, fundVout, nil, time.Time{}) + fundTxOut, fundConfs, err = ew.GetTxOut(fundHash, fundVout, nil, time.Time{}) if err != nil { t.Fatal(err) } @@ -122,12 +122,12 @@ func Test_electrumWallet(t *testing.T) { } t.Log(addr) - err = ew.walletUnlock([]byte(walletPass)) + err = ew.WalletUnlock([]byte(walletPass)) if err != nil { t.Fatal(err) } - _, err = ew.privKeyForAddress(addr) + _, err = ew.PrivKeyForAddress(addr) if err != nil { t.Fatal(err) } diff --git a/client/asset/btc/rpcclient.go b/client/asset/btc/rpcclient.go index 0e42be865a..eb23886efe 100644 --- a/client/asset/btc/rpcclient.go +++ b/client/asset/btc/rpcclient.go @@ -159,7 +159,7 @@ func ChainOK(net dex.Network, str string) bool { return strings.Contains(str, chainStr) } -func (wc *rpcClient) connect(ctx context.Context, _ *sync.WaitGroup) error { +func (wc *rpcClient) Connect(ctx context.Context, _ *sync.WaitGroup) error { wc.ctx = ctx // Check the version. Do it here, so we can also diagnose a bad connection. netVer, codeVer, err := wc.getVersion() @@ -196,11 +196,11 @@ func (wc *rpcClient) connect(ctx context.Context, _ *sync.WaitGroup) error { return nil } -// reconfigure attempts to reconfigure the rpcClient for the new settings. Live +// Reconfigure attempts to reconfigure the rpcClient for the new settings. Live // reconfiguration is only attempted if the new wallet type is walletTypeRPC. If // the special_activelyUsed flag is set, reconfigure will fail if we can't // validate ownership of the current deposit address. -func (wc *rpcClient) reconfigure(cfg *asset.WalletConfig, currentAddress string) (restartRequired bool, err error) { +func (wc *rpcClient) Reconfigure(cfg *asset.WalletConfig, currentAddress string) (restartRequired bool, err error) { if cfg.Type != wc.cloneParams.WalletCFG.Type { restartRequired = true return @@ -323,15 +323,15 @@ func (wc *rpcClient) sendRawTransaction(tx *wire.MsgTx) (txHash *chainhash.Hash, prevOut := &txIn.PreviousOutPoint ops = append(ops, &Output{Pt: NewOutPoint(&prevOut.Hash, prevOut.Index)}) } - if err := wc.lockUnspent(true, ops); err != nil { + if err := wc.LockUnspent(true, ops); err != nil { wc.log.Warnf("error unlocking spent outputs: %v", err) } return txHash, nil } -// getTxOut returns the transaction output info if it's unspent and +// GetTxOut returns the transaction output info if it's unspent and // nil, otherwise. -func (wc *rpcClient) getTxOut(txHash *chainhash.Hash, index uint32, _ []byte, _ time.Time) (*wire.TxOut, uint32, error) { +func (wc *rpcClient) GetTxOut(txHash *chainhash.Hash, index uint32, _ []byte, _ time.Time) (*wire.TxOut, uint32, error) { txOut, err := wc.getTxOutput(txHash, index) if err != nil { return nil, 0, fmt.Errorf("getTxOut error: %w", err) @@ -363,8 +363,8 @@ func (wc *rpcClient) callHashGetter(method string, args anylist) (*chainhash.Has return chainhash.NewHashFromStr(txid) } -// getBlock fetches the MsgBlock. -func (wc *rpcClient) getBlock(h chainhash.Hash) (*wire.MsgBlock, error) { +// GetBlock fetches the MsgBlock. +func (wc *rpcClient) GetBlock(h chainhash.Hash) (*wire.MsgBlock, error) { var blkB dex.Bytes args := anylist{h.String()} if wc.booleanGetBlock { @@ -380,31 +380,31 @@ func (wc *rpcClient) getBlock(h chainhash.Hash) (*wire.MsgBlock, error) { return wc.deserializeBlock(blkB) } -// getBlockHash returns the hash of the block in the best block chain at the +// GetBlockHash returns the hash of the block in the best block chain at the // given height. -func (wc *rpcClient) getBlockHash(blockHeight int64) (*chainhash.Hash, error) { +func (wc *rpcClient) GetBlockHash(blockHeight int64) (*chainhash.Hash, error) { return wc.callHashGetter(methodGetBlockHash, anylist{blockHeight}) } -// getBestBlockHash returns the hash of the best block in the longest block +// GetBestBlockHash returns the hash of the best block in the longest block // chain (aka mainchain). -func (wc *rpcClient) getBestBlockHash() (*chainhash.Hash, error) { +func (wc *rpcClient) GetBestBlockHash() (*chainhash.Hash, error) { return wc.callHashGetter(methodGetBestBlockHash, nil) } -// getBestBlockHeight returns the height of the top mainchain block. -func (wc *rpcClient) getBestBlockHeader() (*BlockHeader, error) { - tipHash, err := wc.getBestBlockHash() +// GetBestBlockHeader returns the height of the top mainchain block. +func (wc *rpcClient) GetBestBlockHeader() (*BlockHeader, error) { + tipHash, err := wc.GetBestBlockHash() if err != nil { return nil, err } - hdr, _, err := wc.getBlockHeader(tipHash) + hdr, _, err := wc.GetBlockHeader(tipHash) return hdr, err } -// getBestBlockHeight returns the height of the top mainchain block. -func (wc *rpcClient) getBestBlockHeight() (int32, error) { - header, err := wc.getBestBlockHeader() +// GetBestBlockHeight returns the height of the top mainchain block. +func (wc *rpcClient) GetBestBlockHeight() (int32, error) { + header, err := wc.GetBestBlockHeader() if err != nil { return -1, err } @@ -413,7 +413,7 @@ func (wc *rpcClient) getBestBlockHeight() (int32, error) { // getChainStamp satisfies chainStamper for manual median time calculations. func (wc *rpcClient) getChainStamp(blockHash *chainhash.Hash) (stamp time.Time, prevHash *chainhash.Hash, err error) { - hdr, _, err := wc.getBlockHeader(blockHash) + hdr, _, err := wc.GetBlockHeader(blockHash) if err != nil { return } @@ -424,15 +424,15 @@ func (wc *rpcClient) getChainStamp(blockHash *chainhash.Hash) (stamp time.Time, return time.Unix(hdr.Time, 0).UTC(), prevHash, nil } -// medianTime is the median time for the current best block. -func (wc *rpcClient) medianTime() (stamp time.Time, err error) { - tipHash, err := wc.getBestBlockHash() +// MedianTime is the median time for the current best block. +func (wc *rpcClient) MedianTime() (stamp time.Time, err error) { + tipHash, err := wc.GetBestBlockHash() if err != nil { return } if wc.manualMedianTime { return CalcMedianTime(func(blockHash *chainhash.Hash) (stamp time.Time, prevHash *chainhash.Hash, err error) { - hdr, _, err := wc.getBlockHeader(blockHash) + hdr, _, err := wc.GetBlockHeader(blockHash) if err != nil { return } @@ -485,23 +485,23 @@ func (wc *rpcClient) GetRawTransaction(txHash *chainhash.Hash) (*wire.MsgTx, err return wc.deserializeTx(txB) } -// balances retrieves a wallet's balance details. -func (wc *rpcClient) balances() (*GetBalancesResult, error) { +// Balances retrieves a wallet's balance details. +func (wc *rpcClient) Balances() (*GetBalancesResult, error) { var balances GetBalancesResult return &balances, wc.call(methodGetBalances, nil, &balances) } -// listUnspent retrieves a list of the wallet's UTXOs. -func (wc *rpcClient) listUnspent() ([]*ListUnspentResult, error) { +// ListUnspent retrieves a list of the wallet's UTXOs. +func (wc *rpcClient) ListUnspent() ([]*ListUnspentResult, error) { unspents := make([]*ListUnspentResult, 0) // TODO: listunspent 0 9999999 []string{}, include_unsafe=false return unspents, wc.call(methodListUnspent, anylist{uint8(0)}, &unspents) } -// lockUnspent locks and unlocks outputs for spending. An output that is part of +// LockUnspent locks and unlocks outputs for spending. An output that is part of // an order, but not yet spent, should be locked until spent or until the order // is canceled or fails. -func (wc *rpcClient) lockUnspent(unlock bool, ops []*Output) error { +func (wc *rpcClient) LockUnspent(unlock bool, ops []*Output) error { var rpcops []*RPCOutpoint // To clear all, this must be nil->null, not empty slice. for _, op := range ops { rpcops = append(rpcops, &RPCOutpoint{ @@ -517,9 +517,9 @@ func (wc *rpcClient) lockUnspent(unlock bool, ops []*Output) error { return err } -// listLockUnspent returns a slice of outpoints for all unspent outputs marked +// ListLockUnspent returns a slice of outpoints for all unspent outputs marked // as locked by a wallet. -func (wc *rpcClient) listLockUnspent() ([]*RPCOutpoint, error) { +func (wc *rpcClient) ListLockUnspent() ([]*RPCOutpoint, error) { var unspents []*RPCOutpoint err := wc.call(methodListLockUnspent, nil, &unspents) if err != nil { @@ -561,9 +561,9 @@ func (wc *rpcClient) listLockUnspent() ([]*RPCOutpoint, error) { return unspents, nil } -// changeAddress gets a new internal address from the wallet. The address will +// ChangeAddress gets a new internal address from the wallet. The address will // be bech32-encoded (P2WPKH). -func (wc *rpcClient) changeAddress() (btcutil.Address, error) { +func (wc *rpcClient) ChangeAddress() (btcutil.Address, error) { var addrStr string var err error switch { @@ -580,7 +580,7 @@ func (wc *rpcClient) changeAddress() (btcutil.Address, error) { return wc.decodeAddr(addrStr, wc.chainParams) } -func (wc *rpcClient) externalAddress() (btcutil.Address, error) { +func (wc *rpcClient) ExternalAddress() (btcutil.Address, error) { if wc.segwit { return wc.address("bech32") } @@ -602,8 +602,8 @@ func (wc *rpcClient) address(aType string) (btcutil.Address, error) { return wc.decodeAddr(addrStr, wc.chainParams) // we should consider returning a string } -// signTx attempts to have the wallet sign the transaction inputs. -func (wc *rpcClient) signTx(inTx *wire.MsgTx) (*wire.MsgTx, error) { +// SignTx attempts to have the wallet sign the transaction inputs. +func (wc *rpcClient) SignTx(inTx *wire.MsgTx) (*wire.MsgTx, error) { txBytes, err := wc.serializeTx(inTx) if err != nil { return nil, fmt.Errorf("tx serialization error: %w", err) @@ -639,8 +639,8 @@ func (wc *rpcClient) listDescriptors(private bool) (*listDescriptorsResult, erro return descriptors, wc.call(methodListDescriptors, anylist{private}, descriptors) } -func (wc *rpcClient) listTransactionsSinceBlock(blockHeight int32) ([]*ListTransactionsResult, error) { - blockHash, err := wc.getBlockHash(int64(blockHeight)) +func (wc *rpcClient) ListTransactionsSinceBlock(blockHeight int32) ([]*ListTransactionsResult, error) { + blockHash, err := wc.GetBlockHash(int64(blockHeight)) if err != nil { return nil, fmt.Errorf("getBlockHash error: %w", err) } @@ -670,9 +670,9 @@ func (wc *rpcClient) listTransactionsSinceBlock(blockHeight int32) ([]*ListTrans return txs, nil } -// privKeyForAddress retrieves the private key associated with the specified +// PrivKeyForAddress retrieves the private key associated with the specified // address. -func (wc *rpcClient) privKeyForAddress(addr string) (*btcec.PrivateKey, error) { +func (wc *rpcClient) PrivKeyForAddress(addr string) (*btcec.PrivateKey, error) { // Use a specialized client's privKey function if wc.privKeyFunc != nil { return wc.privKeyFunc(addr) @@ -841,8 +841,8 @@ masters: return nil, errors.New("no private key found for address") } -// getWalletTransaction retrieves the JSON-RPC gettransaction result. -func (wc *rpcClient) getWalletTransaction(txHash *chainhash.Hash) (*GetTransactionResult, error) { +// GetWalletTransaction retrieves the JSON-RPC gettransaction result. +func (wc *rpcClient) GetWalletTransaction(txHash *chainhash.Hash) (*GetTransactionResult, error) { tx := new(GetTransactionResult) err := wc.call(methodGetTransaction, anylist{txHash.String()}, tx) if err != nil { @@ -854,19 +854,19 @@ func (wc *rpcClient) getWalletTransaction(txHash *chainhash.Hash) (*GetTransacti return tx, nil } -// walletUnlock unlocks the wallet. -func (wc *rpcClient) walletUnlock(pw []byte) error { +// WalletUnlock unlocks the wallet. +func (wc *rpcClient) WalletUnlock(pw []byte) error { // 100000000 comes from bitcoin-cli help walletpassphrase return wc.call(methodUnlock, anylist{string(pw), 100000000}, nil) } -// walletLock locks the wallet. -func (wc *rpcClient) walletLock() error { +// WalletLock locks the wallet. +func (wc *rpcClient) WalletLock() error { return wc.call(methodLock, nil, nil) } -// locked returns the wallet's lock state. -func (wc *rpcClient) locked() bool { +// Locked returns the wallet's lock state. +func (wc *rpcClient) Locked() bool { walletInfo, err := wc.GetWalletInfo() if err != nil { wc.log.Errorf("GetWalletInfo error: %w", err) @@ -880,9 +880,9 @@ func (wc *rpcClient) locked() bool { return time.Unix(*walletInfo.UnlockedUntil, 0).Before(time.Now()) } -// sendTxFeeEstimator returns the fee required to send tx using the provided +// EstimateSendTxFee returns the fee required to send tx using the provided // feeRate. -func (wc *rpcClient) estimateSendTxFee(tx *wire.MsgTx, feeRate uint64, subtract bool) (txfee uint64, err error) { +func (wc *rpcClient) EstimateSendTxFee(tx *wire.MsgTx, feeRate uint64, subtract bool) (txfee uint64, err error) { txBytes, err := wc.serializeTx(tx) if err != nil { return 0, fmt.Errorf("tx serialization error: %w", err) @@ -942,9 +942,9 @@ func (wc *rpcClient) GetWalletInfo() (*GetWalletInfoResult, error) { return wi, wc.call(methodGetWalletInfo, nil, wi) } -// fingerprint returns an identifier for this wallet. Only HD wallets will have +// Fingerprint returns an identifier for this wallet. Only HD wallets will have // an identifier. Descriptor wallets will not. -func (wc *rpcClient) fingerprint() (string, error) { +func (wc *rpcClient) Fingerprint() (string, error) { walletInfo, err := wc.GetWalletInfo() if err != nil { return "", err @@ -968,8 +968,8 @@ func (wc *rpcClient) getAddressInfo(addr btcutil.Address, method string) (*GetAd return ai, wc.call(method, anylist{addrStr}, ai) } -// ownsAddress indicates if an address belongs to the wallet. -func (wc *rpcClient) ownsAddress(addr btcutil.Address) (bool, error) { +// OwnsAddress indicates if an address belongs to the wallet. +func (wc *rpcClient) OwnsAddress(addr btcutil.Address) (bool, error) { method := methodGetAddressInfo if wc.legacyValidateAddressRPC { method = methodValidateAddress @@ -981,8 +981,8 @@ func (wc *rpcClient) ownsAddress(addr btcutil.Address) (bool, error) { return ai.IsMine, nil } -// syncStatus is information about the blockchain sync status. -func (wc *rpcClient) syncStatus() (*asset.SyncStatus, error) { +// SyncStatus is information about the blockchain sync status. +func (wc *rpcClient) SyncStatus() (*asset.SyncStatus, error) { chainInfo, err := wc.getBlockchainInfo() if err != nil { return nil, fmt.Errorf("getblockchaininfo error: %w", err) @@ -995,17 +995,17 @@ func (wc *rpcClient) syncStatus() (*asset.SyncStatus, error) { }, nil } -// swapConfirmations gets the number of confirmations for the specified coin ID +// SwapConfirmations gets the number of confirmations for the specified coin ID // by first checking for a unspent output, and if not found, searching indexed // wallet transactions. -func (wc *rpcClient) swapConfirmations(txHash *chainhash.Hash, vout uint32, _ []byte, _ time.Time) (confs uint32, spent bool, err error) { +func (wc *rpcClient) SwapConfirmations(txHash *chainhash.Hash, vout uint32, _ []byte, _ time.Time) (confs uint32, spent bool, err error) { // Check for an unspent output. txOut, err := wc.getTxOutput(txHash, vout) if err == nil && txOut != nil { return uint32(txOut.Confirmations), false, nil } // Check wallet transactions. - tx, err := wc.getWalletTransaction(txHash) + tx, err := wc.GetWalletTransaction(txHash) if err != nil { if IsTxNotFoundErr(err) { return 0, false, asset.CoinNotFoundError @@ -1027,10 +1027,10 @@ func (wc *rpcClient) getRPCBlockHeader(blockHash *chainhash.Hash) (*BlockHeader, return blkHeader, nil } -// getBlockHeader gets the *blockHeader for the specified block hash. It also +// GetBlockHeader gets the *BlockHeader for the specified block hash. It also // returns a bool value to indicate whether this block is a part of main chain. // For orphaned blocks header.Confirmations is negative (typically -1). -func (wc *rpcClient) getBlockHeader(blockHash *chainhash.Hash) (header *BlockHeader, mainchain bool, err error) { +func (wc *rpcClient) GetBlockHeader(blockHash *chainhash.Hash) (header *BlockHeader, mainchain bool, err error) { hdr, err := wc.getRPCBlockHeader(blockHash) if err != nil { return nil, false, err @@ -1040,10 +1040,10 @@ func (wc *rpcClient) getBlockHeader(blockHash *chainhash.Hash) (header *BlockHea return hdr, mainchain, nil } -// getBlockHeight gets the mainchain height for the specified block. Returns +// GetBlockHeight gets the mainchain height for the specified block. Returns // error for orphaned blocks. -func (wc *rpcClient) getBlockHeight(blockHash *chainhash.Hash) (int32, error) { - hdr, _, err := wc.getBlockHeader(blockHash) +func (wc *rpcClient) GetBlockHeight(blockHash *chainhash.Hash) (int32, error) { + hdr, _, err := wc.GetBlockHeader(blockHash) if err != nil { return -1, err } @@ -1053,7 +1053,7 @@ func (wc *rpcClient) getBlockHeight(blockHash *chainhash.Hash) (int32, error) { return int32(hdr.Height), nil } -func (wc *rpcClient) peerCount() (uint32, error) { +func (wc *rpcClient) PeerCount() (uint32, error) { var r struct { Connections uint32 `json:"connections"` } @@ -1091,9 +1091,9 @@ func (wc *rpcClient) getVersion() (uint64, uint64, error) { return r.Version, r.ProtocolVersion, nil } -// findRedemptionsInMempool attempts to find spending info for the specified +// FindRedemptionsInMempool attempts to find spending info for the specified // contracts by searching every input of all txs in the mempool. -func (wc *rpcClient) findRedemptionsInMempool(ctx context.Context, reqs map[OutPoint]*FindRedemptionReq) (discovered map[OutPoint]*FindRedemptionResult) { +func (wc *rpcClient) FindRedemptionsInMempool(ctx context.Context, reqs map[OutPoint]*FindRedemptionReq) (discovered map[OutPoint]*FindRedemptionResult) { return FindRedemptionsInMempool(ctx, wc.log, reqs, wc.GetRawMempool, wc.GetRawTransaction, wc.segwit, wc.hashTx, wc.chainParams) } @@ -1150,10 +1150,10 @@ func FindRedemptionsInMempool( return } -// searchBlockForRedemptions attempts to find spending info for the specified +// SearchBlockForRedemptions attempts to find spending info for the specified // contracts by searching every input of all txs in the provided block range. -func (wc *rpcClient) searchBlockForRedemptions(ctx context.Context, reqs map[OutPoint]*FindRedemptionReq, blockHash chainhash.Hash) (discovered map[OutPoint]*FindRedemptionResult) { - msgBlock, err := wc.getBlock(blockHash) +func (wc *rpcClient) SearchBlockForRedemptions(ctx context.Context, reqs map[OutPoint]*FindRedemptionReq, blockHash chainhash.Hash) (discovered map[OutPoint]*FindRedemptionResult) { + msgBlock, err := wc.GetBlock(blockHash) if err != nil { wc.log.Errorf("RPC GetBlock error: %v", err) return diff --git a/client/asset/btc/spv_test.go b/client/asset/btc/spv_test.go index 18e1964f07..01cf16d25a 100644 --- a/client/asset/btc/spv_test.go +++ b/client/asset/btc/spv_test.go @@ -489,7 +489,7 @@ func TestSwapConfirmations(t *testing.T) { checkSuccess := func(tag string, expConfs uint32, expSpent bool) { t.Helper() - confs, spent, err := spv.swapConfirmations(&swapTxHash, vout, pkScript, matchTime) + confs, spent, err := spv.SwapConfirmations(&swapTxHash, vout, pkScript, matchTime) if err != nil { t.Fatalf("%s error: %v", tag, err) } @@ -503,7 +503,7 @@ func TestSwapConfirmations(t *testing.T) { checkFailure := func(tag string) { t.Helper() - _, _, err := spv.swapConfirmations(&swapTxHash, vout, pkScript, matchTime) + _, _, err := spv.SwapConfirmations(&swapTxHash, vout, pkScript, matchTime) if err == nil { t.Fatalf("no error for %q test", tag) } @@ -647,7 +647,7 @@ func TestGetTxOut(t *testing.T) { // Abnormal error node.getTransactionErr = tErr - _, _, err := spv.getTxOut(&txHash, vout, pkScript, generateTestBlockTime(blockHeight)) + _, _, err := spv.GetTxOut(&txHash, vout, pkScript, generateTestBlockTime(blockHeight)) if err == nil { t.Fatalf("no error for getWalletTransaction error") } @@ -659,7 +659,7 @@ func TestGetTxOut(t *testing.T) { Bytes: txB, }} - _, confs, err := spv.getTxOut(&txHash, vout, pkScript, generateTestBlockTime(blockHeight)) + _, confs, err := spv.GetTxOut(&txHash, vout, pkScript, generateTestBlockTime(blockHeight)) if err != nil { t.Fatalf("error for wallet transaction found: %v", err) } @@ -675,7 +675,7 @@ func TestGetTxOut(t *testing.T) { spend: &spendingInput{}, checkpoint: *spendBlockHash, }} - op, confs, err := spv.getTxOut(&txHash, vout, pkScript, generateTestBlockTime(blockHeight)) + op, confs, err := spv.GetTxOut(&txHash, vout, pkScript, generateTestBlockTime(blockHeight)) if op != nil || confs != 0 || err != nil { t.Fatal("wrong result for spent txout", op != nil, confs, err) } @@ -686,7 +686,7 @@ func TestGetTxOut(t *testing.T) { // case 1: we have a block hash in the database node.dbBlockForTx[txHash] = &hashEntry{hash: *blockHash} node.getCFilterScripts[*blockHash] = [][]byte{pkScript} - _, _, err = spv.getTxOut(&txHash, vout, pkScript, generateTestBlockTime(blockHeight)) + _, _, err = spv.GetTxOut(&txHash, vout, pkScript, generateTestBlockTime(blockHeight)) if err != nil { t.Fatalf("error for GetUtxo with cached hash: %v", err) } @@ -694,7 +694,7 @@ func TestGetTxOut(t *testing.T) { // case 2: no block hash in db. Will do scan and store them. delete(node.dbBlockForTx, txHash) delete(node.checkpoints, outPt) - _, _, err = spv.getTxOut(&txHash, vout, pkScript, generateTestBlockTime(blockHeight)) + _, _, err = spv.GetTxOut(&txHash, vout, pkScript, generateTestBlockTime(blockHeight)) if err != nil { t.Fatalf("error for GetUtxo with no cached hash: %v", err) } @@ -705,7 +705,7 @@ func TestGetTxOut(t *testing.T) { // case 3: spending tx found first delete(node.checkpoints, outPt) node.getCFilterScripts[*spendBlockHash] = [][]byte{pkScript} - txOut, _, err := spv.getTxOut(&txHash, vout, pkScript, generateTestBlockTime(blockHeight)) + txOut, _, err := spv.GetTxOut(&txHash, vout, pkScript, generateTestBlockTime(blockHeight)) if err != nil { t.Fatalf("error for spent tx: %v", err) } @@ -719,7 +719,7 @@ func TestGetTxOut(t *testing.T) { // We won't actually scan for the output itself, so nil'ing these should // have no effect. node.getCFilterScripts[*blockHash] = nil - _, _, err = spv.getTxOut(&txHash, vout, pkScript, generateTestBlockTime(blockHeight)) + _, _, err = spv.GetTxOut(&txHash, vout, pkScript, generateTestBlockTime(blockHeight)) if err != nil { t.Fatalf("error for checkpointed output: %v", err) } @@ -868,7 +868,7 @@ func TestGetBlockHeader(t *testing.T) { node.mainchain[int64(height)] = &hh } - hdr, mainchain, err := wallet.tipRedeemer.getBlockHeader(&blockHash) + hdr, mainchain, err := wallet.tipRedeemer.GetBlockHeader(&blockHash) if err != nil { t.Fatalf("initial success error: %v", err) } @@ -892,7 +892,7 @@ func TestGetBlockHeader(t *testing.T) { } node.mainchain[int64(blockHeight)] = &chainhash.Hash{0x01} // mainchain ended up with different block - hdr, mainchain, err = wallet.tipRedeemer.getBlockHeader(&blockHash) + hdr, mainchain, err = wallet.tipRedeemer.GetBlockHeader(&blockHash) if err != nil { t.Fatalf("initial success error: %v", err) } @@ -906,7 +906,7 @@ func TestGetBlockHeader(t *testing.T) { // Can't fetch header error. delete(node.verboseBlocks, blockHash) // can't find block by hash - if _, _, err := wallet.tipRedeemer.getBlockHeader(&blockHash); err == nil { + if _, _, err := wallet.tipRedeemer.GetBlockHeader(&blockHash); err == nil { t.Fatalf("Can't fetch header error not propagated") } node.verboseBlocks[blockHash] = &blockHdr // clean up @@ -917,7 +917,7 @@ func TestGetBlockHeader(t *testing.T) { 0: node.mainchain[0], 1: node.mainchain[1], } - hdr, mainchain, err = wallet.tipRedeemer.getBlockHeader(&blockHash) + hdr, mainchain, err = wallet.tipRedeemer.GetBlockHeader(&blockHash) if err != nil { t.Fatalf("invalid tip height not noticed") } diff --git a/client/asset/btc/spv_wrapper.go b/client/asset/btc/spv_wrapper.go index eac8d192c3..a202b3b131 100644 --- a/client/asset/btc/spv_wrapper.go +++ b/client/asset/btc/spv_wrapper.go @@ -28,6 +28,7 @@ import ( "math" "os" "path/filepath" + "sort" "sync" "sync/atomic" "time" @@ -226,9 +227,9 @@ type spvWallet struct { var _ Wallet = (*spvWallet)(nil) var _ tipNotifier = (*spvWallet)(nil) -// reconfigure attempts to reconfigure the rpcClient for the new settings. Live +// Reconfigure attempts to reconfigure the rpcClient for the new settings. Live // reconfiguration is only attempted if the new wallet type is walletTypeSPV. -func (w *spvWallet) reconfigure(cfg *asset.WalletConfig, currentAddress string) (restartRequired bool, err error) { +func (w *spvWallet) Reconfigure(cfg *asset.WalletConfig, currentAddress string) (restartRequired bool, err error) { // If the wallet type is not SPV, then we can't reconfigure the wallet. if cfg.Type != walletTypeSPV { restartRequired = true @@ -258,11 +259,11 @@ func (w *spvWallet) RawRequest(ctx context.Context, method string, params []json return nil, errors.New("RawRequest not available on spv") } -func (w *spvWallet) ownsAddress(addr btcutil.Address) (bool, error) { +func (w *spvWallet) OwnsAddress(addr btcutil.Address) (bool, error) { return w.wallet.HaveAddress(addr) } -func (w *spvWallet) sendRawTransaction(tx *wire.MsgTx) (*chainhash.Hash, error) { +func (w *spvWallet) SendRawTransaction(tx *wire.MsgTx) (*chainhash.Hash, error) { // Publish the transaction in a goroutine so the caller may wait for a given // period before it goes asynchronous and it is assumed that btcwallet at // least succeeded with its DB updates and queueing of the transaction for @@ -306,7 +307,7 @@ func (w *spvWallet) sendRawTransaction(tx *wire.MsgTx) (*chainhash.Hash, error) return &txHash, nil } -func (w *spvWallet) getBlock(blockHash chainhash.Hash) (*wire.MsgBlock, error) { +func (w *spvWallet) GetBlock(blockHash chainhash.Hash) (*wire.MsgBlock, error) { block, err := w.cl.GetBlock(blockHash) if err != nil { return nil, fmt.Errorf("neutrino GetBlock error: %v", err) @@ -315,7 +316,7 @@ func (w *spvWallet) getBlock(blockHash chainhash.Hash) (*wire.MsgBlock, error) { return block.MsgBlock(), nil } -func (w *spvWallet) getBlockHash(blockHeight int64) (*chainhash.Hash, error) { +func (w *spvWallet) GetBlockHash(blockHeight int64) (*chainhash.Hash, error) { return w.cl.GetBlockHash(blockHeight) } @@ -325,17 +326,21 @@ func (w *spvWallet) getBlockHeight(h *chainhash.Hash) (int32, error) { return w.cl.GetBlockHeight(h) } -func (w *spvWallet) getBestBlockHash() (*chainhash.Hash, error) { +func (w *spvWallet) GetBlockHeight(h *chainhash.Hash) (int32, error) { + return w.cl.GetBlockHeight(h) +} + +func (w *spvWallet) GetBestBlockHash() (*chainhash.Hash, error) { blk := w.wallet.SyncedTo() return &blk.Hash, nil } -// getBestBlockHeight returns the height of the best block processed by the +// GetBestBlockHeight returns the height of the best block processed by the // wallet, which indicates the height at which the compact filters have been // retrieved and scanned for wallet addresses. This is may be less than // getChainHeight, which indicates the height that the chain service has reached // in its retrieval of block headers and compact filter headers. -func (w *spvWallet) getBestBlockHeight() (int32, error) { +func (w *spvWallet) GetBestBlockHeight() (int32, error) { return w.wallet.SyncedTo().Height, nil } @@ -348,15 +353,15 @@ func (w *spvWallet) getChainStamp(blockHash *chainhash.Hash) (stamp time.Time, p return hdr.Timestamp, &hdr.PrevBlock, nil } -// medianTime is the median time for the current best block. -func (w *spvWallet) medianTime() (time.Time, error) { +// MedianTime is the median time for the current best block. +func (w *spvWallet) MedianTime() (time.Time, error) { blk := w.wallet.SyncedTo() return CalcMedianTime(w.getChainStamp, &blk.Hash) } -// getChainHeight is only for confirmations since it does not reflect the wallet +// GetChainHeight is only for confirmations since it does not reflect the wallet // manager's sync height, just the chain service. -func (w *spvWallet) getChainHeight() (int32, error) { +func (w *spvWallet) GetChainHeight() (int32, error) { blk, err := w.cl.BestBlock() if err != nil { return -1, err @@ -364,7 +369,7 @@ func (w *spvWallet) getChainHeight() (int32, error) { return blk.Height, err } -func (w *spvWallet) peerCount() (uint32, error) { +func (w *spvWallet) PeerCount() (uint32, error) { return uint32(len(w.cl.Peers())), nil } @@ -406,7 +411,7 @@ func (w *spvWallet) syncHeight() int32 { // the chain service sync stage that comes before the wallet has performed any // address recovery/rescan, and switch to the wallet's sync height when it // reports non-zero height. -func (w *spvWallet) syncStatus() (*asset.SyncStatus, error) { +func (w *spvWallet) SyncStatus() (*asset.SyncStatus, error) { // Chain service headers (block and filter) height. chainBlk, err := w.cl.BestBlock() if err != nil { @@ -496,7 +501,7 @@ func (w *spvWallet) ownsInputs(txid string) bool { return true } -func (w *spvWallet) listTransactionsSinceBlock(blockHeight int32) ([]*ListTransactionsResult, error) { +func (w *spvWallet) ListTransactionsSinceBlock(blockHeight int32) ([]*ListTransactionsResult, error) { acctName := w.wallet.AccountInfo().AccountName tip, err := w.cl.BestBlock() if err != nil { @@ -538,7 +543,7 @@ func (w *spvWallet) listTransactionsSinceBlock(blockHeight int32) ([]*ListTransa } // balances retrieves a wallet's balance details. -func (w *spvWallet) balances() (*GetBalancesResult, error) { +func (w *spvWallet) Balances() (*GetBalancesResult, error) { // Determine trusted vs untrusted coins with listunspent. unspents, err := w.wallet.ListUnspent(0, math.MaxInt32, w.wallet.AccountInfo().AccountName) if err != nil { @@ -575,8 +580,8 @@ func (w *spvWallet) balances() (*GetBalancesResult, error) { }, nil } -// listUnspent retrieves list of the wallet's UTXOs. -func (w *spvWallet) listUnspent() ([]*ListUnspentResult, error) { +// ListUnspent retrieves list of the wallet's UTXOs. +func (w *spvWallet) ListUnspent() ([]*ListUnspentResult, error) { unspents, err := w.wallet.ListUnspent(0, math.MaxInt32, w.wallet.AccountInfo().AccountName) if err != nil { return nil, err @@ -619,10 +624,10 @@ func (w *spvWallet) listUnspent() ([]*ListUnspentResult, error) { return res, nil } -// lockUnspent locks and unlocks outputs for spending. An output that is part of +// LockUnspent locks and unlocks outputs for spending. An output that is part of // an order, but not yet spent, should be locked until spent or until the order // is canceled or fails. -func (w *spvWallet) lockUnspent(unlock bool, ops []*Output) error { +func (w *spvWallet) LockUnspent(unlock bool, ops []*Output) error { switch { case unlock && len(ops) == 0: w.wallet.ResetLockedOutpoints() @@ -639,9 +644,9 @@ func (w *spvWallet) lockUnspent(unlock bool, ops []*Output) error { return nil } -// listLockUnspent returns a slice of outpoints for all unspent outputs marked +// ListLockUnspent returns a slice of outpoints for all unspent outputs marked // as locked by a wallet. -func (w *spvWallet) listLockUnspent() ([]*RPCOutpoint, error) { +func (w *spvWallet) ListLockUnspent() ([]*RPCOutpoint, error) { outpoints := w.wallet.LockedOutpoints() pts := make([]*RPCOutpoint, 0, len(outpoints)) for _, pt := range outpoints { @@ -653,28 +658,28 @@ func (w *spvWallet) listLockUnspent() ([]*RPCOutpoint, error) { return pts, nil } -// changeAddress gets a new internal address from the wallet. The address will +// ChangeAddress gets a new internal address from the wallet. The address will // be bech32-encoded (P2WPKH). -func (w *spvWallet) changeAddress() (btcutil.Address, error) { +func (w *spvWallet) ChangeAddress() (btcutil.Address, error) { return w.wallet.NewChangeAddress(w.acctNum, waddrmgr.KeyScopeBIP0084) } -// externalAddress gets a new bech32-encoded (P2WPKH) external address from the +// ExternalAddress gets a new bech32-encoded (P2WPKH) external address from the // wallet. -func (w *spvWallet) externalAddress() (btcutil.Address, error) { +func (w *spvWallet) ExternalAddress() (btcutil.Address, error) { return w.wallet.NewAddress(w.acctNum, waddrmgr.KeyScopeBIP0084) } // signTx attempts to have the wallet sign the transaction inputs. -func (w *spvWallet) signTx(tx *wire.MsgTx) (*wire.MsgTx, error) { +func (w *spvWallet) SignTx(tx *wire.MsgTx) (*wire.MsgTx, error) { // Can't use btcwallet.Wallet.SignTransaction, because it doesn't work for // segwit transactions (for real?). return tx, w.wallet.SignTx(tx) } -// privKeyForAddress retrieves the private key associated with the specified +// PrivKeyForAddress retrieves the private key associated with the specified // address. -func (w *spvWallet) privKeyForAddress(addr string) (*btcec.PrivateKey, error) { +func (w *spvWallet) PrivKeyForAddress(addr string) (*btcec.PrivateKey, error) { a, err := w.decodeAddr(addr, w.chainParams) if err != nil { return nil, err @@ -693,15 +698,15 @@ func (w *spvWallet) Lock() error { return nil } -// estimateSendTxFee callers should provide at least one output value. -func (w *spvWallet) estimateSendTxFee(tx *wire.MsgTx, feeRate uint64, subtract bool) (fee uint64, err error) { +// EstimateSendTxFee callers should provide at least one output value. +func (w *spvWallet) EstimateSendTxFee(tx *wire.MsgTx, feeRate uint64, subtract bool) (fee uint64, err error) { minTxSize := uint64(tx.SerializeSize()) var sendAmount uint64 for _, txOut := range tx.TxOut { sendAmount += uint64(txOut.Value) } - unspents, err := w.listUnspent() + unspents, err := w.ListUnspent() if err != nil { return 0, fmt.Errorf("error listing unspent outputs: %w", err) } @@ -752,11 +757,11 @@ func (w *spvWallet) estimateSendTxFee(tx *wire.MsgTx, feeRate uint64, subtract b return finalFee, nil } -// swapConfirmations attempts to get the number of confirmations and the spend +// SwapConfirmations attempts to get the number of confirmations and the spend // status for the specified tx output. For swap outputs that were not generated // by this wallet, startTime must be supplied to limit the search. Use the match // time assigned by the server. -func (w *spvWallet) swapConfirmations(txHash *chainhash.Hash, vout uint32, pkScript []byte, +func (w *spvWallet) SwapConfirmations(txHash *chainhash.Hash, vout uint32, pkScript []byte, startTime time.Time) (confs uint32, spent bool, err error) { // First, check if it's a wallet transaction. We probably won't be able @@ -818,7 +823,7 @@ func (w *spvWallet) swapConfirmations(txHash *chainhash.Hash, vout uint32, pkScr } if utxo.blockHash != nil { - bestHeight, err := w.getChainHeight() + bestHeight, err := w.GetChainHeight() if err != nil { return 0, false, fmt.Errorf("getBestBlockHeight error: %v", err) } @@ -841,16 +846,16 @@ func (w *spvWallet) swapConfirmations(txHash *chainhash.Hash, vout uint32, pkScr return confs, false, nil } -func (w *spvWallet) locked() bool { +func (w *spvWallet) Locked() bool { return w.wallet.Locked() } -func (w *spvWallet) walletLock() error { +func (w *spvWallet) WalletLock() error { w.wallet.Lock() return nil } -func (w *spvWallet) walletUnlock(pw []byte) error { +func (w *spvWallet) WalletUnlock(pw []byte) error { return w.Unlock(pw) } @@ -858,10 +863,9 @@ func (w *spvWallet) getBlockHeaderVerbose(blockHash *chainhash.Hash) (*wire.Bloc return w.cl.GetBlockHeader(blockHash) } -// getBlockHeader gets the *blockHeader for the specified block hash. It also // returns a bool value to indicate whether this block is a part of main chain. // For orphaned blocks header.Confirmations is negative. -func (w *spvWallet) getBlockHeader(blockHash *chainhash.Hash) (header *BlockHeader, mainchain bool, err error) { +func (w *spvWallet) GetBlockHeader(blockHash *chainhash.Hash) (header *BlockHeader, mainchain bool, err error) { hdr, err := w.cl.GetBlockHeader(blockHash) if err != nil { return nil, false, err @@ -892,12 +896,12 @@ func (w *spvWallet) getBlockHeader(blockHash *chainhash.Hash) (header *BlockHead }, mainchain, nil } -func (w *spvWallet) getBestBlockHeader() (*BlockHeader, error) { - hash, err := w.getBestBlockHash() +func (w *spvWallet) GetBestBlockHeader() (*BlockHeader, error) { + hash, err := w.GetBestBlockHash() if err != nil { return nil, err } - hdr, _, err := w.getBlockHeader(hash) + hdr, _, err := w.GetBlockHeader(hash) return hdr, err } @@ -905,8 +909,8 @@ func (w *spvWallet) logFilePath() string { return filepath.Join(w.dir, logDirName, logFileName) } -// connect will start the wallet and begin syncing. -func (w *spvWallet) connect(ctx context.Context, wg *sync.WaitGroup) (err error) { +// Connect will start the wallet and begin syncing. +func (w *spvWallet) Connect(ctx context.Context, wg *sync.WaitGroup) (err error) { w.cl, err = w.wallet.Start() if err != nil { return err @@ -1063,9 +1067,9 @@ func (w *spvWallet) numDerivedAddresses() (internal, external uint32, err error) return props.InternalKeyCount, props.ExternalKeyCount, nil } -// fingerprint returns an identifier for this wallet. It is the hash of the +// Fingerprint returns an identifier for this wallet. It is the hash of the // compressed serialization of the account pub key. -func (w *spvWallet) fingerprint() (string, error) { +func (w *spvWallet) Fingerprint() (string, error) { props, err := w.wallet.AccountProperties(waddrmgr.KeyScopeBIP0084, w.acctNum) if err != nil { return "", err @@ -1083,10 +1087,91 @@ func (w *spvWallet) fingerprint() (string, error) { return hex.EncodeToString(btcutil.Hash160(pk.SerializeCompressed())), nil } -// getTxOut finds an unspent transaction output and its number of confirmations. +// mainchainBlockForStoredTx gets the block hash and height for the transaction +// IFF an entry has been stored in the txBlocks index. +func (w *spvWallet) mainchainBlockForStoredTx(txHash *chainhash.Hash) (*chainhash.Hash, int32) { + // Check that the block is still mainchain. + blockHash, blockHeight, err := w.blockForStoredTx(txHash) + if err != nil { + w.log.Errorf("Error retrieving mainchain block height for hash %s", blockHash) + return nil, 0 + } + if blockHash == nil { + return nil, 0 + } + if !w.blockIsMainchain(blockHash, blockHeight) { + return nil, 0 + } + return blockHash, blockHeight +} + +// findBlockForTime locates a good start block so that a search beginning at the +// returned block has a very low likelihood of missing any blocks that have time +// > matchTime. This is done by performing a binary search (sort.Search) to find +// a block with a block time maxFutureBlockTime before matchTime. To ensure +// we also accommodate the median-block time rule and aren't missing anything +// due to out of sequence block times we use an unsophisticated algorithm of +// choosing the first block in an 11 block window with no times >= matchTime. +func (w *spvWallet) findBlockForTime(matchTime time.Time) (int32, error) { + offsetTime := matchTime.Add(-maxFutureBlockTime) + + bestHeight, err := w.GetChainHeight() + if err != nil { + return 0, fmt.Errorf("getChainHeight error: %v", err) + } + + getBlockTimeForHeight := func(height int32) (time.Time, error) { + hash, err := w.cl.GetBlockHash(int64(height)) + if err != nil { + return time.Time{}, err + } + header, err := w.cl.GetBlockHeader(hash) + if err != nil { + return time.Time{}, err + } + return header.Timestamp, nil + } + + iHeight := sort.Search(int(bestHeight), func(h int) bool { + var iTime time.Time + iTime, err = getBlockTimeForHeight(int32(h)) + if err != nil { + return true + } + return iTime.After(offsetTime) + }) + if err != nil { + return 0, fmt.Errorf("binary search error finding best block for time %q: %w", matchTime, err) + } + + // We're actually breaking an assumption of sort.Search here because block + // times aren't always monotonically increasing. This won't matter though as + // long as there are not > medianTimeBlocks blocks with inverted time order. + var count int + var iTime time.Time + for iHeight > 0 { + iTime, err = getBlockTimeForHeight(int32(iHeight)) + if err != nil { + return 0, fmt.Errorf("getBlockTimeForHeight error: %w", err) + } + if iTime.Before(offsetTime) { + count++ + if count == medianTimeBlocks { + return int32(iHeight), nil + } + } else { + count = 0 + } + iHeight-- + } + return 0, nil + +} + +// GetTxOut finds an unspent transaction output and its number of confirmations. // To match the behavior of the RPC method, even if an output is found, if it's // known to be spent, no *wire.TxOut and no error will be returned. -func (w *spvWallet) getTxOut(txHash *chainhash.Hash, vout uint32, pkScript []byte, startTime time.Time) (*wire.TxOut, uint32, error) { +func (w *spvWallet) GetTxOut(txHash *chainhash.Hash, vout uint32, pkScript []byte, startTime time.Time) (*wire.TxOut, uint32, error) { // Check for a wallet transaction first txDetails, err := w.wallet.WalletTransaction(txHash) var blockHash *chainhash.Hash @@ -1168,9 +1253,9 @@ func (w *spvWallet) matchPkScript(blockHash *chainhash.Hash, scripts [][]byte) ( return matchFound, nil } -// searchBlockForRedemptions attempts to find spending info for the specified +// SearchBlockForRedemptions attempts to find spending info for the specified // contracts by searching every input of all txs in the provided block range. -func (w *spvWallet) searchBlockForRedemptions(ctx context.Context, reqs map[OutPoint]*FindRedemptionReq, +func (w *spvWallet) SearchBlockForRedemptions(ctx context.Context, reqs map[OutPoint]*FindRedemptionReq, blockHash chainhash.Hash) (discovered map[OutPoint]*FindRedemptionResult) { // Just match all the scripts together. @@ -1207,8 +1292,8 @@ func (w *spvWallet) searchBlockForRedemptions(ctx context.Context, reqs map[OutP return } -// findRedemptionsInMempool is unsupported for SPV. -func (w *spvWallet) findRedemptionsInMempool(ctx context.Context, reqs map[OutPoint]*FindRedemptionReq) (discovered map[OutPoint]*FindRedemptionResult) { +// FindRedemptionsInMempool is unsupported for SPV. +func (w *spvWallet) FindRedemptionsInMempool(ctx context.Context, reqs map[OutPoint]*FindRedemptionReq) (discovered map[OutPoint]*FindRedemptionResult) { return } @@ -1222,7 +1307,7 @@ func (w *spvWallet) confirmations(txHash *chainhash.Hash, vout uint32) (blockHas if details.Block.Hash != (chainhash.Hash{}) { blockHash = &details.Block.Hash - height, err := w.getChainHeight() + height, err := w.GetChainHeight() if err != nil { return nil, 0, false, err } @@ -1237,12 +1322,12 @@ func (w *spvWallet) confirmations(txHash *chainhash.Hash, vout uint32) (blockHas return blockHash, confs, false, SpentStatusUnknown } -// getWalletTransaction checks the wallet database for the specified +// GetWalletTransaction checks the wallet database for the specified // transaction. Only transactions with output scripts that pay to the wallet or // transactions that spend wallet outputs are stored in the wallet database. // This is pretty much copy-paste from btcwallet 'gettransaction' JSON-RPC // handler. -func (w *spvWallet) getWalletTransaction(txHash *chainhash.Hash) (*GetTransactionResult, error) { +func (w *spvWallet) GetWalletTransaction(txHash *chainhash.Hash) (*GetTransactionResult, error) { // Option # 1 just copies from UnstableAPI.TxDetails. Duplicating the // unexported bucket key feels dirty. // diff --git a/client/asset/btc/wallet.go b/client/asset/btc/wallet.go index 963359f768..bf54898ec6 100644 --- a/client/asset/btc/wallet.go +++ b/client/asset/btc/wallet.go @@ -18,46 +18,46 @@ import ( // all requests with a context.Context. type Wallet interface { RawRequester // for localFeeRate/rpcFeeRate calls - connect(ctx context.Context, wg *sync.WaitGroup) error - sendRawTransaction(tx *wire.MsgTx) (*chainhash.Hash, error) - getTxOut(txHash *chainhash.Hash, index uint32, pkScript []byte, startTime time.Time) (*wire.TxOut, uint32, error) - getBlockHash(blockHeight int64) (*chainhash.Hash, error) - getBestBlockHash() (*chainhash.Hash, error) - getBestBlockHeight() (int32, error) - medianTime() (time.Time, error) - balances() (*GetBalancesResult, error) - listUnspent() ([]*ListUnspentResult, error) // must not return locked coins - lockUnspent(unlock bool, ops []*Output) error - listLockUnspent() ([]*RPCOutpoint, error) - changeAddress() (btcutil.Address, error) // warning: don't just use the Stringer if there's a "recode" function for a clone e.g. BCH - externalAddress() (btcutil.Address, error) - signTx(inTx *wire.MsgTx) (*wire.MsgTx, error) - privKeyForAddress(addr string) (*btcec.PrivateKey, error) - walletUnlock(pw []byte) error - walletLock() error - locked() bool - syncStatus() (*asset.SyncStatus, error) - peerCount() (uint32, error) - swapConfirmations(txHash *chainhash.Hash, vout uint32, contract []byte, startTime time.Time) (confs uint32, spent bool, err error) - getBestBlockHeader() (*BlockHeader, error) - 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) - fingerprint() (string, error) - listTransactionsSinceBlock(blockHeight int32) ([]*ListTransactionsResult, error) + Connect(ctx context.Context, wg *sync.WaitGroup) error + SendRawTransaction(tx *wire.MsgTx) (*chainhash.Hash, error) + GetTxOut(txHash *chainhash.Hash, index uint32, pkScript []byte, startTime time.Time) (*wire.TxOut, uint32, error) + GetBlockHash(blockHeight int64) (*chainhash.Hash, error) + GetBestBlockHash() (*chainhash.Hash, error) + GetBestBlockHeight() (int32, error) + MedianTime() (time.Time, error) + Balances() (*GetBalancesResult, error) + ListUnspent() ([]*ListUnspentResult, error) // must not return locked coins + LockUnspent(unlock bool, ops []*Output) error + ListLockUnspent() ([]*RPCOutpoint, error) + ChangeAddress() (btcutil.Address, error) // warning: don't just use the Stringer if there's a "recode" function for a clone e.g. BCH + ExternalAddress() (btcutil.Address, error) + SignTx(inTx *wire.MsgTx) (*wire.MsgTx, error) + PrivKeyForAddress(addr string) (*btcec.PrivateKey, error) + WalletUnlock(pw []byte) error + WalletLock() error + Locked() bool + SyncStatus() (*asset.SyncStatus, error) + PeerCount() (uint32, error) + SwapConfirmations(txHash *chainhash.Hash, vout uint32, contract []byte, startTime time.Time) (confs uint32, spent bool, err error) + GetBestBlockHeader() (*BlockHeader, error) + 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) + Fingerprint() (string, error) + ListTransactionsSinceBlock(blockHeight int32) ([]*ListTransactionsResult, error) } -type tipRedemptionWallet interface { +type TipRedemptionWallet interface { Wallet - getBlockHeight(*chainhash.Hash) (int32, error) - getBlockHeader(blockHash *chainhash.Hash) (hdr *BlockHeader, mainchain bool, err error) - getBlock(h chainhash.Hash) (*wire.MsgBlock, error) - searchBlockForRedemptions(ctx context.Context, reqs map[OutPoint]*FindRedemptionReq, blockHash chainhash.Hash) (discovered map[OutPoint]*FindRedemptionResult) - findRedemptionsInMempool(ctx context.Context, reqs map[OutPoint]*FindRedemptionReq) (discovered map[OutPoint]*FindRedemptionResult) + GetBlockHeight(*chainhash.Hash) (int32, error) + GetBlockHeader(blockHash *chainhash.Hash) (hdr *BlockHeader, mainchain bool, err error) + GetBlock(h chainhash.Hash) (*wire.MsgBlock, error) + SearchBlockForRedemptions(ctx context.Context, reqs map[OutPoint]*FindRedemptionReq, blockHash chainhash.Hash) (discovered map[OutPoint]*FindRedemptionResult) + FindRedemptionsInMempool(ctx context.Context, reqs map[OutPoint]*FindRedemptionReq) (discovered map[OutPoint]*FindRedemptionResult) } -type txFeeEstimator interface { - estimateSendTxFee(tx *wire.MsgTx, feeRate uint64, subtract bool) (fee uint64, err error) +type TxFeeEstimator interface { + EstimateSendTxFee(tx *wire.MsgTx, feeRate uint64, subtract bool) (fee uint64, err error) } // walletTxChecker provide a fast wallet tx query when block info not needed. diff --git a/client/asset/ltc/ltc.go b/client/asset/ltc/ltc.go index 385f1a21f6..dca32d226d 100644 --- a/client/asset/ltc/ltc.go +++ b/client/asset/ltc/ltc.go @@ -170,24 +170,24 @@ func (d *Driver) Create(params *asset.CreateWalletParams) error { params.Logger, recoveryCfg.NumExternalAddresses, recoveryCfg.NumInternalAddresses, chainParams) } -// customSPVWalletConstructors are functions for setting up custom -// implementations of the btc.BTCWallet interface that may be used by the -// ExchangeWalletSPV instead of the default spv implementation. -var customSPVWalletConstructors = map[string]btc.CustomSPVWalletConstructor{} +// customWalletConstructors are functions for setting up btc.CustomWallet +// implementations that may be used by the btc.ExchangeWalletCustom instead of +// the default spv implementation. +var customWalletConstructors = map[string]btc.CustomWalletConstructor{} -// RegisterCustomSPVWallet registers a function that should be used in creating -// a btc.BTCWallet implementation that the ExchangeWalletSPV will use in place -// of the default spv wallet implementation. External consumers can use this -// function to provide alternative btc.BTCWallet implementations, and must do so -// before attempting to create an ExchangeWalletSPV instance of this type. It'll -// panic if callers try to register a wallet twice. -func RegisterCustomSPVWallet(constructor btc.CustomSPVWalletConstructor, def *asset.WalletDefinition) { +// RegisterCustomWallet registers a function that should be used in creating a +// btc.CustomWallet implementation that btc.ExchangeWalletCustom will use in +// place of the default spv wallet implementation. External consumers can use +// this function to provide alternative btc.CustomWallet implementations, and +// must do so before attempting to create an btc.ExchangeWalletCustom instance +// of this type. It'll panic if callers try to register a wallet twice. +func RegisterCustomWallet(constructor btc.CustomWalletConstructor, def *asset.WalletDefinition) { for _, availableWallets := range WalletInfo.AvailableWallets { if def.Type == availableWallets.Type { panic(fmt.Sprintf("wallet type (%q) is already registered", def.Type)) } } - customSPVWalletConstructors[def.Type] = constructor + customWalletConstructors[def.Type] = constructor WalletInfo.AvailableWallets = append(WalletInfo.AvailableWallets, def) } @@ -243,22 +243,11 @@ func NewWallet(cfg *asset.WalletConfig, logger dex.Logger, network dex.Network) cloneCFG.MinElectrumVersion = *ver return btc.ElectrumWallet(cloneCFG) default: - makeCustomWallet, ok := customSPVWalletConstructors[cfg.Type] + makeCustomWallet, ok := customWalletConstructors[cfg.Type] if !ok { return nil, fmt.Errorf("unknown wallet type %q", cfg.Type) } - - // Create custom wallet first and return early if we encounter any - // error. - ltcWallet, err := makeCustomWallet(cfg.Settings, cloneCFG.ChainParams) - if err != nil { - return nil, fmt.Errorf("custom wallet setup error: %v", err) - } - - walletConstructor := func(_ string, _ *btc.WalletConfig, _ *chaincfg.Params, _ dex.Logger) btc.BTCWallet { - return ltcWallet - } - return btc.OpenSPVWallet(cloneCFG, walletConstructor) + return btc.OpenCustomWallet(cloneCFG, makeCustomWallet) } } From dd4f12b34bac3016a974063548ad70c4b6503963 Mon Sep 17 00:00:00 2001 From: Wisdom Arerosuoghene Date: Mon, 22 Jan 2024 17:22:50 +0100 Subject: [PATCH 2/6] client/asset/btc: expose spv scanFilters method --- client/asset/btc/btc_test.go | 9 +- client/asset/btc/spv_scan_filters.go | 218 +++++++++++++-------------- client/asset/btc/spv_test.go | 59 +++----- client/asset/btc/spv_wrapper.go | 114 ++------------ 4 files changed, 154 insertions(+), 246 deletions(-) diff --git a/client/asset/btc/btc_test.go b/client/asset/btc/btc_test.go index 8099bb516b..0383ba9aa7 100644 --- a/client/asset/btc/btc_test.go +++ b/client/asset/btc/btc_test.go @@ -33,6 +33,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcwallet/wallet" ) var ( @@ -184,7 +185,7 @@ type testData struct { // spv fetchInputInfoTx *wire.MsgTx getCFilterScripts map[chainhash.Hash][][]byte - checkpoints map[OutPoint]*scanCheckpoint + checkpoints map[OutPoint]*ScanCheckpoint confs uint32 confsSpent bool confsErr error @@ -211,12 +212,16 @@ func newTestData() *testData { fetchInputInfoTx: dummyTx(), getCFilterScripts: make(map[chainhash.Hash][][]byte), confsErr: WalletTransactionNotFound, - checkpoints: make(map[OutPoint]*scanCheckpoint), + checkpoints: make(map[OutPoint]*ScanCheckpoint), tipChanged: make(chan asset.WalletNotification, 1), getTransactionMap: make(map[string]*GetTransactionResult), } } +func (c *testData) GetTransactions(startHeight, endHeight int32, accountName string, cancel <-chan struct{}) (*wallet.GetTransactionsResult, error) { + return nil, fmt.Errorf("not implemented") +} + func (c *testData) getBlock(blockHash chainhash.Hash) *msgBlockWithHeight { c.blockchainMtx.Lock() defer c.blockchainMtx.Unlock() diff --git a/client/asset/btc/spv_scan_filters.go b/client/asset/btc/spv_scan_filters.go index 9ee62d1c54..cfde79551f 100644 --- a/client/asset/btc/spv_scan_filters.go +++ b/client/asset/btc/spv_scan_filters.go @@ -13,27 +13,27 @@ import ( "github.com/btcsuite/btcd/wire" ) -// spendingInput is added to a filterScanResult if a spending input is found. -type spendingInput struct { - txHash chainhash.Hash - vin uint32 - blockHash chainhash.Hash - blockHeight uint32 +// SpendingInput is added to a filterScanResult if a spending input is found. +type SpendingInput struct { + TxHash chainhash.Hash + Vin uint32 + BlockHash chainhash.Hash + BlockHeight uint32 } -// filterScanResult is the result from a filter scan. -type filterScanResult struct { - // blockHash is the block that the output was found in. - blockHash *chainhash.Hash - // blockHeight is the height of the block that the output was found in. - blockHeight uint32 - // txOut is the output itself. - txOut *wire.TxOut - // spend will be set if a spending input is found. - spend *spendingInput - // checkpoint is used to track the last block scanned so that future scans +// FilterScanResult is the result from a filter scan. +type FilterScanResult struct { + // BlockHash is the block that the output was found in. + BlockHash *chainhash.Hash + // BlockHeight is the height of the block that the output was found in. + BlockHeight uint32 + // TxOut is the output itself. + TxOut *wire.TxOut + // Spend will be set if a spending input is found. + Spend *SpendingInput + // Checkpoint is used to track the last block scanned so that future scans // can skip scanned blocks. - checkpoint chainhash.Hash + Checkpoint chainhash.Hash } // hashEntry stores a chainhash.Hash with a last-access time that can be used @@ -43,52 +43,52 @@ type hashEntry struct { lastAccess time.Time } -// scanCheckpoint is a cached, incomplete filterScanResult. When another scan -// is requested for an outpoint with a cached *scanCheckpoint, the scan can -// pick up where it left off. -type scanCheckpoint struct { - res *filterScanResult - lastAccess time.Time +// ScanCheckpoint is a cached, incomplete FilterScanResult. When another scan is +// requested for an outpoint with a cached *ScanCheckpoint, the scan can pick up +// where it left off. +type ScanCheckpoint struct { + Res *FilterScanResult + LastAccess time.Time } -// blockInfoReader defines methods for retrieving block information. -type blockInfoReader interface { - getBlockHash(blockHeight int64) (*chainhash.Hash, error) - getBlockHeight(*chainhash.Hash) (int32, error) - getBlockHeaderVerbose(blockHash *chainhash.Hash) (*wire.BlockHeader, error) - getBlock(h chainhash.Hash) (*wire.MsgBlock, error) - getChainHeight() (int32, error) - matchPkScript(blockHash *chainhash.Hash, scripts [][]byte) (bool, error) +// BlockInfoReader defines methods for retrieving block information. +type BlockInfoReader interface { + GetBlockHash(blockHeight int64) (*chainhash.Hash, error) + GetBlockHeight(*chainhash.Hash) (int32, error) + GetBlockHeaderVerbose(blockHash *chainhash.Hash) (*wire.BlockHeader, error) + GetBlock(h chainhash.Hash) (*wire.MsgBlock, error) + GetChainHeight() (int32, error) + MatchPkScript(blockHash *chainhash.Hash, scripts [][]byte) (bool, error) } // BlockFiltersScanner is a utility tool for searching for an output and its // spending input by scanning BIP158 compact filters. Used by SPV wallets to // locate non-wallet transactions. type BlockFiltersScanner struct { - blockInfoReader + BlockInfoReader log dex.Logger cacheExpiration time.Duration checkpointMtx sync.Mutex - checkpoints map[OutPoint]*scanCheckpoint + checkpoints map[OutPoint]*ScanCheckpoint txBlocksMtx sync.Mutex txBlocks map[chainhash.Hash]*hashEntry } // NewBlockFiltersScanner creates a BlockFiltersScanner. -func NewBlockFiltersScanner(blkInfoRdr blockInfoReader, log dex.Logger) *BlockFiltersScanner { +func NewBlockFiltersScanner(blkInfoRdr BlockInfoReader, log dex.Logger) *BlockFiltersScanner { return &BlockFiltersScanner{ - blockInfoReader: blkInfoRdr, + BlockInfoReader: blkInfoRdr, log: log, cacheExpiration: time.Hour * 2, txBlocks: make(map[chainhash.Hash]*hashEntry), - checkpoints: make(map[OutPoint]*scanCheckpoint), + checkpoints: make(map[OutPoint]*ScanCheckpoint), } } -// storeTxBlock stores the block hash for the tx in the cache. -func (s *BlockFiltersScanner) storeTxBlock(txHash, blockHash chainhash.Hash) { +// StoreTxBlock stores the block hash for the tx in the cache. +func (s *BlockFiltersScanner) StoreTxBlock(txHash, blockHash chainhash.Hash) { s.txBlocksMtx.Lock() defer s.txBlocksMtx.Unlock() s.txBlocks[txHash] = &hashEntry{ @@ -97,8 +97,8 @@ func (s *BlockFiltersScanner) storeTxBlock(txHash, blockHash chainhash.Hash) { } } -// txBlock attempts to retrieve the block hash for the tx from the cache. -func (s *BlockFiltersScanner) txBlock(txHash chainhash.Hash) (chainhash.Hash, bool) { +// TxBlock attempts to retrieve the block hash for the tx from the cache. +func (s *BlockFiltersScanner) TxBlock(txHash chainhash.Hash) (chainhash.Hash, bool) { s.txBlocksMtx.Lock() defer s.txBlocksMtx.Unlock() entry, found := s.txBlocks[txHash] @@ -109,46 +109,46 @@ func (s *BlockFiltersScanner) txBlock(txHash chainhash.Hash) (chainhash.Hash, bo return entry.hash, true } -// cacheCheckpoint caches a *filterScanResult so that future scans can be +// CacheCheckpoint caches a *filterScanResult so that future scans can be // skipped or shortened. -func (s *BlockFiltersScanner) cacheCheckpoint(txHash *chainhash.Hash, vout uint32, res *filterScanResult) { - if res.spend != nil && res.blockHash == nil { +func (s *BlockFiltersScanner) CacheCheckpoint(txHash *chainhash.Hash, vout uint32, res *FilterScanResult) { + if res.Spend != nil && res.BlockHash == nil { // Probably set the start time too late. Don't cache anything return } s.checkpointMtx.Lock() defer s.checkpointMtx.Unlock() - s.checkpoints[NewOutPoint(txHash, vout)] = &scanCheckpoint{ - res: res, - lastAccess: time.Now(), + s.checkpoints[NewOutPoint(txHash, vout)] = &ScanCheckpoint{ + Res: res, + LastAccess: time.Now(), } } -// unvalidatedCheckpoint returns any cached *filterScanResult for the outpoint. -func (s *BlockFiltersScanner) unvalidatedCheckpoint(txHash *chainhash.Hash, vout uint32) *filterScanResult { +// UnvalidatedCheckpoint returns any cached *filterScanResult for the outpoint. +func (s *BlockFiltersScanner) UnvalidatedCheckpoint(txHash *chainhash.Hash, vout uint32) *FilterScanResult { s.checkpointMtx.Lock() defer s.checkpointMtx.Unlock() check, found := s.checkpoints[NewOutPoint(txHash, vout)] if !found { return nil } - check.lastAccess = time.Now() - res := *check.res + check.LastAccess = time.Now() + res := *check.Res return &res } -// checkpoint returns a filterScanResult and the checkpoint block hash. If a +// Checkpoint returns a filterScanResult and the checkpoint block hash. If a // result is found with an orphaned checkpoint block hash, it is cleared from // the cache and not returned. -func (s *BlockFiltersScanner) checkpoint(txHash *chainhash.Hash, vout uint32) *filterScanResult { - res := s.unvalidatedCheckpoint(txHash, vout) +func (s *BlockFiltersScanner) Checkpoint(txHash *chainhash.Hash, vout uint32) *FilterScanResult { + res := s.UnvalidatedCheckpoint(txHash, vout) if res == nil { return nil } - if !s.blockIsMainchain(&res.checkpoint, -1) { + if !s.BlockIsMainchain(&res.Checkpoint, -1) { // reorg detected, abandon the checkpoint. s.log.Debugf("abandoning checkpoint %s because checkpoint block %q is orphaned", - NewOutPoint(txHash, vout), res.checkpoint) + NewOutPoint(txHash, vout), res.Checkpoint) s.checkpointMtx.Lock() delete(s.checkpoints, NewOutPoint(txHash, vout)) s.checkpointMtx.Unlock() @@ -171,22 +171,22 @@ func (s *BlockFiltersScanner) CleanCaches(expiration time.Duration) { s.checkpointMtx.Lock() for outPt, check := range s.checkpoints { - if time.Since(check.lastAccess) > expiration { + if time.Since(check.LastAccess) > expiration { delete(s.checkpoints, outPt) } } s.checkpointMtx.Unlock() } -// blockForStoredTx looks for a block hash in the txBlocks index. -func (s *BlockFiltersScanner) blockForStoredTx(txHash *chainhash.Hash) (*chainhash.Hash, int32, error) { +// BlockForStoredTx looks for a block hash in the txBlocks index. +func (s *BlockFiltersScanner) BlockForStoredTx(txHash *chainhash.Hash) (*chainhash.Hash, int32, error) { // Check if we know the block hash for the tx. - blockHash, found := s.txBlock(*txHash) + blockHash, found := s.TxBlock(*txHash) if !found { return nil, 0, nil } // Check that the block is still mainchain. - blockHeight, err := s.getBlockHeight(&blockHash) + blockHeight, err := s.GetBlockHeight(&blockHash) if err != nil { s.log.Errorf("Error retrieving block height for hash %s: %v", blockHash, err) return nil, 0, err @@ -194,17 +194,17 @@ func (s *BlockFiltersScanner) blockForStoredTx(txHash *chainhash.Hash) (*chainha return &blockHash, blockHeight, nil } -// blockIsMainchain will be true if the blockHash is that of a mainchain block. -func (s *BlockFiltersScanner) blockIsMainchain(blockHash *chainhash.Hash, blockHeight int32) bool { +// BlockIsMainchain will be true if the blockHash is that of a mainchain block. +func (s *BlockFiltersScanner) BlockIsMainchain(blockHash *chainhash.Hash, blockHeight int32) bool { if blockHeight < 0 { var err error - blockHeight, err = s.getBlockHeight(blockHash) + blockHeight, err = s.GetBlockHeight(blockHash) if err != nil { s.log.Errorf("Error getting block height for hash %s", blockHash) return false } } - checkHash, err := s.getBlockHash(int64(blockHeight)) + checkHash, err := s.GetBlockHash(int64(blockHeight)) if err != nil { s.log.Errorf("Error retrieving block hash for height %d", blockHeight) return false @@ -213,11 +213,11 @@ func (s *BlockFiltersScanner) blockIsMainchain(blockHash *chainhash.Hash, blockH return *checkHash == *blockHash } -// mainchainBlockForStoredTx gets the block hash and height for the transaction +// MainchainBlockForStoredTx gets the block hash and height for the transaction // IFF an entry has been stored in the txBlocks index. -func (s *BlockFiltersScanner) mainchainBlockForStoredTx(txHash *chainhash.Hash) (*chainhash.Hash, int32) { +func (s *BlockFiltersScanner) MainchainBlockForStoredTx(txHash *chainhash.Hash) (*chainhash.Hash, int32) { // Check that the block is still mainchain. - blockHash, blockHeight, err := s.blockForStoredTx(txHash) + blockHash, blockHeight, err := s.BlockForStoredTx(txHash) if err != nil { s.log.Errorf("Error retrieving mainchain block height for hash %s", blockHash) return nil, 0 @@ -225,33 +225,33 @@ func (s *BlockFiltersScanner) mainchainBlockForStoredTx(txHash *chainhash.Hash) if blockHash == nil { return nil, 0 } - if !s.blockIsMainchain(blockHash, blockHeight) { + if !s.BlockIsMainchain(blockHash, blockHeight) { return nil, 0 } return blockHash, blockHeight } -// findBlockForTime locates a good start block so that a search beginning at the +// FindBlockForTime locates a good start block so that a search beginning at the // returned block has a very low likelihood of missing any blocks that have time // > matchTime. This is done by performing a binary search (sort.Search) to find // a block with a block time maxFutureBlockTime before matchTime. To ensure // we also accommodate the median-block time rule and aren't missing anything // due to out of sequence block times we use an unsophisticated algorithm of // choosing the first block in an 11 block window with no times >= matchTime. -func (s *BlockFiltersScanner) findBlockForTime(matchTime time.Time) (int32, error) { +func (s *BlockFiltersScanner) FindBlockForTime(matchTime time.Time) (int32, error) { offsetTime := matchTime.Add(-maxFutureBlockTime) - bestHeight, err := s.getChainHeight() + bestHeight, err := s.GetChainHeight() if err != nil { return 0, fmt.Errorf("getChainHeight error: %v", err) } getBlockTimeForHeight := func(height int32) (time.Time, error) { - hash, err := s.getBlockHash(int64(height)) + hash, err := s.GetBlockHash(int64(height)) if err != nil { return time.Time{}, err } - header, err := s.getBlockHeaderVerbose(hash) + header, err := s.GetBlockHeaderVerbose(hash) if err != nil { return time.Time{}, err } @@ -300,30 +300,30 @@ func (s *BlockFiltersScanner) findBlockForTime(matchTime time.Time) (int32, erro // both the output and a spending transaction is found. if startTime is // supplied, and the blockHash for the output is not known to the wallet, a // candidate block will be selected with findBlockTime. -func (s *BlockFiltersScanner) ScanFilters(txHash *chainhash.Hash, vout uint32, pkScript []byte, walletTip int32, startTime time.Time, blockHash *chainhash.Hash) (*filterScanResult, error) { +func (s *BlockFiltersScanner) ScanFilters(txHash *chainhash.Hash, vout uint32, pkScript []byte, walletTip int32, startTime time.Time, blockHash *chainhash.Hash) (*FilterScanResult, error) { // TODO: Check that any blockHash supplied is not orphaned? // Check if we know the block hash for the tx. var limitHeight int32 // See if we have a checkpoint to use. - checkPt := s.checkpoint(txHash, vout) + checkPt := s.Checkpoint(txHash, vout) if checkPt != nil { - if checkPt.blockHash != nil && checkPt.spend != nil { + if checkPt.BlockHash != nil && checkPt.Spend != nil { // We already have the output and the spending input, and // checkpointBlock already verified it's still mainchain. return checkPt, nil } - height, err := s.getBlockHeight(&checkPt.checkpoint) + height, err := s.GetBlockHeight(&checkPt.Checkpoint) if err != nil { - return nil, fmt.Errorf("getBlockHeight error: %w", err) + return nil, fmt.Errorf("GetBlockHeight error: %w", err) } limitHeight = height + 1 } else if blockHash == nil { // No checkpoint and no block hash. Gotta guess based on time. - blockHash, limitHeight = s.mainchainBlockForStoredTx(txHash) + blockHash, limitHeight = s.MainchainBlockForStoredTx(txHash) if blockHash == nil { var err error - limitHeight, err = s.findBlockForTime(startTime) + limitHeight, err = s.FindBlockForTime(startTime) if err != nil { return nil, err } @@ -331,7 +331,7 @@ func (s *BlockFiltersScanner) ScanFilters(txHash *chainhash.Hash, vout uint32, p } else { // No checkpoint, but user supplied a block hash. var err error - limitHeight, err = s.getBlockHeight(blockHash) + limitHeight, err = s.GetBlockHeight(blockHash) if err != nil { return nil, fmt.Errorf("error getting height for supplied block hash %s", blockHash) } @@ -350,52 +350,52 @@ func (s *BlockFiltersScanner) ScanFilters(txHash *chainhash.Hash, vout uint32, p // If we found a block, let's store a reference in our local database so we // can maybe bypass a long search next time. - if utxo.blockHash != nil { + if utxo.BlockHash != nil { s.log.Debugf("cfilters scan SUCCEEDED for %v:%d. block hash: %v, spent: %v", - txHash, vout, utxo.blockHash, utxo.spend != nil) - s.storeTxBlock(*txHash, *utxo.blockHash) + txHash, vout, utxo.BlockHash, utxo.Spend != nil) + s.StoreTxBlock(*txHash, *utxo.BlockHash) } - s.cacheCheckpoint(txHash, vout, utxo) + s.CacheCheckpoint(txHash, vout, utxo) return utxo, nil } // filterScanFromHeight scans BIP158 filters beginning at the specified block // height until the tip, or until a spending transaction is found. -func (s *BlockFiltersScanner) filterScanFromHeight(txHash chainhash.Hash, vout uint32, pkScript []byte, walletTip int32, startBlockHeight int32, checkPt *filterScanResult) (*filterScanResult, error) { +func (s *BlockFiltersScanner) filterScanFromHeight(txHash chainhash.Hash, vout uint32, pkScript []byte, walletTip int32, startBlockHeight int32, checkPt *FilterScanResult) (*FilterScanResult, error) { res := checkPt if res == nil { - res = new(filterScanResult) + res = new(FilterScanResult) } search: for height := startBlockHeight; height <= walletTip; height++ { - if res.spend != nil && res.blockHash == nil { + if res.Spend != nil && res.BlockHash == nil { s.log.Warnf("A spending input (%s) was found during the scan but the output (%s) "+ "itself wasn't found. Was the startBlockHeight early enough?", - NewOutPoint(&res.spend.txHash, res.spend.vin), + NewOutPoint(&res.Spend.TxHash, res.Spend.Vin), NewOutPoint(&txHash, vout), ) return res, nil } - blockHash, err := s.getBlockHash(int64(height)) + blockHash, err := s.GetBlockHash(int64(height)) if err != nil { return nil, fmt.Errorf("error getting block hash for height %d: %w", height, err) } - matched, err := s.matchPkScript(blockHash, [][]byte{pkScript}) + matched, err := s.MatchPkScript(blockHash, [][]byte{pkScript}) if err != nil { return nil, fmt.Errorf("matchPkScript error: %w", err) } - res.checkpoint = *blockHash + res.Checkpoint = *blockHash if !matched { continue search } // Pull the block. s.log.Tracef("Block %v matched pkScript for output %v:%d. Pulling the block...", blockHash, txHash, vout) - msgBlock, err := s.getBlock(*blockHash) + msgBlock, err := s.GetBlock(*blockHash) if err != nil { return nil, fmt.Errorf("GetBlock error: %v", err) } @@ -404,19 +404,19 @@ search: nextTx: for _, tx := range msgBlock.Transactions { // Look for a spending input. - if res.spend == nil { + if res.Spend == nil { for vin, txIn := range tx.TxIn { prevOut := &txIn.PreviousOutPoint if prevOut.Hash == txHash && prevOut.Index == vout { - res.spend = &spendingInput{ - txHash: tx.TxHash(), - vin: uint32(vin), - blockHash: *blockHash, - blockHeight: uint32(height), + res.Spend = &SpendingInput{ + TxHash: tx.TxHash(), + Vin: uint32(vin), + BlockHash: *blockHash, + BlockHeight: uint32(height), } - s.log.Tracef("Found txn %v spending %v in block %v (%d)", res.spend.txHash, - txHash, res.spend.blockHash, res.spend.blockHeight) - if res.blockHash != nil { + s.log.Tracef("Found txn %v spending %v in block %v (%d)", res.Spend.TxHash, + txHash, res.Spend.BlockHash, res.Spend.BlockHeight) + if res.BlockHash != nil { break search } // The output could still be in this block, just not @@ -426,16 +426,16 @@ search: } } // Only check for the output if this is the right transaction. - if res.blockHash != nil || tx.TxHash() != txHash { + if res.BlockHash != nil || tx.TxHash() != txHash { continue nextTx } for _, txOut := range tx.TxOut { if bytes.Equal(txOut.PkScript, pkScript) { - res.blockHash = blockHash - res.blockHeight = uint32(height) - res.txOut = txOut - s.log.Tracef("Found txn %v in block %v (%d)", txHash, res.blockHash, height) - if res.spend != nil { + res.BlockHash = blockHash + res.BlockHeight = uint32(height) + res.TxOut = txOut + s.log.Tracef("Found txn %v in block %v (%d)", txHash, res.BlockHash, height) + if res.Spend != nil { break search } // Keep looking for the spending transaction. diff --git a/client/asset/btc/spv_test.go b/client/asset/btc/spv_test.go index 01cf16d25a..d1aa670f76 100644 --- a/client/asset/btc/spv_test.go +++ b/client/asset/btc/spv_test.go @@ -298,22 +298,6 @@ func (c *tBtcWallet) WalletTransaction(txHash *chainhash.Hash) (*wtxmgr.TxDetail }, nil } -func (c *tBtcWallet) getTransaction(txHash *chainhash.Hash) (*GetTransactionResult, error) { - if c.getTransactionErr != nil { - return nil, c.getTransactionErr - } - var txData *GetTransactionResult - if c.getTransactionMap != nil { - if txData = c.getTransactionMap["any"]; txData == nil { - txData = c.getTransactionMap[txHash.String()] - } - } - if txData == nil { - return nil, WalletTransactionNotFound - } - return txData, nil -} - func (c *tBtcWallet) SyncedTo() waddrmgr.BlockStamp { bestHash, bestHeight := c.bestBlock() // NOTE: in reality this may be lower than the chain service's best block blk := c.getBlock(*bestHash) @@ -532,12 +516,14 @@ func TestSwapConfirmations(t *testing.T) { // DB path. node.dbBlockForTx[swapTxHash] = &hashEntry{hash: *swapBlockHash} node.dbBlockForTx[spendTxHash] = &hashEntry{hash: *spendBlockHash} - node.checkpoints[swapOutPt] = &scanCheckpoint{res: &filterScanResult{ - blockHash: swapBlockHash, - blockHeight: swapHeight, - spend: &spendingInput{}, - checkpoint: *spendBlockHash, - }} + node.checkpoints[swapOutPt] = &ScanCheckpoint{ + Res: &FilterScanResult{ + BlockHash: swapBlockHash, + BlockHeight: swapHeight, + Spend: &SpendingInput{}, + Checkpoint: *spendBlockHash, + }, + } checkSuccess("GetSpend", swapConfs, true) delete(node.checkpoints, swapOutPt) delete(node.dbBlockForTx, swapTxHash) @@ -580,9 +566,9 @@ func TestFindBlockForTime(t *testing.T) { matchTime := generateTestBlockTime(searchBlock) const offsetBlock = searchBlock - testBlocksPerBlockTimeOffset const startBlock = offsetBlock - medianTimeBlocks - height, err := spv.findBlockForTime(matchTime) + height, err := spv.FindBlockForTime(matchTime) if err != nil { - t.Fatalf("findBlockForTime error: %v", err) + t.Fatalf("FindBlockForTime error: %v", err) } if height != startBlock { t.Fatalf("wrong height. wanted %d, got %d", startBlock, height) @@ -592,27 +578,27 @@ func TestFindBlockForTime(t *testing.T) { // will continue down 11 more. _, blk := node.getBlockAtHeight(startBlock) blk.msgBlock.Header.Timestamp = generateTestBlockTime(offsetBlock) - height, err = spv.findBlockForTime(matchTime) + height, err = spv.FindBlockForTime(matchTime) if err != nil { - t.Fatalf("findBlockForTime error for shifted start block: %v", err) + t.Fatalf("FindBlockForTime error for shifted start block: %v", err) } if height != startBlock-medianTimeBlocks { t.Fatalf("wrong height. wanted %d, got %d", startBlock-11, height) } // And doing an early enough block just returns genesis - height, err = spv.findBlockForTime(generateTestBlockTime(10)) + height, err = spv.FindBlockForTime(generateTestBlockTime(10)) if err != nil { - t.Fatalf("findBlockForTime error for genesis test: %v", err) + t.Fatalf("FindBlockForTime error for genesis test: %v", err) } if height != 0 { t.Fatalf("not genesis: height = %d", height) } // A time way in the future still returns at least the last 11 blocks. - height, err = spv.findBlockForTime(generateTestBlockTime(100)) + height, err = spv.FindBlockForTime(generateTestBlockTime(100)) if err != nil { - t.Fatalf("findBlockForTime error for future test: %v", err) + t.Fatalf("FindBlockForTime error for future test: %v", err) } // +1 because tip block is included here, as opposed to the shifted start // block, where the shifted block wasn't included. @@ -670,11 +656,12 @@ func TestGetTxOut(t *testing.T) { // No wallet transaction, but we have a spend recorded. node.getTransactionErr = WalletTransactionNotFound node.getTransactionMap = nil - node.checkpoints[outPt] = &scanCheckpoint{res: &filterScanResult{ - blockHash: blockHash, - spend: &spendingInput{}, - checkpoint: *spendBlockHash, - }} + node.checkpoints[outPt] = &ScanCheckpoint{ + Res: &FilterScanResult{ + BlockHash: blockHash, + Spend: &SpendingInput{}, + Checkpoint: *spendBlockHash, + }} op, confs, err := spv.GetTxOut(&txHash, vout, pkScript, generateTestBlockTime(blockHeight)) if op != nil || confs != 0 || err != nil { t.Fatal("wrong result for spent txout", op != nil, confs, err) @@ -714,7 +701,7 @@ func TestGetTxOut(t *testing.T) { } // Make sure we can find it with the checkpoint. - node.checkpoints[outPt].res.spend = nil + node.checkpoints[outPt].Res.Spend = nil node.getCFilterScripts[*spendBlockHash] = nil // We won't actually scan for the output itself, so nil'ing these should // have no effect. diff --git a/client/asset/btc/spv_wrapper.go b/client/asset/btc/spv_wrapper.go index a202b3b131..519df56b97 100644 --- a/client/asset/btc/spv_wrapper.go +++ b/client/asset/btc/spv_wrapper.go @@ -28,7 +28,6 @@ import ( "math" "os" "path/filepath" - "sort" "sync" "sync/atomic" "time" @@ -307,6 +306,10 @@ func (w *spvWallet) SendRawTransaction(tx *wire.MsgTx) (*chainhash.Hash, error) return &txHash, nil } +func (w *spvWallet) GetBlockHeaderVerbose(blockHash *chainhash.Hash) (*wire.BlockHeader, error) { + return w.cl.GetBlockHeader(blockHash) +} + func (w *spvWallet) GetBlock(blockHash chainhash.Hash) (*wire.MsgBlock, error) { block, err := w.cl.GetBlock(blockHash) if err != nil { @@ -320,12 +323,6 @@ func (w *spvWallet) GetBlockHash(blockHeight int64) (*chainhash.Hash, error) { return w.cl.GetBlockHash(blockHeight) } -// getBlockHeight gets the mainchain height for the specified block. Returns -// error for orphaned blocks. -func (w *spvWallet) getBlockHeight(h *chainhash.Hash) (int32, error) { - return w.cl.GetBlockHeight(h) -} - func (w *spvWallet) GetBlockHeight(h *chainhash.Hash) (int32, error) { return w.cl.GetBlockHeight(h) } @@ -797,7 +794,7 @@ func (w *spvWallet) SwapConfirmations(txHash *chainhash.Hash, vout uint32, pkScr // status, but it will allow us to short circuit a longer scan if we already // know the output is spent. if blockHash == nil { - blockHash, _ = w.mainchainBlockForStoredTx(txHash) + blockHash, _ = w.MainchainBlockForStoredTx(txHash) } // Our last option is neutrino. @@ -810,7 +807,7 @@ func (w *spvWallet) SwapConfirmations(txHash *chainhash.Hash, vout uint32, pkScr return 0, false, err } - if utxo.spend == nil && utxo.blockHash == nil { + if utxo.Spend == nil && utxo.BlockHash == nil { if assumedMempool { w.log.Tracef("swapConfirmations - scanFilters did not find %v:%d, assuming in mempool.", txHash, vout) @@ -822,15 +819,15 @@ func (w *spvWallet) SwapConfirmations(txHash *chainhash.Hash, vout uint32, pkScr txHash, vout, startTime, pkScript) } - if utxo.blockHash != nil { + if utxo.BlockHash != nil { bestHeight, err := w.GetChainHeight() if err != nil { return 0, false, fmt.Errorf("getBestBlockHeight error: %v", err) } - confs = uint32(bestHeight) - utxo.blockHeight + 1 + confs = uint32(bestHeight) - utxo.BlockHeight + 1 } - if utxo.spend != nil { + if utxo.Spend != nil { // In the off-chance that a spend was found but not the output itself, // confs will be incorrect here. // In situations where we're looking for the counter-party's swap, we @@ -882,7 +879,7 @@ func (w *spvWallet) GetBlockHeader(blockHash *chainhash.Hash) (header *BlockHead } confirmations := int64(-1) - mainchain = w.blockIsMainchain(blockHash, blockHeight) + mainchain = w.BlockIsMainchain(blockHash, blockHeight) if mainchain { confirmations = int64(confirms(blockHeight, tip.Height)) } @@ -1087,87 +1084,6 @@ func (w *spvWallet) Fingerprint() (string, error) { return hex.EncodeToString(btcutil.Hash160(pk.SerializeCompressed())), nil } -// mainchainBlockForStoredTx gets the block hash and height for the transaction -// IFF an entry has been stored in the txBlocks index. -func (w *spvWallet) mainchainBlockForStoredTx(txHash *chainhash.Hash) (*chainhash.Hash, int32) { - // Check that the block is still mainchain. - blockHash, blockHeight, err := w.blockForStoredTx(txHash) - if err != nil { - w.log.Errorf("Error retrieving mainchain block height for hash %s", blockHash) - return nil, 0 - } - if blockHash == nil { - return nil, 0 - } - if !w.blockIsMainchain(blockHash, blockHeight) { - return nil, 0 - } - return blockHash, blockHeight -} - -// findBlockForTime locates a good start block so that a search beginning at the -// returned block has a very low likelihood of missing any blocks that have time -// > matchTime. This is done by performing a binary search (sort.Search) to find -// a block with a block time maxFutureBlockTime before matchTime. To ensure -// we also accommodate the median-block time rule and aren't missing anything -// due to out of sequence block times we use an unsophisticated algorithm of -// choosing the first block in an 11 block window with no times >= matchTime. -func (w *spvWallet) findBlockForTime(matchTime time.Time) (int32, error) { - offsetTime := matchTime.Add(-maxFutureBlockTime) - - bestHeight, err := w.GetChainHeight() - if err != nil { - return 0, fmt.Errorf("getChainHeight error: %v", err) - } - - getBlockTimeForHeight := func(height int32) (time.Time, error) { - hash, err := w.cl.GetBlockHash(int64(height)) - if err != nil { - return time.Time{}, err - } - header, err := w.cl.GetBlockHeader(hash) - if err != nil { - return time.Time{}, err - } - return header.Timestamp, nil - } - - iHeight := sort.Search(int(bestHeight), func(h int) bool { - var iTime time.Time - iTime, err = getBlockTimeForHeight(int32(h)) - if err != nil { - return true - } - return iTime.After(offsetTime) - }) - if err != nil { - return 0, fmt.Errorf("binary search error finding best block for time %q: %w", matchTime, err) - } - - // We're actually breaking an assumption of sort.Search here because block - // times aren't always monotonically increasing. This won't matter though as - // long as there are not > medianTimeBlocks blocks with inverted time order. - var count int - var iTime time.Time - for iHeight > 0 { - iTime, err = getBlockTimeForHeight(int32(iHeight)) - if err != nil { - return 0, fmt.Errorf("getBlockTimeForHeight error: %w", err) - } - if iTime.Before(offsetTime) { - count++ - if count == medianTimeBlocks { - return int32(iHeight), nil - } - } else { - count = 0 - } - iHeight-- - } - return 0, nil - -} - // GetTxOut finds an unspent transaction output and its number of confirmations. // To match the behavior of the RPC method, even if an output is found, if it's // known to be spent, no *wire.TxOut and no error will be returned. @@ -1217,7 +1133,7 @@ func (w *spvWallet) GetTxOut(txHash *chainhash.Hash, vout uint32, pkScript []byt return nil, 0, err } - if utxo == nil || utxo.spend != nil || utxo.blockHash == nil { + if utxo == nil || utxo.Spend != nil || utxo.BlockHash == nil { return nil, 0, nil } @@ -1226,14 +1142,14 @@ func (w *spvWallet) GetTxOut(txHash *chainhash.Hash, vout uint32, pkScript []byt return nil, 0, fmt.Errorf("BestBlock error: %v", err) } - confs := uint32(confirms(int32(utxo.blockHeight), tip.Height)) + confs := uint32(confirms(int32(utxo.BlockHeight), tip.Height)) - return utxo.txOut, confs, nil + return utxo.TxOut, confs, nil } // matchPkScript pulls the filter for the block and attempts to match the // supplied scripts. -func (w *spvWallet) matchPkScript(blockHash *chainhash.Hash, scripts [][]byte) (bool, error) { +func (w *spvWallet) MatchPkScript(blockHash *chainhash.Hash, scripts [][]byte) (bool, error) { filter, err := w.cl.GetCFilter(*blockHash, wire.GCSFilterRegular) if err != nil { return false, fmt.Errorf("GetCFilter error: %w", err) @@ -1266,7 +1182,7 @@ func (w *spvWallet) SearchBlockForRedemptions(ctx context.Context, reqs map[OutP discovered = make(map[OutPoint]*FindRedemptionResult, len(reqs)) - matchFound, err := w.matchPkScript(&blockHash, scripts) + matchFound, err := w.MatchPkScript(&blockHash, scripts) if err != nil { w.log.Errorf("matchPkScript error: %v", err) return From 1a85248c06c1637c46129465281a4b24298b6832 Mon Sep 17 00:00:00 2001 From: Philemon Ukane Date: Fri, 19 Jan 2024 00:39:30 +0100 Subject: [PATCH 3/6] export useful methods Signed-off-by: Philemon Ukane --- client/asset/btc/btc.go | 4 ++-- client/asset/btc/coin_selection.go | 4 ++-- client/asset/btc/coinmanager.go | 4 ++-- client/asset/btc/redemption_finder.go | 6 +++++- client/asset/btc/rpcclient.go | 4 ++-- client/asset/btc/spv_wrapper.go | 6 +++--- 6 files changed, 16 insertions(+), 12 deletions(-) diff --git a/client/asset/btc/btc.go b/client/asset/btc/btc.go index 09904a01c0..5113ac1c13 100644 --- a/client/asset/btc/btc.go +++ b/client/asset/btc/btc.go @@ -4549,7 +4549,7 @@ func (btc *baseWallet) send(address string, val uint64, feeRate uint64, subtract baseSize += dexbtc.P2PKHOutputSize * 2 } - enough := sendEnough(val, feeRate, subtract, uint64(baseSize), btc.segwit, true) + enough := SendEnough(val, feeRate, subtract, uint64(baseSize), btc.segwit, true) minConfs := uint32(0) coins, _, _, _, inputsSize, _, err := btc.cm.Fund(btc.bondReserves.Load(), minConfs, false, enough) if err != nil { @@ -5135,7 +5135,7 @@ func (btc *baseWallet) MakeBondTx(ver uint16, amt, feeRate uint64, lockTime time } const subtract = false - coins, _, _, _, _, _, err := btc.cm.Fund(0, 0, true, sendEnough(amt, feeRate, subtract, uint64(baseSize), btc.segwit, true)) + coins, _, _, _, _, _, err := btc.cm.Fund(0, 0, true, SendEnough(amt, feeRate, subtract, uint64(baseSize), btc.segwit, true)) if err != nil { return nil, nil, fmt.Errorf("failed to fund bond tx: %w", err) } diff --git a/client/asset/btc/coin_selection.go b/client/asset/btc/coin_selection.go index ef7da03921..e692859f1c 100644 --- a/client/asset/btc/coin_selection.go +++ b/client/asset/btc/coin_selection.go @@ -13,7 +13,7 @@ import ( dexbtc "decred.org/dcrdex/dex/networks/btc" ) -// sendEnough generates a function that can be used as the enough argument to +// SendEnough generates a function that can be used as the enough argument to // the fund method when creating transactions to send funds. If fees are to be // subtracted from the inputs, set subtract so that the required amount excludes // the transaction fee. If change from the transaction should be considered @@ -21,7 +21,7 @@ import ( // enough func will return a non-zero excess value. Otherwise, the enough func // will always return 0, leaving only unselected UTXOs to cover any required // reserves. -func sendEnough(amt, feeRate uint64, subtract bool, baseTxSize uint64, segwit, reportChange bool) EnoughFunc { +func SendEnough(amt, feeRate uint64, subtract bool, baseTxSize uint64, segwit, reportChange bool) EnoughFunc { return func(_, inputSize, sum uint64) (bool, uint64) { txFee := (baseTxSize + inputSize) * feeRate req := amt diff --git a/client/asset/btc/coinmanager.go b/client/asset/btc/coinmanager.go index 43d9e6e106..36dd5375f4 100644 --- a/client/asset/btc/coinmanager.go +++ b/client/asset/btc/coinmanager.go @@ -343,7 +343,7 @@ func (c *CoinManager) spendableUTXOs(confs uint32) ([]*CompositeUTXO, map[OutPoi return nil, nil, 0, err } - utxos, utxoMap, sum, err := convertUnspent(confs, unspents, c.chainParams) + utxos, utxoMap, sum, err := ConvertUnspent(confs, unspents, c.chainParams) if err != nil { return nil, nil, 0, err } @@ -564,7 +564,7 @@ func (c *CoinManager) LockedOutput(pt OutPoint) *UTxO { return c.lockedOutputs[pt] } -func convertUnspent(confs uint32, unspents []*ListUnspentResult, chainParams *chaincfg.Params) ([]*CompositeUTXO, map[OutPoint]*CompositeUTXO, uint64, error) { +func ConvertUnspent(confs uint32, unspents []*ListUnspentResult, chainParams *chaincfg.Params) ([]*CompositeUTXO, map[OutPoint]*CompositeUTXO, uint64, error) { sort.Slice(unspents, func(i, j int) bool { return unspents[i].Amount < unspents[j].Amount }) var sum uint64 utxos := make([]*CompositeUTXO, 0, len(unspents)) diff --git a/client/asset/btc/redemption_finder.go b/client/asset/btc/redemption_finder.go index f8fa5394b0..df55903390 100644 --- a/client/asset/btc/redemption_finder.go +++ b/client/asset/btc/redemption_finder.go @@ -43,6 +43,10 @@ func (req *FindRedemptionReq) sendResult(res *FindRedemptionResult) { } } +func (req *FindRedemptionReq) PkScript() []byte { + return req.pkScript +} + // FindRedemptionResult models the result of a find redemption attempt. type FindRedemptionResult struct { redemptionCoinID dex.Bytes @@ -505,7 +509,7 @@ func (r *RedemptionFinder) CancelRedemptionSearches() { r.mtx.Unlock() } -func findRedemptionsInTxWithHasher(ctx context.Context, segwit bool, reqs map[OutPoint]*FindRedemptionReq, msgTx *wire.MsgTx, +func FindRedemptionsInTxWithHasher(ctx context.Context, segwit bool, reqs map[OutPoint]*FindRedemptionReq, msgTx *wire.MsgTx, chainParams *chaincfg.Params, hashTx func(*wire.MsgTx) *chainhash.Hash) (discovered map[OutPoint]*FindRedemptionResult) { discovered = make(map[OutPoint]*FindRedemptionResult, len(reqs)) diff --git a/client/asset/btc/rpcclient.go b/client/asset/btc/rpcclient.go index eb23886efe..29368d6f33 100644 --- a/client/asset/btc/rpcclient.go +++ b/client/asset/btc/rpcclient.go @@ -1141,7 +1141,7 @@ func FindRedemptionsInMempool( logAbandon(fmt.Sprintf("getrawtransaction error for tx hash %v: %v", txHash, err)) return } - newlyDiscovered := findRedemptionsInTxWithHasher(ctx, segwit, reqs, tx, chainParams, hashTx) + newlyDiscovered := FindRedemptionsInTxWithHasher(ctx, segwit, reqs, tx, chainParams, hashTx) for outPt, res := range newlyDiscovered { discovered[outPt] = res } @@ -1173,7 +1173,7 @@ func SearchBlockForRedemptions( discovered = make(map[OutPoint]*FindRedemptionResult, len(reqs)) for _, msgTx := range msgBlock.Transactions { - newlyDiscovered := findRedemptionsInTxWithHasher(ctx, segwit, reqs, msgTx, chainParams, hashTx) + newlyDiscovered := FindRedemptionsInTxWithHasher(ctx, segwit, reqs, msgTx, chainParams, hashTx) for outPt, res := range newlyDiscovered { discovered[outPt] = res } diff --git a/client/asset/btc/spv_wrapper.go b/client/asset/btc/spv_wrapper.go index 519df56b97..ebe61e80a6 100644 --- a/client/asset/btc/spv_wrapper.go +++ b/client/asset/btc/spv_wrapper.go @@ -708,12 +708,12 @@ func (w *spvWallet) EstimateSendTxFee(tx *wire.MsgTx, feeRate uint64, subtract b return 0, fmt.Errorf("error listing unspent outputs: %w", err) } - utxos, _, _, err := convertUnspent(0, unspents, w.chainParams) + utxos, _, _, err := ConvertUnspent(0, unspents, w.chainParams) if err != nil { return 0, fmt.Errorf("error converting unspent outputs: %w", err) } - enough := sendEnough(sendAmount, feeRate, subtract, minTxSize, true, false) + enough := SendEnough(sendAmount, feeRate, subtract, minTxSize, true, false) sum, _, inputsSize, _, _, _, _, err := TryFund(utxos, enough) if err != nil { return 0, err @@ -1200,7 +1200,7 @@ func (w *spvWallet) SearchBlockForRedemptions(ctx context.Context, reqs map[OutP } for _, msgTx := range block.MsgBlock().Transactions { - newlyDiscovered := findRedemptionsInTxWithHasher(ctx, true, reqs, msgTx, w.chainParams, hashTx) + newlyDiscovered := FindRedemptionsInTxWithHasher(ctx, true, reqs, msgTx, w.chainParams, hashTx) for outPt, res := range newlyDiscovered { discovered[outPt] = res } From 06914b249e64c8ebab70e9628a5f93f41f4655d9 Mon Sep 17 00:00:00 2001 From: Philemon Ukane Date: Fri, 19 Jan 2024 18:33:47 +0100 Subject: [PATCH 4/6] fix comments and remove redundant method Signed-off-by: Philemon Ukane --- client/asset/btc/btc.go | 12 +++++------- client/asset/ltc/ltc.go | 12 +++++------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/client/asset/btc/btc.go b/client/asset/btc/btc.go index 5113ac1c13..860ee30011 100644 --- a/client/asset/btc/btc.go +++ b/client/asset/btc/btc.go @@ -642,14 +642,12 @@ type CustomWallet interface { type CustomWalletConstructor func(settings map[string]string, params *chaincfg.Params) (CustomWallet, error) // customWalletConstructors are functions for setting up CustomWallet -// implementations that may be used by the ExchangeWalletCustom instead of the -// default spv implementation. +// implementations used by ExchangeWalletCustom. var customWalletConstructors = map[string]CustomWalletConstructor{} // RegisterCustomWallet registers a function that should be used in creating a -// CustomWallet implementation that ExchangeWalletCustom will use in place of -// the default spv wallet implementation. External consumers can use this -// function to provide alternative CustomWallet implementations, and must do so +// CustomWallet implementation for ExchangeWalletCustom. External consumers can +// use this function to provide CustomWallet implementation, and must do so // before attempting to create an ExchangeWalletCustom instance of this type. // It'll panic if callers try to register a wallet twice. func RegisterCustomWallet(constructor CustomWalletConstructor, def *asset.WalletDefinition) { @@ -1372,8 +1370,8 @@ func OpenCustomWallet(cfg *BTCCloneCFG, walletConstructor CustomWalletConstructo return nil, err } - // SPV wallets without a FeeEstimator will default to any enabled external - // fee estimator. + // Custom wallets without without a FeeEstimator will default to any enabled + // external fee estimator. if cfg.FeeEstimator == nil { cfg.FeeEstimator = noLocalFeeRate } diff --git a/client/asset/ltc/ltc.go b/client/asset/ltc/ltc.go index dca32d226d..f964bcde9d 100644 --- a/client/asset/ltc/ltc.go +++ b/client/asset/ltc/ltc.go @@ -171,16 +171,14 @@ func (d *Driver) Create(params *asset.CreateWalletParams) error { } // customWalletConstructors are functions for setting up btc.CustomWallet -// implementations that may be used by the btc.ExchangeWalletCustom instead of -// the default spv implementation. +// implementations used by btc.ExchangeWalletCustom. var customWalletConstructors = map[string]btc.CustomWalletConstructor{} // RegisterCustomWallet registers a function that should be used in creating a -// btc.CustomWallet implementation that btc.ExchangeWalletCustom will use in -// place of the default spv wallet implementation. External consumers can use -// this function to provide alternative btc.CustomWallet implementations, and -// must do so before attempting to create an btc.ExchangeWalletCustom instance -// of this type. It'll panic if callers try to register a wallet twice. +// btc.CustomWallet implementation for btc.ExchangeWalletCustom. External +// consumers can use this function to provide btc.CustomWallet implementation, +// and must do so before attempting to create an btc.ExchangeWalletCustom +// instance of this type. It'll panic if callers try to register a wallet twice. func RegisterCustomWallet(constructor btc.CustomWalletConstructor, def *asset.WalletDefinition) { for _, availableWallets := range WalletInfo.AvailableWallets { if def.Type == availableWallets.Type { From 0a1a7a899aa582321644e2965fa340be07079ddd Mon Sep 17 00:00:00 2001 From: Philemon Ukane Date: Fri, 19 Jan 2024 18:50:40 +0100 Subject: [PATCH 5/6] revert AccountInfo changes in https://github.com/decred/dcrdex/pull/2636/commits/d7a54886f062d0916f1f4da64c85f9470246b0bf Signed-off-by: Philemon Ukane --- client/asset/bch/spv.go | 18 ++++-------------- client/asset/btc/btc.go | 5 ++--- client/asset/btc/spv.go | 9 --------- client/asset/btc/spv_test.go | 9 +-------- client/asset/btc/spv_wrapper.go | 16 ++++------------ client/asset/ltc/spv.go | 20 +++++--------------- 6 files changed, 16 insertions(+), 61 deletions(-) diff --git a/client/asset/bch/spv.go b/client/asset/bch/spv.go index fc88fe2fa5..a61a5199d5 100644 --- a/client/asset/bch/spv.go +++ b/client/asset/bch/spv.go @@ -51,11 +51,10 @@ import ( ) const ( - DefaultM uint64 = 784931 // From bchutil. Used for gcs filters. - logDirName = "logs" - neutrinoDBName = "neutrino.db" - defaultAcctNum = 0 - defaultAcctName = "default" + DefaultM uint64 = 784931 // From bchutil. Used for gcs filters. + logDirName = "logs" + neutrinoDBName = "neutrino.db" + defaultAcctNum = 0 ) var ( @@ -248,15 +247,6 @@ func (w *bchSPVWallet) txDetails(txHash *bchchainhash.Hash) (*bchwtxmgr.TxDetail var _ btc.BTCWallet = (*bchSPVWallet)(nil) -// AccountInfo returns the account information of the wallet for use by the -// exchange wallet. -func (w *bchSPVWallet) AccountInfo() btc.XCWalletAccount { - return btc.XCWalletAccount{ - AccountName: defaultAcctName, - AccountNumber: defaultAcctNum, - } -} - func (w *bchSPVWallet) PublishTransaction(btcTx *wire.MsgTx, label string) error { bchTx, err := convertMsgTxToBCH(btcTx) if err != nil { diff --git a/client/asset/btc/btc.go b/client/asset/btc/btc.go index 860ee30011..47491a04b5 100644 --- a/client/asset/btc/btc.go +++ b/client/asset/btc/btc.go @@ -1421,6 +1421,8 @@ func OpenSPVWallet(cfg *BTCCloneCFG, walletConstructor BTCWalletConstructor) (*E spvw := &spvWallet{ chainParams: cfg.ChainParams, cfg: walletCfg, + acctNum: defaultAcctNum, + acctName: defaultAcctName, dir: filepath.Join(cfg.WalletCFG.DataDir, cfg.ChainParams.Name), log: cfg.Logger.SubLogger("SPV"), tipChan: make(chan *BlockVector, 8), @@ -1431,9 +1433,6 @@ func OpenSPVWallet(cfg *BTCCloneCFG, walletConstructor BTCWalletConstructor) (*E spvw.wallet = walletConstructor(spvw.dir, spvw.cfg, spvw.chainParams, spvw.log) btc.setNode(spvw) - // Set account number for easy reference. - spvw.acctNum = spvw.wallet.AccountInfo().AccountNumber - w := &ExchangeWalletSPV{ intermediaryWallet: &intermediaryWallet{ baseWallet: btc, diff --git a/client/asset/btc/spv.go b/client/asset/btc/spv.go index a6e7ae43e0..70d110da3e 100644 --- a/client/asset/btc/spv.go +++ b/client/asset/btc/spv.go @@ -114,15 +114,6 @@ func openSPVWallet(dir string, cfg *WalletConfig, return w } -// AccountInfo returns the account information of the wallet for use by the -// exchange wallet. -func (w *btcSPVWallet) AccountInfo() XCWalletAccount { - return XCWalletAccount{ - AccountName: defaultAcctName, - AccountNumber: defaultAcctNum, - } -} - func (w *btcSPVWallet) Birthday() time.Time { return w.Manager.Birthday() } diff --git a/client/asset/btc/spv_test.go b/client/asset/btc/spv_test.go index d1aa670f76..bb9a4deb21 100644 --- a/client/asset/btc/spv_test.go +++ b/client/asset/btc/spv_test.go @@ -39,14 +39,7 @@ type tBtcWallet struct { *testData } -func (c *tBtcWallet) AccountInfo() XCWalletAccount { - return XCWalletAccount{ - AccountName: defaultAcctName, - AccountNumber: defaultAcctNum, - } -} - -func (c *tBtcWallet) GetTransactions(startBlock, endBlock int32, accountName string, cancel <-chan struct{}) (*wallet.GetTransactionsResult, error) { +func (c *tBtcWallet) ListSinceBlock(start, end, syncHeight int32) ([]btcjson.ListTransactionsResult, error) { return nil, nil } diff --git a/client/asset/btc/spv_wrapper.go b/client/asset/btc/spv_wrapper.go index ebe61e80a6..d4d52fd7e8 100644 --- a/client/asset/btc/spv_wrapper.go +++ b/client/asset/btc/spv_wrapper.go @@ -100,9 +100,6 @@ type BTCWallet interface { WaitForShutdown() ChainSynced() bool // currently unused AccountProperties(scope waddrmgr.KeyScope, acct uint32) (*waddrmgr.AccountProperties, error) - // AccountInfo returns the account information of the wallet for use by the - // exchange wallet. - AccountInfo() XCWalletAccount // The below methods are not implemented by *wallet.Wallet, so must be // implemented by the BTCWallet implementation. WalletTransaction(txHash *chainhash.Hash) (*wtxmgr.TxDetails, error) @@ -120,11 +117,6 @@ type BTCWallet interface { GetTransactions(startHeight, endHeight int32, accountName string, cancel <-chan struct{}) (*wallet.GetTransactionsResult, error) } -type XCWalletAccount struct { - AccountName string - AccountNumber uint32 -} - // BlockNotification is block hash and height delivered by a BTCWallet when it // is finished processing a block. type BlockNotification struct { @@ -211,6 +203,7 @@ type spvWallet struct { wallet BTCWallet cl SPVService acctNum uint32 + acctName string dir string decodeAddr dexbtc.AddressDecoder @@ -499,7 +492,6 @@ func (w *spvWallet) ownsInputs(txid string) bool { } func (w *spvWallet) ListTransactionsSinceBlock(blockHeight int32) ([]*ListTransactionsResult, error) { - acctName := w.wallet.AccountInfo().AccountName tip, err := w.cl.BestBlock() if err != nil { return nil, fmt.Errorf("BestBlock error: %v", err) @@ -508,7 +500,7 @@ func (w *spvWallet) ListTransactionsSinceBlock(blockHeight int32) ([]*ListTransa // We use GetTransactions instead of ListSinceBlock, because ListSinceBlock // does not include transactions that pay to a change address, which // Redeem, Refund, and RedeemBond do. - res, err := w.wallet.GetTransactions(blockHeight, tip.Height, acctName, nil) + res, err := w.wallet.GetTransactions(blockHeight, tip.Height, w.acctName, nil) if err != nil { return nil, err } @@ -542,7 +534,7 @@ func (w *spvWallet) ListTransactionsSinceBlock(blockHeight int32) ([]*ListTransa // balances retrieves a wallet's balance details. func (w *spvWallet) Balances() (*GetBalancesResult, error) { // Determine trusted vs untrusted coins with listunspent. - unspents, err := w.wallet.ListUnspent(0, math.MaxInt32, w.wallet.AccountInfo().AccountName) + unspents, err := w.wallet.ListUnspent(0, math.MaxInt32, w.acctName) if err != nil { return nil, fmt.Errorf("error listing unspent outputs: %w", err) } @@ -579,7 +571,7 @@ func (w *spvWallet) Balances() (*GetBalancesResult, error) { // ListUnspent retrieves list of the wallet's UTXOs. func (w *spvWallet) ListUnspent() ([]*ListUnspentResult, error) { - unspents, err := w.wallet.ListUnspent(0, math.MaxInt32, w.wallet.AccountInfo().AccountName) + unspents, err := w.wallet.ListUnspent(0, math.MaxInt32, w.acctName) if err != nil { return nil, err } diff --git a/client/asset/ltc/spv.go b/client/asset/ltc/spv.go index 7ff09ee85c..c084f7f728 100644 --- a/client/asset/ltc/spv.go +++ b/client/asset/ltc/spv.go @@ -47,12 +47,11 @@ import ( ) const ( - DefaultM uint64 = 784931 // From ltcutil. Used for gcs filters. - logDirName = "logs" - neutrinoDBName = "neutrino.db" - defaultAcctNum = 0 - defaultAcctName = "default" - dbTimeout = 20 * time.Second + DefaultM uint64 = 784931 // From ltcutil. Used for gcs filters. + logDirName = "logs" + neutrinoDBName = "neutrino.db" + defaultAcctNum = 0 + dbTimeout = 20 * time.Second ) var ( @@ -160,15 +159,6 @@ func createSPVWallet(privPass []byte, seed []byte, bday time.Time, walletDir str return nil } -// AccountInfo returns the account information of the wallet for use by the -// exchange wallet. -func (w *ltcSPVWallet) AccountInfo() btc.XCWalletAccount { - return btc.XCWalletAccount{ - AccountName: defaultAcctName, - AccountNumber: defaultAcctNum, - } -} - // walletParams works around a bug in ltcwallet that doesn't recognize // wire.TestNet4 in (*ScopedKeyManager).cloneKeyWithVersion which is called from // AccountProperties. Only do this for the *wallet.Wallet, not the From 912997266ecfddbe7247c29d71769008582dc5a3 Mon Sep 17 00:00:00 2001 From: Philemon Ukane Date: Fri, 19 Jan 2024 19:05:14 +0100 Subject: [PATCH 6/6] export dcr wallet options Signed-off-by: Philemon Ukane --- client/asset/btc/btc.go | 2 +- client/asset/dcr/dcr.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/asset/btc/btc.go b/client/asset/btc/btc.go index 47491a04b5..dbbfb3e0fc 100644 --- a/client/asset/btc/btc.go +++ b/client/asset/btc/btc.go @@ -1370,7 +1370,7 @@ func OpenCustomWallet(cfg *BTCCloneCFG, walletConstructor CustomWalletConstructo return nil, err } - // Custom wallets without without a FeeEstimator will default to any enabled + // Custom wallets without a FeeEstimator will default to any enabled // external fee estimator. if cfg.FeeEstimator == nil { cfg.FeeEstimator = noLocalFeeRate diff --git a/client/asset/dcr/dcr.go b/client/asset/dcr/dcr.go index 87481252a9..6e5974aeeb 100644 --- a/client/asset/dcr/dcr.go +++ b/client/asset/dcr/dcr.go @@ -126,7 +126,7 @@ var ( // be tried over and over with wallet in SPV mode. maxRedeemMempoolAge = time.Hour * 2 - walletOpts = []*asset.ConfigOption{ + WalletOpts = []*asset.ConfigOption{ { Key: "fallbackfee", DisplayName: "Fallback fee rate", @@ -276,7 +276,7 @@ var ( Type: walletTypeSPV, Tab: "Native", Description: "Use the built-in SPV wallet", - ConfigOpts: walletOpts, + ConfigOpts: WalletOpts, Seeded: true, MultiFundingOpts: multiFundingOpts, }, @@ -285,7 +285,7 @@ var ( Tab: "External", Description: "Connect to dcrwallet", DefaultConfigPath: defaultConfigPath, - ConfigOpts: append(rpcOpts, walletOpts...), + ConfigOpts: append(rpcOpts, WalletOpts...), MultiFundingOpts: multiFundingOpts, }, },