/* * Copyright (c) 2014 Conformal Systems LLC * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ package bdb import ( "io" "os" "github.com/btcsuite/bolt" "github.com/conformal/btcwallet/walletdb" ) // convertErr converts some bolt errors to the equivalent walletdb error. func convertErr(err error) error { switch err { // Database open/create errors. case bolt.ErrDatabaseNotOpen: return walletdb.ErrDbNotOpen case bolt.ErrInvalid: return walletdb.ErrInvalid // Transaction errors. case bolt.ErrTxNotWritable: return walletdb.ErrTxNotWritable case bolt.ErrTxClosed: return walletdb.ErrTxClosed // Value/bucket errors. case bolt.ErrBucketNotFound: return walletdb.ErrBucketNotFound case bolt.ErrBucketExists: return walletdb.ErrBucketExists case bolt.ErrBucketNameRequired: return walletdb.ErrBucketNameRequired case bolt.ErrKeyRequired: return walletdb.ErrKeyRequired case bolt.ErrKeyTooLarge: return walletdb.ErrKeyTooLarge case bolt.ErrValueTooLarge: return walletdb.ErrValueTooLarge case bolt.ErrIncompatibleValue: return walletdb.ErrIncompatibleValue } // Return the original error if none of the above applies. return err } // bucket is an internal type used to represent a collection of key/value pairs // and implements the walletdb.Bucket interface. type bucket bolt.Bucket // Enforce bucket implements the walletdb.Bucket interface. var _ walletdb.Bucket = (*bucket)(nil) // Bucket retrieves a nested bucket with the given key. Returns nil if // the bucket does not exist. // // This function is part of the walletdb.Bucket interface implementation. func (b *bucket) Bucket(key []byte) walletdb.Bucket { // This nil check is intentional so the return value can be checked // against nil directly. boltBucket := (*bolt.Bucket)(b).Bucket(key) if boltBucket == nil { return nil } return (*bucket)(boltBucket) } // CreateBucket creates and returns a new nested bucket with the given key. // Returns ErrBucketExists if the bucket already exists, ErrBucketNameRequired // if the key is empty, or ErrIncompatibleValue if the key value is otherwise // invalid. // // This function is part of the walletdb.Bucket interface implementation. func (b *bucket) CreateBucket(key []byte) (walletdb.Bucket, error) { boltBucket, err := (*bolt.Bucket)(b).CreateBucket(key) if err != nil { return nil, convertErr(err) } return (*bucket)(boltBucket), nil } // CreateBucketIfNotExists creates and returns a new nested bucket with the // given key if it does not already exist. Returns ErrBucketNameRequired if the // key is empty or ErrIncompatibleValue if the key value is otherwise invalid. // // This function is part of the walletdb.Bucket interface implementation. func (b *bucket) CreateBucketIfNotExists(key []byte) (walletdb.Bucket, error) { boltBucket, err := (*bolt.Bucket)(b).CreateBucketIfNotExists(key) if err != nil { return nil, convertErr(err) } return (*bucket)(boltBucket), nil } // DeleteBucket removes a nested bucket with the given key. Returns // ErrTxNotWritable if attempted against a read-only transaction and // ErrBucketNotFound if the specified bucket does not exist. // // This function is part of the walletdb.Bucket interface implementation. func (b *bucket) DeleteBucket(key []byte) error { return convertErr((*bolt.Bucket)(b).DeleteBucket(key)) } // ForEach invokes the passed function with every key/value pair in the bucket. // This includes nested buckets, in which case the value is nil, but it does not // include the key/value pairs within those nested buckets. // // 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 walletdb.Bucket interface implementation. func (b *bucket) ForEach(fn func(k, v []byte) error) error { return convertErr((*bolt.Bucket)(b).ForEach(fn)) } // Writable returns whether or not the bucket is writable. // // This function is part of the walletdb.Bucket interface implementation. func (b *bucket) Writable() bool { return (*bolt.Bucket)(b).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 // ErrTxNotWritable if attempted against a read-only transaction. // // This function is part of the walletdb.Bucket interface implementation. func (b *bucket) Put(key, value []byte) error { return convertErr((*bolt.Bucket)(b).Put(key, value)) } // Get returns the value for the given key. Returns nil if the key does // not exist in this bucket (or nested buckets). // // NOTE: The value returned by this function is only valid during a // transaction. Attempting to access it after a transaction has ended // will likely result in an access violation. // // This function is part of the walletdb.Bucket interface implementation. func (b *bucket) Get(key []byte) []byte { return (*bolt.Bucket)(b).Get(key) } // Delete removes the specified key from the bucket. Deleting a key that does // not exist does not return an error. Returns ErrTxNotWritable if attempted // against a read-only transaction. // // This function is part of the walletdb.Bucket interface implementation. func (b *bucket) Delete(key []byte) error { return convertErr((*bolt.Bucket)(b).Delete(key)) } // transaction represents a database transaction. It can either by read-only or // read-write and implements the walletdb.Bucket interface. The transaction // provides a root bucket against which all read and writes occur. type transaction struct { boltTx *bolt.Tx rootBucket *bolt.Bucket } // Enforce transaction implements the walletdb.Tx interface. var _ walletdb.Tx = (*transaction)(nil) // RootBucket returns the top-most bucket for the namespace the transaction was // created from. // // This function is part of the walletdb.Tx interface implementation. func (tx *transaction) RootBucket() walletdb.Bucket { return (*bucket)(tx.rootBucket) } // Commit commits all changes that have been made through the root bucket and // all of its sub-buckets to persistent storage. // // This function is part of the walletdb.Tx interface implementation. func (tx *transaction) Commit() error { return convertErr(tx.boltTx.Commit()) } // Rollback undoes all changes that have been made to the root bucket and all of // its sub-buckets. // // This function is part of the walletdb.Tx interface implementation. func (tx *transaction) Rollback() error { return convertErr(tx.boltTx.Rollback()) } // namespace represents a database namespace that is inteded to support the // concept of a single entity that controls the opening, creating, and closing // of a database while providing other entities their own namespace to work in. // It implements the walletdb.Namespace interface. type namespace struct { db *bolt.DB key []byte } // Enforce namespace implements the walletdb.Namespace interface. var _ walletdb.Namespace = (*namespace)(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 walletdb.Namespace interface implementation. func (ns *namespace) Begin(writable bool) (walletdb.Tx, error) { boltTx, err := ns.db.Begin(writable) if err != nil { return nil, convertErr(err) } bucket := boltTx.Bucket(ns.key) if bucket == nil { return nil, walletdb.ErrBucketNotFound } return &transaction{boltTx: boltTx, rootBucket: bucket}, nil } // View invokes the passed function in the context of a managed read-only // transaction. Any errors returned from the user-supplied function are // returned from this function. // // Calling Rollback on the transaction passed to the user-supplied function will // result in a panic. // // This function is part of the walletdb.Namespace interface implementation. func (ns *namespace) View(fn func(walletdb.Tx) error) error { return convertErr(ns.db.View(func(boltTx *bolt.Tx) error { bucket := boltTx.Bucket(ns.key) if bucket == nil { return walletdb.ErrBucketNotFound } return fn(&transaction{boltTx: boltTx, rootBucket: bucket}) })) } // Update invokes the passed function in the context of a managed read-write // transaction. 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 commited when the user-supplied function // returns a nil error. // // Calling Rollback on the transaction passed to the user-supplied function will // result in a panic. // // This function is part of the walletdb.Namespace interface implementation. func (ns *namespace) Update(fn func(walletdb.Tx) error) error { return convertErr(ns.db.Update(func(boltTx *bolt.Tx) error { bucket := boltTx.Bucket(ns.key) if bucket == nil { return walletdb.ErrBucketNotFound } return fn(&transaction{boltTx: boltTx, rootBucket: bucket}) })) } // db represents a collection of namespaces which are persisted and implements // the walletdb.Db interface. All database access is performed through // transactions which are obtained through the specific Namespace. type db bolt.DB // Enforce db implements the walletdb.Db interface. var _ walletdb.DB = (*db)(nil) // Namespace returns a Namespace interface for the provided key. See the // Namespace interface documentation for more details. Attempting to access a // Namespace on a database that is not open yet or has been closed will result // in ErrDbNotOpen. Namespaces are created in the database on first access. // // This function is part of the walletdb.Db interface implementation. func (db *db) Namespace(key []byte) (walletdb.Namespace, error) { // Check if the namespace needs to be created using a read-only // transaction. This is done because read-only transactions are faster // and don't block like write transactions. var doCreate bool err := (*bolt.DB)(db).View(func(tx *bolt.Tx) error { boltBucket := tx.Bucket(key) if boltBucket == nil { doCreate = true } return nil }) if err != nil { return nil, convertErr(err) } // Create the namespace if needed by using an writable update // transaction. if doCreate { err := (*bolt.DB)(db).Update(func(tx *bolt.Tx) error { _, err := tx.CreateBucket(key) return err }) if err != nil { return nil, convertErr(err) } } return &namespace{db: (*bolt.DB)(db), key: key}, nil } // DeleteNamespace deletes the namespace for the passed key. ErrBucketNotFound // will be returned if the namespace does not exist. // // This function is part of the walletdb.Db interface implementation. func (db *db) DeleteNamespace(key []byte) error { return convertErr((*bolt.DB)(db).Update(func(tx *bolt.Tx) error { return tx.DeleteBucket(key) })) } // Copy writes a copy of the database to the provided writer. This call will // start a read-only transaction to perform all operations. // // This function is part of the walletdb.Db interface implementation. func (db *db) Copy(w io.Writer) error { return convertErr((*bolt.DB)(db).View(func(tx *bolt.Tx) error { return tx.Copy(w) })) } // Close cleanly shuts down the database and syncs all data. // // This function is part of the walletdb.Db interface implementation. func (db *db) Close() error { return convertErr((*bolt.DB)(db).Close()) } // 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 } // openDB opens the database at the provided path. walletdb.ErrDbDoesNotExist // is returned if the database doesn't exist and the create flag is not set. func openDB(dbPath string, create bool) (walletdb.DB, error) { if !create && !fileExists(dbPath) { return nil, walletdb.ErrDbDoesNotExist } boltDB, err := bolt.Open(dbPath, 0600, nil) return (*db)(boltDB), convertErr(err) }