// 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 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 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 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 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 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 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 fetchMinedBalance(ns walletdb.Bucket) (coinutil.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 coinutil.Amount(byteOrder.Uint64(v)), nil }
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 }
func putMinedBalance(ns walletdb.Bucket, amt coinutil.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 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 }
// 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 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 }
// 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) (coinutil.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 coinutil.Amount(byteOrder.Uint64(v[0:8])), putRawCredit(ns, k, v) }
// 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 coinutil.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 }
// 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 }
// 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) (coinutil.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 coinutil.Amount(byteOrder.Uint64(v[0:8])), nil }
func (s *Store) unminedTxs(ns walletdb.Bucket) ([]*wire.MsgTx, error) { var unmined []*wire.MsgTx err := ns.Bucket(bucketUnmined).ForEach(func(k, v []byte) error { // TODO: Parsing transactions from the db may be a little // expensive. It's possible the caller only wants the // serialized transactions. var txHash wire.ShaHash err := readRawUnminedHash(k, &txHash) if err != nil { return err } var rec TxRecord err = readRawTxRecord(&txHash, v, &rec) if err != nil { return err } tx := rec.MsgTx unmined = append(unmined, &tx) return nil }) return unmined, err }
// 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 existsRawUnmined(ns walletdb.Bucket, k []byte) (v []byte) { return ns.Bucket(bucketUnmined).Get(k) }
func makeCreditIterator(ns walletdb.Bucket, prefix []byte) creditIterator { c := ns.Bucket(bucketCredits).Cursor() return creditIterator{c: c, prefix: prefix} }
func existsCredit(ns walletdb.Bucket, txHash *wire.ShaHash, index uint32, block *Block) (k, v []byte) { k = keyCredit(txHash, index, block) v = ns.Bucket(bucketCredits).Get(k) return }
func makeDebitIterator(ns walletdb.Bucket, prefix []byte) debitIterator { c := ns.Bucket(bucketDebits).Cursor() return debitIterator{c: c, prefix: prefix} }
func existsRawCredit(ns walletdb.Bucket, k []byte) []byte { return ns.Bucket(bucketCredits).Get(k) }