Skip to content

Commit

Permalink
Squash all changes since last review.
Browse files Browse the repository at this point in the history
  • Loading branch information
jrick committed Apr 16, 2015
1 parent 6b52431 commit 4747c4c
Show file tree
Hide file tree
Showing 9 changed files with 2,444 additions and 646 deletions.
271 changes: 176 additions & 95 deletions wtxmgr/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,20 @@ import (
// ck: The current cursor key
// cv: The current cursor value
//
// Functions use the naming scheme `Op[Raw]Type`, which performs the
// operation `Op` on the type `Type`, optionally dealing with raw keys
// and values if `Raw` is used. The following operations are used:
// Functions use the naming scheme `Op[Raw]Type[Field]`, which performs the
// operation `Op` on the type `Type`, optionally dealing with raw keys and
// values if `Raw` is used. Fetch and extract operations may only need read
// some portion of a key or value, in which case `Field` describes the component
// being returned. The following operations are used:
//
// key: return a db key for some data
// value: return a db value for some data
// put: insert or replace a value into a bucket
// fetch: read and return a value
// read: read a value into an out parameter
// exists: return the raw (nil if not found) value for some data
// delete: remove a k/v pair
// key: return a db key for some data
// value: return a db value for some data
// put: insert or replace a value into a bucket
// fetch: read and return a value
// read: read a value into an out parameter
// exists: return the raw (nil if not found) value for some data
// delete: remove a k/v pair
// extract: perform an unchecked slice to extract a key or value
//
// Other operations which are specific to the types being operated on
// should be explained in a comment.
Expand All @@ -59,9 +62,11 @@ import (
// keys iterating in order.
var byteOrder = binary.BigEndian

// Database versions. Versions start at 1 and increment for each database
// change.
const (
// LatestTxStoreVersion is the most recent tx store version.
LatestTxStoreVersion = 1
// LatestVersion is the most recent store version.
LatestVersion = 1
)

// This package makes assumptions that the width of a wire.ShaHash is always 32
Expand Down Expand Up @@ -246,15 +251,6 @@ func readRawBlockRecord(k, v []byte, block *blockRecord) error {
return nil
}

func fetchBlockRecord(ns walletdb.Bucket, height int32) (*blockRecord, error) {
k := make([]byte, 4)
byteOrder.PutUint32(k, uint32(height))
v := ns.Bucket(bucketBlocks).Get(k)
block := new(blockRecord)
err := readRawBlockRecord(k, v, block)
return block, err
}

type blockIterator struct {
c walletdb.Cursor
seek []byte
Expand Down Expand Up @@ -453,6 +449,22 @@ func fetchTxRecord(ns walletdb.Bucket, txHash *wire.ShaHash, block *Block) (*TxR
return rec, err
}

// TODO: This reads more than necessary. Pass the pkscript location instead to
// avoid the wire.MsgTx deserialization.
func fetchRawTxRecordPkScript(k, v []byte, index uint32) ([]byte, error) {
var rec TxRecord
copy(rec.Hash[:], k) // Silly but need an array
err := readRawTxRecord(&rec.Hash, v, &rec)
if err != nil {
return nil, err
}
if int(index) >= len(rec.MsgTx.TxOut) {
str := "missing transaction output for credit index"
return nil, storeError(ErrData, str, nil)
}
return rec.MsgTx.TxOut[index].PkScript, nil
}

func existsTxRecord(ns walletdb.Bucket, txHash *wire.ShaHash, block *Block) (k, v []byte) {
k = keyTxRecord(txHash, block)
v = ns.Bucket(bucketTxRecords).Get(k)
Expand Down Expand Up @@ -517,18 +529,11 @@ func keyCredit(txHash *wire.ShaHash, index uint32, block *Block) []byte {
return k
}

func valueCredit(cred *credit) []byte {
var v []byte
if cred.spentBy.Index == ^uint32(0) {
v = make([]byte, 9)
} else {
v = make([]byte, 81)
v[8] = 1 << 0
copy(v[9:41], cred.spentBy.TxHash[:])
byteOrder.PutUint32(v[41:45], uint32(cred.spentBy.Height))
copy(v[45:77], cred.spentBy.Block.Hash[:])
byteOrder.PutUint32(v[77:81], cred.spentBy.Index)
}
// valueUnspentCredit creates a new credit value for an unspent credit. All
// credits are created unspent, and are only marked spent later, so there is no
// value function to create either spent or unspent credits.
func valueUnspentCredit(cred *credit) []byte {
v := make([]byte, 9)
byteOrder.PutUint64(v, uint64(cred.amount))
if cred.change {
v[8] |= 1 << 1
Expand All @@ -545,12 +550,23 @@ func putRawCredit(ns walletdb.Bucket, k, v []byte) error {
return nil
}

func putCredit(ns walletdb.Bucket, cred *credit) error {
// putUnspentCredit puts a credit record for an unspent credit. It may only be
// used when the credit is already know to be unspent, or spent by an
// unconfirmed transaction.
func putUnspentCredit(ns walletdb.Bucket, cred *credit) error {
k := keyCredit(&cred.outPoint.Hash, cred.outPoint.Index, &cred.block)
v := valueCredit(cred)
v := valueUnspentCredit(cred)
return putRawCredit(ns, k, v)
}

func extractRawCreditTxRecordKey(k []byte) []byte {
return k[0:68]
}

func extractRawCreditIndex(k []byte) uint32 {
return byteOrder.Uint32(k[68:72])
}

// fetchRawCreditAmount returns the amount of the credit.
func fetchRawCreditAmount(v []byte) (btcutil.Amount, error) {
if len(v) < 9 {
Expand Down Expand Up @@ -597,16 +613,16 @@ func fetchRawCreditUnspentValue(k []byte) ([]byte, error) {
// spendRawCredit marks the credit with a given key as mined at some particular
// block as spent by the input at some transaction incidence. The debited
// amount is returned.
func spendCredit(ns walletdb.Bucket, k []byte, spender *IndexedIncidence) (btcutil.Amount, error) {
func spendCredit(ns walletdb.Bucket, k []byte, spender *indexedIncidence) (btcutil.Amount, error) {
v := ns.Bucket(bucketCredits).Get(k)
newv := make([]byte, 81)
copy(newv, v)
v = newv
v[8] |= 1 << 0
copy(v[9:41], spender.TxHash[:])
byteOrder.PutUint32(v[41:45], uint32(spender.Height))
copy(v[45:77], spender.Block.Hash[:])
byteOrder.PutUint32(v[77:81], spender.Index)
copy(v[9:41], spender.txHash[:])
byteOrder.PutUint32(v[41:45], uint32(spender.block.Height))
copy(v[45:77], spender.block.Hash[:])
byteOrder.PutUint32(v[77:81], spender.index)

return btcutil.Amount(byteOrder.Uint64(v[0:8])), putRawCredit(ns, k, v)
}
Expand Down Expand Up @@ -665,6 +681,12 @@ func deleteRawCredit(ns walletdb.Bucket, k []byte) error {
// if it.err != nil {
// // Handle error
// }
//
// The elem's Spent field is not set to true if the credits is spent by an
// unmined transaction. To check for this case:
//
// k := canonicalOutPoint(&txHash, it.elem.Index)
// it.elem.Spent |= existsRawUnminedInput(ns, k) != nil
type creditIterator struct {
c walletdb.Cursor // Set to nil after final iteration
prefix []byte
Expand Down Expand Up @@ -850,6 +872,10 @@ func putDebit(ns walletdb.Bucket, txHash *wire.ShaHash, index uint32, amount btc
return nil
}

func extractRawDebitCreditKey(v []byte) []byte {
return v[8:80]
}

// existsDebit checks for the existance of a debit. If found, the debit and
// previous credit keys are returned. If the debit does not exist, both keys
// are nil.
Expand Down Expand Up @@ -1168,14 +1194,104 @@ func deleteRawUnminedInput(ns walletdb.Bucket, k []byte) error {
return nil
}

// upgradeStore opens the tx store using the specified namespace or creates
// and initializes it if it does not already exist. It also provides
// facilities to upgrade the data in the namespace to newer versions.
func upgradeStore(namespace walletdb.Namespace) error {
// Initialize the buckets and root bucket fields as needed.
// openStore opens an existing transaction store from the passed namespace. If
// necessary, an already existing store is upgraded to newer db format.
func openStore(namespace walletdb.Namespace) error {
var version uint32
err := scopedView(namespace, func(ns walletdb.Bucket) error {
// Verify a store already exists and upgrade as necessary.
v := ns.Get(rootVersion)
if len(v) != 4 {
return nil
}
version = byteOrder.Uint32(v)
return nil
})
if err != nil {
const desc = "failed to open existing store"
if serr, ok := err.(Error); ok {
serr.Desc = desc + ": " + serr.Desc
return serr
}
return storeError(ErrDatabase, desc, err)
}

// The initial version is one. If no store exists and no version was
// saved, this variable will be zero.
if version == 0 {
str := "no transaction store exists in namespace"
return storeError(ErrNoExists, str, nil)
}

// Cannot continue if the saved database is too new for this software.
// This probably indicates an outdated binary.
if version > LatestVersion {
str := fmt.Sprintf("recorded version %d is newer that latest "+
"understood version %d", version, LatestVersion)
return storeError(ErrUnknownVersion, str, nil)
}

// Upgrade the tx store as needed, one version at a time, until
// LatestVersion is reached. Versions are not skipped when performing
// database upgrades, and each upgrade is done in its own transaction.
//
// No upgrades yet.
//if version < LatestVersion {
// err := scopedUpdate(namespace, func(ns walletdb.Bucket) error {
// })
// if err != nil {
// // Handle err
// }
//}

return nil
}

// createStore creates the tx store (with the latest db version) in the passed
// namespace. If a store already exists, ErrAlreadyExists is returned.
func createStore(namespace walletdb.Namespace) error {
// Initialize the buckets and root bucket fields as needed.
err := scopedUpdate(namespace, func(ns walletdb.Bucket) error {
_, err := ns.CreateBucketIfNotExists(bucketBlocks)
// Write the latest store version. Existing versions are not
// allowed, as they indicate the store already exists.
v := ns.Get(rootVersion)
if v != nil {
str := "transaction store already exists in namespace"
return storeError(ErrAlreadyExists, str, nil)
}
v = make([]byte, 4)
byteOrder.PutUint32(v, LatestVersion)
err := ns.Put(rootVersion, v)
if err != nil {
str := "failed to store latest database version"
return storeError(ErrDatabase, str, err)
}

// Save the creation date of the store if it does not already
// exist.
v = ns.Get(rootCreateDate)
if v == nil {
v = make([]byte, 8)
byteOrder.PutUint64(v, uint64(time.Now().Unix()))
err := ns.Put(rootCreateDate, v)
if err != nil {
str := "failed to store database creation time"
return storeError(ErrDatabase, str, err)
}
}

// Write a zero balance if it does not already exist.
v = ns.Get(rootMinedBalance)
if v == nil {
v = make([]byte, 8)
err := ns.Put(rootMinedBalance, v)
if err != nil {
str := "failed to write zero balance"
return storeError(ErrDatabase, str, err)
}
}

_, err = ns.CreateBucketIfNotExists(bucketBlocks)
if err != nil {
str := "failed to create blocks bucket"
return storeError(ErrDatabase, str, err)
Expand Down Expand Up @@ -1223,58 +1339,17 @@ func upgradeStore(namespace walletdb.Namespace) error {
return storeError(ErrDatabase, str, err)
}

// Save the most recent tx store version if it isn't already
// there, otherwise keep track of it for potential upgrades.
v := ns.Get(rootVersion)
if len(v) != 4 {
v = make([]byte, 4)
byteOrder.PutUint32(v, version)
err := ns.Put(rootVersion, v)
if err != nil {
str := "failed to store latest database version"
return storeError(ErrDatabase, str, err)
}
version = LatestTxStoreVersion
} else {
version = byteOrder.Uint32(v)
}

// Save the creation date of the store if it does not already
// exist.
v = ns.Get(rootCreateDate)
if v == nil {
v = make([]byte, 8)
byteOrder.PutUint64(v, uint64(time.Now().Unix()))
err := ns.Put(rootCreateDate, v)
if err != nil {
str := "failed to store database creation time"
return storeError(ErrDatabase, str, err)
}
}

// Write a zero balance if it does not already exist.
v = ns.Get(rootMinedBalance)
if v == nil {
v = make([]byte, 8)
err := ns.Put(rootMinedBalance, v)
if err != nil {
str := "failed to write zero balance"
return storeError(ErrDatabase, str, err)
}
}

return nil
})
if err != nil {
str := "failed to update database"
return storeError(ErrDatabase, str, err)
const desc = "failed to create new store"
if serr, ok := err.(Error); ok {
serr.Desc = desc + ": " + serr.Desc
return serr
}
return storeError(ErrDatabase, desc, err)
}

// Upgrade the tx store as needed.
// No upgrades yet.
//if version < LatestTxStoreVersion {
//}

return nil
}

Expand All @@ -1288,8 +1363,14 @@ func scopedUpdate(ns walletdb.Namespace, f func(walletdb.Bucket) error) error {
if err != nil {
rbErr := tx.Rollback()
if rbErr != nil {
str := "rollback failed"
return storeError(ErrDatabase, str, rbErr)
const desc = "rollback failed"
serr, ok := err.(Error)
if !ok {
// This really shouldn't happen.
return storeError(ErrDatabase, desc, rbErr)
}
serr.Desc = desc + ": " + serr.Desc
return serr
}
return err
}
Expand Down
Loading

0 comments on commit 4747c4c

Please sign in to comment.