decred.org/dcrdex@v1.0.5/server/asset/dcr/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 dcr 5 6 import ( 7 "bytes" 8 "context" 9 "errors" 10 "fmt" 11 "time" 12 13 "decred.org/dcrdex/dex" 14 dexdcr "decred.org/dcrdex/dex/networks/dcr" 15 "decred.org/dcrdex/server/asset" 16 "github.com/decred/dcrd/chaincfg/chainhash" 17 "github.com/decred/dcrd/dcrec" 18 "github.com/decred/dcrd/txscript/v4/stdaddr" 19 "github.com/decred/dcrd/txscript/v4/stdscript" 20 ) 21 22 const ErrReorgDetected = dex.ErrorKind("reorg detected") 23 24 // TXIO is common information stored with an Input or Output. 25 type TXIO struct { 26 // Because a TXIO's validity and block info can change after creation, keep a 27 // Backend around to query the state of the tx and update the block info. 28 dcr *Backend 29 tx *Tx 30 // The height and hash of the transaction's best known block. 31 height uint32 32 blockHash chainhash.Hash 33 // The number of confirmations needed for maturity. For outputs of coinbase 34 // transactions and stake-related transactions, this will be set to 35 // chaincfg.Params.CoinbaseMaturity (256 for mainchain). For other supported 36 // script types, this will be zero. 37 maturity int32 38 // While the TXIO's tx is still in mempool, the tip hash will be stored. 39 // This enables an optimization in the Confirmations method to return zero 40 // without extraneous RPC calls. 41 lastLookup *chainhash.Hash 42 } 43 44 // confirmations returns the number of confirmations for a tx's transaction. 45 // Because a tx can become invalid after once being considered valid, validity 46 // should be verified again on every call. An error will be returned if this tx 47 // is no longer ready to spend. An unmined transaction should have zero 48 // confirmations. A transaction in the current best block should have one 49 // confirmation. The value -1 will be returned with any error. This function is 50 // NOT thread-safe. 51 func (txio *TXIO) confirmations(ctx context.Context, checkApproval bool) (int64, error) { 52 tipHash := txio.dcr.blockCache.tipHash() 53 // If the tx was in a mempool transaction, check if it has been confirmed. 54 if txio.height == 0 { 55 // If the tip hasn't changed, don't do anything here. 56 if txio.lastLookup == nil || *txio.lastLookup != tipHash { 57 txio.lastLookup = &tipHash 58 verboseTx, err := txio.dcr.node.GetRawTransactionVerbose(ctx, &txio.tx.hash) 59 if err != nil { 60 if isTxNotFoundErr(err) { 61 return -1, asset.CoinNotFoundError 62 } 63 return -1, fmt.Errorf("confirmations: GetRawTransactionVerbose for txid %s: %w", txio.tx.hash, translateRPCCancelErr(err)) 64 } 65 // More than zero confirmations would indicate that the transaction has 66 // been mined. Collect the block info and update the tx fields. 67 if verboseTx.Confirmations > 0 { 68 blk, err := txio.dcr.getBlockInfo(ctx, verboseTx.BlockHash) 69 if err != nil { 70 return -1, err 71 } 72 txio.height = blk.height 73 txio.blockHash = blk.hash 74 } 75 return verboseTx.Confirmations, nil 76 } 77 } else { 78 // The tx was included in a block, but make sure that the tx's block has 79 // not been orphaned or voted as invalid. 80 mainchainBlock, found := txio.dcr.blockCache.atHeight(txio.height) 81 if !found || mainchainBlock.hash != txio.blockHash { 82 return -1, ErrReorgDetected 83 } 84 if mainchainBlock != nil && checkApproval { 85 nextBlock, err := txio.dcr.getMainchainDcrBlock(ctx, txio.height+1) 86 if err != nil { 87 return -1, fmt.Errorf("error retrieving approving block tx %s: %w", txio.tx.hash, err) 88 } 89 if nextBlock != nil && !nextBlock.vote { 90 return -1, fmt.Errorf("transaction %s block %s has been voted as invalid", txio.tx.hash, nextBlock.hash) 91 } 92 } 93 } 94 // If the height is still 0, this is a mempool transaction. 95 if txio.height == 0 { 96 return 0, nil 97 } 98 // Otherwise just check that there hasn't been a reorg which would render the 99 // output immature. This would be exceedingly rare (impossible?). 100 confs := int32(txio.dcr.blockCache.tipHeight()) - int32(txio.height) + 1 101 if confs < txio.maturity { 102 return -1, fmt.Errorf("transaction %s became immature", txio.tx.hash) 103 } 104 return int64(confs), nil 105 } 106 107 // TxID is a string identifier for the transaction, typically a hexadecimal 108 // representation of the byte-reversed transaction hash. 109 func (txio *TXIO) TxID() string { 110 return txio.tx.hash.String() 111 } 112 113 // FeeRate returns the transaction fee rate, in atoms/byte. 114 func (txio *TXIO) FeeRate() uint64 { 115 return txio.tx.feeRate 116 } 117 118 // Input is a transaction input. 119 type Input struct { 120 TXIO 121 vin uint32 122 } 123 124 var _ asset.Coin = (*Input)(nil) 125 126 // Value is the value of the previous output spent by the input. 127 func (input *Input) Value() uint64 { 128 return input.TXIO.tx.ins[input.vin].value 129 } 130 131 // String creates a human-readable representation of a Decred transaction input 132 // in the format "{txid = [transaction hash], vin = [input index]}". 133 func (input *Input) String() string { 134 return fmt.Sprintf("{txid = %s, vin = %d}", input.TxID(), input.vin) 135 } 136 137 // Confirmations returns the number of confirmations on this input's 138 // transaction. 139 func (input *Input) Confirmations(ctx context.Context) (int64, error) { 140 confs, err := input.confirmations(ctx, false) 141 if errors.Is(err, ErrReorgDetected) { 142 newInput, err := input.dcr.input(&input.tx.hash, input.vin) 143 if err != nil { 144 return -1, fmt.Errorf("input block is not mainchain") 145 } 146 *input = *newInput 147 return input.Confirmations(ctx) 148 } 149 return confs, err 150 } 151 152 // ID returns the coin ID. 153 func (input *Input) ID() []byte { 154 return toCoinID(&input.tx.hash, input.vin) 155 } 156 157 // spendsCoin checks whether a particular coin is spent in this coin's tx. 158 func (input *Input) spendsCoin(coinID []byte) (bool, error) { 159 txHash, vout, err := decodeCoinID(coinID) 160 if err != nil { 161 return false, fmt.Errorf("error decoding coin ID %x: %w", coinID, err) 162 } 163 if uint32(len(input.tx.ins)) < input.vin+1 { 164 return false, nil 165 } 166 txIn := input.tx.ins[input.vin] 167 return txIn.prevTx == *txHash && txIn.vout == vout, nil 168 } 169 170 // Output represents a transaction output. 171 type Output struct { 172 TXIO 173 vout uint32 174 // The output value. 175 value uint64 176 // Addresses encoded by the pkScript or the redeem script in the case of a 177 // P2SH pkScript. 178 addresses []string 179 // A bitmask for script type information. 180 scriptType dexdcr.ScriptType 181 scriptVersion uint16 182 // If the pkScript, or redeemScript in the case of a P2SH pkScript, is 183 // non-standard according to txscript. 184 nonStandardScript bool 185 // The output's scriptPubkey. 186 pkScript []byte 187 // If the pubkey script is P2SH, the Output will only be generated if 188 // the redeem script is supplied and the script-hash validated. If the 189 // pubkey script is not P2SH, redeemScript will be nil. 190 redeemScript []byte 191 // numSigs is the number of signatures required to spend this output. 192 numSigs int 193 // spendSize stores the best estimate of the size (bytes) of the serialized 194 // transaction input that spends this Output. 195 spendSize uint32 196 } 197 198 // Confirmations returns the number of confirmations for a transaction output. 199 // Because it is possible for an output that was once considered valid to later 200 // be considered invalid, this method can return an error to indicate the output 201 // is no longer valid. The definition of output validity should not be confused 202 // with the validity of regular tree transactions that is voted on by 203 // stakeholders. While stakeholder approval is a part of output validity, there 204 // are other considerations as well. 205 func (output *Output) Confirmations(ctx context.Context) (int64, error) { 206 confs, err := output.confirmations(ctx, false) 207 if errors.Is(err, ErrReorgDetected) { 208 newOut, err := output.dcr.output(&output.tx.hash, output.vout, output.redeemScript) 209 if err != nil { 210 if !errors.Is(err, asset.ErrRequestTimeout) { 211 err = fmt.Errorf("output block is not mainchain") 212 } 213 return -1, err 214 } 215 *output = *newOut 216 return output.Confirmations(ctx) 217 } 218 return confs, err 219 } 220 221 var _ asset.Coin = (*Output)(nil) 222 223 // SpendSize returns the maximum size of the serialized TxIn that spends this 224 // Output, in bytes. 225 func (output *Output) SpendSize() uint32 { 226 return output.spendSize 227 } 228 229 // ID returns the coin ID. 230 func (output *Output) ID() []byte { 231 return toCoinID(&output.tx.hash, output.vout) 232 } 233 234 // Value is the output value, in atoms. 235 func (output *Output) Value() uint64 { 236 return output.value // == output.TXIO.tx.outs[output.vout].value 237 } 238 239 func (output *Output) Addresses() []string { 240 return output.addresses 241 } 242 243 // String creates a human-readable representation of a Decred transaction output 244 // in the format "{txid = [transaction hash], vout = [output index]}". 245 func (output *Output) String() string { 246 return fmt.Sprintf("{txid = %s, vout = %d}", output.TxID(), output.vout) 247 } 248 249 // Auth verifies that the output pays to the supplied public key(s). This is an 250 // asset.FundingCoin method. 251 func (output *Output) Auth(pubkeys, sigs [][]byte, msg []byte) error { 252 if len(pubkeys) < output.numSigs { 253 return fmt.Errorf("not enough signatures for output %s:%d. expected %d, got %d", output.tx.hash, output.vout, output.numSigs, len(pubkeys)) 254 } 255 evalScript := output.pkScript 256 if output.scriptType.IsP2SH() { 257 evalScript = output.redeemScript 258 } 259 scriptType, scriptAddrs := dexdcr.ExtractScriptAddrs(output.scriptVersion, evalScript, chainParams) 260 if scriptType == dexdcr.ScriptUnsupported { 261 return fmt.Errorf("non-standard script") 262 } 263 // Ensure that at least 1 signature is required to spend this output. 264 // Non-standard scripts are already be caught, but check again here in case 265 // this can happen another way. Note that Auth may be called via an 266 // interface, where this requirement may not fit into a generic spendability 267 // check. 268 if scriptAddrs.NRequired == 0 { 269 return fmt.Errorf("script requires no signatures to spend") 270 } 271 if scriptAddrs.NRequired != output.numSigs { 272 return fmt.Errorf("signature requirement mismatch for output %s:%d. %d != %d", output.tx.hash, output.vout, scriptAddrs.NRequired, output.numSigs) 273 } 274 matches, err := pkMatches(pubkeys, scriptAddrs.PubKeys, nil) 275 if err != nil { 276 return fmt.Errorf("error during pubkey matching: %w", err) 277 } 278 m, err := pkMatches(pubkeys, scriptAddrs.PkHashes, stdaddr.Hash160) 279 if err != nil { 280 return fmt.Errorf("error during pubkey hash matching: %w", err) 281 } 282 matches = append(matches, m...) 283 if len(matches) < output.numSigs { 284 return fmt.Errorf("not enough pubkey matches to satisfy the script for output %s:%d. expected %d, got %d", output.tx.hash, output.vout, output.numSigs, len(matches)) 285 } 286 for _, match := range matches { 287 err := checkSig(msg, match.pubkey, sigs[match.idx], match.sigType) 288 if err != nil { 289 return err 290 } 291 292 } 293 return nil 294 } 295 296 // TODO: Eliminate the UTXO type. Instead use Output (asset.Coin) and check for 297 // spendability in the consumer as needed. This is left as is to retain current 298 // behavior with respect to the unspent requirements. 299 300 // A UTXO is information regarding an unspent transaction output. 301 type UTXO struct { 302 *Output 303 } 304 305 func (utxo *UTXO) Coin() asset.Coin { 306 return utxo 307 } 308 309 // Confirmations returns the number of confirmations on this output's 310 // transaction. See also (*Output).Confirmations. This function differs from the 311 // Output method in that it is necessary to relocate the utxo after a reorg, it 312 // may error if the output is spent. 313 func (utxo *UTXO) Confirmations(ctx context.Context) (int64, error) { 314 confs, err := utxo.confirmations(ctx, !utxo.scriptType.IsStake()) 315 if errors.Is(err, ErrReorgDetected) { 316 // See if we can find the utxo in another block. 317 newUtxo, err := utxo.dcr.utxo(ctx, &utxo.tx.hash, utxo.vout, utxo.redeemScript) 318 if err != nil { 319 return -1, fmt.Errorf("utxo block is not mainchain") 320 } 321 *utxo = *newUtxo 322 return utxo.Confirmations(ctx) 323 } 324 return confs, err 325 } 326 327 var _ asset.FundingCoin = (*UTXO)(nil) 328 329 type pkMatch struct { 330 pubkey []byte 331 sigType dcrec.SignatureType 332 idx int 333 } 334 335 // pkMatches looks through a set of addresses and a returns a set of match 336 // structs with details about the match. 337 func pkMatches(pubkeys [][]byte, addrs []stdaddr.Address, hasher func([]byte) []byte) ([]pkMatch, error) { 338 matches := make([]pkMatch, 0, len(pubkeys)) 339 if hasher == nil { 340 hasher = func(a []byte) []byte { return a } 341 } 342 matchIndex := make(map[string]struct{}) 343 for _, addr := range addrs { 344 addrStr := addr.String() 345 addrScript, err := dexdcr.AddressScript(addr) 346 if err != nil { 347 return nil, err 348 } 349 sigType, err := dexdcr.AddressSigType(addr) 350 if err != nil { 351 return nil, err 352 } 353 for i, pubkey := range pubkeys { 354 if bytes.Equal(addrScript, hasher(pubkey)) { 355 _, alreadyFound := matchIndex[addrStr] 356 if alreadyFound { 357 continue 358 } 359 matchIndex[addrStr] = struct{}{} 360 matches = append(matches, pkMatch{ 361 pubkey: pubkey, 362 sigType: sigType, 363 idx: i, 364 }) 365 break 366 } 367 } 368 } 369 return matches, nil 370 } 371 372 // auditContract checks that the Contract is a swap contract and extracts the 373 // receiving address and contract value on success. 374 func auditContract(op *Output) (*asset.Contract, error) { 375 tx := op.tx 376 if len(tx.outs) <= int(op.vout) { 377 return nil, fmt.Errorf("invalid index %d for transaction %s", op.vout, tx.hash) 378 } 379 // InputInfo via (*Backend).output would already have screened out script 380 // versions >0, but do it again to be safe. However, note that this will 381 // break when other versions become standard. 382 if op.scriptVersion != 0 { 383 return nil, fmt.Errorf("invalid script version %d", op.scriptVersion) 384 } 385 output := tx.outs[int(op.vout)] 386 if output.version != 0 { 387 return nil, fmt.Errorf("unsupported script version %d", output.version) 388 } 389 scriptHash := stdscript.ExtractScriptHashV0(output.pkScript) 390 if scriptHash == nil { 391 return nil, fmt.Errorf("specified output %s:%d is not P2SH", tx.hash, op.vout) 392 } 393 if !bytes.Equal(stdaddr.Hash160(op.redeemScript), scriptHash) { 394 return nil, fmt.Errorf("swap contract hash mismatch for %s:%d", tx.hash, op.vout) 395 } 396 _, receiver, lockTime, secretHash, err := dexdcr.ExtractSwapDetails(op.redeemScript, chainParams) 397 if err != nil { 398 return nil, fmt.Errorf("error parsing swap contract for %s:%d: %w", tx.hash, op.vout, err) 399 } 400 return &asset.Contract{ 401 Coin: op, 402 SwapAddress: receiver.String(), 403 ContractData: op.redeemScript, 404 SecretHash: secretHash, 405 LockTime: time.Unix(int64(lockTime), 0), 406 TxData: op.tx.raw, 407 }, nil 408 }