// rangeUnminedTransactions executes the function f with TxDetails for every // unmined transaction. f is not executed if no unmined transactions exist. // Error returns from f (if any) are propigated to the caller. Returns true // (signaling breaking out of a RangeTransactions) iff f executes and returns // true. func (s *Store) rangeUnminedTransactions(ns walletdb.Bucket, f func([]TxDetails) (bool, error)) (bool, error) { var details []TxDetails err := ns.Bucket(bucketUnmined).ForEach(func(k, v []byte) error { if len(k) < 32 { str := fmt.Sprintf("%s: short key (expected %d "+ "bytes, read %d)", bucketUnmined, 32, len(k)) return storeError(ErrData, str, nil) } var txHash wire.ShaHash copy(txHash[:], k) detail, err := s.unminedTxDetails(ns, &txHash, v) if err != nil { return err } // Because the key was created while foreach-ing over the // bucket, it should be impossible for unminedTxDetails to ever // successfully return a nil details struct. details = append(details, *detail) return nil }) if err == nil && len(details) > 0 { return f(details) } return false, err }
func deleteRawDebit(ns walletdb.Bucket, k []byte) error { err := ns.Bucket(bucketDebits).Delete(k) if err != nil { str := "failed to delete debit" return storeError(ErrDatabase, str, err) } return nil }
func putRawBlockRecord(ns walletdb.Bucket, k, v []byte) error { err := ns.Bucket(bucketBlocks).Put(k, v) if err != nil { str := "failed to store block" return storeError(ErrDatabase, str, err) } return nil }
func putRawCredit(ns walletdb.Bucket, k, v []byte) error { err := ns.Bucket(bucketCredits).Put(k, v) if err != nil { str := "failed to put credit" return storeError(ErrDatabase, str, err) } return nil }
func putRawUnmined(ns walletdb.Bucket, k, v []byte) error { err := ns.Bucket(bucketUnmined).Put(k, v) if err != nil { str := "failed to put unmined record" return storeError(ErrDatabase, str, err) } return nil }
func deleteRawUnmined(ns walletdb.Bucket, k []byte) error { err := ns.Bucket(bucketUnmined).Delete(k) if err != nil { str := "failed to delete unmined record" return storeError(ErrDatabase, str, err) } return nil }
func fetchTxRecord(ns walletdb.Bucket, txHash *wire.ShaHash, block *Block) (*TxRecord, error) { k := keyTxRecord(txHash, block) v := ns.Bucket(bucketTxRecords).Get(k) rec := new(TxRecord) err := readRawTxRecord(txHash, v, rec) return rec, err }
func putRawTxRecord(ns walletdb.Bucket, k, v []byte) error { err := ns.Bucket(bucketTxRecords).Put(k, v) if err != nil { str := fmt.Sprintf("%s: put failed", bucketTxRecords) return storeError(ErrDatabase, str, err) } return nil }
func putRawUnspent(ns walletdb.Bucket, k, v []byte) error { err := ns.Bucket(bucketUnspent).Put(k, v) if err != nil { str := "cannot put unspent" return storeError(ErrDatabase, str, err) } return nil }
func fetchMinedBalance(ns walletdb.Bucket) (btcutil.Amount, error) { v := ns.Get(rootMinedBalance) if len(v) != 8 { str := fmt.Sprintf("balance: short read (expected 8 bytes, "+ "read %v)", len(v)) return 0, storeError(ErrData, str, nil) } return btcutil.Amount(byteOrder.Uint64(v)), nil }
func putUnspent(ns walletdb.Bucket, outPoint *wire.OutPoint, block *Block) error { k := canonicalOutPoint(&outPoint.Hash, outPoint.Index) v := valueUnspent(block) err := ns.Bucket(bucketUnspent).Put(k, v) if err != nil { str := "cannot put unspent" return storeError(ErrDatabase, str, err) } return nil }
func putMinedBalance(ns walletdb.Bucket, amt btcutil.Amount) error { v := make([]byte, 8) byteOrder.PutUint64(v, uint64(amt)) err := ns.Put(rootMinedBalance, v) if err != nil { str := "failed to put balance" return storeError(ErrDatabase, str, err) } return nil }
// testDeleteValues removes all of the provided key/value pairs from the // provided bucket. func testDeleteValues(tc *testContext, bucket walletdb.Bucket, values map[string]string) bool { for k := range values { if err := bucket.Delete([]byte(k)); err != nil { tc.t.Errorf("Delete: unexpected error: %v", err) return false } } return true }
func fetchBlockTime(ns walletdb.Bucket, height int32) (time.Time, error) { k := keyBlockRecord(height) v := ns.Bucket(bucketBlocks).Get(k) if len(v) < 44 { str := fmt.Sprintf("%s: short read (expected %d bytes, read %d)", bucketBlocks, 44, len(v)) return time.Time{}, storeError(ErrData, str, nil) } return time.Unix(int64(byteOrder.Uint64(v[32:40])), 0), nil }
// latestTxRecord searches for the newest recorded mined transaction record with // a matching hash. In case of a hash collision, the record from the newest // block is returned. Returns (nil, nil) if no matching transactions are found. func latestTxRecord(ns walletdb.Bucket, txHash *wire.ShaHash) (k, v []byte) { prefix := txHash[:] c := ns.Bucket(bucketTxRecords).Cursor() ck, cv := c.Seek(prefix) var lastKey, lastVal []byte for bytes.HasPrefix(ck, prefix) { lastKey, lastVal = ck, cv ck, cv = c.Next() } return lastKey, lastVal }
func (s *Store) unminedTxHashes(ns walletdb.Bucket) ([]*wire.ShaHash, error) { var hashes []*wire.ShaHash err := ns.Bucket(bucketUnmined).ForEach(func(k, v []byte) error { hash := new(wire.ShaHash) err := readRawUnminedHash(k, hash) if err == nil { hashes = append(hashes, hash) } return err }) return hashes, err }
// 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) { 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.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) }
func putTxRecord(ns walletdb.Bucket, rec *TxRecord, block *Block) error { k := keyTxRecord(&rec.Hash, block) v, err := valueTxRecord(rec) if err != nil { return err } err = ns.Bucket(bucketTxRecords).Put(k, v) if err != nil { str := fmt.Sprintf("%s: put failed for %v", bucketTxRecords, rec.Hash) return storeError(ErrDatabase, str, err) } return nil }
// 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. func existsDebit(ns walletdb.Bucket, txHash *wire.ShaHash, index uint32, block *Block) (k, credKey []byte, err error) { k = keyDebit(txHash, index, block) v := ns.Bucket(bucketDebits).Get(k) if v == nil { return nil, nil, nil } if len(v) < 80 { str := fmt.Sprintf("%s: short read (expected 80 bytes, read %v)", bucketDebits, len(v)) return nil, nil, storeError(ErrData, str, nil) } return k, v[8:80], nil }
// existsRawUnspent returns the credit key if there exists an output recorded // for the raw unspent key. It returns nil if the k/v pair does not exist. func existsRawUnspent(ns walletdb.Bucket, k []byte) (credKey []byte) { if len(k) < 36 { return nil } v := ns.Bucket(bucketUnspent).Get(k) if len(v) < 36 { return nil } credKey = make([]byte, 72) copy(credKey, k[:32]) copy(credKey[32:68], v) copy(credKey[68:72], k[32:36]) return credKey }
// testPutValues stores all of the provided key/value pairs in the provided // bucket while checking for errors. func testPutValues(tc *testContext, bucket walletdb.Bucket, values map[string]string) bool { for k, v := range values { var vBytes []byte if v != "" { vBytes = []byte(v) } if err := bucket.Put([]byte(k), vBytes); err != nil { tc.t.Errorf("Put: unexpected error: %v", err) return false } } return true }
func putDebit(ns walletdb.Bucket, txHash *wire.ShaHash, index uint32, amount btcutil.Amount, block *Block, credKey []byte) error { k := keyDebit(txHash, index, block) v := make([]byte, 80) byteOrder.PutUint64(v, uint64(amount)) copy(v[8:80], credKey) err := ns.Bucket(bucketDebits).Put(k, v) if err != nil { str := fmt.Sprintf("failed to update debit %s input %d", txHash, index) return storeError(ErrDatabase, str, err) } return nil }
// unspendRawCredit rewrites the credit for the given key as unspent. The // output amount of the credit is returned. It returns without error if no // credit exists for the key. func unspendRawCredit(ns walletdb.Bucket, k []byte) (btcutil.Amount, error) { b := ns.Bucket(bucketCredits) v := b.Get(k) if v == nil { return 0, nil } newv := make([]byte, 9) copy(newv, v) newv[8] &^= 1 << 0 err := b.Put(k, newv) if err != nil { str := "failed to put credit" return 0, storeError(ErrDatabase, str, err) } return btcutil.Amount(byteOrder.Uint64(v[0:8])), nil }
// testGetValues checks that all of the provided key/value pairs can be // retrieved from the database and the retrieved values match the provided // values. func testGetValues(tc *testContext, bucket walletdb.Bucket, values map[string]string) bool { for k, v := range values { var vBytes []byte if v != "" { vBytes = []byte(v) } gotValue := bucket.Get([]byte(k)) if !reflect.DeepEqual(gotValue, vBytes) { tc.t.Errorf("Get: unexpected value - got %s, want %s", gotValue, vBytes) return false } } return true }
func (s *Store) unminedTxRecords(ns walletdb.Bucket) (map[wire.ShaHash]*TxRecord, error) { unmined := make(map[wire.ShaHash]*TxRecord) err := ns.Bucket(bucketUnmined).ForEach(func(k, v []byte) error { var txHash wire.ShaHash err := readRawUnminedHash(k, &txHash) if err != nil { return err } rec := new(TxRecord) err = readRawTxRecord(&txHash, v, rec) if err != nil { return err } unmined[rec.Hash] = rec return nil }) return unmined, err }
func (s *Store) unspentOutputs(ns walletdb.Bucket) ([]Credit, error) { var unspent []Credit var op wire.OutPoint var block Block err := ns.Bucket(bucketUnspent).ForEach(func(k, v []byte) error { err := readCanonicalOutPoint(k, &op) if err != nil { return err } if existsRawUnminedInput(ns, k) != nil { // Output is spent by an unmined transaction. // Skip this k/v pair. return nil } err = readUnspentBlock(v, &block) if err != nil { return err } blockTime, err := fetchBlockTime(ns, block.Height) if err != nil { return err } // TODO(jrick): reading the entire transaction should // be avoidable. Creating the credit only requires the // output amount and pkScript. rec, err := fetchTxRecord(ns, &op.Hash, &block) if err != nil { return err } txOut := rec.MsgTx.TxOut[op.Index] cred := Credit{ OutPoint: op, BlockMeta: BlockMeta{ Block: block, Time: blockTime, }, Amount: btcutil.Amount(txOut.Value), PkScript: txOut.PkScript, Received: rec.Received, FromCoinBase: blockchain.IsCoinBaseTx(&rec.MsgTx), } unspent = append(unspent, cred) return nil }) if err != nil { if _, ok := err.(Error); ok { return nil, err } str := "failed iterating unspent bucket" return nil, storeError(ErrDatabase, str, err) } err = ns.Bucket(bucketUnminedCredits).ForEach(func(k, v []byte) error { if existsRawUnminedInput(ns, k) != nil { // Output is spent by an unmined transaction. // Skip to next unmined credit. return nil } err := readCanonicalOutPoint(k, &op) if err != nil { return err } // TODO(jrick): Reading/parsing the entire transaction record // just for the output amount and script can be avoided. recVal := existsRawUnmined(ns, op.Hash[:]) var rec TxRecord err = readRawTxRecord(&op.Hash, recVal, &rec) if err != nil { return err } txOut := rec.MsgTx.TxOut[op.Index] cred := Credit{ OutPoint: op, BlockMeta: BlockMeta{ Block: Block{Height: -1}, }, Amount: btcutil.Amount(txOut.Value), PkScript: txOut.PkScript, Received: rec.Received, FromCoinBase: blockchain.IsCoinBaseTx(&rec.MsgTx), } unspent = append(unspent, cred) return nil }) if err != nil { if _, ok := err.(Error); ok { return nil, err } str := "failed iterating unmined credits bucket" return nil, storeError(ErrDatabase, str, err) } return unspent, nil }
func makeDebitIterator(ns walletdb.Bucket, prefix []byte) debitIterator { c := ns.Bucket(bucketDebits).Cursor() return debitIterator{c: c, prefix: prefix} }
// testBucketInterface ensures the bucket interface is working properly by // exercising all of its functions. func testBucketInterface(tc *testContext, bucket walletdb.Bucket) bool { if bucket.Writable() != tc.isWritable { tc.t.Errorf("Bucket writable state does not match.") return false } if tc.isWritable { // keyValues holds the keys and values to use when putting // values into the bucket. var keyValues = map[string]string{ "bucketkey1": "foo1", "bucketkey2": "foo2", "bucketkey3": "foo3", } if !testPutValues(tc, bucket, keyValues) { return false } if !testGetValues(tc, bucket, keyValues) { return false } // Iterate all of the keys using ForEach while making sure the // stored values are the expected values. keysFound := make(map[string]struct{}, len(keyValues)) err := bucket.ForEach(func(k, v []byte) error { kString := string(k) wantV, ok := keyValues[kString] if !ok { return fmt.Errorf("ForEach: key '%s' should "+ "exist", kString) } if !reflect.DeepEqual(v, []byte(wantV)) { return fmt.Errorf("ForEach: value for key '%s' "+ "does not match - got %s, want %s", kString, v, wantV) } keysFound[kString] = struct{}{} return nil }) if err != nil { tc.t.Errorf("%v", err) return false } // Ensure all keys were iterated. for k := range keyValues { if _, ok := keysFound[k]; !ok { tc.t.Errorf("ForEach: key '%s' was not iterated "+ "when it should have been", k) return false } } // Delete the keys and ensure they were deleted. if !testDeleteValues(tc, bucket, keyValues) { return false } if !testGetValues(tc, bucket, rollbackValues(keyValues)) { return false } // Ensure creating a new bucket works as expected. testBucketName := []byte("testbucket") testBucket, err := bucket.CreateBucket(testBucketName) if err != nil { tc.t.Errorf("CreateBucket: unexpected error: %v", err) return false } if !testNestedBucket(tc, testBucket) { return false } // Ensure creating a bucket that already exists fails with the // expected error. wantErr := walletdb.ErrBucketExists if _, err := bucket.CreateBucket(testBucketName); err != wantErr { tc.t.Errorf("CreateBucket: unexpected error - got %v, "+ "want %v", err, wantErr) return false } // Ensure CreateBucketIfNotExists returns an existing bucket. testBucket, err = bucket.CreateBucketIfNotExists(testBucketName) if err != nil { tc.t.Errorf("CreateBucketIfNotExists: unexpected "+ "error: %v", err) return false } if !testNestedBucket(tc, testBucket) { return false } // Ensure retrieving and existing bucket works as expected. testBucket = bucket.Bucket(testBucketName) if !testNestedBucket(tc, testBucket) { return false } // Ensure deleting a bucket works as intended. if err := bucket.DeleteBucket(testBucketName); err != nil { tc.t.Errorf("DeleteBucket: unexpected error: %v", err) return false } if b := bucket.Bucket(testBucketName); b != nil { tc.t.Errorf("DeleteBucket: bucket '%s' still exists", testBucketName) return false } // Ensure deleting a bucket that doesn't exist returns the // expected error. wantErr = walletdb.ErrBucketNotFound if err := bucket.DeleteBucket(testBucketName); err != wantErr { tc.t.Errorf("DeleteBucket: unexpected error - got %v, "+ "want %v", err, wantErr) return false } // Ensure CreateBucketIfNotExists creates a new bucket when // it doesn't already exist. testBucket, err = bucket.CreateBucketIfNotExists(testBucketName) if err != nil { tc.t.Errorf("CreateBucketIfNotExists: unexpected "+ "error: %v", err) return false } if !testNestedBucket(tc, testBucket) { return false } // Delete the test bucket to avoid leaving it around for future // calls. if err := bucket.DeleteBucket(testBucketName); err != nil { tc.t.Errorf("DeleteBucket: unexpected error: %v", err) return false } if b := bucket.Bucket(testBucketName); b != nil { tc.t.Errorf("DeleteBucket: bucket '%s' still exists", testBucketName) return false } } else { // Put should fail with bucket that is not writable. wantErr := walletdb.ErrTxNotWritable failBytes := []byte("fail") if err := bucket.Put(failBytes, failBytes); err != wantErr { tc.t.Errorf("Put did not fail with unwritable bucket") return false } // Delete should fail with bucket that is not writable. if err := bucket.Delete(failBytes); err != wantErr { tc.t.Errorf("Put did not fail with unwritable bucket") return false } // CreateBucket should fail with bucket that is not writable. if _, err := bucket.CreateBucket(failBytes); err != wantErr { tc.t.Errorf("CreateBucket did not fail with unwritable " + "bucket") return false } // CreateBucketIfNotExists should fail with bucket that is not // writable. if _, err := bucket.CreateBucketIfNotExists(failBytes); err != wantErr { tc.t.Errorf("CreateBucketIfNotExists did not fail with " + "unwritable bucket") return false } // DeleteBucket should fail with bucket that is not writable. if err := bucket.DeleteBucket(failBytes); err != wantErr { tc.t.Errorf("DeleteBucket did not fail with unwritable " + "bucket") return false } } return true }
func (s *Store) balance(ns walletdb.Bucket, minConf int32, syncHeight int32) (btcutil.Amount, error) { bal, err := fetchMinedBalance(ns) if err != nil { return 0, err } // Subtract the balance for each credit that is spent by an unmined // transaction. var op wire.OutPoint var block Block err = ns.Bucket(bucketUnspent).ForEach(func(k, v []byte) error { err := readCanonicalOutPoint(k, &op) if err != nil { return err } err = readUnspentBlock(v, &block) if err != nil { return err } if existsRawUnminedInput(ns, k) != nil { _, v := existsCredit(ns, &op.Hash, op.Index, &block) amt, err := fetchRawCreditAmount(v) if err != nil { return err } bal -= amt } return nil }) if err != nil { if _, ok := err.(Error); ok { return 0, err } str := "failed iterating unspent outputs" return 0, storeError(ErrDatabase, str, err) } // Decrement the balance for any unspent credit with less than // minConf confirmations and any (unspent) immature coinbase credit. stopConf := minConf if blockchain.CoinbaseMaturity > stopConf { stopConf = blockchain.CoinbaseMaturity } lastHeight := syncHeight - stopConf blockIt := makeReverseBlockIterator(ns) for blockIt.prev() { block := &blockIt.elem if block.Height < lastHeight { break } for i := range block.transactions { txHash := &block.transactions[i] rec, err := fetchTxRecord(ns, txHash, &block.Block) if err != nil { return 0, err } numOuts := uint32(len(rec.MsgTx.TxOut)) for i := uint32(0); i < numOuts; i++ { // Avoid double decrementing the credit amount // if it was already removed for being spent by // an unmined tx. opKey := canonicalOutPoint(txHash, i) if existsRawUnminedInput(ns, opKey) != nil { continue } _, v := existsCredit(ns, txHash, i, &block.Block) if v == nil { continue } amt, spent, err := fetchRawCreditAmountSpent(v) if err != nil { return 0, err } if spent { continue } confs := syncHeight - block.Height + 1 if confs < minConf || (blockchain.IsCoinBaseTx(&rec.MsgTx) && confs < blockchain.CoinbaseMaturity) { bal -= amt } } } } if blockIt.err != nil { return 0, blockIt.err } // If unmined outputs are included, increment the balance for each // output that is unspent. if minConf == 0 { err = ns.Bucket(bucketUnminedCredits).ForEach(func(k, v []byte) error { if existsRawUnminedInput(ns, k) != nil { // Output is spent by an unmined transaction. // Skip to next unmined credit. return nil } amount, err := fetchRawUnminedCreditAmount(v) if err != nil { return err } bal += amount return nil }) if err != nil { if _, ok := err.(Error); ok { return 0, err } str := "failed to iterate over unmined credits bucket" return 0, storeError(ErrDatabase, str, err) } } return bal, nil }
func existsRawUnmined(ns walletdb.Bucket, k []byte) (v []byte) { return ns.Bucket(bucketUnmined).Get(k) }