// ConnectBlock is invoked by the index manager when a new block has been // connected to the main chain. This indexer adds a mapping for each address // the transactions in the block involve. // // This is part of the Indexer interface. func (idx *AddrIndex) ConnectBlock(dbTx database.Tx, block *btcutil.Block, view *blockchain.UtxoViewpoint) error { // The offset and length of the transactions within the serialized // block. txLocs, err := block.TxLoc() if err != nil { return err } // Get the internal block ID associated with the block. blockID, err := dbFetchBlockIDByHash(dbTx, block.Hash()) if err != nil { return err } // Build all of the address to transaction mappings in a local map. addrsToTxns := make(writeIndexData) idx.indexBlock(addrsToTxns, block, view) // Add all of the index entries for each address. addrIdxBucket := dbTx.Metadata().Bucket(addrIndexKey) for addrKey, txIdxs := range addrsToTxns { for _, txIdx := range txIdxs { err := dbPutAddrIndexEntry(addrIdxBucket, addrKey, blockID, txLocs[txIdx]) if err != nil { return err } } } return nil }
// dbFetchTx looks up the passed transaction hash in the transaction index and // loads it from the database. func dbFetchTx(dbTx database.Tx, hash *wire.ShaHash) (*wire.MsgTx, error) { // Look up the location of the transaction. blockRegion, err := dbFetchTxIndexEntry(dbTx, hash) if err != nil { return nil, err } if blockRegion == nil { return nil, fmt.Errorf("transaction %v not found", hash) } // Load the raw transaction bytes from the database. txBytes, err := dbTx.FetchBlockRegion(blockRegion) if err != nil { return nil, err } // Deserialize the transaction. var msgTx wire.MsgTx err = msgTx.Deserialize(bytes.NewReader(txBytes)) if err != nil { return nil, err } return &msgTx, nil }
// TxRegionsForAddress returns a slice of block regions which identify each // transaction that involves the passed address according to the specified // number to skip, number requested, and whether or not the results should be // reversed. It also returns the number actually skipped since it could be less // in the case where there are not enough entries. // // NOTE: These results only include transactions confirmed in blocks. See the // UnconfirmedTxnsForAddress method for obtaining unconfirmed transactions // that involve a given address. // // This function is safe for concurrent access. func (idx *AddrIndex) TxRegionsForAddress(dbTx database.Tx, addr btcutil.Address, numToSkip, numRequested uint32, reverse bool) ([]database.BlockRegion, uint32, error) { addrKey, err := addrToKey(addr) if err != nil { return nil, 0, err } var regions []database.BlockRegion var skipped uint32 err = idx.db.View(func(dbTx database.Tx) error { // Create closure to lookup the block hash given the ID using // the database transaction. fetchBlockHash := func(id []byte) (*chainhash.Hash, error) { // Deserialize and populate the result. return dbFetchBlockHashBySerializedID(dbTx, id) } var err error addrIdxBucket := dbTx.Metadata().Bucket(addrIndexKey) regions, skipped, err = dbFetchAddrIndexEntries(addrIdxBucket, addrKey, numToSkip, numRequested, reverse, fetchBlockHash) return err }) return regions, skipped, err }
// maybeCreateIndexes determines if each of the enabled indexes have already // been created and creates them if not. func (m *Manager) maybeCreateIndexes(dbTx database.Tx) error { indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName) for _, indexer := range m.enabledIndexes { // Nothing to do if the index tip already exists. idxKey := indexer.Key() if indexesBucket.Get(idxKey) != nil { continue } // The tip for the index does not exist, so create it and // invoke the create callback for the index so it can perform // any one-time initialization it requires. if err := indexer.Create(dbTx); err != nil { return err } // Set the tip for the index to values which represent an // uninitialized index. err := dbPutIndexerTip(dbTx, idxKey, &wire.ShaHash{}, -1) if err != nil { return err } } return nil }
// dbFetchUtxoEntry uses an existing database transaction to fetch all unspent // outputs for the provided Bitcoin transaction hash from the utxo set. // // When there is no entry for the provided hash, nil will be returned for the // both the entry and the error. func dbFetchUtxoEntry(dbTx database.Tx, hash *chainhash.Hash) (*UtxoEntry, error) { // Fetch the unspent transaction output information for the passed // transaction hash. Return now when there is no entry. utxoBucket := dbTx.Metadata().Bucket(utxoSetBucketName) serializedUtxo := utxoBucket.Get(hash[:]) if serializedUtxo == nil { return nil, nil } // A non-nil zero-length entry means there is an entry in the database // for a fully spent transaction which should never be the case. if len(serializedUtxo) == 0 { return nil, AssertError(fmt.Sprintf("database contains entry "+ "for fully spent tx %v", hash)) } // Deserialize the utxo entry and return it. entry, err := deserializeUtxoEntry(serializedUtxo) if err != nil { // Ensure any deserialization errors are returned as database // corruption errors. if isDeserializeErr(err) { return nil, database.Error{ ErrorCode: database.ErrCorruption, Description: fmt.Sprintf("corrupt utxo entry "+ "for %v: %v", hash, err), } } return nil, err } return entry, nil }
// dbFetchTxIndexEntry uses an existing database transaction to fetch the block // region for the provided transaction hash from the transaction index. When // there is no entry for the provided hash, nil will be returned for the both // the region and the error. func dbFetchTxIndexEntry(dbTx database.Tx, txHash *chainhash.Hash) (*database.BlockRegion, error) { // Load the record from the database and return now if it doesn't exist. txIndex := dbTx.Metadata().Bucket(txIndexKey) serializedData := txIndex.Get(txHash[:]) if len(serializedData) == 0 { return nil, nil } // Ensure the serialized data has enough bytes to properly deserialize. if len(serializedData) < 12 { return nil, database.Error{ ErrorCode: database.ErrCorruption, Description: fmt.Sprintf("corrupt transaction index "+ "entry for %s", txHash), } } // Load the block hash associated with the block ID. hash, err := dbFetchBlockHashBySerializedID(dbTx, serializedData[0:4]) if err != nil { return nil, database.Error{ ErrorCode: database.ErrCorruption, Description: fmt.Sprintf("corrupt transaction index "+ "entry for %s: %v", txHash, err), } } // Deserialize the final entry. region := database.BlockRegion{Hash: &chainhash.Hash{}} copy(region.Hash[:], hash[:]) region.Offset = byteOrder.Uint32(serializedData[4:8]) region.Len = byteOrder.Uint32(serializedData[8:12]) return ®ion, nil }
// dbPutIndexerTip uses an existing database transaction to update or add the // current tip for the given index to the provided values. func dbPutIndexerTip(dbTx database.Tx, idxKey []byte, hash *wire.ShaHash, height int32) error { serialized := make([]byte, wire.HashSize+4) copy(serialized, hash[:]) byteOrder.PutUint32(serialized[wire.HashSize:], uint32(height)) indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName) return indexesBucket.Put(idxKey, serialized) }
// dbFetchBlockIDByHash uses an existing database transaction to retrieve the // block id for the provided hash from the index. func dbFetchBlockIDByHash(dbTx database.Tx, hash *chainhash.Hash) (uint32, error) { hashIndex := dbTx.Metadata().Bucket(idByHashIndexBucketName) serializedID := hashIndex.Get(hash[:]) if serializedID == nil { return 0, errNoBlockIDEntry } return byteOrder.Uint32(serializedID), nil }
// dbRemoveTxIndexEntry uses an existing database transaction to remove the most // recent transaction index entry for the given hash. func dbRemoveTxIndexEntry(dbTx database.Tx, txHash *chainhash.Hash) error { txIndex := dbTx.Metadata().Bucket(txIndexKey) serializedData := txIndex.Get(txHash[:]) if len(serializedData) == 0 { return fmt.Errorf("can't remove non-existent transaction %s "+ "from the transaction index", txHash) } return txIndex.Delete(txHash[:]) }
// Create is invoked when the indexer manager determines the index needs // to be created for the first time. It creates the buckets for the hash-based // transaction index and the internal block ID indexes. // // This is part of the Indexer interface. func (idx *TxIndex) Create(dbTx database.Tx) error { meta := dbTx.Metadata() if _, err := meta.CreateBucket(idByHashIndexBucketName); err != nil { return err } if _, err := meta.CreateBucket(hashByIDIndexBucketName); err != nil { return err } _, err := meta.CreateBucket(txIndexKey) return err }
// dbFetchHeightByHash uses an existing database transaction to retrieve the // height for the provided hash from the index. func dbFetchHeightByHash(dbTx database.Tx, hash *chainhash.Hash) (int32, error) { meta := dbTx.Metadata() hashIndex := meta.Bucket(hashIndexBucketName) serializedHeight := hashIndex.Get(hash[:]) if serializedHeight == nil { str := fmt.Sprintf("block %s is not in the main chain", hash) return 0, errNotInMainChain(str) } return int32(byteOrder.Uint32(serializedHeight)), nil }
// dbFetchBlockHashBySerializedID uses an existing database transaction to // retrieve the hash for the provided serialized block id from the index. func dbFetchBlockHashBySerializedID(dbTx database.Tx, serializedID []byte) (*chainhash.Hash, error) { idIndex := dbTx.Metadata().Bucket(hashByIDIndexBucketName) hashBytes := idIndex.Get(serializedID) if hashBytes == nil { return nil, errNoBlockIDEntry } var hash chainhash.Hash copy(hash[:], hashBytes) return &hash, nil }
// dbPutBestState uses an existing database transaction to update the best chain // state with the given parameters. func dbPutBestState(dbTx database.Tx, snapshot *BestState, workSum *big.Int) error { // Serialize the current best chain state. serializedData := serializeBestChainState(bestChainState{ hash: *snapshot.Hash, height: uint32(snapshot.Height), totalTxns: snapshot.TotalTxns, workSum: workSum, }) // Store the current best chain state into the database. return dbTx.Metadata().Put(chainStateKeyName, serializedData) }
// dbFetchHeaderByHash uses an existing database transaction to retrieve the // block header for the provided hash. func dbFetchHeaderByHash(dbTx database.Tx, hash *chainhash.Hash) (*wire.BlockHeader, error) { headerBytes, err := dbTx.FetchBlockHeader(hash) if err != nil { return nil, err } var header wire.BlockHeader err = header.Deserialize(bytes.NewReader(headerBytes)) if err != nil { return nil, err } return &header, nil }
// dbRemoveBlockIndex uses an existing database transaction remove block index // entries from the hash to height and height to hash mappings for the provided // values. func dbRemoveBlockIndex(dbTx database.Tx, hash *chainhash.Hash, height int32) error { // Remove the block hash to height mapping. meta := dbTx.Metadata() hashIndex := meta.Bucket(hashIndexBucketName) if err := hashIndex.Delete(hash[:]); err != nil { return err } // Remove the block height to hash mapping. var serializedHeight [4]byte byteOrder.PutUint32(serializedHeight[:], uint32(height)) heightIndex := meta.Bucket(heightIndexBucketName) return heightIndex.Delete(serializedHeight[:]) }
// dbFetchIndexerTip uses an existing database transaction to retrieve the // hash and height of the current tip for the provided index. func dbFetchIndexerTip(dbTx database.Tx, idxKey []byte) (*wire.ShaHash, int32, error) { indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName) serialized := indexesBucket.Get(idxKey) if len(serialized) < wire.HashSize+4 { return nil, 0, database.Error{ ErrorCode: database.ErrCorruption, Description: fmt.Sprintf("unexpected end of data for "+ "index %q tip", string(idxKey)), } } var hash wire.ShaHash copy(hash[:], serialized[:wire.HashSize]) height := int32(byteOrder.Uint32(serialized[wire.HashSize:])) return &hash, height, nil }
// DisconnectBlock is invoked by the index manager when a block has been // disconnected from the main chain. This indexer removes the address mappings // each transaction in the block involve. // // This is part of the Indexer interface. func (idx *AddrIndex) DisconnectBlock(dbTx database.Tx, block *btcutil.Block, view *blockchain.UtxoViewpoint) error { // Build all of the address to transaction mappings in a local map. addrsToTxns := make(writeIndexData) idx.indexBlock(addrsToTxns, block, view) // Remove all of the index entries for each address. bucket := dbTx.Metadata().Bucket(addrIndexKey) for addrKey, txIdxs := range addrsToTxns { err := dbRemoveAddrIndexEntries(bucket, addrKey, len(txIdxs)) if err != nil { return err } } return nil }
// dbPutBlockIDIndexEntry uses an existing database transaction to update or add // the index entries for the hash to id and id to hash mappings for the provided // values. func dbPutBlockIDIndexEntry(dbTx database.Tx, hash *chainhash.Hash, id uint32) error { // Serialize the height for use in the index entries. var serializedID [4]byte byteOrder.PutUint32(serializedID[:], id) // Add the block hash to ID mapping to the index. meta := dbTx.Metadata() hashIndex := meta.Bucket(idByHashIndexBucketName) if err := hashIndex.Put(hash[:], serializedID[:]); err != nil { return err } // Add the block ID to hash mapping to the index. idIndex := meta.Bucket(hashByIDIndexBucketName) return idIndex.Put(serializedID[:], hash[:]) }
// dbFetchHashByHeight uses an existing database transaction to retrieve the // hash for the provided height from the index. func dbFetchHashByHeight(dbTx database.Tx, height int32) (*chainhash.Hash, error) { var serializedHeight [4]byte byteOrder.PutUint32(serializedHeight[:], uint32(height)) meta := dbTx.Metadata() heightIndex := meta.Bucket(heightIndexBucketName) hashBytes := heightIndex.Get(serializedHeight[:]) if hashBytes == nil { str := fmt.Sprintf("no block at height %d exists", height) return nil, errNotInMainChain(str) } var hash chainhash.Hash copy(hash[:], hashBytes) return &hash, nil }
// dbRemoveBlockIDIndexEntry uses an existing database transaction remove index // entries from the hash to id and id to hash mappings for the provided hash. func dbRemoveBlockIDIndexEntry(dbTx database.Tx, hash *chainhash.Hash) error { // Remove the block hash to ID mapping. meta := dbTx.Metadata() hashIndex := meta.Bucket(idByHashIndexBucketName) serializedID := hashIndex.Get(hash[:]) if serializedID == nil { return nil } if err := hashIndex.Delete(hash[:]); err != nil { return err } // Remove the block ID to hash mapping. idIndex := meta.Bucket(hashByIDIndexBucketName) return idIndex.Delete(serializedID) }
// dbPutBlockIndex uses an existing database transaction to update or add the // block index entries for the hash to height and height to hash mappings for // the provided values. func dbPutBlockIndex(dbTx database.Tx, hash *chainhash.Hash, height int32) error { // Serialize the height for use in the index entries. var serializedHeight [4]byte byteOrder.PutUint32(serializedHeight[:], uint32(height)) // Add the block hash to height mapping to the index. meta := dbTx.Metadata() hashIndex := meta.Bucket(hashIndexBucketName) if err := hashIndex.Put(hash[:], serializedHeight[:]); err != nil { return err } // Add the block height to hash mapping to the index. heightIndex := meta.Bucket(heightIndexBucketName) return heightIndex.Put(serializedHeight[:], hash[:]) }
// dbPutUtxoView uses an existing database transaction to update the utxo set // in the database based on the provided utxo view contents and state. In // particular, only the entries that have been marked as modified are written // to the database. func dbPutUtxoView(dbTx database.Tx, view *UtxoViewpoint) error { utxoBucket := dbTx.Metadata().Bucket(utxoSetBucketName) for txHashIter, entry := range view.entries { // No need to update the database if the entry was not modified. if entry == nil || !entry.modified { continue } // Serialize the utxo entry without any entries that have been // spent. serialized, err := serializeUtxoEntry(entry) if err != nil { return err } // Make a copy of the hash because the iterator changes on each // loop iteration and thus slicing it directly would cause the // data to change out from under the put/delete funcs below. txHash := txHashIter // Remove the utxo entry if it is now fully spent. if serialized == nil { if err := utxoBucket.Delete(txHash[:]); err != nil { return err } continue } // At this point the utxo entry is not fully spent, so store its // serialization in the database. err = utxoBucket.Put(txHash[:], serialized) if err != nil { return err } } return nil }
// dbFetchBlockByHeight uses an existing database transaction to retrieve the // raw block for the provided height, deserialize it, and return a btcutil.Block // with the height set. func dbFetchBlockByHeight(dbTx database.Tx, height int32) (*btcutil.Block, error) { // First find the hash associated with the provided height in the index. hash, err := dbFetchHashByHeight(dbTx, height) if err != nil { return nil, err } // Load the raw block bytes from the database. blockBytes, err := dbTx.FetchBlock(hash) if err != nil { return nil, err } // Create the encapsulated block and set the height appropriately. block, err := btcutil.NewBlockFromBytes(blockBytes) if err != nil { return nil, err } block.SetHeight(height) return block, nil }
// dbFetchSpendJournalEntry fetches the spend journal entry for the passed // block and deserializes it into a slice of spent txout entries. The provided // view MUST have the utxos referenced by all of the transactions available for // the passed block since that information is required to reconstruct the spent // txouts. func dbFetchSpendJournalEntry(dbTx database.Tx, block *btcutil.Block, view *UtxoViewpoint) ([]spentTxOut, error) { // Exclude the coinbase transaction since it can't spend anything. spendBucket := dbTx.Metadata().Bucket(spendJournalBucketName) serialized := spendBucket.Get(block.Hash()[:]) blockTxns := block.MsgBlock().Transactions[1:] stxos, err := deserializeSpendJournalEntry(serialized, blockTxns, view) if err != nil { // Ensure any deserialization errors are returned as database // corruption errors. if isDeserializeErr(err) { return nil, database.Error{ ErrorCode: database.ErrCorruption, Description: fmt.Sprintf("corrupt spend "+ "information for %v: %v", block.Hash(), err), } } return nil, err } return stxos, nil }
// dbPutTxIndexEntry uses an existing database transaction to update the // transaction index given the provided serialized data that is expected to have // been serialized putTxIndexEntry. func dbPutTxIndexEntry(dbTx database.Tx, txHash *chainhash.Hash, serializedData []byte) error { txIndex := dbTx.Metadata().Bucket(txIndexKey) return txIndex.Put(txHash[:], serializedData) }
// Create is invoked when the indexer manager determines the index needs // to be created for the first time. It creates the bucket for the address // index. // // This is part of the Indexer interface. func (idx *AddrIndex) Create(dbTx database.Tx) error { _, err := dbTx.Metadata().CreateBucket(addrIndexKey) return err }
// dbRemoveSpendJournalEntry uses an existing database transaction to remove the // spend journal entry for the passed block hash. func dbRemoveSpendJournalEntry(dbTx database.Tx, blockHash *chainhash.Hash) error { spendBucket := dbTx.Metadata().Bucket(spendJournalBucketName) return spendBucket.Delete(blockHash[:]) }
// dbPutSpendJournalEntry uses an existing database transaction to update the // spend journal entry for the given block hash using the provided slice of // spent txouts. The spent txouts slice must contain an entry for every txout // the transactions in the block spend in the order they are spent. func dbPutSpendJournalEntry(dbTx database.Tx, blockHash *chainhash.Hash, stxos []spentTxOut) error { spendBucket := dbTx.Metadata().Bucket(spendJournalBucketName) serialized := serializeSpendJournalEntry(stxos) return spendBucket.Put(blockHash[:], serialized) }
// dbMainChainHasBlock uses an existing database transaction to return whether // or not the main chain contains the block identified by the provided hash. func dbMainChainHasBlock(dbTx database.Tx, hash *chainhash.Hash) bool { hashIndex := dbTx.Metadata().Bucket(hashIndexBucketName) return hashIndex.Get(hash[:]) != nil }