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