You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
346 lines
8.8 KiB
346 lines
8.8 KiB
// Copyright (c) 2013-2014 The btcsuite developers |
|
// Use of this source code is governed by an ISC |
|
// license that can be found in the LICENSE file. |
|
|
|
package ldb |
|
|
|
import ( |
|
"bytes" |
|
"encoding/binary" |
|
|
|
"github.com/btcsuite/btcd/database" |
|
"github.com/btcsuite/btcd/wire" |
|
"github.com/btcsuite/btcutil" |
|
"github.com/btcsuite/goleveldb/leveldb" |
|
) |
|
|
|
// FetchBlockBySha - return a btcutil Block |
|
func (db *LevelDb) FetchBlockBySha(sha *wire.ShaHash) (blk *btcutil.Block, err error) { |
|
db.dbLock.Lock() |
|
defer db.dbLock.Unlock() |
|
return db.fetchBlockBySha(sha) |
|
} |
|
|
|
// fetchBlockBySha - return a btcutil Block |
|
// Must be called with db lock held. |
|
func (db *LevelDb) fetchBlockBySha(sha *wire.ShaHash) (blk *btcutil.Block, err error) { |
|
|
|
buf, height, err := db.fetchSha(sha) |
|
if err != nil { |
|
return |
|
} |
|
|
|
blk, err = btcutil.NewBlockFromBytes(buf) |
|
if err != nil { |
|
return |
|
} |
|
blk.SetHeight(height) |
|
|
|
return |
|
} |
|
|
|
// FetchBlockHeightBySha returns the block height for the given hash. This is |
|
// part of the database.Db interface implementation. |
|
func (db *LevelDb) FetchBlockHeightBySha(sha *wire.ShaHash) (int64, error) { |
|
db.dbLock.Lock() |
|
defer db.dbLock.Unlock() |
|
|
|
return db.getBlkLoc(sha) |
|
} |
|
|
|
// FetchBlockHeaderBySha - return a ShaHash |
|
func (db *LevelDb) FetchBlockHeaderBySha(sha *wire.ShaHash) (bh *wire.BlockHeader, err error) { |
|
db.dbLock.Lock() |
|
defer db.dbLock.Unlock() |
|
|
|
// Read the raw block from the database. |
|
buf, _, err := db.fetchSha(sha) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
// Only deserialize the header portion and ensure the transaction count |
|
// is zero since this is a standalone header. |
|
var blockHeader wire.BlockHeader |
|
err = blockHeader.Deserialize(bytes.NewReader(buf)) |
|
if err != nil { |
|
return nil, err |
|
} |
|
bh = &blockHeader |
|
|
|
return bh, err |
|
} |
|
|
|
func (db *LevelDb) getBlkLoc(sha *wire.ShaHash) (int64, error) { |
|
key := shaBlkToKey(sha) |
|
|
|
data, err := db.lDb.Get(key, db.ro) |
|
if err != nil { |
|
if err == leveldb.ErrNotFound { |
|
err = database.ErrBlockShaMissing |
|
} |
|
return 0, err |
|
} |
|
|
|
// deserialize |
|
blkHeight := binary.LittleEndian.Uint64(data) |
|
|
|
return int64(blkHeight), nil |
|
} |
|
|
|
func (db *LevelDb) getBlkByHeight(blkHeight int64) (rsha *wire.ShaHash, rbuf []byte, err error) { |
|
var blkVal []byte |
|
|
|
key := int64ToKey(blkHeight) |
|
|
|
blkVal, err = db.lDb.Get(key, db.ro) |
|
if err != nil { |
|
log.Tracef("failed to find height %v", blkHeight) |
|
return // exists ??? |
|
} |
|
|
|
var sha wire.ShaHash |
|
|
|
sha.SetBytes(blkVal[0:32]) |
|
|
|
blockdata := make([]byte, len(blkVal[32:])) |
|
copy(blockdata[:], blkVal[32:]) |
|
|
|
return &sha, blockdata, nil |
|
} |
|
|
|
func (db *LevelDb) getBlk(sha *wire.ShaHash) (rblkHeight int64, rbuf []byte, err error) { |
|
var blkHeight int64 |
|
|
|
blkHeight, err = db.getBlkLoc(sha) |
|
if err != nil { |
|
return |
|
} |
|
|
|
var buf []byte |
|
|
|
_, buf, err = db.getBlkByHeight(blkHeight) |
|
if err != nil { |
|
return |
|
} |
|
return blkHeight, buf, nil |
|
} |
|
|
|
func (db *LevelDb) setBlk(sha *wire.ShaHash, blkHeight int64, buf []byte) { |
|
// serialize |
|
var lw [8]byte |
|
binary.LittleEndian.PutUint64(lw[0:8], uint64(blkHeight)) |
|
|
|
shaKey := shaBlkToKey(sha) |
|
blkKey := int64ToKey(blkHeight) |
|
|
|
blkVal := make([]byte, len(sha)+len(buf)) |
|
copy(blkVal[0:], sha[:]) |
|
copy(blkVal[len(sha):], buf) |
|
|
|
db.lBatch().Put(shaKey, lw[:]) |
|
db.lBatch().Put(blkKey, blkVal) |
|
} |
|
|
|
// insertSha stores a block hash and its associated data block with a |
|
// previous sha of `prevSha'. |
|
// insertSha shall be called with db lock held |
|
func (db *LevelDb) insertBlockData(sha *wire.ShaHash, prevSha *wire.ShaHash, buf []byte) (int64, error) { |
|
oBlkHeight, err := db.getBlkLoc(prevSha) |
|
if err != nil { |
|
// check current block count |
|
// if count != 0 { |
|
// err = database.PrevShaMissing |
|
// return |
|
// } |
|
oBlkHeight = -1 |
|
if db.nextBlock != 0 { |
|
return 0, err |
|
} |
|
} |
|
|
|
// TODO(drahn) check curfile filesize, increment curfile if this puts it over |
|
blkHeight := oBlkHeight + 1 |
|
|
|
db.setBlk(sha, blkHeight, buf) |
|
|
|
// update the last block cache |
|
db.lastBlkShaCached = true |
|
db.lastBlkSha = *sha |
|
db.lastBlkIdx = blkHeight |
|
db.nextBlock = blkHeight + 1 |
|
|
|
return blkHeight, nil |
|
} |
|
|
|
// fetchSha returns the datablock for the given ShaHash. |
|
func (db *LevelDb) fetchSha(sha *wire.ShaHash) (rbuf []byte, |
|
rblkHeight int64, err error) { |
|
var blkHeight int64 |
|
var buf []byte |
|
|
|
blkHeight, buf, err = db.getBlk(sha) |
|
if err != nil { |
|
return |
|
} |
|
|
|
return buf, blkHeight, nil |
|
} |
|
|
|
// ExistsSha looks up the given block hash |
|
// returns true if it is present in the database. |
|
func (db *LevelDb) ExistsSha(sha *wire.ShaHash) (bool, error) { |
|
db.dbLock.Lock() |
|
defer db.dbLock.Unlock() |
|
|
|
// not in cache, try database |
|
return db.blkExistsSha(sha) |
|
} |
|
|
|
// blkExistsSha looks up the given block hash |
|
// returns true if it is present in the database. |
|
// CALLED WITH LOCK HELD |
|
func (db *LevelDb) blkExistsSha(sha *wire.ShaHash) (bool, error) { |
|
key := shaBlkToKey(sha) |
|
|
|
return db.lDb.Has(key, db.ro) |
|
} |
|
|
|
// FetchBlockShaByHeight returns a block hash based on its height in the |
|
// block chain. |
|
func (db *LevelDb) FetchBlockShaByHeight(height int64) (sha *wire.ShaHash, err error) { |
|
db.dbLock.Lock() |
|
defer db.dbLock.Unlock() |
|
|
|
return db.fetchBlockShaByHeight(height) |
|
} |
|
|
|
// fetchBlockShaByHeight returns a block hash based on its height in the |
|
// block chain. |
|
func (db *LevelDb) fetchBlockShaByHeight(height int64) (rsha *wire.ShaHash, err error) { |
|
key := int64ToKey(height) |
|
|
|
blkVal, err := db.lDb.Get(key, db.ro) |
|
if err != nil { |
|
log.Tracef("failed to find height %v", height) |
|
return // exists ??? |
|
} |
|
|
|
var sha wire.ShaHash |
|
sha.SetBytes(blkVal[0:32]) |
|
|
|
return &sha, nil |
|
} |
|
|
|
// FetchHeightRange looks up a range of blocks by the start and ending |
|
// heights. Fetch is inclusive of the start height and exclusive of the |
|
// ending height. To fetch all hashes from the start height until no |
|
// more are present, use the special id `AllShas'. |
|
func (db *LevelDb) FetchHeightRange(startHeight, endHeight int64) (rshalist []wire.ShaHash, err error) { |
|
db.dbLock.Lock() |
|
defer db.dbLock.Unlock() |
|
|
|
var endidx int64 |
|
if endHeight == database.AllShas { |
|
endidx = startHeight + 500 |
|
} else { |
|
endidx = endHeight |
|
} |
|
|
|
shalist := make([]wire.ShaHash, 0, endidx-startHeight) |
|
for height := startHeight; height < endidx; height++ { |
|
// TODO(drahn) fix blkFile from height |
|
|
|
key := int64ToKey(height) |
|
blkVal, lerr := db.lDb.Get(key, db.ro) |
|
if lerr != nil { |
|
break |
|
} |
|
|
|
var sha wire.ShaHash |
|
sha.SetBytes(blkVal[0:32]) |
|
shalist = append(shalist, sha) |
|
} |
|
|
|
if err != nil { |
|
return |
|
} |
|
//log.Tracef("FetchIdxRange idx %v %v returned %v shas err %v", startHeight, endHeight, len(shalist), err) |
|
|
|
return shalist, nil |
|
} |
|
|
|
// NewestSha returns the hash and block height of the most recent (end) block of |
|
// the block chain. It will return the zero hash, -1 for the block height, and |
|
// no error (nil) if there are not any blocks in the database yet. |
|
func (db *LevelDb) NewestSha() (rsha *wire.ShaHash, rblkid int64, err error) { |
|
db.dbLock.Lock() |
|
defer db.dbLock.Unlock() |
|
|
|
if db.lastBlkIdx == -1 { |
|
return &wire.ShaHash{}, -1, nil |
|
} |
|
sha := db.lastBlkSha |
|
|
|
return &sha, db.lastBlkIdx, nil |
|
} |
|
|
|
// checkAddrIndexVersion returns an error if the address index version stored |
|
// in the database is less than the current version, or if it doesn't exist. |
|
// This function is used on startup to signal OpenDB to drop the address index |
|
// if it's in an old, incompatible format. |
|
func (db *LevelDb) checkAddrIndexVersion() error { |
|
db.dbLock.Lock() |
|
defer db.dbLock.Unlock() |
|
|
|
data, err := db.lDb.Get(addrIndexVersionKey, db.ro) |
|
if err != nil { |
|
return database.ErrAddrIndexDoesNotExist |
|
} |
|
|
|
indexVersion := binary.LittleEndian.Uint16(data) |
|
|
|
if indexVersion != uint16(addrIndexCurrentVersion) { |
|
return database.ErrAddrIndexDoesNotExist |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// fetchAddrIndexTip returns the last block height and block sha to be indexed. |
|
// Meta-data about the address tip is currently cached in memory, and will be |
|
// updated accordingly by functions that modify the state. This function is |
|
// used on start up to load the info into memory. Callers will use the public |
|
// version of this function below, which returns our cached copy. |
|
func (db *LevelDb) fetchAddrIndexTip() (*wire.ShaHash, int64, error) { |
|
db.dbLock.Lock() |
|
defer db.dbLock.Unlock() |
|
|
|
data, err := db.lDb.Get(addrIndexMetaDataKey, db.ro) |
|
if err != nil { |
|
return &wire.ShaHash{}, -1, database.ErrAddrIndexDoesNotExist |
|
} |
|
|
|
var blkSha wire.ShaHash |
|
blkSha.SetBytes(data[0:32]) |
|
|
|
blkHeight := binary.LittleEndian.Uint64(data[32:]) |
|
|
|
return &blkSha, int64(blkHeight), nil |
|
} |
|
|
|
// FetchAddrIndexTip returns the hash and block height of the most recent |
|
// block whose transactions have been indexed by address. It will return |
|
// ErrAddrIndexDoesNotExist along with a zero hash, and -1 if the |
|
// addrindex hasn't yet been built up. |
|
func (db *LevelDb) FetchAddrIndexTip() (*wire.ShaHash, int64, error) { |
|
db.dbLock.Lock() |
|
defer db.dbLock.Unlock() |
|
|
|
if db.lastAddrIndexBlkIdx == -1 { |
|
return &wire.ShaHash{}, -1, database.ErrAddrIndexDoesNotExist |
|
} |
|
sha := db.lastAddrIndexBlkSha |
|
|
|
return &sha, db.lastAddrIndexBlkIdx, nil |
|
}
|
|
|