decred.org/dcrwallet/v3@v3.1.0/wallet/udb/stakedb.go (about)

     1  // Copyright (c) 2015-2017 The Decred developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package udb
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/binary"
    10  	"io"
    11  	"time"
    12  
    13  	"decred.org/dcrwallet/v3/errors"
    14  	"decred.org/dcrwallet/v3/wallet/walletdb"
    15  	"github.com/decred/dcrd/blockchain/stake/v5"
    16  	"github.com/decred/dcrd/chaincfg/chainhash"
    17  	"github.com/decred/dcrd/dcrutil/v4"
    18  	"github.com/decred/dcrd/wire"
    19  )
    20  
    21  const (
    22  	// Size of various types in bytes.
    23  	int8Size  = 1
    24  	int16Size = 2
    25  	int32Size = 4
    26  	int64Size = 8
    27  	hashSize  = 32
    28  
    29  	// stakePoolUserTicketSize is the size
    30  	// of a serialized stake pool user
    31  	// ticket.
    32  	// hash + uint32 + uint8 + uint32 + hash
    33  	stakePoolUserTicketSize = 32 + 4 + 1 + 4 + 32
    34  
    35  	// stakePoolTicketsPrefixSize is the length of
    36  	// stakePoolTicketsPrefix.
    37  	stakePoolTicketsPrefixSize = 5
    38  
    39  	// stakePoolInvalidPrefixSize is the length of
    40  	// stakePoolInvalidPrefix.
    41  	stakePoolInvalidPrefixSize = 5
    42  
    43  	// scriptHashLen is the length of a HASH160
    44  	// hash.
    45  	scriptHashSize = 20
    46  )
    47  
    48  var (
    49  	// sstxTicket2PKHPrefix is the PkScript byte prefix for an SStx
    50  	// P2PKH ticket output. The entire prefix is 0xba76a914, but we
    51  	// only use the first 3 bytes.
    52  	sstxTicket2PKHPrefix = []byte{0xba, 0x76, 0xa9}
    53  
    54  	// sstxTicket2SHPrefix is the PkScript byte prefix for an SStx
    55  	// P2SH ticket output.
    56  	sstxTicket2SHPrefix = []byte{0xba, 0xa9, 0x14}
    57  
    58  	// stakePoolTicketsPrefix is the byte slice prefix for valid
    59  	// tickets in the stake pool for a given user.
    60  	stakePoolTicketsPrefix = []byte("tickt")
    61  
    62  	// stakePoolInvalidPrefix is the byte slice prefix for invalid
    63  	// tickets in the stake pool for a given user.
    64  	stakePoolInvalidPrefix = []byte("invld")
    65  )
    66  
    67  // Key names for various database fields.
    68  // sstxRecords
    69  //
    70  //	key: sstx tx hash
    71  //	val: sstxRecord
    72  //
    73  // ssgenRecords
    74  //
    75  //	key: sstx tx hash
    76  //	val: serialized slice of ssgenRecords
    77  var (
    78  	// Bucket names.
    79  	sstxRecordsBucketName  = []byte("sstxrecords")
    80  	ssgenRecordsBucketName = []byte("ssgenrecords")
    81  	ssrtxRecordsBucketName = []byte("ssrtxrecords")
    82  
    83  	// Db related key names (main bucket).
    84  	stakeStoreCreateDateName = []byte("stakestorecreated")
    85  )
    86  
    87  // deserializeSStxRecord deserializes the passed serialized tx record information.
    88  func deserializeSStxRecord(serializedSStxRecord []byte, dbVersion uint32) (*sstxRecord, error) {
    89  	switch {
    90  	case dbVersion < 3:
    91  		record := new(sstxRecord)
    92  
    93  		curPos := 0
    94  
    95  		// Read MsgTx size (as a uint64).
    96  		msgTxLen := int(binary.LittleEndian.Uint64(
    97  			serializedSStxRecord[curPos : curPos+int64Size]))
    98  		curPos += int64Size
    99  
   100  		// Pretend to read the pkScrLoc for the 0th output pkScript.
   101  		curPos += int32Size
   102  
   103  		// Read the intended voteBits and extended voteBits length (uint8).
   104  		record.voteBitsSet = false
   105  		voteBitsLen := int(serializedSStxRecord[curPos])
   106  		if voteBitsLen != 0 {
   107  			record.voteBitsSet = true
   108  		}
   109  		curPos += int8Size
   110  
   111  		// Read the assumed 2 byte VoteBits as well as the extended
   112  		// votebits (75 bytes max).
   113  		record.voteBits = binary.LittleEndian.Uint16(
   114  			serializedSStxRecord[curPos : curPos+int16Size])
   115  		curPos += int16Size
   116  		if voteBitsLen != 0 {
   117  			record.voteBitsExt = make([]byte, voteBitsLen-int16Size)
   118  			copy(record.voteBitsExt, serializedSStxRecord[curPos:curPos+voteBitsLen-int16Size])
   119  		}
   120  		curPos += stake.MaxSingleBytePushLength - int16Size
   121  
   122  		// Prepare a buffer for the msgTx.
   123  		buf := bytes.NewBuffer(serializedSStxRecord[curPos : curPos+msgTxLen])
   124  		curPos += msgTxLen
   125  
   126  		// Deserialize transaction.
   127  		msgTx := new(wire.MsgTx)
   128  		err := msgTx.Deserialize(buf)
   129  		if err != nil {
   130  			if errors.Is(err, io.EOF) {
   131  				err = io.ErrUnexpectedEOF
   132  			}
   133  			return nil, err
   134  		}
   135  
   136  		// Create and save the dcrutil.Tx of the read MsgTx and set its index.
   137  		tx := dcrutil.NewTx(msgTx)
   138  		tx.SetIndex(dcrutil.TxIndexUnknown)
   139  		tx.SetTree(wire.TxTreeStake)
   140  		record.tx = tx
   141  
   142  		// Read received unix time (int64).
   143  		received := int64(binary.LittleEndian.Uint64(
   144  			serializedSStxRecord[curPos : curPos+int64Size]))
   145  		record.ts = time.Unix(received, 0)
   146  
   147  		return record, nil
   148  
   149  	case dbVersion >= 3:
   150  		// Don't need to read the pkscript location, so first four bytes are
   151  		// skipped.
   152  		serializedSStxRecord = serializedSStxRecord[4:]
   153  
   154  		var tx wire.MsgTx
   155  		err := tx.Deserialize(bytes.NewReader(serializedSStxRecord))
   156  		if err != nil {
   157  			return nil, err
   158  		}
   159  		unixTime := int64(binary.LittleEndian.Uint64(serializedSStxRecord[tx.SerializeSize():]))
   160  		return &sstxRecord{tx: dcrutil.NewTx(&tx), ts: time.Unix(unixTime, 0)}, nil
   161  
   162  	default:
   163  		panic("unreachable")
   164  	}
   165  }
   166  
   167  // deserializeSStxTicketHash160 deserializes and returns a 20 byte script
   168  // hash for a ticket's 0th output.
   169  func deserializeSStxTicketHash160(serializedSStxRecord []byte, dbVersion uint32) (hash160 []byte, p2sh bool, err error) {
   170  	var pkscriptLocOffset int
   171  	var txOffset int
   172  	switch {
   173  	case dbVersion < 3:
   174  		pkscriptLocOffset = 8 // After transaction size
   175  		txOffset = 8 + 4 + 1 + stake.MaxSingleBytePushLength
   176  	case dbVersion >= 3:
   177  		pkscriptLocOffset = 0
   178  		txOffset = 4
   179  	}
   180  
   181  	pkscriptLoc := int(binary.LittleEndian.Uint32(serializedSStxRecord[pkscriptLocOffset:])) + txOffset
   182  
   183  	// Pop off the script prefix, then pop off the 20 bytes
   184  	// HASH160 pubkey or script hash.
   185  	prefixBytes := serializedSStxRecord[pkscriptLoc : pkscriptLoc+3]
   186  	scriptHash := make([]byte, 20)
   187  	p2sh = false
   188  	switch {
   189  	case bytes.Equal(prefixBytes, sstxTicket2PKHPrefix):
   190  		scrHashLoc := pkscriptLoc + 4
   191  		if scrHashLoc+20 >= len(serializedSStxRecord) {
   192  			return nil, false, errors.E(errors.IO, "bad sstx record size")
   193  		}
   194  		copy(scriptHash, serializedSStxRecord[scrHashLoc:scrHashLoc+20])
   195  	case bytes.Equal(prefixBytes, sstxTicket2SHPrefix):
   196  		scrHashLoc := pkscriptLoc + 3
   197  		if scrHashLoc+20 >= len(serializedSStxRecord) {
   198  			return nil, false, errors.E(errors.IO, "bad sstx record size")
   199  		}
   200  		copy(scriptHash, serializedSStxRecord[scrHashLoc:scrHashLoc+20])
   201  		p2sh = true
   202  	}
   203  
   204  	return scriptHash, p2sh, nil
   205  }
   206  
   207  // serializeSSTxRecord returns the serialization of the passed txrecord row.
   208  func serializeSStxRecord(record *sstxRecord, dbVersion uint32) ([]byte, error) {
   209  	switch {
   210  	case dbVersion < 3:
   211  		msgTx := record.tx.MsgTx()
   212  		msgTxSize := int64(msgTx.SerializeSize())
   213  
   214  		size := 0
   215  
   216  		// tx tree is implicit (stake)
   217  
   218  		// size of msgTx (recast to int64)
   219  		size += int64Size
   220  
   221  		// byte index of the ticket pk script
   222  		size += int32Size
   223  
   224  		// intended votebits length (uint8)
   225  		size += int8Size
   226  
   227  		// intended votebits (75 bytes)
   228  		size += stake.MaxSingleBytePushLength
   229  
   230  		// msgTx size is variable.
   231  		size += int(msgTxSize)
   232  
   233  		// timestamp (int64)
   234  		size += int64Size
   235  
   236  		buf := make([]byte, size)
   237  
   238  		curPos := 0
   239  
   240  		// Write msgTx size (as a uint64).
   241  		binary.LittleEndian.PutUint64(buf[curPos:curPos+int64Size], uint64(msgTxSize))
   242  		curPos += int64Size
   243  
   244  		// Write the pkScript loc for the ticket output as a uint32.
   245  		pkScrLoc := msgTx.PkScriptLocs()
   246  		binary.LittleEndian.PutUint32(buf[curPos:curPos+int32Size], uint32(pkScrLoc[0]))
   247  		curPos += int32Size
   248  
   249  		// Write the intended votebits length (uint8). Hardcode the uint16
   250  		// size for now.
   251  		buf[curPos] = byte(int16Size + len(record.voteBitsExt))
   252  		curPos += int8Size
   253  
   254  		// Write the first two bytes for the intended votebits (75 bytes max),
   255  		// then write the extended vote bits.
   256  		binary.LittleEndian.PutUint16(buf[curPos:curPos+int16Size], record.voteBits)
   257  		curPos += int16Size
   258  		copy(buf[curPos:], record.voteBitsExt)
   259  		curPos += stake.MaxSingleBytePushLength - 2
   260  
   261  		// Serialize and write transaction.
   262  		var b bytes.Buffer
   263  		b.Grow(msgTx.SerializeSize())
   264  		err := msgTx.Serialize(&b)
   265  		if err != nil {
   266  			return buf, err
   267  		}
   268  		copy(buf[curPos:curPos+int(msgTxSize)], b.Bytes())
   269  		curPos += int(msgTxSize)
   270  
   271  		// Write received unix time (int64).
   272  		binary.LittleEndian.PutUint64(buf[curPos:curPos+int64Size], uint64(record.ts.Unix()))
   273  
   274  		return buf, nil
   275  
   276  	case dbVersion >= 3:
   277  		tx := record.tx.MsgTx()
   278  		txSize := tx.SerializeSize()
   279  
   280  		buf := make([]byte, 4+txSize+8) // pkscript location + tx + unix timestamp
   281  		pkScrLoc := tx.PkScriptLocs()
   282  		binary.LittleEndian.PutUint32(buf, uint32(pkScrLoc[0]))
   283  		err := tx.Serialize(bytes.NewBuffer(buf[4:4]))
   284  		if err != nil {
   285  			return nil, err
   286  		}
   287  		binary.LittleEndian.PutUint64(buf[4+txSize:], uint64(record.ts.Unix()))
   288  		return buf, nil
   289  
   290  	default:
   291  		panic("unreachable")
   292  	}
   293  }
   294  
   295  // stakeStoreExists returns whether or not the stake store has already
   296  // been created in the given database namespace.
   297  func stakeStoreExists(ns walletdb.ReadBucket) bool {
   298  	mainBucket := ns.NestedReadBucket(mainBucketName)
   299  	return mainBucket != nil
   300  }
   301  
   302  // fetchSStxRecord retrieves a tx record from the sstx records bucket
   303  // with the given hash.
   304  func fetchSStxRecord(ns walletdb.ReadBucket, hash *chainhash.Hash, dbVersion uint32) (*sstxRecord, error) {
   305  	bucket := ns.NestedReadBucket(sstxRecordsBucketName)
   306  
   307  	key := hash[:]
   308  	val := bucket.Get(key)
   309  	if val == nil {
   310  		return nil, errors.E(errors.NotExist, errors.Errorf("no ticket purchase %v", hash))
   311  	}
   312  
   313  	return deserializeSStxRecord(val, dbVersion)
   314  }
   315  
   316  // fetchSStxRecordSStxTicketHash160 retrieves a ticket 0th output script or
   317  // pubkeyhash from the sstx records bucket with the given hash.
   318  func fetchSStxRecordSStxTicketHash160(ns walletdb.ReadBucket, hash *chainhash.Hash, dbVersion uint32) (hash160 []byte, p2sh bool, err error) {
   319  	bucket := ns.NestedReadBucket(sstxRecordsBucketName)
   320  
   321  	key := hash[:]
   322  	val := bucket.Get(key)
   323  	if val == nil {
   324  		return nil, false, errors.E(errors.NotExist, errors.Errorf("no ticket purchase %v", hash))
   325  	}
   326  
   327  	return deserializeSStxTicketHash160(val, dbVersion)
   328  }
   329  
   330  // putSStxRecord inserts a given SStx record to the SStxrecords bucket.
   331  func putSStxRecord(ns walletdb.ReadWriteBucket, record *sstxRecord, dbVersion uint32) error {
   332  	bucket := ns.NestedReadWriteBucket(sstxRecordsBucketName)
   333  
   334  	// Write the serialized txrecord keyed by the tx hash.
   335  	serializedSStxRecord, err := serializeSStxRecord(record, dbVersion)
   336  	if err != nil {
   337  		return errors.E(errors.IO, err)
   338  	}
   339  	err = bucket.Put(record.tx.Hash()[:], serializedSStxRecord)
   340  	if err != nil {
   341  		return errors.E(errors.IO, err)
   342  	}
   343  	return nil
   344  }
   345  
   346  // deserializeUserTicket deserializes the passed serialized user
   347  // ticket information.
   348  func deserializeUserTicket(serializedTicket []byte) (*PoolTicket, error) {
   349  	// Cursory check to make sure that the size of the
   350  	// ticket makes sense.
   351  	if len(serializedTicket)%stakePoolUserTicketSize != 0 {
   352  		return nil, errors.E(errors.IO, "invalid pool ticket record size")
   353  	}
   354  
   355  	record := new(PoolTicket)
   356  
   357  	curPos := 0
   358  
   359  	// Insert the ticket hash into the record.
   360  	copy(record.Ticket[:], serializedTicket[curPos:curPos+hashSize])
   361  	curPos += hashSize
   362  
   363  	// Insert the ticket height into the record.
   364  	record.HeightTicket = binary.LittleEndian.Uint32(
   365  		serializedTicket[curPos : curPos+int32Size])
   366  	curPos += int32Size
   367  
   368  	// Insert the status into the record.
   369  	record.Status = TicketStatus(serializedTicket[curPos])
   370  	curPos += int8Size
   371  
   372  	// Insert the spent by height into the record.
   373  	record.HeightSpent = binary.LittleEndian.Uint32(
   374  		serializedTicket[curPos : curPos+int32Size])
   375  	curPos += int32Size
   376  
   377  	// Insert the spending hash into the record.
   378  	copy(record.SpentBy[:], serializedTicket[curPos:curPos+hashSize])
   379  
   380  	return record, nil
   381  }
   382  
   383  // deserializeUserTickets deserializes the passed serialized pool
   384  // users tickets information.
   385  func deserializeUserTickets(serializedTickets []byte) ([]*PoolTicket, error) {
   386  	// Cursory check to make sure that the number of records
   387  	// makes sense.
   388  	if len(serializedTickets)%stakePoolUserTicketSize != 0 {
   389  		err := io.ErrUnexpectedEOF
   390  		return nil, err
   391  	}
   392  
   393  	numRecords := len(serializedTickets) / stakePoolUserTicketSize
   394  
   395  	records := make([]*PoolTicket, numRecords)
   396  
   397  	// Loop through all the records, deserialize them, and
   398  	// store them.
   399  	for i := 0; i < numRecords; i++ {
   400  		record, err := deserializeUserTicket(
   401  			serializedTickets[i*stakePoolUserTicketSize : (i+
   402  				1)*stakePoolUserTicketSize])
   403  		if err != nil {
   404  			return nil, err
   405  		}
   406  
   407  		records[i] = record
   408  	}
   409  
   410  	return records, nil
   411  }
   412  
   413  // serializeUserTicket returns the serialization of a single stake pool
   414  // user ticket.
   415  func serializeUserTicket(record *PoolTicket) []byte {
   416  	buf := make([]byte, stakePoolUserTicketSize)
   417  
   418  	curPos := 0
   419  
   420  	// Write the ticket hash.
   421  	copy(buf[curPos:curPos+hashSize], record.Ticket[:])
   422  	curPos += hashSize
   423  
   424  	// Write the ticket block height.
   425  	binary.LittleEndian.PutUint32(buf[curPos:curPos+int32Size], record.HeightTicket)
   426  	curPos += int32Size
   427  
   428  	// Write the ticket status.
   429  	buf[curPos] = byte(record.Status)
   430  	curPos += int8Size
   431  
   432  	// Write the spending height.
   433  	binary.LittleEndian.PutUint32(buf[curPos:curPos+int32Size], record.HeightSpent)
   434  	curPos += int32Size
   435  
   436  	// Write the spending tx hash.
   437  	copy(buf[curPos:curPos+hashSize], record.SpentBy[:])
   438  
   439  	return buf
   440  }
   441  
   442  // serializeUserTickets returns the serialization of the passed stake pool
   443  // user tickets slice.
   444  func serializeUserTickets(records []*PoolTicket) []byte {
   445  	numRecords := len(records)
   446  
   447  	buf := make([]byte, numRecords*stakePoolUserTicketSize)
   448  
   449  	// Serialize and write each record into the slice sequentially.
   450  	for i := 0; i < numRecords; i++ {
   451  		recordBytes := serializeUserTicket(records[i])
   452  
   453  		copy(buf[i*stakePoolUserTicketSize:(i+1)*stakePoolUserTicketSize],
   454  			recordBytes)
   455  	}
   456  
   457  	return buf
   458  }
   459  
   460  // fetchStakePoolUserTickets retrieves pool user tickets from the meta bucket with
   461  // the given hash.
   462  func fetchStakePoolUserTickets(ns walletdb.ReadBucket, scriptHash [20]byte) ([]*PoolTicket, error) {
   463  	bucket := ns.NestedReadBucket(metaBucketName)
   464  
   465  	key := make([]byte, stakePoolTicketsPrefixSize+scriptHashSize)
   466  	copy(key[0:stakePoolTicketsPrefixSize], stakePoolTicketsPrefix)
   467  	copy(key[stakePoolTicketsPrefixSize:stakePoolTicketsPrefixSize+scriptHashSize],
   468  		scriptHash[:])
   469  	val := bucket.Get(key)
   470  	if val == nil {
   471  		return nil, errors.E(errors.NotExist, errors.Errorf("no ticket purchase for hash160 %x", &scriptHash))
   472  	}
   473  
   474  	return deserializeUserTickets(val)
   475  }
   476  
   477  // duplicateExistsInUserTickets checks to see if an exact duplicated of a
   478  // record already exists in a slice of user ticket records.
   479  func duplicateExistsInUserTickets(record *PoolTicket, records []*PoolTicket) bool {
   480  	for _, r := range records {
   481  		if *r == *record {
   482  			return true
   483  		}
   484  	}
   485  	return false
   486  }
   487  
   488  // recordExistsInUserTickets checks to see if a record already exists
   489  // in a slice of user ticket records. If it does exist, it returns
   490  // the location where it exists in the slice.
   491  func recordExistsInUserTickets(record *PoolTicket, records []*PoolTicket) (bool, int) {
   492  	for i, r := range records {
   493  		if r.Ticket == record.Ticket {
   494  			return true, i
   495  		}
   496  	}
   497  	return false, 0
   498  }
   499  
   500  // updateStakePoolUserTickets updates a database entry for a pool user's tickets.
   501  // The function pulls the current entry in the database, checks to see if the
   502  // ticket is already there, updates it accordingly, or adds it to the list of
   503  // tickets.
   504  func updateStakePoolUserTickets(ns walletdb.ReadWriteBucket, scriptHash [20]byte, record *PoolTicket) error {
   505  	// Fetch the current content of the key.
   506  	// Possible buggy behaviour: If deserialization fails,
   507  	// we won't detect it here. We assume we're throwing
   508  	// ErrPoolUserTicketsNotFound.
   509  	oldRecords, _ := fetchStakePoolUserTickets(ns, scriptHash)
   510  
   511  	// Don't reinsert duplicate records we already have.
   512  	if duplicateExistsInUserTickets(record, oldRecords) {
   513  		return nil
   514  	}
   515  
   516  	// Does this modify an old record? If so, modify the record
   517  	// itself and push. Otherwise, we need to insert a new
   518  	// record.
   519  	var records []*PoolTicket
   520  	preExists, loc := recordExistsInUserTickets(record, oldRecords)
   521  	if preExists {
   522  		records = oldRecords
   523  		records[loc] = record
   524  	} else {
   525  		// Either create a slice if currently nothing exists for this
   526  		// key in the db, or append the entry to the slice.
   527  		if oldRecords == nil {
   528  			records = make([]*PoolTicket, 1)
   529  			records[0] = record
   530  		} else {
   531  			records = append(oldRecords, record)
   532  		}
   533  	}
   534  
   535  	bucket := ns.NestedReadWriteBucket(metaBucketName)
   536  	key := make([]byte, stakePoolTicketsPrefixSize+scriptHashSize)
   537  	copy(key[0:stakePoolTicketsPrefixSize], stakePoolTicketsPrefix)
   538  	copy(key[stakePoolTicketsPrefixSize:stakePoolTicketsPrefixSize+scriptHashSize],
   539  		scriptHash[:])
   540  
   541  	// Write the serialized ticket data keyed by the script.
   542  	serializedRecords := serializeUserTickets(records)
   543  
   544  	err := bucket.Put(key, serializedRecords)
   545  	if err != nil {
   546  		return errors.E(errors.IO, err)
   547  	}
   548  	return nil
   549  }
   550  
   551  // deserializeUserInvalTickets deserializes the passed serialized pool
   552  // users invalid tickets information.
   553  func deserializeUserInvalTickets(serializedTickets []byte) ([]*chainhash.Hash, error) {
   554  	// Cursory check to make sure that the number of records
   555  	// makes sense.
   556  	if len(serializedTickets)%chainhash.HashSize != 0 {
   557  		err := io.ErrUnexpectedEOF
   558  		return nil, err
   559  	}
   560  
   561  	numRecords := len(serializedTickets) / chainhash.HashSize
   562  
   563  	records := make([]*chainhash.Hash, numRecords)
   564  
   565  	// Loop through all the ssgen records, deserialize them, and
   566  	// store them.
   567  	for i := 0; i < numRecords; i++ {
   568  		start := i * chainhash.HashSize
   569  		end := (i + 1) * chainhash.HashSize
   570  		h, err := chainhash.NewHash(serializedTickets[start:end])
   571  		if err != nil {
   572  			return nil, err
   573  		}
   574  
   575  		records[i] = h
   576  	}
   577  
   578  	return records, nil
   579  }
   580  
   581  // serializeUserInvalTickets returns the serialization of the passed stake pool
   582  // invalid user tickets slice.
   583  func serializeUserInvalTickets(records []*chainhash.Hash) []byte {
   584  	numRecords := len(records)
   585  
   586  	buf := make([]byte, numRecords*chainhash.HashSize)
   587  
   588  	// Serialize and write each record into the slice sequentially.
   589  	for i := 0; i < numRecords; i++ {
   590  		start := i * chainhash.HashSize
   591  		end := (i + 1) * chainhash.HashSize
   592  		copy(buf[start:end], records[i][:])
   593  	}
   594  
   595  	return buf
   596  }
   597  
   598  // fetchStakePoolUserInvalTickets retrieves the list of invalid pool user tickets
   599  // from the meta bucket with the given hash.
   600  func fetchStakePoolUserInvalTickets(ns walletdb.ReadBucket, scriptHash [20]byte) ([]*chainhash.Hash, error) {
   601  	bucket := ns.NestedReadBucket(metaBucketName)
   602  
   603  	key := make([]byte, stakePoolInvalidPrefixSize+scriptHashSize)
   604  	copy(key[0:stakePoolInvalidPrefixSize], stakePoolInvalidPrefix)
   605  	copy(key[stakePoolInvalidPrefixSize:stakePoolInvalidPrefixSize+scriptHashSize],
   606  		scriptHash[:])
   607  	val := bucket.Get(key)
   608  	if val == nil {
   609  		return nil, errors.E(errors.NotExist, errors.Errorf("no pool ticket for hash160 %x", &scriptHash))
   610  	}
   611  
   612  	return deserializeUserInvalTickets(val)
   613  }
   614  
   615  // duplicateExistsInInvalTickets checks to see if an exact duplicated of a
   616  // record already exists in a slice of invalid user ticket records.
   617  func duplicateExistsInInvalTickets(record *chainhash.Hash, records []*chainhash.Hash) bool {
   618  	for _, r := range records {
   619  		if *r == *record {
   620  			return true
   621  		}
   622  	}
   623  	return false
   624  }
   625  
   626  // removeStakePoolInvalUserTickets removes the ticket hash from the inval
   627  // ticket bucket.
   628  func removeStakePoolInvalUserTickets(ns walletdb.ReadWriteBucket, scriptHash [20]byte, record *chainhash.Hash) error {
   629  	// Fetch the current content of the key.
   630  	// Possible buggy behaviour: If deserialization fails,
   631  	// we won't detect it here. We assume we're throwing
   632  	// ErrPoolUserInvalTcktsNotFound.
   633  	oldRecords, _ := fetchStakePoolUserInvalTickets(ns, scriptHash)
   634  
   635  	// Don't need to remove records that don't exist.
   636  	if !duplicateExistsInInvalTickets(record, oldRecords) {
   637  		return nil
   638  	}
   639  
   640  	var newRecords []*chainhash.Hash
   641  	for i := range oldRecords {
   642  		if record.IsEqual(oldRecords[i]) {
   643  			newRecords = append(oldRecords[:i:i], oldRecords[i+1:]...)
   644  		}
   645  	}
   646  
   647  	if newRecords == nil {
   648  		return nil
   649  	}
   650  
   651  	bucket := ns.NestedReadWriteBucket(metaBucketName)
   652  	key := make([]byte, stakePoolInvalidPrefixSize+scriptHashSize)
   653  	copy(key[0:stakePoolInvalidPrefixSize], stakePoolInvalidPrefix)
   654  	copy(key[stakePoolInvalidPrefixSize:stakePoolInvalidPrefixSize+scriptHashSize],
   655  		scriptHash[:])
   656  
   657  	// Write the serialized invalid user ticket hashes.
   658  	serializedRecords := serializeUserInvalTickets(newRecords)
   659  
   660  	err := bucket.Put(key, serializedRecords)
   661  	if err != nil {
   662  		return errors.E(errors.IO, err)
   663  	}
   664  
   665  	return nil
   666  }
   667  
   668  // updateStakePoolInvalUserTickets updates a database entry for a pool user's
   669  // invalid tickets. The function pulls the current entry in the database,
   670  // checks to see if the ticket is already there. If it is it returns, otherwise
   671  // it adds it to the list of tickets.
   672  func updateStakePoolInvalUserTickets(ns walletdb.ReadWriteBucket, scriptHash [20]byte, record *chainhash.Hash) error {
   673  	// Fetch the current content of the key.
   674  	// Possible buggy behaviour: If deserialization fails,
   675  	// we won't detect it here. We assume we're throwing
   676  	// ErrPoolUserInvalTcktsNotFound.
   677  	oldRecords, _ := fetchStakePoolUserInvalTickets(ns, scriptHash)
   678  
   679  	// Don't reinsert duplicate records we already have.
   680  	if duplicateExistsInInvalTickets(record, oldRecords) {
   681  		return nil
   682  	}
   683  
   684  	// Either create a slice if currently nothing exists for this
   685  	// key in the db, or append the entry to the slice.
   686  	var records []*chainhash.Hash
   687  	if oldRecords == nil {
   688  		records = make([]*chainhash.Hash, 1)
   689  		records[0] = record
   690  	} else {
   691  		records = append(oldRecords, record)
   692  	}
   693  
   694  	bucket := ns.NestedReadWriteBucket(metaBucketName)
   695  	key := make([]byte, stakePoolInvalidPrefixSize+scriptHashSize)
   696  	copy(key[0:stakePoolInvalidPrefixSize], stakePoolInvalidPrefix)
   697  	copy(key[stakePoolInvalidPrefixSize:stakePoolInvalidPrefixSize+scriptHashSize],
   698  		scriptHash[:])
   699  
   700  	// Write the serialized invalid user ticket hashes.
   701  	serializedRecords := serializeUserInvalTickets(records)
   702  
   703  	err := bucket.Put(key, serializedRecords)
   704  	if err != nil {
   705  		return errors.E(errors.IO, err)
   706  	}
   707  	return nil
   708  }
   709  
   710  // initialize creates the DB if it doesn't exist, and otherwise
   711  // loads the database.
   712  func initializeEmpty(ns walletdb.ReadWriteBucket) error {
   713  	// Initialize the buckets and main db fields as needed.
   714  	mainBucket, err := ns.CreateBucketIfNotExists(mainBucketName)
   715  	if err != nil {
   716  		return errors.E(errors.IO, err)
   717  	}
   718  
   719  	_, err = ns.CreateBucketIfNotExists(sstxRecordsBucketName)
   720  	if err != nil {
   721  		return errors.E(errors.IO, err)
   722  	}
   723  
   724  	_, err = ns.CreateBucketIfNotExists(ssgenRecordsBucketName)
   725  	if err != nil {
   726  		return errors.E(errors.IO, err)
   727  	}
   728  
   729  	_, err = ns.CreateBucketIfNotExists(ssrtxRecordsBucketName)
   730  	if err != nil {
   731  		return errors.E(errors.IO, err)
   732  	}
   733  
   734  	_, err = ns.CreateBucketIfNotExists(metaBucketName)
   735  	if err != nil {
   736  		return errors.E(errors.IO, err)
   737  	}
   738  
   739  	createBytes := mainBucket.Get(stakeStoreCreateDateName)
   740  	if createBytes == nil {
   741  		createDate := uint64(time.Now().Unix())
   742  		var buf [8]byte
   743  		binary.LittleEndian.PutUint64(buf[:], createDate)
   744  		err := mainBucket.Put(stakeStoreCreateDateName, buf[:])
   745  		if err != nil {
   746  			return errors.E(errors.IO, err)
   747  		}
   748  	}
   749  
   750  	return nil
   751  }