github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/btcutil/blockchain/utxoviewpoint.go (about) 1 // Copyright (c) 2015-2016 The btcsuite developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package blockchain 6 7 import ( 8 "fmt" 9 10 "github.com/mit-dci/lit/btcutil" 11 "github.com/mit-dci/lit/btcutil/chaincfg/chainhash" 12 "github.com/mit-dci/lit/btcutil/database" 13 "github.com/mit-dci/lit/btcutil/txscript" 14 ) 15 16 // utxoOutput houses details about an individual unspent transaction output such 17 // as whether or not it is spent, its public key script, and how much it pays. 18 // 19 // Standard public key scripts are stored in the database using a compressed 20 // format. Since the vast majority of scripts are of the standard form, a fairly 21 // significant savings is achieved by discarding the portions of the standard 22 // scripts that can be reconstructed. 23 // 24 // Also, since it is common for only a specific output in a given utxo entry to 25 // be referenced from a redeeming transaction, the script and amount for a given 26 // output is not uncompressed until the first time it is accessed. This 27 // provides a mechanism to avoid the overhead of needlessly uncompressing all 28 // outputs for a given utxo entry at the time of load. 29 type utxoOutput struct { 30 spent bool // Output is spent. 31 compressed bool // The amount and public key script are compressed. 32 amount int64 // The amount of the output. 33 pkScript []byte // The public key script for the output. 34 } 35 36 // maybeDecompress decompresses the amount and public key script fields of the 37 // utxo and marks it decompressed if needed. 38 func (o *utxoOutput) maybeDecompress(version int32) { 39 // Nothing to do if it's not compressed. 40 if !o.compressed { 41 return 42 } 43 44 o.amount = int64(decompressTxOutAmount(uint64(o.amount))) 45 o.pkScript = decompressScript(o.pkScript, version) 46 o.compressed = false 47 } 48 49 // UtxoEntry contains contextual information about an unspent transaction such 50 // as whether or not it is a coinbase transaction, which block it was found in, 51 // and the spent status of its outputs. 52 type UtxoEntry struct { 53 modified bool // Entry changed since load. 54 version int32 // The version of this tx. 55 isCoinBase bool // Whether entry is a coinbase tx. 56 blockHeight int32 // Height of block containing tx. 57 sparseOutputs map[uint32]*utxoOutput // Sparse map of unspent outputs. 58 } 59 60 // Version returns the version of the transaction the utxo represents. 61 func (entry *UtxoEntry) Version() int32 { 62 return entry.version 63 } 64 65 // IsCoinBase returns whether or not the transaction the utxo entry represents 66 // is a coinbase. 67 func (entry *UtxoEntry) IsCoinBase() bool { 68 return entry.isCoinBase 69 } 70 71 // BlockHeight returns the height of the block containing the transaction the 72 // utxo entry represents. 73 func (entry *UtxoEntry) BlockHeight() int32 { 74 return entry.blockHeight 75 } 76 77 // IsOutputSpent returns whether or not the provided output index has been 78 // spent based upon the current state of the unspent transaction output view 79 // the entry was obtained from. 80 // 81 // Returns true if the output index references an output that does not exist 82 // either due to it being invalid or because the output is not part of the view 83 // due to previously being spent/pruned. 84 func (entry *UtxoEntry) IsOutputSpent(outputIndex uint32) bool { 85 output, ok := entry.sparseOutputs[outputIndex] 86 if !ok { 87 return true 88 } 89 90 return output.spent 91 } 92 93 // SpendOutput marks the output at the provided index as spent. Specifying an 94 // output index that does not exist will not have any effect. 95 func (entry *UtxoEntry) SpendOutput(outputIndex uint32) { 96 output, ok := entry.sparseOutputs[outputIndex] 97 if !ok { 98 return 99 } 100 101 // Nothing to do if the output is already spent. 102 if output.spent { 103 return 104 } 105 106 entry.modified = true 107 output.spent = true 108 return 109 } 110 111 // IsFullySpent returns whether or not the transaction the utxo entry represents 112 // is fully spent. 113 func (entry *UtxoEntry) IsFullySpent() bool { 114 // The entry is not fully spent if any of the outputs are unspent. 115 for _, output := range entry.sparseOutputs { 116 if !output.spent { 117 return false 118 } 119 } 120 121 return true 122 } 123 124 // AmountByIndex returns the amount of the provided output index. 125 // 126 // Returns 0 if the output index references an output that does not exist 127 // either due to it being invalid or because the output is not part of the view 128 // due to previously being spent/pruned. 129 func (entry *UtxoEntry) AmountByIndex(outputIndex uint32) int64 { 130 output, ok := entry.sparseOutputs[outputIndex] 131 if !ok { 132 return 0 133 } 134 135 // Ensure the output is decompressed before returning the amount. 136 output.maybeDecompress(entry.version) 137 return output.amount 138 } 139 140 // PkScriptByIndex returns the public key script for the provided output index. 141 // 142 // Returns nil if the output index references an output that does not exist 143 // either due to it being invalid or because the output is not part of the view 144 // due to previously being spent/pruned. 145 func (entry *UtxoEntry) PkScriptByIndex(outputIndex uint32) []byte { 146 output, ok := entry.sparseOutputs[outputIndex] 147 if !ok { 148 return nil 149 } 150 151 // Ensure the output is decompressed before returning the script. 152 output.maybeDecompress(entry.version) 153 return output.pkScript 154 } 155 156 // Clone returns a deep copy of the utxo entry. 157 func (entry *UtxoEntry) Clone() *UtxoEntry { 158 if entry == nil { 159 return nil 160 } 161 162 newEntry := &UtxoEntry{ 163 version: entry.version, 164 isCoinBase: entry.isCoinBase, 165 blockHeight: entry.blockHeight, 166 sparseOutputs: make(map[uint32]*utxoOutput), 167 } 168 for outputIndex, output := range entry.sparseOutputs { 169 newEntry.sparseOutputs[outputIndex] = &utxoOutput{ 170 spent: output.spent, 171 compressed: output.compressed, 172 amount: output.amount, 173 pkScript: output.pkScript, 174 } 175 } 176 return newEntry 177 } 178 179 // newUtxoEntry returns a new unspent transaction output entry with the provided 180 // coinbase flag and block height ready to have unspent outputs added. 181 func newUtxoEntry(version int32, isCoinBase bool, blockHeight int32) *UtxoEntry { 182 return &UtxoEntry{ 183 version: version, 184 isCoinBase: isCoinBase, 185 blockHeight: blockHeight, 186 sparseOutputs: make(map[uint32]*utxoOutput), 187 } 188 } 189 190 // UtxoViewpoint represents a view into the set of unspent transaction outputs 191 // from a specific point of view in the chain. For example, it could be for 192 // the end of the main chain, some point in the history of the main chain, or 193 // down a side chain. 194 // 195 // The unspent outputs are needed by other transactions for things such as 196 // script validation and double spend prevention. 197 type UtxoViewpoint struct { 198 entries map[chainhash.Hash]*UtxoEntry 199 bestHash chainhash.Hash 200 } 201 202 // BestHash returns the hash of the best block in the chain the view currently 203 // respresents. 204 func (view *UtxoViewpoint) BestHash() *chainhash.Hash { 205 return &view.bestHash 206 } 207 208 // SetBestHash sets the hash of the best block in the chain the view currently 209 // respresents. 210 func (view *UtxoViewpoint) SetBestHash(hash *chainhash.Hash) { 211 view.bestHash = *hash 212 } 213 214 // LookupEntry returns information about a given transaction according to the 215 // current state of the view. It will return nil if the passed transaction 216 // hash does not exist in the view or is otherwise not available such as when 217 // it has been disconnected during a reorg. 218 func (view *UtxoViewpoint) LookupEntry(txHash *chainhash.Hash) *UtxoEntry { 219 entry, ok := view.entries[*txHash] 220 if !ok { 221 return nil 222 } 223 224 return entry 225 } 226 227 // AddTxOuts adds all outputs in the passed transaction which are not provably 228 // unspendable to the view. When the view already has entries for any of the 229 // outputs, they are simply marked unspent. All fields will be updated for 230 // existing entries since it's possible it has changed during a reorg. 231 func (view *UtxoViewpoint) AddTxOuts(tx *btcutil.Tx, blockHeight int32) { 232 // When there are not already any utxos associated with the transaction, 233 // add a new entry for it to the view. 234 entry := view.LookupEntry(tx.Hash()) 235 if entry == nil { 236 entry = newUtxoEntry(tx.MsgTx().Version, IsCoinBase(tx), 237 blockHeight) 238 view.entries[*tx.Hash()] = entry 239 } else { 240 entry.blockHeight = blockHeight 241 } 242 entry.modified = true 243 244 // Loop all of the transaction outputs and add those which are not 245 // provably unspendable. 246 for txOutIdx, txOut := range tx.MsgTx().TxOut { 247 if txscript.IsUnspendable(txOut.PkScript) { 248 continue 249 } 250 251 // Update existing entries. All fields are updated because it's 252 // possible (although extremely unlikely) that the existing 253 // entry is being replaced by a different transaction with the 254 // same hash. This is allowed so long as the previous 255 // transaction is fully spent. 256 if output, ok := entry.sparseOutputs[uint32(txOutIdx)]; ok { 257 output.spent = false 258 output.compressed = false 259 output.amount = txOut.Value 260 output.pkScript = txOut.PkScript 261 continue 262 } 263 264 // Add the unspent transaction output. 265 entry.sparseOutputs[uint32(txOutIdx)] = &utxoOutput{ 266 spent: false, 267 compressed: false, 268 amount: txOut.Value, 269 pkScript: txOut.PkScript, 270 } 271 } 272 return 273 } 274 275 // connectTransaction updates the view by adding all new utxos created by the 276 // passed transaction and marking all utxos that the transactions spend as 277 // spent. In addition, when the 'stxos' argument is not nil, it will be updated 278 // to append an entry for each spent txout. An error will be returned if the 279 // view does not contain the required utxos. 280 func (view *UtxoViewpoint) connectTransaction(tx *btcutil.Tx, blockHeight int32, stxos *[]spentTxOut) error { 281 // Coinbase transactions don't have any inputs to spend. 282 if IsCoinBase(tx) { 283 // Add the transaction's outputs as available utxos. 284 view.AddTxOuts(tx, blockHeight) 285 return nil 286 } 287 288 // Spend the referenced utxos by marking them spent in the view and, 289 // if a slice was provided for the spent txout details, append an entry 290 // to it. 291 for _, txIn := range tx.MsgTx().TxIn { 292 originIndex := txIn.PreviousOutPoint.Index 293 entry := view.entries[txIn.PreviousOutPoint.Hash] 294 295 // Ensure the referenced utxo exists in the view. This should 296 // never happen unless there is a bug is introduced in the code. 297 if entry == nil { 298 return AssertError(fmt.Sprintf("view missing input %v", 299 txIn.PreviousOutPoint)) 300 } 301 entry.SpendOutput(originIndex) 302 303 // Don't create the stxo details if not requested. 304 if stxos == nil { 305 continue 306 } 307 308 // Populate the stxo details using the utxo entry. When the 309 // transaction is fully spent, set the additional stxo fields 310 // accordingly since those details will no longer be available 311 // in the utxo set. 312 var stxo = spentTxOut{ 313 compressed: false, 314 version: entry.Version(), 315 amount: entry.AmountByIndex(originIndex), 316 pkScript: entry.PkScriptByIndex(originIndex), 317 } 318 if entry.IsFullySpent() { 319 stxo.height = entry.BlockHeight() 320 stxo.isCoinBase = entry.IsCoinBase() 321 } 322 323 // Append the entry to the provided spent txouts slice. 324 *stxos = append(*stxos, stxo) 325 } 326 327 // Add the transaction's outputs as available utxos. 328 view.AddTxOuts(tx, blockHeight) 329 return nil 330 } 331 332 // connectTransactions updates the view by adding all new utxos created by all 333 // of the transactions in the passed block, marking all utxos the transactions 334 // spend as spent, and setting the best hash for the view to the passed block. 335 // In addition, when the 'stxos' argument is not nil, it will be updated to 336 // append an entry for each spent txout. 337 func (view *UtxoViewpoint) connectTransactions(block *btcutil.Block, stxos *[]spentTxOut) error { 338 for _, tx := range block.Transactions() { 339 err := view.connectTransaction(tx, block.Height(), stxos) 340 if err != nil { 341 return err 342 } 343 } 344 345 // Update the best hash for view to include this block since all of its 346 // transactions have been connected. 347 view.SetBestHash(block.Hash()) 348 return nil 349 } 350 351 // disconnectTransactions updates the view by removing all of the transactions 352 // created by the passed block, restoring all utxos the transactions spent by 353 // using the provided spent txo information, and setting the best hash for the 354 // view to the block before the passed block. 355 func (view *UtxoViewpoint) disconnectTransactions(block *btcutil.Block, stxos []spentTxOut) error { 356 // Sanity check the correct number of stxos are provided. 357 if len(stxos) != countSpentOutputs(block) { 358 return AssertError("disconnectTransactions called with bad " + 359 "spent transaction out information") 360 } 361 362 // Loop backwards through all transactions so everything is unspent in 363 // reverse order. This is necessary since transactions later in a block 364 // can spend from previous ones. 365 stxoIdx := len(stxos) - 1 366 transactions := block.Transactions() 367 for txIdx := len(transactions) - 1; txIdx > -1; txIdx-- { 368 tx := transactions[txIdx] 369 370 // Clear this transaction from the view if it already exists or 371 // create a new empty entry for when it does not. This is done 372 // because the code relies on its existence in the view in order 373 // to signal modifications have happened. 374 isCoinbase := txIdx == 0 375 entry := view.entries[*tx.Hash()] 376 if entry == nil { 377 entry = newUtxoEntry(tx.MsgTx().Version, isCoinbase, 378 block.Height()) 379 view.entries[*tx.Hash()] = entry 380 } 381 entry.modified = true 382 entry.sparseOutputs = make(map[uint32]*utxoOutput) 383 384 // Loop backwards through all of the transaction inputs (except 385 // for the coinbase which has no inputs) and unspend the 386 // referenced txos. This is necessary to match the order of the 387 // spent txout entries. 388 if isCoinbase { 389 continue 390 } 391 for txInIdx := len(tx.MsgTx().TxIn) - 1; txInIdx > -1; txInIdx-- { 392 // Ensure the spent txout index is decremented to stay 393 // in sync with the transaction input. 394 stxo := &stxos[stxoIdx] 395 stxoIdx-- 396 397 // When there is not already an entry for the referenced 398 // transaction in the view, it means it was fully spent, 399 // so create a new utxo entry in order to resurrect it. 400 txIn := tx.MsgTx().TxIn[txInIdx] 401 originHash := &txIn.PreviousOutPoint.Hash 402 originIndex := txIn.PreviousOutPoint.Index 403 entry := view.entries[*originHash] 404 if entry == nil { 405 entry = newUtxoEntry(stxo.version, 406 stxo.isCoinBase, stxo.height) 407 view.entries[*originHash] = entry 408 } 409 410 // Mark the entry as modified since it is either new 411 // or will be changed below. 412 entry.modified = true 413 414 // Restore the specific utxo using the stxo data from 415 // the spend journal if it doesn't already exist in the 416 // view. 417 output, ok := entry.sparseOutputs[originIndex] 418 if !ok { 419 // Add the unspent transaction output. 420 entry.sparseOutputs[originIndex] = &utxoOutput{ 421 spent: false, 422 compressed: stxo.compressed, 423 amount: stxo.amount, 424 pkScript: stxo.pkScript, 425 } 426 continue 427 } 428 429 // Mark the existing referenced transaction output as 430 // unspent. 431 output.spent = false 432 } 433 } 434 435 // Update the best hash for view to the previous block since all of the 436 // transactions for the current block have been disconnected. 437 view.SetBestHash(&block.MsgBlock().Header.PrevBlock) 438 return nil 439 } 440 441 // Entries returns the underlying map that stores of all the utxo entries. 442 func (view *UtxoViewpoint) Entries() map[chainhash.Hash]*UtxoEntry { 443 return view.entries 444 } 445 446 // commit prunes all entries marked modified that are now fully spent and marks 447 // all entries as unmodified. 448 func (view *UtxoViewpoint) commit() { 449 for txHash, entry := range view.entries { 450 if entry == nil || (entry.modified && entry.IsFullySpent()) { 451 delete(view.entries, txHash) 452 continue 453 } 454 455 entry.modified = false 456 } 457 } 458 459 // fetchUtxosMain fetches unspent transaction output data about the provided 460 // set of transactions from the point of view of the end of the main chain at 461 // the time of the call. 462 // 463 // Upon completion of this function, the view will contain an entry for each 464 // requested transaction. Fully spent transactions, or those which otherwise 465 // don't exist, will result in a nil entry in the view. 466 func (view *UtxoViewpoint) fetchUtxosMain(db database.DB, txSet map[chainhash.Hash]struct{}) error { 467 // Nothing to do if there are no requested hashes. 468 if len(txSet) == 0 { 469 return nil 470 } 471 472 // Load the unspent transaction output information for the requested set 473 // of transactions from the point of view of the end of the main chain. 474 // 475 // NOTE: Missing entries are not considered an error here and instead 476 // will result in nil entries in the view. This is intentionally done 477 // since other code uses the presence of an entry in the store as a way 478 // to optimize spend and unspend updates to apply only to the specific 479 // utxos that the caller needs access to. 480 return db.View(func(dbTx database.Tx) error { 481 for hash := range txSet { 482 hashCopy := hash 483 entry, err := dbFetchUtxoEntry(dbTx, &hashCopy) 484 if err != nil { 485 return err 486 } 487 488 view.entries[hash] = entry 489 } 490 491 return nil 492 }) 493 } 494 495 // fetchUtxos loads utxo details about provided set of transaction hashes into 496 // the view from the database as needed unless they already exist in the view in 497 // which case they are ignored. 498 func (view *UtxoViewpoint) fetchUtxos(db database.DB, txSet map[chainhash.Hash]struct{}) error { 499 // Nothing to do if there are no requested hashes. 500 if len(txSet) == 0 { 501 return nil 502 } 503 504 // Filter entries that are already in the view. 505 txNeededSet := make(map[chainhash.Hash]struct{}) 506 for hash := range txSet { 507 // Already loaded into the current view. 508 if _, ok := view.entries[hash]; ok { 509 continue 510 } 511 512 txNeededSet[hash] = struct{}{} 513 } 514 515 // Request the input utxos from the database. 516 return view.fetchUtxosMain(db, txNeededSet) 517 } 518 519 // fetchInputUtxos loads utxo details about the input transactions referenced 520 // by the transactions in the given block into the view from the database as 521 // needed. In particular, referenced entries that are earlier in the block are 522 // added to the view and entries that are already in the view are not modified. 523 func (view *UtxoViewpoint) fetchInputUtxos(db database.DB, block *btcutil.Block) error { 524 // Build a map of in-flight transactions because some of the inputs in 525 // this block could be referencing other transactions earlier in this 526 // block which are not yet in the chain. 527 txInFlight := map[chainhash.Hash]int{} 528 transactions := block.Transactions() 529 for i, tx := range transactions { 530 txInFlight[*tx.Hash()] = i 531 } 532 533 // Loop through all of the transaction inputs (except for the coinbase 534 // which has no inputs) collecting them into sets of what is needed and 535 // what is already known (in-flight). 536 txNeededSet := make(map[chainhash.Hash]struct{}) 537 for i, tx := range transactions[1:] { 538 for _, txIn := range tx.MsgTx().TxIn { 539 // It is acceptable for a transaction input to reference 540 // the output of another transaction in this block only 541 // if the referenced transaction comes before the 542 // current one in this block. Add the outputs of the 543 // referenced transaction as available utxos when this 544 // is the case. Otherwise, the utxo details are still 545 // needed. 546 // 547 // NOTE: The >= is correct here because i is one less 548 // than the actual position of the transaction within 549 // the block due to skipping the coinbase. 550 originHash := &txIn.PreviousOutPoint.Hash 551 if inFlightIndex, ok := txInFlight[*originHash]; ok && 552 i >= inFlightIndex { 553 554 originTx := transactions[inFlightIndex] 555 view.AddTxOuts(originTx, block.Height()) 556 continue 557 } 558 559 // Don't request entries that are already in the view 560 // from the database. 561 if _, ok := view.entries[*originHash]; ok { 562 continue 563 } 564 565 txNeededSet[*originHash] = struct{}{} 566 } 567 } 568 569 // Request the input utxos from the database. 570 return view.fetchUtxosMain(db, txNeededSet) 571 } 572 573 // NewUtxoViewpoint returns a new empty unspent transaction output view. 574 func NewUtxoViewpoint() *UtxoViewpoint { 575 return &UtxoViewpoint{ 576 entries: make(map[chainhash.Hash]*UtxoEntry), 577 } 578 } 579 580 // FetchUtxoView loads utxo details about the input transactions referenced by 581 // the passed transaction from the point of view of the end of the main chain. 582 // It also attempts to fetch the utxo details for the transaction itself so the 583 // returned view can be examined for duplicate unspent transaction outputs. 584 // 585 // This function is safe for concurrent access however the returned view is NOT. 586 func (b *BlockChain) FetchUtxoView(tx *btcutil.Tx) (*UtxoViewpoint, error) { 587 b.chainLock.RLock() 588 defer b.chainLock.RUnlock() 589 590 // Create a set of needed transactions based on those referenced by the 591 // inputs of the passed transaction. Also, add the passed transaction 592 // itself as a way for the caller to detect duplicates that are not 593 // fully spent. 594 txNeededSet := make(map[chainhash.Hash]struct{}) 595 txNeededSet[*tx.Hash()] = struct{}{} 596 if !IsCoinBase(tx) { 597 for _, txIn := range tx.MsgTx().TxIn { 598 txNeededSet[txIn.PreviousOutPoint.Hash] = struct{}{} 599 } 600 } 601 602 // Request the utxos from the point of view of the end of the main 603 // chain. 604 view := NewUtxoViewpoint() 605 err := view.fetchUtxosMain(b.db, txNeededSet) 606 return view, err 607 } 608 609 // FetchUtxoEntry loads and returns the unspent transaction output entry for the 610 // passed hash from the point of view of the end of the main chain. 611 // 612 // NOTE: Requesting a hash for which there is no data will NOT return an error. 613 // Instead both the entry and the error will be nil. This is done to allow 614 // pruning of fully spent transactions. In practice this means the caller must 615 // check if the returned entry is nil before invoking methods on it. 616 // 617 // This function is safe for concurrent access however the returned entry (if 618 // any) is NOT. 619 func (b *BlockChain) FetchUtxoEntry(txHash *chainhash.Hash) (*UtxoEntry, error) { 620 b.chainLock.RLock() 621 defer b.chainLock.RUnlock() 622 623 var entry *UtxoEntry 624 err := b.db.View(func(dbTx database.Tx) error { 625 var err error 626 entry, err = dbFetchUtxoEntry(dbTx, txHash) 627 return err 628 }) 629 if err != nil { 630 return nil, err 631 } 632 633 return entry, nil 634 }