github.com/lbryio/lbcd@v0.22.119/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/lbryio/lbcd/chaincfg/chainhash" 11 "github.com/lbryio/lbcd/database" 12 "github.com/lbryio/lbcd/txscript" 13 "github.com/lbryio/lbcd/wire" 14 btcutil "github.com/lbryio/lbcutil" 15 ) 16 17 // txoFlags is a bitmask defining additional information and state for a 18 // transaction output in a utxo view. 19 type txoFlags uint8 20 21 const ( 22 // tfCoinBase indicates that a txout was contained in a coinbase tx. 23 tfCoinBase txoFlags = 1 << iota 24 25 // tfSpent indicates that a txout is spent. 26 tfSpent 27 28 // tfModified indicates that a txout has been modified since it was 29 // loaded. 30 tfModified 31 ) 32 33 // UtxoEntry houses details about an individual transaction output in a utxo 34 // view such as whether or not it was contained in a coinbase tx, the height of 35 // the block that contains the tx, whether or not it is spent, its public key 36 // script, and how much it pays. 37 type UtxoEntry struct { 38 // NOTE: Additions, deletions, or modifications to the order of the 39 // definitions in this struct should not be changed without considering 40 // how it affects alignment on 64-bit platforms. The current order is 41 // specifically crafted to result in minimal padding. There will be a 42 // lot of these in memory, so a few extra bytes of padding adds up. 43 44 amount int64 45 pkScript []byte // The public key script for the output. 46 blockHeight int32 // Height of block containing tx. 47 48 // packedFlags contains additional info about output such as whether it 49 // is a coinbase, whether it is spent, and whether it has been modified 50 // since it was loaded. This approach is used in order to reduce memory 51 // usage since there will be a lot of these in memory. 52 packedFlags txoFlags 53 } 54 55 // isModified returns whether or not the output has been modified since it was 56 // loaded. 57 func (entry *UtxoEntry) isModified() bool { 58 return entry.packedFlags&tfModified == tfModified 59 } 60 61 // IsCoinBase returns whether or not the output was contained in a coinbase 62 // transaction. 63 func (entry *UtxoEntry) IsCoinBase() bool { 64 return entry.packedFlags&tfCoinBase == tfCoinBase 65 } 66 67 // BlockHeight returns the height of the block containing the output. 68 func (entry *UtxoEntry) BlockHeight() int32 { 69 return entry.blockHeight 70 } 71 72 // IsSpent returns whether or not the output has been spent based upon the 73 // current state of the unspent transaction output view it was obtained from. 74 func (entry *UtxoEntry) IsSpent() bool { 75 return entry.packedFlags&tfSpent == tfSpent 76 } 77 78 // Spend marks the output as spent. Spending an output that is already spent 79 // has no effect. 80 func (entry *UtxoEntry) Spend() { 81 // Nothing to do if the output is already spent. 82 if entry.IsSpent() { 83 return 84 } 85 86 // Mark the output as spent and modified. 87 entry.packedFlags |= tfSpent | tfModified 88 } 89 90 // Amount returns the amount of the output. 91 func (entry *UtxoEntry) Amount() int64 { 92 return entry.amount 93 } 94 95 // PkScript returns the public key script for the output. 96 func (entry *UtxoEntry) PkScript() []byte { 97 return entry.pkScript 98 } 99 100 // Clone returns a shallow copy of the utxo entry. 101 func (entry *UtxoEntry) Clone() *UtxoEntry { 102 if entry == nil { 103 return nil 104 } 105 106 return &UtxoEntry{ 107 amount: entry.amount, 108 pkScript: entry.pkScript, 109 blockHeight: entry.blockHeight, 110 packedFlags: entry.packedFlags, 111 } 112 } 113 114 // NewUtxoEntry returns a new UtxoEntry built from the arguments. 115 func NewUtxoEntry( 116 txOut *wire.TxOut, blockHeight int32, isCoinbase bool) *UtxoEntry { 117 var cbFlag txoFlags 118 if isCoinbase { 119 cbFlag |= tfCoinBase 120 } 121 122 return &UtxoEntry{ 123 amount: txOut.Value, 124 pkScript: txOut.PkScript, 125 blockHeight: blockHeight, 126 packedFlags: cbFlag, 127 } 128 } 129 130 // UtxoViewpoint represents a view into the set of unspent transaction outputs 131 // from a specific point of view in the chain. For example, it could be for 132 // the end of the main chain, some point in the history of the main chain, or 133 // down a side chain. 134 // 135 // The unspent outputs are needed by other transactions for things such as 136 // script validation and double spend prevention. 137 type UtxoViewpoint struct { 138 entries map[wire.OutPoint]*UtxoEntry 139 bestHash chainhash.Hash 140 } 141 142 // BestHash returns the hash of the best block in the chain the view currently 143 // respresents. 144 func (view *UtxoViewpoint) BestHash() *chainhash.Hash { 145 return &view.bestHash 146 } 147 148 // SetBestHash sets the hash of the best block in the chain the view currently 149 // respresents. 150 func (view *UtxoViewpoint) SetBestHash(hash *chainhash.Hash) { 151 view.bestHash = *hash 152 } 153 154 // LookupEntry returns information about a given transaction output according to 155 // the current state of the view. It will return nil if the passed output does 156 // not exist in the view or is otherwise not available such as when it has been 157 // disconnected during a reorg. 158 func (view *UtxoViewpoint) LookupEntry(outpoint wire.OutPoint) *UtxoEntry { 159 return view.entries[outpoint] 160 } 161 162 // addTxOut adds the specified output to the view if it is not provably 163 // unspendable. When the view already has an entry for the output, it will be 164 // marked unspent. All fields will be updated for existing entries since it's 165 // possible it has changed during a reorg. 166 func (view *UtxoViewpoint) addTxOut(outpoint wire.OutPoint, txOut *wire.TxOut, isCoinBase bool, blockHeight int32) { 167 // Don't add provably unspendable outputs. 168 if txscript.IsUnspendable(txOut.PkScript) { 169 return 170 } 171 172 // Update existing entries. All fields are updated because it's 173 // possible (although extremely unlikely) that the existing entry is 174 // being replaced by a different transaction with the same hash. This 175 // is allowed so long as the previous transaction is fully spent. 176 entry := view.LookupEntry(outpoint) 177 if entry == nil { 178 entry = new(UtxoEntry) 179 view.entries[outpoint] = entry 180 } 181 182 entry.amount = txOut.Value 183 entry.pkScript = txOut.PkScript 184 entry.blockHeight = blockHeight 185 entry.packedFlags = tfModified 186 if isCoinBase { 187 entry.packedFlags |= tfCoinBase 188 } 189 } 190 191 // AddTxOut adds the specified output of the passed transaction to the view if 192 // it exists and is not provably unspendable. When the view already has an 193 // entry for the output, it will be marked unspent. All fields will be updated 194 // for existing entries since it's possible it has changed during a reorg. 195 func (view *UtxoViewpoint) AddTxOut(tx *btcutil.Tx, txOutIdx uint32, blockHeight int32) { 196 // Can't add an output for an out of bounds index. 197 if txOutIdx >= uint32(len(tx.MsgTx().TxOut)) { 198 return 199 } 200 201 // Update existing entries. All fields are updated because it's 202 // possible (although extremely unlikely) that the existing entry is 203 // being replaced by a different transaction with the same hash. This 204 // is allowed so long as the previous transaction is fully spent. 205 prevOut := wire.OutPoint{Hash: *tx.Hash(), Index: txOutIdx} 206 txOut := tx.MsgTx().TxOut[txOutIdx] 207 view.addTxOut(prevOut, txOut, IsCoinBase(tx), blockHeight) 208 } 209 210 // AddTxOuts adds all outputs in the passed transaction which are not provably 211 // unspendable to the view. When the view already has entries for any of the 212 // outputs, they are simply marked unspent. All fields will be updated for 213 // existing entries since it's possible it has changed during a reorg. 214 func (view *UtxoViewpoint) AddTxOuts(tx *btcutil.Tx, blockHeight int32) { 215 // Loop all of the transaction outputs and add those which are not 216 // provably unspendable. 217 isCoinBase := IsCoinBase(tx) 218 prevOut := wire.OutPoint{Hash: *tx.Hash()} 219 for txOutIdx, txOut := range tx.MsgTx().TxOut { 220 // Update existing entries. All fields are updated because it's 221 // possible (although extremely unlikely) that the existing 222 // entry is being replaced by a different transaction with the 223 // same hash. This is allowed so long as the previous 224 // transaction is fully spent. 225 prevOut.Index = uint32(txOutIdx) 226 view.addTxOut(prevOut, txOut, isCoinBase, blockHeight) 227 } 228 } 229 230 // connectTransaction updates the view by adding all new utxos created by the 231 // passed transaction and marking all utxos that the transactions spend as 232 // spent. In addition, when the 'stxos' argument is not nil, it will be updated 233 // to append an entry for each spent txout. An error will be returned if the 234 // view does not contain the required utxos. 235 func (view *UtxoViewpoint) connectTransaction(tx *btcutil.Tx, blockHeight int32, stxos *[]SpentTxOut) error { 236 // Coinbase transactions don't have any inputs to spend. 237 if IsCoinBase(tx) { 238 // Add the transaction's outputs as available utxos. 239 view.AddTxOuts(tx, blockHeight) 240 return nil 241 } 242 243 // Spend the referenced utxos by marking them spent in the view and, 244 // if a slice was provided for the spent txout details, append an entry 245 // to it. 246 for _, txIn := range tx.MsgTx().TxIn { 247 // Ensure the referenced utxo exists in the view. This should 248 // never happen unless there is a bug is introduced in the code. 249 entry := view.entries[txIn.PreviousOutPoint] 250 if entry == nil { 251 return AssertError(fmt.Sprintf("view missing input %v", 252 txIn.PreviousOutPoint)) 253 } 254 255 // Only create the stxo details if requested. 256 if stxos != nil { 257 // Populate the stxo details using the utxo entry. 258 var stxo = SpentTxOut{ 259 Amount: entry.Amount(), 260 PkScript: entry.PkScript(), 261 Height: entry.BlockHeight(), 262 IsCoinBase: entry.IsCoinBase(), 263 } 264 *stxos = append(*stxos, stxo) 265 } 266 267 // Mark the entry as spent. This is not done until after the 268 // relevant details have been accessed since spending it might 269 // clear the fields from memory in the future. 270 entry.Spend() 271 } 272 273 // Add the transaction's outputs as available utxos. 274 view.AddTxOuts(tx, blockHeight) 275 return nil 276 } 277 278 // connectTransactions updates the view by adding all new utxos created by all 279 // of the transactions in the passed block, marking all utxos the transactions 280 // spend as spent, and setting the best hash for the view to the passed block. 281 // In addition, when the 'stxos' argument is not nil, it will be updated to 282 // append an entry for each spent txout. 283 func (view *UtxoViewpoint) connectTransactions(block *btcutil.Block, stxos *[]SpentTxOut) error { 284 for _, tx := range block.Transactions() { 285 err := view.connectTransaction(tx, block.Height(), stxos) 286 if err != nil { 287 return err 288 } 289 } 290 291 // Update the best hash for view to include this block since all of its 292 // transactions have been connected. 293 view.SetBestHash(block.Hash()) 294 return nil 295 } 296 297 // fetchEntryByHash attempts to find any available utxo for the given hash by 298 // searching the entire set of possible outputs for the given hash. It checks 299 // the view first and then falls back to the database if needed. 300 func (view *UtxoViewpoint) fetchEntryByHash(db database.DB, hash *chainhash.Hash) (*UtxoEntry, error) { 301 // First attempt to find a utxo with the provided hash in the view. 302 prevOut := wire.OutPoint{Hash: *hash} 303 for idx := uint32(0); idx < MaxOutputsPerBlock; idx++ { 304 prevOut.Index = idx 305 entry := view.LookupEntry(prevOut) 306 if entry != nil { 307 return entry, nil 308 } 309 } 310 311 // Check the database since it doesn't exist in the view. This will 312 // often by the case since only specifically referenced utxos are loaded 313 // into the view. 314 var entry *UtxoEntry 315 err := db.View(func(dbTx database.Tx) error { 316 var err error 317 entry, err = dbFetchUtxoEntryByHash(dbTx, hash) 318 return err 319 }) 320 return entry, err 321 } 322 323 // disconnectTransactions updates the view by removing all of the transactions 324 // created by the passed block, restoring all utxos the transactions spent by 325 // using the provided spent txo information, and setting the best hash for the 326 // view to the block before the passed block. 327 func (view *UtxoViewpoint) disconnectTransactions(db database.DB, block *btcutil.Block, stxos []SpentTxOut) error { 328 // Sanity check the correct number of stxos are provided. 329 if len(stxos) != countSpentOutputs(block) { 330 return AssertError("disconnectTransactions called with bad " + 331 "spent transaction out information") 332 } 333 334 // Loop backwards through all transactions so everything is unspent in 335 // reverse order. This is necessary since transactions later in a block 336 // can spend from previous ones. 337 stxoIdx := len(stxos) - 1 338 transactions := block.Transactions() 339 for txIdx := len(transactions) - 1; txIdx > -1; txIdx-- { 340 tx := transactions[txIdx] 341 342 // All entries will need to potentially be marked as a coinbase. 343 var packedFlags txoFlags 344 isCoinBase := txIdx == 0 345 if isCoinBase { 346 packedFlags |= tfCoinBase 347 } 348 349 // Mark all of the spendable outputs originally created by the 350 // transaction as spent. It is instructive to note that while 351 // the outputs aren't actually being spent here, rather they no 352 // longer exist, since a pruned utxo set is used, there is no 353 // practical difference between a utxo that does not exist and 354 // one that has been spent. 355 // 356 // When the utxo does not already exist in the view, add an 357 // entry for it and then mark it spent. This is done because 358 // the code relies on its existence in the view in order to 359 // signal modifications have happened. 360 txHash := tx.Hash() 361 prevOut := wire.OutPoint{Hash: *txHash} 362 for txOutIdx, txOut := range tx.MsgTx().TxOut { 363 if txscript.IsUnspendable(txOut.PkScript) { 364 continue 365 } 366 367 prevOut.Index = uint32(txOutIdx) 368 entry := view.entries[prevOut] 369 if entry == nil { 370 entry = &UtxoEntry{ 371 amount: txOut.Value, 372 pkScript: txOut.PkScript, 373 blockHeight: block.Height(), 374 packedFlags: packedFlags, 375 } 376 377 view.entries[prevOut] = entry 378 } 379 380 entry.Spend() 381 } 382 383 // Loop backwards through all of the transaction inputs (except 384 // for the coinbase which has no inputs) and unspend the 385 // referenced txos. This is necessary to match the order of the 386 // spent txout entries. 387 if isCoinBase { 388 continue 389 } 390 for txInIdx := len(tx.MsgTx().TxIn) - 1; txInIdx > -1; txInIdx-- { 391 // Ensure the spent txout index is decremented to stay 392 // in sync with the transaction input. 393 stxo := &stxos[stxoIdx] 394 stxoIdx-- 395 396 // When there is not already an entry for the referenced 397 // output in the view, it means it was previously spent, 398 // so create a new utxo entry in order to resurrect it. 399 originOut := &tx.MsgTx().TxIn[txInIdx].PreviousOutPoint 400 entry := view.entries[*originOut] 401 if entry == nil { 402 entry = new(UtxoEntry) 403 view.entries[*originOut] = entry 404 } 405 406 // The legacy v1 spend journal format only stored the 407 // coinbase flag and height when the output was the last 408 // unspent output of the transaction. As a result, when 409 // the information is missing, search for it by scanning 410 // all possible outputs of the transaction since it must 411 // be in one of them. 412 // 413 // It should be noted that this is quite inefficient, 414 // but it realistically will almost never run since all 415 // new entries include the information for all outputs 416 // and thus the only way this will be hit is if a long 417 // enough reorg happens such that a block with the old 418 // spend data is being disconnected. The probability of 419 // that in practice is extremely low to begin with and 420 // becomes vanishingly small the more new blocks are 421 // connected. In the case of a fresh database that has 422 // only ever run with the new v2 format, this code path 423 // will never run. 424 if stxo.Height == 0 { 425 utxo, err := view.fetchEntryByHash(db, txHash) 426 if err != nil { 427 return err 428 } 429 if utxo == nil { 430 return AssertError(fmt.Sprintf("unable "+ 431 "to resurrect legacy stxo %v", 432 *originOut)) 433 } 434 435 stxo.Height = utxo.BlockHeight() 436 stxo.IsCoinBase = utxo.IsCoinBase() 437 } 438 439 // Restore the utxo using the stxo data from the spend 440 // journal and mark it as modified. 441 entry.amount = stxo.Amount 442 entry.pkScript = stxo.PkScript 443 entry.blockHeight = stxo.Height 444 entry.packedFlags = tfModified 445 if stxo.IsCoinBase { 446 entry.packedFlags |= tfCoinBase 447 } 448 } 449 } 450 451 // Update the best hash for view to the previous block since all of the 452 // transactions for the current block have been disconnected. 453 view.SetBestHash(&block.MsgBlock().Header.PrevBlock) 454 return nil 455 } 456 457 // RemoveEntry removes the given transaction output from the current state of 458 // the view. It will have no effect if the passed output does not exist in the 459 // view. 460 func (view *UtxoViewpoint) RemoveEntry(outpoint wire.OutPoint) { 461 delete(view.entries, outpoint) 462 } 463 464 // Entries returns the underlying map that stores of all the utxo entries. 465 func (view *UtxoViewpoint) Entries() map[wire.OutPoint]*UtxoEntry { 466 return view.entries 467 } 468 469 // commit prunes all entries marked modified that are now fully spent and marks 470 // all entries as unmodified. 471 func (view *UtxoViewpoint) commit() { 472 for outpoint, entry := range view.entries { 473 if entry == nil || (entry.isModified() && entry.IsSpent()) { 474 delete(view.entries, outpoint) 475 continue 476 } 477 478 entry.packedFlags ^= tfModified 479 } 480 } 481 482 // fetchUtxosMain fetches unspent transaction output data about the provided 483 // set of outpoints from the point of view of the end of the main chain at the 484 // time of the call. 485 // 486 // Upon completion of this function, the view will contain an entry for each 487 // requested outpoint. Spent outputs, or those which otherwise don't exist, 488 // will result in a nil entry in the view. 489 func (view *UtxoViewpoint) fetchUtxosMain(db database.DB, outpoints map[wire.OutPoint]struct{}) error { 490 // Nothing to do if there are no requested outputs. 491 if len(outpoints) == 0 { 492 return nil 493 } 494 495 // Load the requested set of unspent transaction outputs from the point 496 // of view of the end of the main chain. 497 // 498 // NOTE: Missing entries are not considered an error here and instead 499 // will result in nil entries in the view. This is intentionally done 500 // so other code can use the presence of an entry in the store as a way 501 // to unnecessarily avoid attempting to reload it from the database. 502 return db.View(func(dbTx database.Tx) error { 503 for outpoint := range outpoints { 504 entry, err := dbFetchUtxoEntry(dbTx, outpoint) 505 if err != nil { 506 return err 507 } 508 509 view.entries[outpoint] = entry 510 } 511 512 return nil 513 }) 514 } 515 516 // fetchUtxos loads the unspent transaction outputs for the provided set of 517 // outputs into the view from the database as needed unless they already exist 518 // in the view in which case they are ignored. 519 func (view *UtxoViewpoint) fetchUtxos(db database.DB, outpoints map[wire.OutPoint]struct{}) error { 520 // Nothing to do if there are no requested outputs. 521 if len(outpoints) == 0 { 522 return nil 523 } 524 525 // Filter entries that are already in the view. 526 neededSet := make(map[wire.OutPoint]struct{}) 527 for outpoint := range outpoints { 528 // Already loaded into the current view. 529 if _, ok := view.entries[outpoint]; ok { 530 continue 531 } 532 533 neededSet[outpoint] = struct{}{} 534 } 535 536 // Request the input utxos from the database. 537 return view.fetchUtxosMain(db, neededSet) 538 } 539 540 // fetchInputUtxos loads the unspent transaction outputs for the inputs 541 // referenced by the transactions in the given block into the view from the 542 // database as needed. In particular, referenced entries that are earlier in 543 // the block are added to the view and entries that are already in the view are 544 // not modified. 545 func (view *UtxoViewpoint) fetchInputUtxos(db database.DB, block *btcutil.Block) error { 546 // Build a map of in-flight transactions because some of the inputs in 547 // this block could be referencing other transactions earlier in this 548 // block which are not yet in the chain. 549 txInFlight := map[chainhash.Hash]int{} 550 transactions := block.Transactions() 551 for i, tx := range transactions { 552 txInFlight[*tx.Hash()] = i 553 } 554 555 // Loop through all of the transaction inputs (except for the coinbase 556 // which has no inputs) collecting them into sets of what is needed and 557 // what is already known (in-flight). 558 neededSet := make(map[wire.OutPoint]struct{}) 559 for i, tx := range transactions[1:] { 560 for _, txIn := range tx.MsgTx().TxIn { 561 // It is acceptable for a transaction input to reference 562 // the output of another transaction in this block only 563 // if the referenced transaction comes before the 564 // current one in this block. Add the outputs of the 565 // referenced transaction as available utxos when this 566 // is the case. Otherwise, the utxo details are still 567 // needed. 568 // 569 // NOTE: The >= is correct here because i is one less 570 // than the actual position of the transaction within 571 // the block due to skipping the coinbase. 572 originHash := &txIn.PreviousOutPoint.Hash 573 if inFlightIndex, ok := txInFlight[*originHash]; ok && 574 i >= inFlightIndex { 575 576 originTx := transactions[inFlightIndex] 577 view.AddTxOuts(originTx, block.Height()) 578 continue 579 } 580 581 // Don't request entries that are already in the view 582 // from the database. 583 if _, ok := view.entries[txIn.PreviousOutPoint]; ok { 584 continue 585 } 586 587 neededSet[txIn.PreviousOutPoint] = struct{}{} 588 } 589 } 590 591 // Request the input utxos from the database. 592 return view.fetchUtxosMain(db, neededSet) 593 } 594 595 // NewUtxoViewpoint returns a new empty unspent transaction output view. 596 func NewUtxoViewpoint() *UtxoViewpoint { 597 return &UtxoViewpoint{ 598 entries: make(map[wire.OutPoint]*UtxoEntry), 599 } 600 } 601 602 // FetchUtxoView loads unspent transaction outputs for the inputs referenced by 603 // the passed transaction from the point of view of the end of the main chain. 604 // It also attempts to fetch the utxos for the outputs of the transaction itself 605 // so the returned view can be examined for duplicate transactions. 606 // 607 // This function is safe for concurrent access however the returned view is NOT. 608 func (b *BlockChain) FetchUtxoView(tx *btcutil.Tx) (*UtxoViewpoint, error) { 609 // Create a set of needed outputs based on those referenced by the 610 // inputs of the passed transaction and the outputs of the transaction 611 // itself. 612 neededSet := make(map[wire.OutPoint]struct{}) 613 prevOut := wire.OutPoint{Hash: *tx.Hash()} 614 for txOutIdx := range tx.MsgTx().TxOut { 615 prevOut.Index = uint32(txOutIdx) 616 neededSet[prevOut] = struct{}{} 617 } 618 if !IsCoinBase(tx) { 619 for _, txIn := range tx.MsgTx().TxIn { 620 neededSet[txIn.PreviousOutPoint] = struct{}{} 621 } 622 } 623 624 // Request the utxos from the point of view of the end of the main 625 // chain. 626 view := NewUtxoViewpoint() 627 b.chainLock.RLock() 628 err := view.fetchUtxosMain(b.db, neededSet) 629 b.chainLock.RUnlock() 630 return view, err 631 } 632 633 // FetchUtxoEntry loads and returns the requested unspent transaction output 634 // from the point of view of the end of the main chain. 635 // 636 // NOTE: Requesting an output for which there is no data will NOT return an 637 // error. Instead both the entry and the error will be nil. This is done to 638 // allow pruning of spent transaction outputs. In practice this means the 639 // caller must check if the returned entry is nil before invoking methods on it. 640 // 641 // This function is safe for concurrent access however the returned entry (if 642 // any) is NOT. 643 func (b *BlockChain) FetchUtxoEntry(outpoint wire.OutPoint) (*UtxoEntry, error) { 644 b.chainLock.RLock() 645 defer b.chainLock.RUnlock() 646 647 var entry *UtxoEntry 648 err := b.db.View(func(dbTx database.Tx) error { 649 var err error 650 entry, err = dbFetchUtxoEntry(dbTx, outpoint) 651 return err 652 }) 653 if err != nil { 654 return nil, err 655 } 656 657 return entry, nil 658 }