28a5e6fc65
Co-authored-by: Brannon King <countprimes@gmail.com>
2026 lines
68 KiB
Go
2026 lines
68 KiB
Go
// Copyright (c) 2015-2016 The btcsuite developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package ffldb
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"sort"
|
|
"sync"
|
|
|
|
"github.com/btcsuite/goleveldb/leveldb"
|
|
"github.com/btcsuite/goleveldb/leveldb/comparer"
|
|
ldberrors "github.com/btcsuite/goleveldb/leveldb/errors"
|
|
"github.com/btcsuite/goleveldb/leveldb/filter"
|
|
"github.com/btcsuite/goleveldb/leveldb/iterator"
|
|
"github.com/btcsuite/goleveldb/leveldb/opt"
|
|
"github.com/btcsuite/goleveldb/leveldb/util"
|
|
"github.com/lbryio/lbcd/chaincfg/chainhash"
|
|
"github.com/lbryio/lbcd/database"
|
|
"github.com/lbryio/lbcd/database/internal/treap"
|
|
"github.com/lbryio/lbcd/wire"
|
|
btcutil "github.com/lbryio/lbcutil"
|
|
)
|
|
|
|
const (
|
|
// metadataDbName is the name used for the metadata database.
|
|
metadataDbName = "metadata"
|
|
|
|
// blockHdrSize is the size of a block header. This is simply the
|
|
// constant from wire and is only provided here for convenience since
|
|
// wire.MaxBlockHeaderPayload is quite long.
|
|
blockHdrSize = wire.MaxBlockHeaderPayload
|
|
|
|
// blockHdrOffset defines the offsets into a block index row for the
|
|
// block header.
|
|
//
|
|
// The serialized block index row format is:
|
|
// <blocklocation><blockheader>
|
|
blockHdrOffset = blockLocSize
|
|
)
|
|
|
|
var (
|
|
// byteOrder is the preferred byte order used through the database and
|
|
// block files. Sometimes big endian will be used to allow ordered byte
|
|
// sortable integer values.
|
|
byteOrder = binary.LittleEndian
|
|
|
|
// bucketIndexPrefix is the prefix used for all entries in the bucket
|
|
// index.
|
|
bucketIndexPrefix = []byte("bidx")
|
|
|
|
// curBucketIDKeyName is the name of the key used to keep track of the
|
|
// current bucket ID counter.
|
|
curBucketIDKeyName = []byte("bidx-cbid")
|
|
|
|
// metadataBucketID is the ID of the top-level metadata bucket.
|
|
// It is the value 0 encoded as an unsigned big-endian uint32.
|
|
metadataBucketID = [4]byte{}
|
|
|
|
// blockIdxBucketID is the ID of the internal block metadata bucket.
|
|
// It is the value 1 encoded as an unsigned big-endian uint32.
|
|
blockIdxBucketID = [4]byte{0x00, 0x00, 0x00, 0x01}
|
|
|
|
// blockIdxBucketName is the bucket used internally to track block
|
|
// metadata.
|
|
blockIdxBucketName = []byte("ffldb-blockidx")
|
|
|
|
// writeLocKeyName is the key used to store the current write file
|
|
// location.
|
|
writeLocKeyName = []byte("ffldb-writeloc")
|
|
)
|
|
|
|
// Common error strings.
|
|
const (
|
|
// errDbNotOpenStr is the text to use for the database.ErrDbNotOpen
|
|
// error code.
|
|
errDbNotOpenStr = "database is not open"
|
|
|
|
// errTxClosedStr is the text to use for the database.ErrTxClosed error
|
|
// code.
|
|
errTxClosedStr = "database tx is closed"
|
|
)
|
|
|
|
// bulkFetchData is allows a block location to be specified along with the
|
|
// index it was requested from. This in turn allows the bulk data loading
|
|
// functions to sort the data accesses based on the location to improve
|
|
// performance while keeping track of which result the data is for.
|
|
type bulkFetchData struct {
|
|
*blockLocation
|
|
replyIndex int
|
|
}
|
|
|
|
// bulkFetchDataSorter implements sort.Interface to allow a slice of
|
|
// bulkFetchData to be sorted. In particular it sorts by file and then
|
|
// offset so that reads from files are grouped and linear.
|
|
type bulkFetchDataSorter []bulkFetchData
|
|
|
|
// Len returns the number of items in the slice. It is part of the
|
|
// sort.Interface implementation.
|
|
func (s bulkFetchDataSorter) Len() int {
|
|
return len(s)
|
|
}
|
|
|
|
// Swap swaps the items at the passed indices. It is part of the
|
|
// sort.Interface implementation.
|
|
func (s bulkFetchDataSorter) Swap(i, j int) {
|
|
s[i], s[j] = s[j], s[i]
|
|
}
|
|
|
|
// Less returns whether the item with index i should sort before the item with
|
|
// index j. It is part of the sort.Interface implementation.
|
|
func (s bulkFetchDataSorter) Less(i, j int) bool {
|
|
if s[i].blockFileNum < s[j].blockFileNum {
|
|
return true
|
|
}
|
|
if s[i].blockFileNum > s[j].blockFileNum {
|
|
return false
|
|
}
|
|
|
|
return s[i].fileOffset < s[j].fileOffset
|
|
}
|
|
|
|
// makeDbErr creates a database.Error given a set of arguments.
|
|
func makeDbErr(c database.ErrorCode, desc string, err error) database.Error {
|
|
return database.Error{ErrorCode: c, Description: desc, Err: err}
|
|
}
|
|
|
|
// convertErr converts the passed leveldb error into a database error with an
|
|
// equivalent error code and the passed description. It also sets the passed
|
|
// error as the underlying error.
|
|
func convertErr(desc string, ldbErr error) database.Error {
|
|
// Use the driver-specific error code by default. The code below will
|
|
// update this with the converted error if it's recognized.
|
|
var code = database.ErrDriverSpecific
|
|
|
|
switch {
|
|
// Database corruption errors.
|
|
case ldberrors.IsCorrupted(ldbErr):
|
|
code = database.ErrCorruption
|
|
|
|
// Database open/create errors.
|
|
case ldbErr == leveldb.ErrClosed:
|
|
code = database.ErrDbNotOpen
|
|
|
|
// Transaction errors.
|
|
case ldbErr == leveldb.ErrSnapshotReleased:
|
|
code = database.ErrTxClosed
|
|
case ldbErr == leveldb.ErrIterReleased:
|
|
code = database.ErrTxClosed
|
|
}
|
|
|
|
return database.Error{ErrorCode: code, Description: desc, Err: ldbErr}
|
|
}
|
|
|
|
// copySlice returns a copy of the passed slice. This is mostly used to copy
|
|
// leveldb iterator keys and values since they are only valid until the iterator
|
|
// is moved instead of during the entirety of the transaction.
|
|
func copySlice(slice []byte) []byte {
|
|
ret := make([]byte, len(slice))
|
|
copy(ret, slice)
|
|
return ret
|
|
}
|
|
|
|
// cursor is an internal type used to represent a cursor over key/value pairs
|
|
// and nested buckets of a bucket and implements the database.Cursor interface.
|
|
type cursor struct {
|
|
bucket *bucket
|
|
dbIter iterator.Iterator
|
|
pendingIter iterator.Iterator
|
|
currentIter iterator.Iterator
|
|
}
|
|
|
|
// Enforce cursor implements the database.Cursor interface.
|
|
var _ database.Cursor = (*cursor)(nil)
|
|
|
|
// Bucket returns the bucket the cursor was created for.
|
|
//
|
|
// This function is part of the database.Cursor interface implementation.
|
|
func (c *cursor) Bucket() database.Bucket {
|
|
// Ensure transaction state is valid.
|
|
if err := c.bucket.tx.checkClosed(); err != nil {
|
|
return nil
|
|
}
|
|
|
|
return c.bucket
|
|
}
|
|
|
|
// Delete removes the current key/value pair the cursor is at without
|
|
// invalidating the cursor.
|
|
//
|
|
// Returns the following errors as required by the interface contract:
|
|
// - ErrIncompatibleValue if attempted when the cursor points to a nested
|
|
// bucket
|
|
// - ErrTxNotWritable if attempted against a read-only transaction
|
|
// - ErrTxClosed if the transaction has already been closed
|
|
//
|
|
// This function is part of the database.Cursor interface implementation.
|
|
func (c *cursor) Delete() error {
|
|
// Ensure transaction state is valid.
|
|
if err := c.bucket.tx.checkClosed(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Error if the cursor is exhausted.
|
|
if c.currentIter == nil {
|
|
str := "cursor is exhausted"
|
|
return makeDbErr(database.ErrIncompatibleValue, str, nil)
|
|
}
|
|
|
|
// Do not allow buckets to be deleted via the cursor.
|
|
key := c.currentIter.Key()
|
|
if bytes.HasPrefix(key, bucketIndexPrefix) {
|
|
str := "buckets may not be deleted from a cursor"
|
|
return makeDbErr(database.ErrIncompatibleValue, str, nil)
|
|
}
|
|
|
|
c.bucket.tx.deleteKey(copySlice(key), true)
|
|
return nil
|
|
}
|
|
|
|
// skipPendingUpdates skips any keys at the current database iterator position
|
|
// that are being updated by the transaction. The forwards flag indicates the
|
|
// direction the cursor is moving.
|
|
func (c *cursor) skipPendingUpdates(forwards bool) {
|
|
for c.dbIter.Valid() {
|
|
var skip bool
|
|
key := c.dbIter.Key()
|
|
if c.bucket.tx.pendingRemove.Has(key) {
|
|
skip = true
|
|
} else if c.bucket.tx.pendingKeys.Has(key) {
|
|
skip = true
|
|
}
|
|
if !skip {
|
|
break
|
|
}
|
|
|
|
if forwards {
|
|
c.dbIter.Next()
|
|
} else {
|
|
c.dbIter.Prev()
|
|
}
|
|
}
|
|
}
|
|
|
|
// chooseIterator first skips any entries in the database iterator that are
|
|
// being updated by the transaction and sets the current iterator to the
|
|
// appropriate iterator depending on their validity and the order they compare
|
|
// in while taking into account the direction flag. When the cursor is being
|
|
// moved forwards and both iterators are valid, the iterator with the smaller
|
|
// key is chosen and vice versa when the cursor is being moved backwards.
|
|
func (c *cursor) chooseIterator(forwards bool) bool {
|
|
// Skip any keys at the current database iterator position that are
|
|
// being updated by the transaction.
|
|
c.skipPendingUpdates(forwards)
|
|
|
|
// When both iterators are exhausted, the cursor is exhausted too.
|
|
if !c.dbIter.Valid() && !c.pendingIter.Valid() {
|
|
c.currentIter = nil
|
|
return false
|
|
}
|
|
|
|
// Choose the database iterator when the pending keys iterator is
|
|
// exhausted.
|
|
if !c.pendingIter.Valid() {
|
|
c.currentIter = c.dbIter
|
|
return true
|
|
}
|
|
|
|
// Choose the pending keys iterator when the database iterator is
|
|
// exhausted.
|
|
if !c.dbIter.Valid() {
|
|
c.currentIter = c.pendingIter
|
|
return true
|
|
}
|
|
|
|
// Both iterators are valid, so choose the iterator with either the
|
|
// smaller or larger key depending on the forwards flag.
|
|
compare := bytes.Compare(c.dbIter.Key(), c.pendingIter.Key())
|
|
if (forwards && compare > 0) || (!forwards && compare < 0) {
|
|
c.currentIter = c.pendingIter
|
|
} else {
|
|
c.currentIter = c.dbIter
|
|
}
|
|
return true
|
|
}
|
|
|
|
// First positions the cursor at the first key/value pair and returns whether or
|
|
// not the pair exists.
|
|
//
|
|
// This function is part of the database.Cursor interface implementation.
|
|
func (c *cursor) First() bool {
|
|
// Ensure transaction state is valid.
|
|
if err := c.bucket.tx.checkClosed(); err != nil {
|
|
return false
|
|
}
|
|
|
|
// Seek to the first key in both the database and pending iterators and
|
|
// choose the iterator that is both valid and has the smaller key.
|
|
c.dbIter.First()
|
|
c.pendingIter.First()
|
|
return c.chooseIterator(true)
|
|
}
|
|
|
|
// Last positions the cursor at the last key/value pair and returns whether or
|
|
// not the pair exists.
|
|
//
|
|
// This function is part of the database.Cursor interface implementation.
|
|
func (c *cursor) Last() bool {
|
|
// Ensure transaction state is valid.
|
|
if err := c.bucket.tx.checkClosed(); err != nil {
|
|
return false
|
|
}
|
|
|
|
// Seek to the last key in both the database and pending iterators and
|
|
// choose the iterator that is both valid and has the larger key.
|
|
c.dbIter.Last()
|
|
c.pendingIter.Last()
|
|
return c.chooseIterator(false)
|
|
}
|
|
|
|
// Next moves the cursor one key/value pair forward and returns whether or not
|
|
// the pair exists.
|
|
//
|
|
// This function is part of the database.Cursor interface implementation.
|
|
func (c *cursor) Next() bool {
|
|
// Ensure transaction state is valid.
|
|
if err := c.bucket.tx.checkClosed(); err != nil {
|
|
return false
|
|
}
|
|
|
|
// Nothing to return if cursor is exhausted.
|
|
if c.currentIter == nil {
|
|
return false
|
|
}
|
|
|
|
// Move the current iterator to the next entry and choose the iterator
|
|
// that is both valid and has the smaller key.
|
|
c.currentIter.Next()
|
|
return c.chooseIterator(true)
|
|
}
|
|
|
|
// Prev moves the cursor one key/value pair backward and returns whether or not
|
|
// the pair exists.
|
|
//
|
|
// This function is part of the database.Cursor interface implementation.
|
|
func (c *cursor) Prev() bool {
|
|
// Ensure transaction state is valid.
|
|
if err := c.bucket.tx.checkClosed(); err != nil {
|
|
return false
|
|
}
|
|
|
|
// Nothing to return if cursor is exhausted.
|
|
if c.currentIter == nil {
|
|
return false
|
|
}
|
|
|
|
// Move the current iterator to the previous entry and choose the
|
|
// iterator that is both valid and has the larger key.
|
|
c.currentIter.Prev()
|
|
return c.chooseIterator(false)
|
|
}
|
|
|
|
// Seek positions the cursor at the first key/value pair that is greater than or
|
|
// equal to the passed seek key. Returns false if no suitable key was found.
|
|
//
|
|
// This function is part of the database.Cursor interface implementation.
|
|
func (c *cursor) Seek(seek []byte) bool {
|
|
// Ensure transaction state is valid.
|
|
if err := c.bucket.tx.checkClosed(); err != nil {
|
|
return false
|
|
}
|
|
|
|
// Seek to the provided key in both the database and pending iterators
|
|
// then choose the iterator that is both valid and has the larger key.
|
|
seekKey := bucketizedKey(c.bucket.id, seek)
|
|
c.dbIter.Seek(seekKey)
|
|
c.pendingIter.Seek(seekKey)
|
|
return c.chooseIterator(true)
|
|
}
|
|
|
|
// rawKey returns the current key the cursor is pointing to without stripping
|
|
// the current bucket prefix or bucket index prefix.
|
|
func (c *cursor) rawKey() []byte {
|
|
// Nothing to return if cursor is exhausted.
|
|
if c.currentIter == nil {
|
|
return nil
|
|
}
|
|
|
|
return copySlice(c.currentIter.Key())
|
|
}
|
|
|
|
// Key returns the current key the cursor is pointing to.
|
|
//
|
|
// This function is part of the database.Cursor interface implementation.
|
|
func (c *cursor) Key() []byte {
|
|
// Ensure transaction state is valid.
|
|
if err := c.bucket.tx.checkClosed(); err != nil {
|
|
return nil
|
|
}
|
|
|
|
// Nothing to return if cursor is exhausted.
|
|
if c.currentIter == nil {
|
|
return nil
|
|
}
|
|
|
|
// Slice out the actual key name and make a copy since it is no longer
|
|
// valid after iterating to the next item.
|
|
//
|
|
// The key is after the bucket index prefix and parent ID when the
|
|
// cursor is pointing to a nested bucket.
|
|
key := c.currentIter.Key()
|
|
if bytes.HasPrefix(key, bucketIndexPrefix) {
|
|
key = key[len(bucketIndexPrefix)+4:]
|
|
return copySlice(key)
|
|
}
|
|
|
|
// The key is after the bucket ID when the cursor is pointing to a
|
|
// normal entry.
|
|
key = key[len(c.bucket.id):]
|
|
return copySlice(key)
|
|
}
|
|
|
|
// rawValue returns the current value the cursor is pointing to without
|
|
// stripping without filtering bucket index values.
|
|
func (c *cursor) rawValue() []byte {
|
|
// Nothing to return if cursor is exhausted.
|
|
if c.currentIter == nil {
|
|
return nil
|
|
}
|
|
|
|
return copySlice(c.currentIter.Value())
|
|
}
|
|
|
|
// Value returns the current value the cursor is pointing to. This will be nil
|
|
// for nested buckets.
|
|
//
|
|
// This function is part of the database.Cursor interface implementation.
|
|
func (c *cursor) Value() []byte {
|
|
// Ensure transaction state is valid.
|
|
if err := c.bucket.tx.checkClosed(); err != nil {
|
|
return nil
|
|
}
|
|
|
|
// Nothing to return if cursor is exhausted.
|
|
if c.currentIter == nil {
|
|
return nil
|
|
}
|
|
|
|
// Return nil for the value when the cursor is pointing to a nested
|
|
// bucket.
|
|
if bytes.HasPrefix(c.currentIter.Key(), bucketIndexPrefix) {
|
|
return nil
|
|
}
|
|
|
|
return copySlice(c.currentIter.Value())
|
|
}
|
|
|
|
// cursorType defines the type of cursor to create.
|
|
type cursorType int
|
|
|
|
// The following constants define the allowed cursor types.
|
|
const (
|
|
// ctKeys iterates through all of the keys in a given bucket.
|
|
ctKeys cursorType = iota
|
|
|
|
// ctBuckets iterates through all directly nested buckets in a given
|
|
// bucket.
|
|
ctBuckets
|
|
|
|
// ctFull iterates through both the keys and the directly nested buckets
|
|
// in a given bucket.
|
|
ctFull
|
|
)
|
|
|
|
// cursorFinalizer is either invoked when a cursor is being garbage collected or
|
|
// called manually to ensure the underlying cursor iterators are released.
|
|
func cursorFinalizer(c *cursor) {
|
|
c.dbIter.Release()
|
|
c.pendingIter.Release()
|
|
}
|
|
|
|
// newCursor returns a new cursor for the given bucket, bucket ID, and cursor
|
|
// type.
|
|
//
|
|
// NOTE: The caller is responsible for calling the cursorFinalizer function on
|
|
// the returned cursor.
|
|
func newCursor(b *bucket, bucketID []byte, cursorTyp cursorType) *cursor {
|
|
var dbIter, pendingIter iterator.Iterator
|
|
switch cursorTyp {
|
|
case ctKeys:
|
|
keyRange := util.BytesPrefix(bucketID)
|
|
dbIter = b.tx.snapshot.NewIterator(keyRange)
|
|
pendingKeyIter := newLdbTreapIter(b.tx, keyRange)
|
|
pendingIter = pendingKeyIter
|
|
|
|
case ctBuckets:
|
|
// The serialized bucket index key format is:
|
|
// <bucketindexprefix><parentbucketid><bucketname>
|
|
|
|
// Create an iterator for the both the database and the pending
|
|
// keys which are prefixed by the bucket index identifier and
|
|
// the provided bucket ID.
|
|
prefix := make([]byte, len(bucketIndexPrefix)+4)
|
|
copy(prefix, bucketIndexPrefix)
|
|
copy(prefix[len(bucketIndexPrefix):], bucketID)
|
|
bucketRange := util.BytesPrefix(prefix)
|
|
|
|
dbIter = b.tx.snapshot.NewIterator(bucketRange)
|
|
pendingBucketIter := newLdbTreapIter(b.tx, bucketRange)
|
|
pendingIter = pendingBucketIter
|
|
|
|
case ctFull:
|
|
fallthrough
|
|
default:
|
|
// The serialized bucket index key format is:
|
|
// <bucketindexprefix><parentbucketid><bucketname>
|
|
prefix := make([]byte, len(bucketIndexPrefix)+4)
|
|
copy(prefix, bucketIndexPrefix)
|
|
copy(prefix[len(bucketIndexPrefix):], bucketID)
|
|
bucketRange := util.BytesPrefix(prefix)
|
|
keyRange := util.BytesPrefix(bucketID)
|
|
|
|
// Since both keys and buckets are needed from the database,
|
|
// create an individual iterator for each prefix and then create
|
|
// a merged iterator from them.
|
|
dbKeyIter := b.tx.snapshot.NewIterator(keyRange)
|
|
dbBucketIter := b.tx.snapshot.NewIterator(bucketRange)
|
|
iters := []iterator.Iterator{dbKeyIter, dbBucketIter}
|
|
dbIter = iterator.NewMergedIterator(iters,
|
|
comparer.DefaultComparer, true)
|
|
|
|
// Since both keys and buckets are needed from the pending keys,
|
|
// create an individual iterator for each prefix and then create
|
|
// a merged iterator from them.
|
|
pendingKeyIter := newLdbTreapIter(b.tx, keyRange)
|
|
pendingBucketIter := newLdbTreapIter(b.tx, bucketRange)
|
|
iters = []iterator.Iterator{pendingKeyIter, pendingBucketIter}
|
|
pendingIter = iterator.NewMergedIterator(iters,
|
|
comparer.DefaultComparer, true)
|
|
}
|
|
|
|
// Create the cursor using the iterators.
|
|
return &cursor{bucket: b, dbIter: dbIter, pendingIter: pendingIter}
|
|
}
|
|
|
|
// bucket is an internal type used to represent a collection of key/value pairs
|
|
// and implements the database.Bucket interface.
|
|
type bucket struct {
|
|
tx *transaction
|
|
id [4]byte
|
|
}
|
|
|
|
// Enforce bucket implements the database.Bucket interface.
|
|
var _ database.Bucket = (*bucket)(nil)
|
|
|
|
// bucketIndexKey returns the actual key to use for storing and retrieving a
|
|
// child bucket in the bucket index. This is required because additional
|
|
// information is needed to distinguish nested buckets with the same name.
|
|
func bucketIndexKey(parentID [4]byte, key []byte) []byte {
|
|
// The serialized bucket index key format is:
|
|
// <bucketindexprefix><parentbucketid><bucketname>
|
|
indexKey := make([]byte, len(bucketIndexPrefix)+4+len(key))
|
|
copy(indexKey, bucketIndexPrefix)
|
|
copy(indexKey[len(bucketIndexPrefix):], parentID[:])
|
|
copy(indexKey[len(bucketIndexPrefix)+4:], key)
|
|
return indexKey
|
|
}
|
|
|
|
// bucketizedKey returns the actual key to use for storing and retrieving a key
|
|
// for the provided bucket ID. This is required because bucketizing is handled
|
|
// through the use of a unique prefix per bucket.
|
|
func bucketizedKey(bucketID [4]byte, key []byte) []byte {
|
|
// The serialized block index key format is:
|
|
// <bucketid><key>
|
|
bKey := make([]byte, 4+len(key))
|
|
copy(bKey, bucketID[:])
|
|
copy(bKey[4:], key)
|
|
return bKey
|
|
}
|
|
|
|
// Bucket retrieves a nested bucket with the given key. Returns nil if
|
|
// the bucket does not exist.
|
|
//
|
|
// This function is part of the database.Bucket interface implementation.
|
|
func (b *bucket) Bucket(key []byte) database.Bucket {
|
|
// Ensure transaction state is valid.
|
|
if err := b.tx.checkClosed(); err != nil {
|
|
return nil
|
|
}
|
|
|
|
// Attempt to fetch the ID for the child bucket. The bucket does not
|
|
// exist if the bucket index entry does not exist.
|
|
childID := b.tx.fetchKey(bucketIndexKey(b.id, key))
|
|
if childID == nil {
|
|
return nil
|
|
}
|
|
|
|
childBucket := &bucket{tx: b.tx}
|
|
copy(childBucket.id[:], childID)
|
|
return childBucket
|
|
}
|
|
|
|
// CreateBucket creates and returns a new nested bucket with the given key.
|
|
//
|
|
// Returns the following errors as required by the interface contract:
|
|
// - ErrBucketExists if the bucket already exists
|
|
// - ErrBucketNameRequired if the key is empty
|
|
// - ErrIncompatibleValue if the key is otherwise invalid for the particular
|
|
// implementation
|
|
// - ErrTxNotWritable if attempted against a read-only transaction
|
|
// - ErrTxClosed if the transaction has already been closed
|
|
//
|
|
// This function is part of the database.Bucket interface implementation.
|
|
func (b *bucket) CreateBucket(key []byte) (database.Bucket, error) {
|
|
// Ensure transaction state is valid.
|
|
if err := b.tx.checkClosed(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Ensure the transaction is writable.
|
|
if !b.tx.writable {
|
|
str := "create bucket requires a writable database transaction"
|
|
return nil, makeDbErr(database.ErrTxNotWritable, str, nil)
|
|
}
|
|
|
|
// Ensure a key was provided.
|
|
if len(key) == 0 {
|
|
str := "create bucket requires a key"
|
|
return nil, makeDbErr(database.ErrBucketNameRequired, str, nil)
|
|
}
|
|
|
|
// Ensure bucket does not already exist.
|
|
bidxKey := bucketIndexKey(b.id, key)
|
|
if b.tx.hasKey(bidxKey) {
|
|
str := "bucket already exists"
|
|
return nil, makeDbErr(database.ErrBucketExists, str, nil)
|
|
}
|
|
|
|
// Find the appropriate next bucket ID to use for the new bucket. In
|
|
// the case of the special internal block index, keep the fixed ID.
|
|
var childID [4]byte
|
|
if b.id == metadataBucketID && bytes.Equal(key, blockIdxBucketName) {
|
|
childID = blockIdxBucketID
|
|
} else {
|
|
var err error
|
|
childID, err = b.tx.nextBucketID()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Add the new bucket to the bucket index.
|
|
if err := b.tx.putKey(bidxKey, childID[:]); err != nil {
|
|
str := fmt.Sprintf("failed to create bucket with key %q", key)
|
|
return nil, convertErr(str, err)
|
|
}
|
|
return &bucket{tx: b.tx, id: childID}, nil
|
|
}
|
|
|
|
// CreateBucketIfNotExists creates and returns a new nested bucket with the
|
|
// given key if it does not already exist.
|
|
//
|
|
// Returns the following errors as required by the interface contract:
|
|
// - ErrBucketNameRequired if the key is empty
|
|
// - ErrIncompatibleValue if the key is otherwise invalid for the particular
|
|
// implementation
|
|
// - ErrTxNotWritable if attempted against a read-only transaction
|
|
// - ErrTxClosed if the transaction has already been closed
|
|
//
|
|
// This function is part of the database.Bucket interface implementation.
|
|
func (b *bucket) CreateBucketIfNotExists(key []byte) (database.Bucket, error) {
|
|
// Ensure transaction state is valid.
|
|
if err := b.tx.checkClosed(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Ensure the transaction is writable.
|
|
if !b.tx.writable {
|
|
str := "create bucket requires a writable database transaction"
|
|
return nil, makeDbErr(database.ErrTxNotWritable, str, nil)
|
|
}
|
|
|
|
// Return existing bucket if it already exists, otherwise create it.
|
|
if bucket := b.Bucket(key); bucket != nil {
|
|
return bucket, nil
|
|
}
|
|
return b.CreateBucket(key)
|
|
}
|
|
|
|
// DeleteBucket removes a nested bucket with the given key.
|
|
//
|
|
// Returns the following errors as required by the interface contract:
|
|
// - ErrBucketNotFound if the specified bucket does not exist
|
|
// - ErrTxNotWritable if attempted against a read-only transaction
|
|
// - ErrTxClosed if the transaction has already been closed
|
|
//
|
|
// This function is part of the database.Bucket interface implementation.
|
|
func (b *bucket) DeleteBucket(key []byte) error {
|
|
// Ensure transaction state is valid.
|
|
if err := b.tx.checkClosed(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Ensure the transaction is writable.
|
|
if !b.tx.writable {
|
|
str := "delete bucket requires a writable database transaction"
|
|
return makeDbErr(database.ErrTxNotWritable, str, nil)
|
|
}
|
|
|
|
// Attempt to fetch the ID for the child bucket. The bucket does not
|
|
// exist if the bucket index entry does not exist. In the case of the
|
|
// special internal block index, keep the fixed ID.
|
|
bidxKey := bucketIndexKey(b.id, key)
|
|
childID := b.tx.fetchKey(bidxKey)
|
|
if childID == nil {
|
|
str := fmt.Sprintf("bucket %q does not exist", key)
|
|
return makeDbErr(database.ErrBucketNotFound, str, nil)
|
|
}
|
|
|
|
// Remove all nested buckets and their keys.
|
|
childIDs := [][]byte{childID}
|
|
for len(childIDs) > 0 {
|
|
childID = childIDs[len(childIDs)-1]
|
|
childIDs = childIDs[:len(childIDs)-1]
|
|
|
|
// Delete all keys in the nested bucket.
|
|
keyCursor := newCursor(b, childID, ctKeys)
|
|
for ok := keyCursor.First(); ok; ok = keyCursor.Next() {
|
|
b.tx.deleteKey(keyCursor.rawKey(), false)
|
|
}
|
|
cursorFinalizer(keyCursor)
|
|
|
|
// Iterate through all nested buckets.
|
|
bucketCursor := newCursor(b, childID, ctBuckets)
|
|
for ok := bucketCursor.First(); ok; ok = bucketCursor.Next() {
|
|
// Push the id of the nested bucket onto the stack for
|
|
// the next iteration.
|
|
childID := bucketCursor.rawValue()
|
|
childIDs = append(childIDs, childID)
|
|
|
|
// Remove the nested bucket from the bucket index.
|
|
b.tx.deleteKey(bucketCursor.rawKey(), false)
|
|
}
|
|
cursorFinalizer(bucketCursor)
|
|
}
|
|
|
|
// Remove the nested bucket from the bucket index. Any buckets nested
|
|
// under it were already removed above.
|
|
b.tx.deleteKey(bidxKey, true)
|
|
return nil
|
|
}
|
|
|
|
// Cursor returns a new cursor, allowing for iteration over the bucket's
|
|
// key/value pairs and nested buckets in forward or backward order.
|
|
//
|
|
// You must seek to a position using the First, Last, or Seek functions before
|
|
// calling the Next, Prev, Key, or Value functions. Failure to do so will
|
|
// result in the same return values as an exhausted cursor, which is false for
|
|
// the Prev and Next functions and nil for Key and Value functions.
|
|
//
|
|
// This function is part of the database.Bucket interface implementation.
|
|
func (b *bucket) Cursor() database.Cursor {
|
|
// Ensure transaction state is valid.
|
|
if err := b.tx.checkClosed(); err != nil {
|
|
return &cursor{bucket: b}
|
|
}
|
|
|
|
// Create the cursor and setup a runtime finalizer to ensure the
|
|
// iterators are released when the cursor is garbage collected.
|
|
c := newCursor(b, b.id[:], ctFull)
|
|
runtime.SetFinalizer(c, cursorFinalizer)
|
|
return c
|
|
}
|
|
|
|
// ForEach invokes the passed function with every key/value pair in the bucket.
|
|
// This does not include nested buckets or the key/value pairs within those
|
|
// nested buckets.
|
|
//
|
|
// WARNING: It is not safe to mutate data while iterating with this method.
|
|
// Doing so may cause the underlying cursor to be invalidated and return
|
|
// unexpected keys and/or values.
|
|
//
|
|
// Returns the following errors as required by the interface contract:
|
|
// - ErrTxClosed if the transaction has already been closed
|
|
//
|
|
// NOTE: The values returned by this function are only valid during a
|
|
// transaction. Attempting to access them after a transaction has ended will
|
|
// likely result in an access violation.
|
|
//
|
|
// This function is part of the database.Bucket interface implementation.
|
|
func (b *bucket) ForEach(fn func(k, v []byte) error) error {
|
|
// Ensure transaction state is valid.
|
|
if err := b.tx.checkClosed(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Invoke the callback for each cursor item. Return the error returned
|
|
// from the callback when it is non-nil.
|
|
c := newCursor(b, b.id[:], ctKeys)
|
|
defer cursorFinalizer(c)
|
|
for ok := c.First(); ok; ok = c.Next() {
|
|
err := fn(c.Key(), c.Value())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ForEachBucket invokes the passed function with the key of every nested bucket
|
|
// in the current bucket. This does not include any nested buckets within those
|
|
// nested buckets.
|
|
//
|
|
// WARNING: It is not safe to mutate data while iterating with this method.
|
|
// Doing so may cause the underlying cursor to be invalidated and return
|
|
// unexpected keys.
|
|
//
|
|
// Returns the following errors as required by the interface contract:
|
|
// - ErrTxClosed if the transaction has already been closed
|
|
//
|
|
// NOTE: The values returned by this function are only valid during a
|
|
// transaction. Attempting to access them after a transaction has ended will
|
|
// likely result in an access violation.
|
|
//
|
|
// This function is part of the database.Bucket interface implementation.
|
|
func (b *bucket) ForEachBucket(fn func(k []byte) error) error {
|
|
// Ensure transaction state is valid.
|
|
if err := b.tx.checkClosed(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Invoke the callback for each cursor item. Return the error returned
|
|
// from the callback when it is non-nil.
|
|
c := newCursor(b, b.id[:], ctBuckets)
|
|
defer cursorFinalizer(c)
|
|
for ok := c.First(); ok; ok = c.Next() {
|
|
err := fn(c.Key())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Writable returns whether or not the bucket is writable.
|
|
//
|
|
// This function is part of the database.Bucket interface implementation.
|
|
func (b *bucket) Writable() bool {
|
|
return b.tx.writable
|
|
}
|
|
|
|
// Put saves the specified key/value pair to the bucket. Keys that do not
|
|
// already exist are added and keys that already exist are overwritten.
|
|
//
|
|
// Returns the following errors as required by the interface contract:
|
|
// - ErrKeyRequired if the key is empty
|
|
// - ErrIncompatibleValue if the key is the same as an existing bucket
|
|
// - ErrTxNotWritable if attempted against a read-only transaction
|
|
// - ErrTxClosed if the transaction has already been closed
|
|
//
|
|
// This function is part of the database.Bucket interface implementation.
|
|
func (b *bucket) Put(key, value []byte) error {
|
|
// Ensure transaction state is valid.
|
|
if err := b.tx.checkClosed(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Ensure the transaction is writable.
|
|
if !b.tx.writable {
|
|
str := "setting a key requires a writable database transaction"
|
|
return makeDbErr(database.ErrTxNotWritable, str, nil)
|
|
}
|
|
|
|
// Ensure a key was provided.
|
|
if len(key) == 0 {
|
|
str := "put requires a key"
|
|
return makeDbErr(database.ErrKeyRequired, str, nil)
|
|
}
|
|
|
|
return b.tx.putKey(bucketizedKey(b.id, key), value)
|
|
}
|
|
|
|
// Get returns the value for the given key. Returns nil if the key does not
|
|
// exist in this bucket. An empty slice is returned for keys that exist but
|
|
// have no value assigned.
|
|
//
|
|
// NOTE: The value returned by this function is only valid during a transaction.
|
|
// Attempting to access it after a transaction has ended results in undefined
|
|
// behavior. Additionally, the value must NOT be modified by the caller.
|
|
//
|
|
// This function is part of the database.Bucket interface implementation.
|
|
func (b *bucket) Get(key []byte) []byte {
|
|
// Ensure transaction state is valid.
|
|
if err := b.tx.checkClosed(); err != nil {
|
|
return nil
|
|
}
|
|
|
|
// Nothing to return if there is no key.
|
|
if len(key) == 0 {
|
|
return nil
|
|
}
|
|
|
|
return b.tx.fetchKey(bucketizedKey(b.id, key))
|
|
}
|
|
|
|
// Delete removes the specified key from the bucket. Deleting a key that does
|
|
// not exist does not return an error.
|
|
//
|
|
// Returns the following errors as required by the interface contract:
|
|
// - ErrKeyRequired if the key is empty
|
|
// - ErrIncompatibleValue if the key is the same as an existing bucket
|
|
// - ErrTxNotWritable if attempted against a read-only transaction
|
|
// - ErrTxClosed if the transaction has already been closed
|
|
//
|
|
// This function is part of the database.Bucket interface implementation.
|
|
func (b *bucket) Delete(key []byte) error {
|
|
// Ensure transaction state is valid.
|
|
if err := b.tx.checkClosed(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Ensure the transaction is writable.
|
|
if !b.tx.writable {
|
|
str := "deleting a value requires a writable database transaction"
|
|
return makeDbErr(database.ErrTxNotWritable, str, nil)
|
|
}
|
|
|
|
// Nothing to do if there is no key.
|
|
if len(key) == 0 {
|
|
return nil
|
|
}
|
|
|
|
b.tx.deleteKey(bucketizedKey(b.id, key), true)
|
|
return nil
|
|
}
|
|
|
|
// pendingBlock houses a block that will be written to disk when the database
|
|
// transaction is committed.
|
|
type pendingBlock struct {
|
|
hash *chainhash.Hash
|
|
bytes []byte
|
|
}
|
|
|
|
// transaction represents a database transaction. It can either be read-only or
|
|
// read-write and implements the database.Tx interface. The transaction
|
|
// provides a root bucket against which all read and writes occur.
|
|
type transaction struct {
|
|
managed bool // Is the transaction managed?
|
|
closed bool // Is the transaction closed?
|
|
writable bool // Is the transaction writable?
|
|
db *db // DB instance the tx was created from.
|
|
snapshot *dbCacheSnapshot // Underlying snapshot for txns.
|
|
metaBucket *bucket // The root metadata bucket.
|
|
blockIdxBucket *bucket // The block index bucket.
|
|
|
|
// Blocks that need to be stored on commit. The pendingBlocks map is
|
|
// kept to allow quick lookups of pending data by block hash.
|
|
pendingBlocks map[chainhash.Hash]int
|
|
pendingBlockData []pendingBlock
|
|
|
|
// Keys that need to be stored or deleted on commit.
|
|
pendingKeys *treap.Mutable
|
|
pendingRemove *treap.Mutable
|
|
|
|
// Active iterators that need to be notified when the pending keys have
|
|
// been updated so the cursors can properly handle updates to the
|
|
// transaction state.
|
|
activeIterLock sync.RWMutex
|
|
activeIters []*treap.Iterator
|
|
}
|
|
|
|
// Enforce transaction implements the database.Tx interface.
|
|
var _ database.Tx = (*transaction)(nil)
|
|
|
|
// removeActiveIter removes the passed iterator from the list of active
|
|
// iterators against the pending keys treap.
|
|
func (tx *transaction) removeActiveIter(iter *treap.Iterator) {
|
|
// An indexing for loop is intentionally used over a range here as range
|
|
// does not reevaluate the slice on each iteration nor does it adjust
|
|
// the index for the modified slice.
|
|
tx.activeIterLock.Lock()
|
|
for i := 0; i < len(tx.activeIters); i++ {
|
|
if tx.activeIters[i] == iter {
|
|
copy(tx.activeIters[i:], tx.activeIters[i+1:])
|
|
tx.activeIters[len(tx.activeIters)-1] = nil
|
|
tx.activeIters = tx.activeIters[:len(tx.activeIters)-1]
|
|
}
|
|
}
|
|
tx.activeIterLock.Unlock()
|
|
}
|
|
|
|
// addActiveIter adds the passed iterator to the list of active iterators for
|
|
// the pending keys treap.
|
|
func (tx *transaction) addActiveIter(iter *treap.Iterator) {
|
|
tx.activeIterLock.Lock()
|
|
tx.activeIters = append(tx.activeIters, iter)
|
|
tx.activeIterLock.Unlock()
|
|
}
|
|
|
|
// notifyActiveIters notifies all of the active iterators for the pending keys
|
|
// treap that it has been updated.
|
|
func (tx *transaction) notifyActiveIters() {
|
|
tx.activeIterLock.RLock()
|
|
for _, iter := range tx.activeIters {
|
|
iter.ForceReseek()
|
|
}
|
|
tx.activeIterLock.RUnlock()
|
|
}
|
|
|
|
// checkClosed returns an error if the the database or transaction is closed.
|
|
func (tx *transaction) checkClosed() error {
|
|
// The transaction is no longer valid if it has been closed.
|
|
if tx.closed {
|
|
return makeDbErr(database.ErrTxClosed, errTxClosedStr, nil)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// hasKey returns whether or not the provided key exists in the database while
|
|
// taking into account the current transaction state.
|
|
func (tx *transaction) hasKey(key []byte) bool {
|
|
// When the transaction is writable, check the pending transaction
|
|
// state first.
|
|
if tx.writable {
|
|
if tx.pendingRemove.Has(key) {
|
|
return false
|
|
}
|
|
if tx.pendingKeys.Has(key) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
// Consult the database cache and underlying database.
|
|
return tx.snapshot.Has(key)
|
|
}
|
|
|
|
// putKey adds the provided key to the list of keys to be updated in the
|
|
// database when the transaction is committed.
|
|
//
|
|
// NOTE: This function must only be called on a writable transaction. Since it
|
|
// is an internal helper function, it does not check.
|
|
func (tx *transaction) putKey(key, value []byte) error {
|
|
// Prevent the key from being deleted if it was previously scheduled
|
|
// to be deleted on transaction commit.
|
|
tx.pendingRemove.Delete(key)
|
|
|
|
// Add the key/value pair to the list to be written on transaction
|
|
// commit.
|
|
tx.pendingKeys.Put(key, value)
|
|
tx.notifyActiveIters()
|
|
return nil
|
|
}
|
|
|
|
// fetchKey attempts to fetch the provided key from the database cache (and
|
|
// hence underlying database) while taking into account the current transaction
|
|
// state. Returns nil if the key does not exist.
|
|
func (tx *transaction) fetchKey(key []byte) []byte {
|
|
// When the transaction is writable, check the pending transaction
|
|
// state first.
|
|
if tx.writable {
|
|
if tx.pendingRemove.Has(key) {
|
|
return nil
|
|
}
|
|
if value := tx.pendingKeys.Get(key); value != nil {
|
|
return value
|
|
}
|
|
}
|
|
|
|
// Consult the database cache and underlying database.
|
|
return tx.snapshot.Get(key)
|
|
}
|
|
|
|
// deleteKey adds the provided key to the list of keys to be deleted from the
|
|
// database when the transaction is committed. The notify iterators flag is
|
|
// useful to delay notifying iterators about the changes during bulk deletes.
|
|
//
|
|
// NOTE: This function must only be called on a writable transaction. Since it
|
|
// is an internal helper function, it does not check.
|
|
func (tx *transaction) deleteKey(key []byte, notifyIterators bool) {
|
|
// Remove the key from the list of pendings keys to be written on
|
|
// transaction commit if needed.
|
|
tx.pendingKeys.Delete(key)
|
|
|
|
// Add the key to the list to be deleted on transaction commit.
|
|
tx.pendingRemove.Put(key, nil)
|
|
|
|
// Notify the active iterators about the change if the flag is set.
|
|
if notifyIterators {
|
|
tx.notifyActiveIters()
|
|
}
|
|
}
|
|
|
|
// nextBucketID returns the next bucket ID to use for creating a new bucket.
|
|
//
|
|
// NOTE: This function must only be called on a writable transaction. Since it
|
|
// is an internal helper function, it does not check.
|
|
func (tx *transaction) nextBucketID() ([4]byte, error) {
|
|
// Load the currently highest used bucket ID.
|
|
curIDBytes := tx.fetchKey(curBucketIDKeyName)
|
|
curBucketNum := binary.BigEndian.Uint32(curIDBytes)
|
|
|
|
// Increment and update the current bucket ID and return it.
|
|
var nextBucketID [4]byte
|
|
binary.BigEndian.PutUint32(nextBucketID[:], curBucketNum+1)
|
|
if err := tx.putKey(curBucketIDKeyName, nextBucketID[:]); err != nil {
|
|
return [4]byte{}, err
|
|
}
|
|
return nextBucketID, nil
|
|
}
|
|
|
|
// Metadata returns the top-most bucket for all metadata storage.
|
|
//
|
|
// This function is part of the database.Tx interface implementation.
|
|
func (tx *transaction) Metadata() database.Bucket {
|
|
return tx.metaBucket
|
|
}
|
|
|
|
// hasBlock returns whether or not a block with the given hash exists.
|
|
func (tx *transaction) hasBlock(hash *chainhash.Hash) bool {
|
|
// Return true if the block is pending to be written on commit since
|
|
// it exists from the viewpoint of this transaction.
|
|
if _, exists := tx.pendingBlocks[*hash]; exists {
|
|
return true
|
|
}
|
|
|
|
return tx.hasKey(bucketizedKey(blockIdxBucketID, hash[:]))
|
|
}
|
|
|
|
// StoreBlock stores the provided block into the database. There are no checks
|
|
// to ensure the block connects to a previous block, contains double spends, or
|
|
// any additional functionality such as transaction indexing. It simply stores
|
|
// the block in the database.
|
|
//
|
|
// Returns the following errors as required by the interface contract:
|
|
// - ErrBlockExists when the block hash already exists
|
|
// - ErrTxNotWritable if attempted against a read-only transaction
|
|
// - ErrTxClosed if the transaction has already been closed
|
|
//
|
|
// This function is part of the database.Tx interface implementation.
|
|
func (tx *transaction) StoreBlock(block *btcutil.Block) error {
|
|
// Ensure transaction state is valid.
|
|
if err := tx.checkClosed(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Ensure the transaction is writable.
|
|
if !tx.writable {
|
|
str := "store block requires a writable database transaction"
|
|
return makeDbErr(database.ErrTxNotWritable, str, nil)
|
|
}
|
|
|
|
// Reject the block if it already exists.
|
|
blockHash := block.Hash()
|
|
if tx.hasBlock(blockHash) {
|
|
str := fmt.Sprintf("block %s already exists", blockHash)
|
|
return makeDbErr(database.ErrBlockExists, str, nil)
|
|
}
|
|
|
|
blockBytes, err := block.Bytes()
|
|
if err != nil {
|
|
str := fmt.Sprintf("failed to get serialized bytes for block %s",
|
|
blockHash)
|
|
return makeDbErr(database.ErrDriverSpecific, str, err)
|
|
}
|
|
|
|
// Add the block to be stored to the list of pending blocks to store
|
|
// when the transaction is committed. Also, add it to pending blocks
|
|
// map so it is easy to determine the block is pending based on the
|
|
// block hash.
|
|
if tx.pendingBlocks == nil {
|
|
tx.pendingBlocks = make(map[chainhash.Hash]int)
|
|
}
|
|
tx.pendingBlocks[*blockHash] = len(tx.pendingBlockData)
|
|
tx.pendingBlockData = append(tx.pendingBlockData, pendingBlock{
|
|
hash: blockHash,
|
|
bytes: blockBytes,
|
|
})
|
|
log.Tracef("Added block %s to pending blocks", blockHash)
|
|
|
|
return nil
|
|
}
|
|
|
|
// HasBlock returns whether or not a block with the given hash exists in the
|
|
// database.
|
|
//
|
|
// Returns the following errors as required by the interface contract:
|
|
// - ErrTxClosed if the transaction has already been closed
|
|
//
|
|
// This function is part of the database.Tx interface implementation.
|
|
func (tx *transaction) HasBlock(hash *chainhash.Hash) (bool, error) {
|
|
// Ensure transaction state is valid.
|
|
if err := tx.checkClosed(); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return tx.hasBlock(hash), nil
|
|
}
|
|
|
|
// HasBlocks returns whether or not the blocks with the provided hashes
|
|
// exist in the database.
|
|
//
|
|
// Returns the following errors as required by the interface contract:
|
|
// - ErrTxClosed if the transaction has already been closed
|
|
//
|
|
// This function is part of the database.Tx interface implementation.
|
|
func (tx *transaction) HasBlocks(hashes []chainhash.Hash) ([]bool, error) {
|
|
// Ensure transaction state is valid.
|
|
if err := tx.checkClosed(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
results := make([]bool, len(hashes))
|
|
for i := range hashes {
|
|
results[i] = tx.hasBlock(&hashes[i])
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
// fetchBlockRow fetches the metadata stored in the block index for the provided
|
|
// hash. It will return ErrBlockNotFound if there is no entry.
|
|
func (tx *transaction) fetchBlockRow(hash *chainhash.Hash) ([]byte, error) {
|
|
blockRow := tx.blockIdxBucket.Get(hash[:])
|
|
if blockRow == nil {
|
|
str := fmt.Sprintf("block %s does not exist", hash)
|
|
return nil, makeDbErr(database.ErrBlockNotFound, str, nil)
|
|
}
|
|
|
|
return blockRow, nil
|
|
}
|
|
|
|
// FetchBlockHeader returns the raw serialized bytes for the block header
|
|
// identified by the given hash. The raw bytes are in the format returned by
|
|
// Serialize on a wire.BlockHeader.
|
|
//
|
|
// Returns the following errors as required by the interface contract:
|
|
// - ErrBlockNotFound if the requested block hash does not exist
|
|
// - ErrTxClosed if the transaction has already been closed
|
|
// - ErrCorruption if the database has somehow become corrupted
|
|
//
|
|
// NOTE: The data returned by this function is only valid during a
|
|
// database transaction. Attempting to access it after a transaction
|
|
// has ended results in undefined behavior. This constraint prevents
|
|
// additional data copies and allows support for memory-mapped database
|
|
// implementations.
|
|
//
|
|
// This function is part of the database.Tx interface implementation.
|
|
func (tx *transaction) FetchBlockHeader(hash *chainhash.Hash) ([]byte, error) {
|
|
return tx.FetchBlockRegion(&database.BlockRegion{
|
|
Hash: hash,
|
|
Offset: 0,
|
|
Len: blockHdrSize,
|
|
})
|
|
}
|
|
|
|
// FetchBlockHeaders returns the raw serialized bytes for the block headers
|
|
// identified by the given hashes. The raw bytes are in the format returned by
|
|
// Serialize on a wire.BlockHeader.
|
|
//
|
|
// Returns the following errors as required by the interface contract:
|
|
// - ErrBlockNotFound if the any of the requested block hashes do not exist
|
|
// - ErrTxClosed if the transaction has already been closed
|
|
// - ErrCorruption if the database has somehow become corrupted
|
|
//
|
|
// NOTE: The data returned by this function is only valid during a database
|
|
// transaction. Attempting to access it after a transaction has ended results
|
|
// in undefined behavior. This constraint prevents additional data copies and
|
|
// allows support for memory-mapped database implementations.
|
|
//
|
|
// This function is part of the database.Tx interface implementation.
|
|
func (tx *transaction) FetchBlockHeaders(hashes []chainhash.Hash) ([][]byte, error) {
|
|
regions := make([]database.BlockRegion, len(hashes))
|
|
for i := range hashes {
|
|
regions[i].Hash = &hashes[i]
|
|
regions[i].Offset = 0
|
|
regions[i].Len = blockHdrSize
|
|
}
|
|
return tx.FetchBlockRegions(regions)
|
|
}
|
|
|
|
// FetchBlock returns the raw serialized bytes for the block identified by the
|
|
// given hash. The raw bytes are in the format returned by Serialize on a
|
|
// wire.MsgBlock.
|
|
//
|
|
// Returns the following errors as required by the interface contract:
|
|
// - ErrBlockNotFound if the requested block hash does not exist
|
|
// - ErrTxClosed if the transaction has already been closed
|
|
// - ErrCorruption if the database has somehow become corrupted
|
|
//
|
|
// In addition, returns ErrDriverSpecific if any failures occur when reading the
|
|
// block files.
|
|
//
|
|
// NOTE: The data returned by this function is only valid during a database
|
|
// transaction. Attempting to access it after a transaction has ended results
|
|
// in undefined behavior. This constraint prevents additional data copies and
|
|
// allows support for memory-mapped database implementations.
|
|
//
|
|
// This function is part of the database.Tx interface implementation.
|
|
func (tx *transaction) FetchBlock(hash *chainhash.Hash) ([]byte, error) {
|
|
// Ensure transaction state is valid.
|
|
if err := tx.checkClosed(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// When the block is pending to be written on commit return the bytes
|
|
// from there.
|
|
if idx, exists := tx.pendingBlocks[*hash]; exists {
|
|
return tx.pendingBlockData[idx].bytes, nil
|
|
}
|
|
|
|
// Lookup the location of the block in the files from the block index.
|
|
blockRow, err := tx.fetchBlockRow(hash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
location := deserializeBlockLoc(blockRow)
|
|
|
|
// Read the block from the appropriate location. The function also
|
|
// performs a checksum over the data to detect data corruption.
|
|
blockBytes, err := tx.db.store.readBlock(hash, location)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return blockBytes, nil
|
|
}
|
|
|
|
// FetchBlocks returns the raw serialized bytes for the blocks identified by the
|
|
// given hashes. The raw bytes are in the format returned by Serialize on a
|
|
// wire.MsgBlock.
|
|
//
|
|
// Returns the following errors as required by the interface contract:
|
|
// - ErrBlockNotFound if any of the requested block hashed do not exist
|
|
// - ErrTxClosed if the transaction has already been closed
|
|
// - ErrCorruption if the database has somehow become corrupted
|
|
//
|
|
// In addition, returns ErrDriverSpecific if any failures occur when reading the
|
|
// block files.
|
|
//
|
|
// NOTE: The data returned by this function is only valid during a database
|
|
// transaction. Attempting to access it after a transaction has ended results
|
|
// in undefined behavior. This constraint prevents additional data copies and
|
|
// allows support for memory-mapped database implementations.
|
|
//
|
|
// This function is part of the database.Tx interface implementation.
|
|
func (tx *transaction) FetchBlocks(hashes []chainhash.Hash) ([][]byte, error) {
|
|
// Ensure transaction state is valid.
|
|
if err := tx.checkClosed(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// NOTE: This could check for the existence of all blocks before loading
|
|
// any of them which would be faster in the failure case, however
|
|
// callers will not typically be calling this function with invalid
|
|
// values, so optimize for the common case.
|
|
|
|
// Load the blocks.
|
|
blocks := make([][]byte, len(hashes))
|
|
for i := range hashes {
|
|
var err error
|
|
blocks[i], err = tx.FetchBlock(&hashes[i])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return blocks, nil
|
|
}
|
|
|
|
// fetchPendingRegion attempts to fetch the provided region from any block which
|
|
// are pending to be written on commit. It will return nil for the byte slice
|
|
// when the region references a block which is not pending. When the region
|
|
// does reference a pending block, it is bounds checked and returns
|
|
// ErrBlockRegionInvalid if invalid.
|
|
func (tx *transaction) fetchPendingRegion(region *database.BlockRegion) ([]byte, error) {
|
|
// Nothing to do if the block is not pending to be written on commit.
|
|
idx, exists := tx.pendingBlocks[*region.Hash]
|
|
if !exists {
|
|
return nil, nil
|
|
}
|
|
|
|
// Ensure the region is within the bounds of the block.
|
|
blockBytes := tx.pendingBlockData[idx].bytes
|
|
blockLen := uint32(len(blockBytes))
|
|
endOffset := region.Offset + region.Len
|
|
if endOffset < region.Offset || endOffset > blockLen {
|
|
str := fmt.Sprintf("block %s region offset %d, length %d "+
|
|
"exceeds block length of %d", region.Hash,
|
|
region.Offset, region.Len, blockLen)
|
|
return nil, makeDbErr(database.ErrBlockRegionInvalid, str, nil)
|
|
}
|
|
|
|
// Return the bytes from the pending block.
|
|
return blockBytes[region.Offset:endOffset:endOffset], nil
|
|
}
|
|
|
|
// FetchBlockRegion returns the raw serialized bytes for the given block region.
|
|
//
|
|
// For example, it is possible to directly extract Bitcoin transactions and/or
|
|
// scripts from a block with this function. Depending on the backend
|
|
// implementation, this can provide significant savings by avoiding the need to
|
|
// load entire blocks.
|
|
//
|
|
// The raw bytes are in the format returned by Serialize on a wire.MsgBlock and
|
|
// the Offset field in the provided BlockRegion is zero-based and relative to
|
|
// the start of the block (byte 0).
|
|
//
|
|
// Returns the following errors as required by the interface contract:
|
|
// - ErrBlockNotFound if the requested block hash does not exist
|
|
// - ErrBlockRegionInvalid if the region exceeds the bounds of the associated
|
|
// block
|
|
// - ErrTxClosed if the transaction has already been closed
|
|
// - ErrCorruption if the database has somehow become corrupted
|
|
//
|
|
// In addition, returns ErrDriverSpecific if any failures occur when reading the
|
|
// block files.
|
|
//
|
|
// NOTE: The data returned by this function is only valid during a database
|
|
// transaction. Attempting to access it after a transaction has ended results
|
|
// in undefined behavior. This constraint prevents additional data copies and
|
|
// allows support for memory-mapped database implementations.
|
|
//
|
|
// This function is part of the database.Tx interface implementation.
|
|
func (tx *transaction) FetchBlockRegion(region *database.BlockRegion) ([]byte, error) {
|
|
// Ensure transaction state is valid.
|
|
if err := tx.checkClosed(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// When the block is pending to be written on commit return the bytes
|
|
// from there.
|
|
if tx.pendingBlocks != nil {
|
|
regionBytes, err := tx.fetchPendingRegion(region)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if regionBytes != nil {
|
|
return regionBytes, nil
|
|
}
|
|
}
|
|
|
|
// Lookup the location of the block in the files from the block index.
|
|
blockRow, err := tx.fetchBlockRow(region.Hash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
location := deserializeBlockLoc(blockRow)
|
|
|
|
// Ensure the region is within the bounds of the block.
|
|
endOffset := region.Offset + region.Len
|
|
if endOffset < region.Offset || endOffset > location.blockLen {
|
|
str := fmt.Sprintf("block %s region offset %d, length %d "+
|
|
"exceeds block length of %d", region.Hash,
|
|
region.Offset, region.Len, location.blockLen)
|
|
return nil, makeDbErr(database.ErrBlockRegionInvalid, str, nil)
|
|
|
|
}
|
|
|
|
// Read the region from the appropriate disk block file.
|
|
regionBytes, err := tx.db.store.readBlockRegion(location, region.Offset,
|
|
region.Len)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return regionBytes, nil
|
|
}
|
|
|
|
// FetchBlockRegions returns the raw serialized bytes for the given block
|
|
// regions.
|
|
//
|
|
// For example, it is possible to directly extract Bitcoin transactions and/or
|
|
// scripts from various blocks with this function. Depending on the backend
|
|
// implementation, this can provide significant savings by avoiding the need to
|
|
// load entire blocks.
|
|
//
|
|
// The raw bytes are in the format returned by Serialize on a wire.MsgBlock and
|
|
// the Offset fields in the provided BlockRegions are zero-based and relative to
|
|
// the start of the block (byte 0).
|
|
//
|
|
// Returns the following errors as required by the interface contract:
|
|
// - ErrBlockNotFound if any of the request block hashes do not exist
|
|
// - ErrBlockRegionInvalid if one or more region exceed the bounds of the
|
|
// associated block
|
|
// - ErrTxClosed if the transaction has already been closed
|
|
// - ErrCorruption if the database has somehow become corrupted
|
|
//
|
|
// In addition, returns ErrDriverSpecific if any failures occur when reading the
|
|
// block files.
|
|
//
|
|
// NOTE: The data returned by this function is only valid during a database
|
|
// transaction. Attempting to access it after a transaction has ended results
|
|
// in undefined behavior. This constraint prevents additional data copies and
|
|
// allows support for memory-mapped database implementations.
|
|
//
|
|
// This function is part of the database.Tx interface implementation.
|
|
func (tx *transaction) FetchBlockRegions(regions []database.BlockRegion) ([][]byte, error) {
|
|
// Ensure transaction state is valid.
|
|
if err := tx.checkClosed(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// NOTE: This could check for the existence of all blocks before
|
|
// deserializing the locations and building up the fetch list which
|
|
// would be faster in the failure case, however callers will not
|
|
// typically be calling this function with invalid values, so optimize
|
|
// for the common case.
|
|
|
|
// NOTE: A potential optimization here would be to combine adjacent
|
|
// regions to reduce the number of reads.
|
|
|
|
// In order to improve efficiency of loading the bulk data, first grab
|
|
// the block location for all of the requested block hashes and sort
|
|
// the reads by filenum:offset so that all reads are grouped by file
|
|
// and linear within each file. This can result in quite a significant
|
|
// performance increase depending on how spread out the requested hashes
|
|
// are by reducing the number of file open/closes and random accesses
|
|
// needed. The fetchList is intentionally allocated with a cap because
|
|
// some of the regions might be fetched from the pending blocks and
|
|
// hence there is no need to fetch those from disk.
|
|
blockRegions := make([][]byte, len(regions))
|
|
fetchList := make([]bulkFetchData, 0, len(regions))
|
|
for i := range regions {
|
|
region := ®ions[i]
|
|
|
|
// When the block is pending to be written on commit grab the
|
|
// bytes from there.
|
|
if tx.pendingBlocks != nil {
|
|
regionBytes, err := tx.fetchPendingRegion(region)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if regionBytes != nil {
|
|
blockRegions[i] = regionBytes
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Lookup the location of the block in the files from the block
|
|
// index.
|
|
blockRow, err := tx.fetchBlockRow(region.Hash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
location := deserializeBlockLoc(blockRow)
|
|
|
|
// Ensure the region is within the bounds of the block.
|
|
endOffset := region.Offset + region.Len
|
|
if endOffset < region.Offset || endOffset > location.blockLen {
|
|
str := fmt.Sprintf("block %s region offset %d, length "+
|
|
"%d exceeds block length of %d", region.Hash,
|
|
region.Offset, region.Len, location.blockLen)
|
|
return nil, makeDbErr(database.ErrBlockRegionInvalid, str, nil)
|
|
}
|
|
|
|
fetchList = append(fetchList, bulkFetchData{&location, i})
|
|
}
|
|
sort.Sort(bulkFetchDataSorter(fetchList))
|
|
|
|
// Read all of the regions in the fetch list and set the results.
|
|
for i := range fetchList {
|
|
fetchData := &fetchList[i]
|
|
ri := fetchData.replyIndex
|
|
region := ®ions[ri]
|
|
location := fetchData.blockLocation
|
|
regionBytes, err := tx.db.store.readBlockRegion(*location,
|
|
region.Offset, region.Len)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
blockRegions[ri] = regionBytes
|
|
}
|
|
|
|
return blockRegions, nil
|
|
}
|
|
|
|
// close marks the transaction closed then releases any pending data, the
|
|
// underlying snapshot, the transaction read lock, and the write lock when the
|
|
// transaction is writable.
|
|
func (tx *transaction) close() {
|
|
tx.closed = true
|
|
|
|
// Clear pending blocks that would have been written on commit.
|
|
tx.pendingBlocks = nil
|
|
tx.pendingBlockData = nil
|
|
|
|
// Clear pending keys that would have been written or deleted on commit.
|
|
tx.pendingKeys = nil
|
|
tx.pendingRemove = nil
|
|
|
|
// Release the snapshot.
|
|
if tx.snapshot != nil {
|
|
tx.snapshot.Release()
|
|
tx.snapshot = nil
|
|
}
|
|
|
|
tx.db.closeLock.RUnlock()
|
|
|
|
// Release the writer lock for writable transactions to unblock any
|
|
// other write transaction which are possibly waiting.
|
|
if tx.writable {
|
|
tx.db.writeLock.Unlock()
|
|
}
|
|
}
|
|
|
|
// writePendingAndCommit writes pending block data to the flat block files,
|
|
// updates the metadata with their locations as well as the new current write
|
|
// location, and commits the metadata to the memory database cache. It also
|
|
// properly handles rollback in the case of failures.
|
|
//
|
|
// This function MUST only be called when there is pending data to be written.
|
|
func (tx *transaction) writePendingAndCommit() error {
|
|
// Save the current block store write position for potential rollback.
|
|
// These variables are only updated here in this function and there can
|
|
// only be one write transaction active at a time, so it's safe to store
|
|
// them for potential rollback.
|
|
wc := tx.db.store.writeCursor
|
|
wc.RLock()
|
|
oldBlkFileNum := wc.curFileNum
|
|
oldBlkOffset := wc.curOffset
|
|
wc.RUnlock()
|
|
|
|
// rollback is a closure that is used to rollback all writes to the
|
|
// block files.
|
|
rollback := func() {
|
|
// Rollback any modifications made to the block files if needed.
|
|
tx.db.store.handleRollback(oldBlkFileNum, oldBlkOffset)
|
|
}
|
|
|
|
// Loop through all of the pending blocks to store and write them.
|
|
for _, blockData := range tx.pendingBlockData {
|
|
log.Tracef("Storing block %s", blockData.hash)
|
|
location, err := tx.db.store.writeBlock(blockData.bytes)
|
|
if err != nil {
|
|
rollback()
|
|
return err
|
|
}
|
|
|
|
// Add a record in the block index for the block. The record
|
|
// includes the location information needed to locate the block
|
|
// on the filesystem as well as the block header since they are
|
|
// so commonly needed.
|
|
blockRow := serializeBlockLoc(location)
|
|
err = tx.blockIdxBucket.Put(blockData.hash[:], blockRow)
|
|
if err != nil {
|
|
rollback()
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Update the metadata for the current write file and offset.
|
|
writeRow := serializeWriteRow(wc.curFileNum, wc.curOffset)
|
|
if err := tx.metaBucket.Put(writeLocKeyName, writeRow); err != nil {
|
|
rollback()
|
|
return convertErr("failed to store write cursor", err)
|
|
}
|
|
|
|
// Atomically update the database cache. The cache automatically
|
|
// handles flushing to the underlying persistent storage database.
|
|
return tx.db.cache.commitTx(tx)
|
|
}
|
|
|
|
// Commit commits all changes that have been made to the root metadata bucket
|
|
// and all of its sub-buckets to the database cache which is periodically synced
|
|
// to persistent storage. In addition, it commits all new blocks directly to
|
|
// persistent storage bypassing the db cache. Blocks can be rather large, so
|
|
// this help increase the amount of cache available for the metadata updates and
|
|
// is safe since blocks are immutable.
|
|
//
|
|
// This function is part of the database.Tx interface implementation.
|
|
func (tx *transaction) Commit() error {
|
|
// Prevent commits on managed transactions.
|
|
if tx.managed {
|
|
tx.close()
|
|
panic("managed transaction commit not allowed")
|
|
}
|
|
|
|
// Ensure transaction state is valid.
|
|
if err := tx.checkClosed(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Regardless of whether the commit succeeds, the transaction is closed
|
|
// on return.
|
|
defer tx.close()
|
|
|
|
// Ensure the transaction is writable.
|
|
if !tx.writable {
|
|
str := "Commit requires a writable database transaction"
|
|
return makeDbErr(database.ErrTxNotWritable, str, nil)
|
|
}
|
|
|
|
// Write pending data. The function will rollback if any errors occur.
|
|
return tx.writePendingAndCommit()
|
|
}
|
|
|
|
// Rollback undoes all changes that have been made to the root bucket and all of
|
|
// its sub-buckets.
|
|
//
|
|
// This function is part of the database.Tx interface implementation.
|
|
func (tx *transaction) Rollback() error {
|
|
// Prevent rollbacks on managed transactions.
|
|
if tx.managed {
|
|
tx.close()
|
|
panic("managed transaction rollback not allowed")
|
|
}
|
|
|
|
// Ensure transaction state is valid.
|
|
if err := tx.checkClosed(); err != nil {
|
|
return err
|
|
}
|
|
|
|
tx.close()
|
|
return nil
|
|
}
|
|
|
|
// db represents a collection of namespaces which are persisted and implements
|
|
// the database.DB interface. All database access is performed through
|
|
// transactions which are obtained through the specific Namespace.
|
|
type db struct {
|
|
writeLock sync.Mutex // Limit to one write transaction at a time.
|
|
closeLock sync.RWMutex // Make database close block while txns active.
|
|
closed bool // Is the database closed?
|
|
store *blockStore // Handles read/writing blocks to flat files.
|
|
cache *dbCache // Cache layer which wraps underlying leveldb DB.
|
|
}
|
|
|
|
// Enforce db implements the database.DB interface.
|
|
var _ database.DB = (*db)(nil)
|
|
|
|
// Type returns the database driver type the current database instance was
|
|
// created with.
|
|
//
|
|
// This function is part of the database.DB interface implementation.
|
|
func (db *db) Type() string {
|
|
return dbType
|
|
}
|
|
|
|
// begin is the implementation function for the Begin database method. See its
|
|
// documentation for more details.
|
|
//
|
|
// This function is only separate because it returns the internal transaction
|
|
// which is used by the managed transaction code while the database method
|
|
// returns the interface.
|
|
func (db *db) begin(writable bool) (*transaction, error) {
|
|
// Whenever a new writable transaction is started, grab the write lock
|
|
// to ensure only a single write transaction can be active at the same
|
|
// time. This lock will not be released until the transaction is
|
|
// closed (via Rollback or Commit).
|
|
if writable {
|
|
db.writeLock.Lock()
|
|
}
|
|
|
|
// Whenever a new transaction is started, grab a read lock against the
|
|
// database to ensure Close will wait for the transaction to finish.
|
|
// This lock will not be released until the transaction is closed (via
|
|
// Rollback or Commit).
|
|
db.closeLock.RLock()
|
|
if db.closed {
|
|
db.closeLock.RUnlock()
|
|
if writable {
|
|
db.writeLock.Unlock()
|
|
}
|
|
return nil, makeDbErr(database.ErrDbNotOpen, errDbNotOpenStr,
|
|
nil)
|
|
}
|
|
|
|
// Grab a snapshot of the database cache (which in turn also handles the
|
|
// underlying database).
|
|
snapshot, err := db.cache.Snapshot()
|
|
if err != nil {
|
|
db.closeLock.RUnlock()
|
|
if writable {
|
|
db.writeLock.Unlock()
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
|
|
// The metadata and block index buckets are internal-only buckets, so
|
|
// they have defined IDs.
|
|
tx := &transaction{
|
|
writable: writable,
|
|
db: db,
|
|
snapshot: snapshot,
|
|
pendingKeys: treap.NewMutable(),
|
|
pendingRemove: treap.NewMutable(),
|
|
}
|
|
tx.metaBucket = &bucket{tx: tx, id: metadataBucketID}
|
|
tx.blockIdxBucket = &bucket{tx: tx, id: blockIdxBucketID}
|
|
return tx, nil
|
|
}
|
|
|
|
// Begin starts a transaction which is either read-only or read-write depending
|
|
// on the specified flag. Multiple read-only transactions can be started
|
|
// simultaneously while only a single read-write transaction can be started at a
|
|
// time. The call will block when starting a read-write transaction when one is
|
|
// already open.
|
|
//
|
|
// NOTE: The transaction must be closed by calling Rollback or Commit on it when
|
|
// it is no longer needed. Failure to do so will result in unclaimed memory.
|
|
//
|
|
// This function is part of the database.DB interface implementation.
|
|
func (db *db) Begin(writable bool) (database.Tx, error) {
|
|
return db.begin(writable)
|
|
}
|
|
|
|
// rollbackOnPanic rolls the passed transaction back if the code in the calling
|
|
// function panics. This is needed since the mutex on a transaction must be
|
|
// released and a panic in called code would prevent that from happening.
|
|
//
|
|
// NOTE: This can only be handled manually for managed transactions since they
|
|
// control the life-cycle of the transaction. As the documentation on Begin
|
|
// calls out, callers opting to use manual transactions will have to ensure the
|
|
// transaction is rolled back on panic if it desires that functionality as well
|
|
// or the database will fail to close since the read-lock will never be
|
|
// released.
|
|
func rollbackOnPanic(tx *transaction) {
|
|
if err := recover(); err != nil {
|
|
tx.managed = false
|
|
_ = tx.Rollback()
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
// View invokes the passed function in the context of a managed read-only
|
|
// transaction with the root bucket for the namespace. Any errors returned from
|
|
// the user-supplied function are returned from this function.
|
|
//
|
|
// This function is part of the database.DB interface implementation.
|
|
func (db *db) View(fn func(database.Tx) error) error {
|
|
// Start a read-only transaction.
|
|
tx, err := db.begin(false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Since the user-provided function might panic, ensure the transaction
|
|
// releases all mutexes and resources. There is no guarantee the caller
|
|
// won't use recover and keep going. Thus, the database must still be
|
|
// in a usable state on panics due to caller issues.
|
|
defer rollbackOnPanic(tx)
|
|
|
|
tx.managed = true
|
|
err = fn(tx)
|
|
tx.managed = false
|
|
if err != nil {
|
|
// The error is ignored here because nothing was written yet
|
|
// and regardless of a rollback failure, the tx is closed now
|
|
// anyways.
|
|
_ = tx.Rollback()
|
|
return err
|
|
}
|
|
|
|
return tx.Rollback()
|
|
}
|
|
|
|
// Update invokes the passed function in the context of a managed read-write
|
|
// transaction with the root bucket for the namespace. Any errors returned from
|
|
// the user-supplied function will cause the transaction to be rolled back and
|
|
// are returned from this function. Otherwise, the transaction is committed
|
|
// when the user-supplied function returns a nil error.
|
|
//
|
|
// This function is part of the database.DB interface implementation.
|
|
func (db *db) Update(fn func(database.Tx) error) error {
|
|
// Start a read-write transaction.
|
|
tx, err := db.begin(true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Since the user-provided function might panic, ensure the transaction
|
|
// releases all mutexes and resources. There is no guarantee the caller
|
|
// won't use recover and keep going. Thus, the database must still be
|
|
// in a usable state on panics due to caller issues.
|
|
defer rollbackOnPanic(tx)
|
|
|
|
tx.managed = true
|
|
err = fn(tx)
|
|
tx.managed = false
|
|
if err != nil {
|
|
// The error is ignored here because nothing was written yet
|
|
// and regardless of a rollback failure, the tx is closed now
|
|
// anyways.
|
|
_ = tx.Rollback()
|
|
return err
|
|
}
|
|
|
|
return tx.Commit()
|
|
}
|
|
|
|
// Close cleanly shuts down the database and syncs all data. It will block
|
|
// until all database transactions have been finalized (rolled back or
|
|
// committed).
|
|
//
|
|
// This function is part of the database.DB interface implementation.
|
|
func (db *db) Close() error {
|
|
// Since all transactions have a read lock on this mutex, this will
|
|
// cause Close to wait for all readers to complete.
|
|
db.closeLock.Lock()
|
|
defer db.closeLock.Unlock()
|
|
|
|
if db.closed {
|
|
return makeDbErr(database.ErrDbNotOpen, errDbNotOpenStr, nil)
|
|
}
|
|
db.closed = true
|
|
|
|
// NOTE: Since the above lock waits for all transactions to finish and
|
|
// prevents any new ones from being started, it is safe to flush the
|
|
// cache and clear all state without the individual locks.
|
|
|
|
// Close the database cache which will flush any existing entries to
|
|
// disk and close the underlying leveldb database. Any error is saved
|
|
// and returned at the end after the remaining cleanup since the
|
|
// database will be marked closed even if this fails given there is no
|
|
// good way for the caller to recover from a failure here anyways.
|
|
closeErr := db.cache.Close()
|
|
|
|
// Close any open flat files that house the blocks.
|
|
wc := db.store.writeCursor
|
|
if wc.curFile.file != nil {
|
|
_ = wc.curFile.file.Close()
|
|
wc.curFile.file = nil
|
|
}
|
|
for _, blockFile := range db.store.openBlockFiles {
|
|
_ = blockFile.file.Close()
|
|
}
|
|
db.store.openBlockFiles = nil
|
|
db.store.openBlocksLRU.Init()
|
|
db.store.fileNumToLRUElem = nil
|
|
|
|
return closeErr
|
|
}
|
|
|
|
// filesExists reports whether the named file or directory exists.
|
|
func fileExists(name string) bool {
|
|
if _, err := os.Stat(name); err != nil {
|
|
if os.IsNotExist(err) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// initDB creates the initial buckets and values used by the package. This is
|
|
// mainly in a separate function for testing purposes.
|
|
func initDB(ldb *leveldb.DB) error {
|
|
// The starting block file write cursor location is file num 0, offset
|
|
// 0.
|
|
batch := new(leveldb.Batch)
|
|
batch.Put(bucketizedKey(metadataBucketID, writeLocKeyName),
|
|
serializeWriteRow(0, 0))
|
|
|
|
// Create block index bucket and set the current bucket id.
|
|
//
|
|
// NOTE: Since buckets are virtualized through the use of prefixes,
|
|
// there is no need to store the bucket index data for the metadata
|
|
// bucket in the database. However, the first bucket ID to use does
|
|
// need to account for it to ensure there are no key collisions.
|
|
batch.Put(bucketIndexKey(metadataBucketID, blockIdxBucketName),
|
|
blockIdxBucketID[:])
|
|
batch.Put(curBucketIDKeyName, blockIdxBucketID[:])
|
|
|
|
// Write everything as a single batch.
|
|
if err := ldb.Write(batch, nil); err != nil {
|
|
str := fmt.Sprintf("failed to initialize metadata database: %v",
|
|
err)
|
|
return convertErr(str, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// openDB opens the database at the provided path. database.ErrDbDoesNotExist
|
|
// is returned if the database doesn't exist and the create flag is not set.
|
|
func openDB(dbPath string, network wire.BitcoinNet, create bool) (database.DB, error) {
|
|
// Error if the database doesn't exist and the create flag is not set.
|
|
metadataDbPath := filepath.Join(dbPath, metadataDbName)
|
|
dbExists := fileExists(metadataDbPath)
|
|
if !create && !dbExists {
|
|
str := fmt.Sprintf("database %q does not exist", metadataDbPath)
|
|
return nil, makeDbErr(database.ErrDbDoesNotExist, str, nil)
|
|
}
|
|
|
|
// Ensure the full path to the database exists.
|
|
if !dbExists {
|
|
// The error can be ignored here since the call to
|
|
// leveldb.OpenFile will fail if the directory couldn't be
|
|
// created.
|
|
_ = os.MkdirAll(dbPath, 0700)
|
|
}
|
|
|
|
// Open the metadata database (will create it if needed).
|
|
opts := opt.Options{
|
|
ErrorIfExist: create,
|
|
Strict: opt.DefaultStrict,
|
|
Compression: opt.NoCompression,
|
|
Filter: filter.NewBloomFilter(10),
|
|
}
|
|
ldb, err := leveldb.OpenFile(metadataDbPath, &opts)
|
|
if err != nil {
|
|
return nil, convertErr(err.Error(), err)
|
|
}
|
|
|
|
// Create the block store which includes scanning the existing flat
|
|
// block files to find what the current write cursor position is
|
|
// according to the data that is actually on disk. Also create the
|
|
// database cache which wraps the underlying leveldb database to provide
|
|
// write caching.
|
|
store := newBlockStore(dbPath, network)
|
|
cache := newDbCache(ldb, store, defaultCacheSize, defaultFlushSecs)
|
|
pdb := &db{store: store, cache: cache}
|
|
|
|
// Perform any reconciliation needed between the block and metadata as
|
|
// well as database initialization, if needed.
|
|
return reconcileDB(pdb, create)
|
|
}
|