decred.org/dcrdex@v1.0.5/server/asset/btc/utxo.go (about) 1 // This code is available on the terms of the project LICENSE.md file, 2 // also available online at https://blueoakcouncil.org/license/1.0.0. 3 4 package btc 5 6 import ( 7 "bytes" 8 "context" 9 "errors" 10 "fmt" 11 12 "decred.org/dcrdex/dex" 13 dexbtc "decred.org/dcrdex/dex/networks/btc" 14 "decred.org/dcrdex/server/asset" 15 "github.com/btcsuite/btcd/btcutil" 16 "github.com/btcsuite/btcd/chaincfg/chainhash" 17 ) 18 19 const ErrReorgDetected = dex.ErrorKind("reorg detected") 20 21 // TXIO is common information stored with an Input or Output. 22 type TXIO struct { 23 // Because a TXIO's validity and block info can change after creation, keep a 24 // Backend around to query the state of the tx and update the block info. 25 btc *Backend 26 tx *Tx 27 // The height and hash of the transaction's best known block. 28 height uint32 29 blockHash chainhash.Hash 30 // The number of confirmations needed for maturity. For outputs of coinbase 31 // transactions, this will be set to chaincfg.Params.CoinbaseMaturity (256 for 32 // mainchain). For other supported script types, this will be zero. 33 maturity int32 34 // While the TXIO's tx is still in mempool, the tip hash will be stored. 35 // This enables an optimization in the Confirmations method to return zero 36 // without extraneous RPC calls. 37 lastLookup *chainhash.Hash 38 } 39 40 // confirmations returns the number of confirmations for a TXIO's transaction. 41 // Because a tx can become invalid after once being considered valid, validity 42 // should be verified again on every call. An error will be returned if this 43 // TXIO is no longer ready to spend. An unmined transaction should have zero 44 // confirmations. A transaction in the current best block should have one 45 // confirmation. The value -1 will be returned with any error. 46 func (txio *TXIO) confirmations() (int64, error) { 47 btc := txio.btc 48 tipHash := btc.blockCache.tipHash() 49 // If the tx was a mempool transaction, check if it has been confirmed. 50 if txio.height == 0 { 51 // If the tip hasn't changed, don't do anything here. 52 if txio.lastLookup == nil || *txio.lastLookup != tipHash { 53 txio.lastLookup = &tipHash 54 verboseTx, err := txio.btc.node.GetRawTransactionVerbose(&txio.tx.hash) 55 if err != nil { 56 return -1, fmt.Errorf("GetRawTransactionVerbose for txid %s: %w", txio.tx.hash, err) 57 } 58 // More than zero confirmations would indicate that the transaction has 59 // been mined. Collect the block info and update the tx fields. 60 if verboseTx.Confirmations > 0 { 61 blk, err := txio.btc.getBlockInfo(verboseTx.BlockHash) 62 if err != nil { 63 return -1, err 64 } 65 txio.height = blk.height 66 txio.blockHash = blk.hash 67 } 68 return int64(verboseTx.Confirmations), nil 69 } 70 } else { 71 // The tx was included in a block, but make sure that the tx's block has 72 // not been orphaned. 73 mainchainBlock, found := btc.blockCache.atHeight(txio.height) 74 if !found || mainchainBlock.hash != txio.blockHash { 75 return -1, ErrReorgDetected 76 } 77 } 78 // If the height is still 0, this is a mempool transaction. 79 if txio.height == 0 { 80 return 0, nil 81 } 82 // Otherwise just check that there hasn't been a reorg which would render the 83 // output immature. This would be exceedingly rare (impossible?). 84 confs := int32(btc.blockCache.tipHeight()) - int32(txio.height) + 1 85 if confs < txio.maturity { 86 return -1, fmt.Errorf("transaction %s became immature", txio.tx.hash) 87 } 88 return int64(confs), nil 89 } 90 91 // TxID is a string identifier for the transaction, typically a hexadecimal 92 // representation of the byte-reversed transaction hash. 93 func (txio *TXIO) TxID() string { 94 return txio.tx.hash.String() 95 } 96 97 // FeeRate returns the transaction fee rate, in satoshi/vbyte. 98 func (txio *TXIO) FeeRate() uint64 { 99 return txio.tx.feeRate 100 } 101 102 func (txio *TXIO) InputsValue() uint64 { 103 return txio.tx.inputSum 104 } 105 106 func (txio *TXIO) RawTx() []byte { 107 return txio.tx.raw 108 } 109 110 // Input is a transaction input. 111 type Input struct { 112 TXIO 113 vin uint32 114 } 115 116 var _ asset.Coin = (*Input)(nil) 117 118 // Value is the value of the previous output spent by the input. 119 func (input *Input) Value() uint64 { 120 return input.TXIO.tx.ins[input.vin].value 121 } 122 123 // String creates a human-readable representation of a Bitcoin transaction input 124 // in the format "{txid = [transaction hash], vin = [input index]}". 125 func (input *Input) String() string { 126 return fmt.Sprintf("{txid = %s, vin = %d}", input.TxID(), input.vin) 127 } 128 129 // Confirmations returns the number of confirmations on this input's 130 // transaction. 131 func (input *Input) Confirmations(context.Context) (int64, error) { 132 confs, err := input.confirmations() 133 if errors.Is(err, ErrReorgDetected) { 134 newInput, err := input.btc.input(&input.tx.hash, input.vin) 135 if err != nil { 136 return -1, fmt.Errorf("input block is not mainchain") 137 } 138 *input = *newInput 139 return input.Confirmations(context.Background()) 140 } 141 return confs, err 142 } 143 144 // ID returns the coin ID. 145 func (input *Input) ID() []byte { 146 return toCoinID(&input.tx.hash, input.vin) 147 } 148 149 // spendsCoin checks whether a particular coin is spent in this coin's tx. 150 func (input *Input) spendsCoin(coinID []byte) (bool, error) { 151 txHash, vout, err := decodeCoinID(coinID) 152 if err != nil { 153 return false, fmt.Errorf("error decoding coin ID %x: %w", coinID, err) 154 } 155 if uint32(len(input.tx.ins)) < input.vin+1 { 156 return false, nil 157 } 158 txIn := input.tx.ins[input.vin] 159 return txIn.prevTx == *txHash && txIn.vout == vout, nil 160 } 161 162 // Output represents a transaction output. 163 type Output struct { 164 TXIO 165 vout uint32 166 // The output value. 167 value uint64 168 addresses []string 169 // A bitmask for script type information. 170 scriptType dexbtc.BTCScriptType 171 // If the pkScript, or redeemScript in the case of a P2SH/P2WSH pkScript, is 172 // non-standard according to txscript. 173 nonStandardScript bool 174 // The output's scriptPubkey. 175 pkScript []byte 176 // If the pubkey script is P2SH or P2WSH, the Output will only be generated 177 // if the redeem script is supplied and the script-hash validated. For P2PKH 178 // and P2WPKH pubkey scripts, the redeem script should be nil. 179 redeemScript []byte 180 // numSigs is the number of signatures required to spend this output. 181 numSigs int 182 // spendSize stores the best estimate of the size (bytes) of the serialized 183 // transaction input that spends this Output. 184 spendSize uint32 185 } 186 187 // Confirmations returns the number of confirmations on this output's 188 // transaction. 189 func (output *Output) Confirmations(context.Context) (int64, error) { 190 confs, err := output.confirmations() 191 if errors.Is(err, ErrReorgDetected) { 192 newOut, err := output.btc.output(&output.tx.hash, output.vout, output.redeemScript) 193 if err != nil { 194 return -1, fmt.Errorf("output block is not mainchain") 195 } 196 *output = *newOut 197 return output.Confirmations(context.Background()) 198 } 199 return confs, err 200 } 201 202 var _ asset.Coin = (*Output)(nil) 203 204 // SpendSize returns the maximum size of the serialized TxIn that spends this 205 // output, in bytes. This is a method of the asset.Output interface. 206 func (output *Output) SpendSize() uint32 { 207 return output.spendSize 208 } 209 210 // ID returns the coin ID. 211 func (output *Output) ID() []byte { 212 return toCoinID(&output.tx.hash, output.vout) 213 } 214 215 // Value is the output value, in satoshis. 216 func (output *Output) Value() uint64 { 217 return output.value // == output.TXIO.tx.outs[output.vout].value 218 } 219 220 func (output *Output) Addresses() []string { 221 return output.addresses 222 } 223 224 // String creates a human-readable representation of a Bitcoin transaction output 225 // in the format "{txid = [transaction hash], vout = [output index]}". 226 func (output *Output) String() string { 227 return fmt.Sprintf("{txid = %s, vout = %d}", output.TxID(), output.vout) 228 } 229 230 // Auth verifies that the output pays to the supplied public key(s). This is an 231 // asset.Backend method. 232 func (output *Output) Auth(pubkeys, sigs [][]byte, msg []byte) error { 233 // If there are not enough pubkeys, no reason to check anything. 234 if len(pubkeys) < output.numSigs { 235 return fmt.Errorf("not enough signatures for output %s:%d. expected %d, got %d", 236 output.tx.hash, output.vout, output.numSigs, len(pubkeys)) 237 } 238 // Extract the addresses from the pubkey scripts and redeem scripts. 239 evalScript := output.pkScript 240 if output.scriptType.IsP2SH() || output.scriptType.IsP2WSH() { 241 evalScript = output.redeemScript 242 } 243 scriptAddrs, nonStandard, err := dexbtc.ExtractScriptAddrs(evalScript, output.btc.chainParams) 244 if err != nil { 245 return err 246 } 247 if nonStandard { 248 return fmt.Errorf("non-standard script") 249 } 250 // Ensure that at least 1 signature is required to spend this output. 251 // Non-standard scripts are already be caught, but check again here in case 252 // this can happen another way. Note that Auth may be called via an 253 // interface, where this requirement may not fit into a generic spendability 254 // check. 255 if scriptAddrs.NRequired == 0 { 256 return fmt.Errorf("script requires no signatures to spend") 257 } 258 // Sanity check that the required signature count matches the count parsed 259 // during output initialization. 260 if scriptAddrs.NRequired != output.numSigs { 261 return fmt.Errorf("signature requirement mismatch. required: %d, matched: %d", 262 scriptAddrs.NRequired, output.numSigs) 263 } 264 matches := append(pkMatches(pubkeys, scriptAddrs.PubKeys, nil), 265 pkMatches(pubkeys, scriptAddrs.PkHashes, btcutil.Hash160)...) 266 if len(matches) < output.numSigs { 267 return fmt.Errorf("not enough pubkey matches to satisfy the script for output %s:%d. expected %d, got %d", 268 output.tx.hash, output.vout, output.numSigs, len(matches)) 269 } 270 for _, match := range matches { 271 err := checkSig(msg, match.pubkey, sigs[match.idx]) 272 if err != nil { 273 return err 274 } 275 } 276 return nil 277 } 278 279 // TODO: Eliminate the UTXO type. Instead use Output (asset.Coin) and check for 280 // spendability in the consumer as needed. This is left as is to retain current 281 // behavior with respect to the unspent requirements. 282 283 // A UTXO is information regarding an unspent transaction output. 284 type UTXO struct { 285 *Output 286 } 287 288 func (utxo *UTXO) Coin() asset.Coin { 289 return utxo 290 } 291 292 // Confirmations returns the number of confirmations on this output's 293 // transaction. See also (*Output).Confirmations. This function differs from the 294 // Output method in that it is necessary to relocate the utxo after a reorg, it 295 // may error if the output is spent. 296 func (utxo *UTXO) Confirmations(context.Context) (int64, error) { 297 confs, err := utxo.confirmations() 298 if errors.Is(err, ErrReorgDetected) { 299 // See if we can find the utxo in another block. 300 newUtxo, err := utxo.btc.utxo(&utxo.tx.hash, utxo.vout, utxo.redeemScript) 301 if err != nil { 302 return -1, fmt.Errorf("utxo error: %w", err) 303 } 304 *utxo = *newUtxo 305 return utxo.Confirmations(context.Background()) 306 } 307 return confs, err 308 } 309 310 var _ asset.FundingCoin = (*UTXO)(nil) 311 312 type pkMatch struct { 313 pubkey []byte 314 idx int 315 } 316 317 // pkMatches looks through a set of addresses and a returns a set of match 318 // structs with details about the match. 319 func pkMatches(pubkeys [][]byte, addrs []btcutil.Address, hasher func([]byte) []byte) []pkMatch { 320 matches := make([]pkMatch, 0, len(pubkeys)) 321 if hasher == nil { 322 hasher = func(a []byte) []byte { return a } 323 } 324 matchIndex := make(map[string]struct{}) 325 for _, addr := range addrs { 326 for i, pubkey := range pubkeys { 327 if bytes.Equal(addr.ScriptAddress(), hasher(pubkey)) { 328 addrStr := addr.String() 329 _, alreadyFound := matchIndex[addrStr] 330 if alreadyFound { 331 continue 332 } 333 matchIndex[addrStr] = struct{}{} 334 matches = append(matches, pkMatch{ 335 pubkey: pubkey, 336 idx: i, 337 }) 338 break 339 } 340 } 341 } 342 return matches 343 }