decred.org/dcrdex@v1.0.3/client/asset/btc/wallet.go (about)

     1  package btc
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sort"
     7  	"sync"
     8  	"time"
     9  
    10  	"decred.org/dcrdex/client/asset"
    11  	"github.com/btcsuite/btcd/btcec/v2"
    12  	"github.com/btcsuite/btcd/btcutil"
    13  	"github.com/btcsuite/btcd/chaincfg/chainhash"
    14  	"github.com/btcsuite/btcd/wire"
    15  )
    16  
    17  // Wallet is the interface that BTC wallet backends must implement. TODO: plumb
    18  // all requests with a context.Context.
    19  type Wallet interface {
    20  	RawRequester // for localFeeRate/rpcFeeRate calls
    21  	connect(ctx context.Context, wg *sync.WaitGroup) error
    22  	sendRawTransaction(tx *wire.MsgTx) (*chainhash.Hash, error)
    23  	getTxOut(txHash *chainhash.Hash, index uint32, pkScript []byte, startTime time.Time) (*wire.TxOut, uint32, error)
    24  	getBlockHash(blockHeight int64) (*chainhash.Hash, error)
    25  	getBestBlockHash() (*chainhash.Hash, error)
    26  	getBestBlockHeight() (int32, error)
    27  	medianTime() (time.Time, error)
    28  	balances() (*GetBalancesResult, error)
    29  	listUnspent() ([]*ListUnspentResult, error) // must not return locked coins
    30  	lockUnspent(unlock bool, ops []*Output) error
    31  	listLockUnspent() ([]*RPCOutpoint, error)
    32  	changeAddress() (btcutil.Address, error) // warning: don't just use the Stringer if there's a "recode" function for a clone e.g. BCH
    33  	externalAddress() (btcutil.Address, error)
    34  	signTx(inTx *wire.MsgTx) (*wire.MsgTx, error)
    35  	privKeyForAddress(addr string) (*btcec.PrivateKey, error)
    36  	walletUnlock(pw []byte) error
    37  	walletLock() error
    38  	locked() bool
    39  	syncStatus() (*asset.SyncStatus, error)
    40  	peerCount() (uint32, error)
    41  	swapConfirmations(txHash *chainhash.Hash, vout uint32, contract []byte, startTime time.Time) (confs uint32, spent bool, err error)
    42  	getBestBlockHeader() (*BlockHeader, error)
    43  	ownsAddress(addr btcutil.Address) (bool, error) // this should probably just take a string
    44  	getWalletTransaction(txHash *chainhash.Hash) (*GetTransactionResult, error)
    45  	reconfigure(walletCfg *asset.WalletConfig, currentAddress string) (restartRequired bool, err error)
    46  	fingerprint() (string, error)
    47  	listTransactionsSinceBlock(blockHeight int32) ([]*ListTransactionsResult, error)
    48  }
    49  
    50  type tipRedemptionWallet interface {
    51  	Wallet
    52  	getBlockHeight(*chainhash.Hash) (int32, error)
    53  	getBlockHeader(blockHash *chainhash.Hash) (hdr *BlockHeader, mainchain bool, err error)
    54  	getBlock(h chainhash.Hash) (*wire.MsgBlock, error)
    55  	searchBlockForRedemptions(ctx context.Context, reqs map[OutPoint]*FindRedemptionReq, blockHash chainhash.Hash) (discovered map[OutPoint]*FindRedemptionResult)
    56  	findRedemptionsInMempool(ctx context.Context, reqs map[OutPoint]*FindRedemptionReq) (discovered map[OutPoint]*FindRedemptionResult)
    57  }
    58  
    59  type txFeeEstimator interface {
    60  	estimateSendTxFee(tx *wire.MsgTx, feeRate uint64, subtract bool) (fee uint64, err error)
    61  }
    62  
    63  // walletTxChecker provide a fast wallet tx query when block info not needed.
    64  // This should be treated as an optimization method, where getWalletTransaction
    65  // may always be used in its place if it does not exists.
    66  type walletTxChecker interface {
    67  	checkWalletTx(txid string) ([]byte, uint32, error)
    68  }
    69  
    70  // tipNotifier can be implemented if the Wallet is able to provide a stream of
    71  // blocks as they are finished being processed.
    72  type tipNotifier interface {
    73  	tipFeed() <-chan *BlockVector
    74  }
    75  
    76  const medianTimeBlocks = 11
    77  
    78  // chainStamper is a source of the timestamp and the previous block hash for a
    79  // specified block. A chainStamper is used to manually calculate the median time
    80  // for a block.
    81  type chainStamper func(*chainhash.Hash) (stamp time.Time, prevHash *chainhash.Hash, err error)
    82  
    83  // CalcMedianTime calculates the median time of the previous 11 block headers.
    84  // The median time is used for validating time-locked transactions. See notes in
    85  // btcd/blockchain (*blockNode).CalcPastMedianTime() regarding incorrectly
    86  // calculated median time for blocks 1, 3, 5, 7, and 9.
    87  func CalcMedianTime(stamper chainStamper, blockHash *chainhash.Hash) (time.Time, error) {
    88  	timestamps := make([]int64, 0, medianTimeBlocks)
    89  
    90  	zeroHash := chainhash.Hash{}
    91  
    92  	h := blockHash
    93  	for i := 0; i < medianTimeBlocks; i++ {
    94  		stamp, prevHash, err := stamper(h)
    95  		if err != nil {
    96  			return time.Time{}, fmt.Errorf("BlockHeader error for hash %q: %v", h, err)
    97  		}
    98  		timestamps = append(timestamps, stamp.Unix())
    99  
   100  		if *prevHash == zeroHash {
   101  			break
   102  		}
   103  		h = prevHash
   104  	}
   105  
   106  	sort.Slice(timestamps, func(i, j int) bool {
   107  		return timestamps[i] < timestamps[j]
   108  	})
   109  
   110  	medianTimestamp := timestamps[len(timestamps)/2]
   111  	return time.Unix(medianTimestamp, 0), nil
   112  }