// putMeta puts a k-v into the meta bucket. func putMeta(ns walletdb.ReadWriteBucket, key []byte, n int32) error { bucket := ns.NestedReadWriteBucket(metaBucketName) err := bucket.Put(key, uint32ToBytes(uint32(n))) if err != nil { str := fmt.Sprintf("failed to store meta key '%s'", key) return stakeStoreError(ErrDatabase, str, err) } return nil }
// testDeleteValues removes all of the provided key/value pairs from the // provided bucket. func testDeleteValues(tc *testContext, bucket walletdb.ReadWriteBucket, 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 }
// testPutValues stores all of the provided key/value pairs in the provided // bucket while checking for errors. func testPutValues(tc *testContext, bucket walletdb.ReadWriteBucket, 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 }
// updateStakePoolUserTickets updates a database entry for a pool user's tickets. // The function pulls the current entry in the database, checks to see if the // ticket is already there, updates it accordingly, or adds it to the list of // tickets. func updateStakePoolUserTickets(ns walletdb.ReadWriteBucket, scriptHash [20]byte, record *PoolTicket) error { // Fetch the current content of the key. // Possible buggy behaviour: If deserialization fails, // we won't detect it here. We assume we're throwing // ErrPoolUserTicketsNotFound. oldRecords, _ := fetchStakePoolUserTickets(ns, scriptHash) // Don't reinsert duplicate records we already have. if duplicateExistsInUserTickets(record, oldRecords) { return nil } // Does this modify an old record? If so, modify the record // itself and push. Otherwise, we need to insert a new // record. var records []*PoolTicket preExists, loc := recordExistsInUserTickets(record, oldRecords) if preExists { records = oldRecords records[loc] = record } else { // Either create a slice if currently nothing exists for this // key in the db, or append the entry to the slice. if oldRecords == nil { records = make([]*PoolTicket, 1) records[0] = record } else { records = append(oldRecords, record) } } bucket := ns.NestedReadWriteBucket(metaBucketName) key := make([]byte, stakePoolTicketsPrefixSize+scriptHashSize) copy(key[0:stakePoolTicketsPrefixSize], stakePoolTicketsPrefix) copy(key[stakePoolTicketsPrefixSize:stakePoolTicketsPrefixSize+scriptHashSize], scriptHash[:]) // Write the serialized ticket data keyed by the script. serializedRecords := serializeUserTickets(records) err := bucket.Put(key, serializedRecords) if err != nil { str := fmt.Sprintf("failed to store pool user ticket records '%x'", scriptHash) return stakeStoreError(ErrDatabase, str, err) } return nil }
// updateSStxRecord updates a sstx record in the sstx records bucket. func updateSStxRecord(ns walletdb.ReadWriteBucket, record *sstxRecord, voteBits stake.VoteBits) error { bucket := ns.NestedReadWriteBucket(sstxRecordsBucketName) // Write the serialized txrecord keyed by the tx hash. serializedSStxRecord, err := serializeSStxRecord(record, voteBits) if err != nil { str := fmt.Sprintf("failed to serialize sstxrecord '%s'", record.tx.Sha()) return stakeStoreError(ErrDatabase, str, err) } err = bucket.Put(record.tx.Sha().Bytes(), serializedSStxRecord) if err != nil { str := fmt.Sprintf("failed to store sstxrecord '%s'", record.tx.Sha()) return stakeStoreError(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.ReadWriteBucket, 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 }
// updateSStxRecordVoteBits updates an individual ticket's intended voteBits // which are used to override the default voteBits when voting. func updateSStxRecordVoteBits(ns walletdb.ReadWriteBucket, hash *chainhash.Hash, voteBits stake.VoteBits) error { if len(voteBits.ExtendedBits) > stake.MaxSingleBytePushLength-2 { str := fmt.Sprintf("voteBitsExt too long (got %v bytes, want max %v)", len(voteBits.ExtendedBits), stake.MaxSingleBytePushLength-2) return stakeStoreError(ErrData, str, nil) } bucket := ns.NestedReadWriteBucket(sstxRecordsBucketName) key := hash.Bytes() val := bucket.Get(key) if val == nil { str := fmt.Sprintf("missing sstx record for hash '%s'", hash.String()) return stakeStoreError(ErrSStxNotFound, str, nil) } valLen := len(val) valCopy := make([]byte, valLen, valLen) copy(valCopy, val) // Move the cursor to the voteBits position and rewrite it. curPos := 0 curPos += int64Size curPos += int32Size // Write the intended votebits length (uint8). valCopy[curPos] = byte(int16Size + len(voteBits.ExtendedBits)) curPos += int8Size // Write the first two bytes for the intended votebits. byteOrder.PutUint16(valCopy[curPos:curPos+int16Size], voteBits.Bits) curPos += int16Size // Copy the remaining data from voteBitsExt. copy(valCopy[curPos:], voteBits.ExtendedBits[:]) err := bucket.Put(key, valCopy) if err != nil { str := fmt.Sprintf("failed to update sstxrecord votebits for '%s'", hash) return stakeStoreError(ErrDatabase, str, err) } return nil }
// removeStakePoolInvalUserTickets removes the ticket hash from the inval // ticket bucket. func removeStakePoolInvalUserTickets(ns walletdb.ReadWriteBucket, scriptHash [20]byte, record *chainhash.Hash) error { // Fetch the current content of the key. // Possible buggy behaviour: If deserialization fails, // we won't detect it here. We assume we're throwing // ErrPoolUserInvalTcktsNotFound. oldRecords, _ := fetchStakePoolUserInvalTickets(ns, scriptHash) // Don't need to remove records that don't exist. if !duplicateExistsInInvalTickets(record, oldRecords) { return nil } var newRecords []*chainhash.Hash for i := range oldRecords { if record.IsEqual(oldRecords[i]) { newRecords = append(oldRecords[:i:i], oldRecords[i+1:]...) } } if newRecords == nil { return nil } bucket := ns.NestedReadWriteBucket(metaBucketName) key := make([]byte, stakePoolInvalidPrefixSize+scriptHashSize) copy(key[0:stakePoolInvalidPrefixSize], stakePoolInvalidPrefix) copy(key[stakePoolInvalidPrefixSize:stakePoolInvalidPrefixSize+scriptHashSize], scriptHash[:]) // Write the serialized invalid user ticket hashes. serializedRecords := serializeUserInvalTickets(newRecords) err := bucket.Put(key, serializedRecords) if err != nil { str := fmt.Sprintf("failed to store pool user invalid ticket "+ "records '%x'", scriptHash) return stakeStoreError(ErrDatabase, str, err) } return nil }
// updateStakePoolInvalUserTickets updates a database entry for a pool user's // invalid tickets. The function pulls the current entry in the database, // checks to see if the ticket is already there. If it is it returns, otherwise // it adds it to the list of tickets. func updateStakePoolInvalUserTickets(ns walletdb.ReadWriteBucket, scriptHash [20]byte, record *chainhash.Hash) error { // Fetch the current content of the key. // Possible buggy behaviour: If deserialization fails, // we won't detect it here. We assume we're throwing // ErrPoolUserInvalTcktsNotFound. oldRecords, _ := fetchStakePoolUserInvalTickets(ns, scriptHash) // Don't reinsert duplicate records we already have. if duplicateExistsInInvalTickets(record, oldRecords) { return nil } // Either create a slice if currently nothing exists for this // key in the db, or append the entry to the slice. var records []*chainhash.Hash if oldRecords == nil { records = make([]*chainhash.Hash, 1) records[0] = record } else { records = append(oldRecords, record) } bucket := ns.NestedReadWriteBucket(metaBucketName) key := make([]byte, stakePoolInvalidPrefixSize+scriptHashSize) copy(key[0:stakePoolInvalidPrefixSize], stakePoolInvalidPrefix) copy(key[stakePoolInvalidPrefixSize:stakePoolInvalidPrefixSize+scriptHashSize], scriptHash[:]) // Write the serialized invalid user ticket hashes. serializedRecords := serializeUserInvalTickets(records) err := bucket.Put(key, serializedRecords) if err != nil { str := fmt.Sprintf("failed to store pool user invalid ticket "+ "records '%x'", scriptHash) return stakeStoreError(ErrDatabase, str, err) } return nil }
// updateSSRtxRecord updates an SSRtx record in the SSRtx records bucket. func updateSSRtxRecord(ns walletdb.ReadWriteBucket, hash *chainhash.Hash, record *ssrtxRecord) error { // Fetch the current content of the key. // Possible buggy behaviour: If deserialization fails, // we won't detect it here. We assume we're throwing // ErrSSRtxsNotFound. oldRecords, _ := fetchSSRtxRecords(ns, hash) // Don't reinsert records we already have. if ssrtxRecordExistsInRecords(record, oldRecords) { return nil } bucket := ns.NestedReadWriteBucket(ssrtxRecordsBucketName) var records []*ssrtxRecord // Either create a slice if currently nothing exists for this // key in the db, or append the entry to the slice. if oldRecords == nil { records = make([]*ssrtxRecord, 1) records[0] = record } else { records = append(oldRecords, record) } // Write the serialized SSRtxs keyed by the sstx hash. serializedSSRtxsRecords := serializeSSRtxRecords(records) err := bucket.Put(hash.Bytes(), serializedSSRtxsRecords) if err != nil { str := fmt.Sprintf("failed to store ssrtx records '%s'", hash) return stakeStoreError(ErrDatabase, str, err) } return nil }
// testBucketInterface ensures the bucket interface is working properly by // exercising all of its functions. func testBucketInterface(tc *testContext, bucket walletdb.ReadWriteBucket) 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.ReadWriteBucket(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.ReadWriteBucket(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.ReadWriteBucket(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 }
// initialize creates the DB if it doesn't exist, and otherwise // loads the database. func initializeEmpty(ns walletdb.ReadWriteBucket) error { // Initialize the buckets and main db fields as needed. mainBucket, err := ns.CreateBucketIfNotExists(mainBucketName) if err != nil { str := "failed to create main bucket" return stakeStoreError(ErrDatabase, str, err) } _, err = ns.CreateBucketIfNotExists(sstxRecordsBucketName) if err != nil { str := "failed to create sstx records bucket" return stakeStoreError(ErrDatabase, str, err) } _, err = ns.CreateBucketIfNotExists(ssgenRecordsBucketName) if err != nil { str := "failed to create ssgen records bucket" return stakeStoreError(ErrDatabase, str, err) } _, err = ns.CreateBucketIfNotExists(ssrtxRecordsBucketName) if err != nil { str := "failed to create ssrtx records bucket" return stakeStoreError(ErrDatabase, str, err) } _, err = ns.CreateBucketIfNotExists(metaBucketName) if err != nil { str := "failed to create meta bucket" return stakeStoreError(ErrDatabase, str, err) } // Save the most recent tx store version if it isn't already // there, otherwise keep track of it for potential upgrades. var version uint32 verBytes := mainBucket.Get(stakeStoreVersionName) if verBytes == nil { version = LatestStakeMgrVersion var buf [4]byte byteOrder.PutUint32(buf[:], version) err := mainBucket.Put(stakeStoreVersionName, buf[:]) if err != nil { str := "failed to store latest database version" return stakeStoreError(ErrDatabase, str, err) } } else { version = byteOrder.Uint32(verBytes) } var createDate uint64 createBytes := mainBucket.Get(stakeStoreCreateDateName) if createBytes == nil { createDate = uint64(time.Now().Unix()) var buf [8]byte byteOrder.PutUint64(buf[:], createDate) err := mainBucket.Put(stakeStoreCreateDateName, buf[:]) if err != nil { str := "failed to store database creation time" return stakeStoreError(ErrDatabase, str, err) } } else { createDate = byteOrder.Uint64(createBytes) } if err != nil { str := "failed to load database" return stakeStoreError(ErrDatabase, str, err) } return nil }