func putSeries(ns walletdb.ReadWriteBucket, poolID []byte, version, ID uint32, active bool, reqSigs uint32, pubKeysEncrypted, privKeysEncrypted [][]byte) error {
	row := &dbSeriesRow{
		version:           version,
		active:            active,
		reqSigs:           reqSigs,
		pubKeysEncrypted:  pubKeysEncrypted,
		privKeysEncrypted: privKeysEncrypted,
	}
	return putSeriesRow(ns, poolID, ID, row)
}
putSeries stores the given series inside a voting pool bucket named after
poolID. The voting pool bucket does not need to be created beforehand.
package votingpool
import (
	"bytes"
	"encoding/binary"
	"encoding/gob"
	"fmt"
	"github.com/gcash/bchd/txscript"
	"github.com/gcash/bchd/wire"
	"github.com/gcash/bchutil"
	"github.com/gcash/bchwallet/snacl"
	"github.com/gcash/bchwallet/walletdb"
)
const (
	seriesKeyLength = snacl.Overhead + 111 + snacl.NonceSize
	seriesMinSerial = 4 + 1 + 4 + 4
	seriesMaxSerial = seriesMinSerial + 15*seriesKeyLength*2
	seriesMaxVersion = 1
)
var (
	usedAddrsBucketName   = []byte("usedaddrs")
	seriesBucketName      = []byte("series")
	withdrawalsBucketName = []byte("withdrawals")
	seriesNullPrivKey = [seriesKeyLength]byte{}
)
type dbSeriesRow struct {
	version           uint32
	active            bool
	reqSigs           uint32
	pubKeysEncrypted  [][]byte
	privKeysEncrypted [][]byte
}
type dbWithdrawalRow struct {
	Requests      []dbOutputRequest
	StartAddress  dbWithdrawalAddress
	ChangeStart   dbChangeAddress
	LastSeriesID  uint32
	DustThreshold bchutil.Amount
	Status        dbWithdrawalStatus
}
type dbWithdrawalAddress struct {
	SeriesID uint32
	Branch   Branch
	Index    Index
}
type dbChangeAddress struct {
	SeriesID uint32
	Index    Index
}
type dbOutputRequest struct {
	Addr        string
	Amount      bchutil.Amount
	Server      string
	Transaction uint32
}
type dbWithdrawalOutput struct {
	OutBailmentID OutBailmentID
	Status        outputStatus
	Outpoints     []dbOutBailmentOutpoint
}
type dbOutBailmentOutpoint struct {
	Ntxid  Ntxid
	Index  uint32
	Amount bchutil.Amount
}
type dbChangeAwareTx struct {
	SerializedMsgTx []byte
	ChangeIdx       int32
}
type dbWithdrawalStatus struct {
	NextInputAddr  dbWithdrawalAddress
	NextChangeAddr dbChangeAddress
	Fees           bchutil.Amount
	Outputs        map[OutBailmentID]dbWithdrawalOutput
	Sigs           map[Ntxid]TxSigs
	Amounts        map[Ntxid]InputAmounts
	Transactions   map[Ntxid]dbChangeAwareTx
}
func getUsedAddrBucketID(seriesID uint32, branch Branch) []byte {
	var bucketID [9]byte
	binary.LittleEndian.PutUint32(bucketID[0:4], seriesID)
	bucketID[4] = ':'
	binary.LittleEndian.PutUint32(bucketID[5:9], uint32(branch))
	return bucketID[:]
}
func putUsedAddrHash(ns walletdb.ReadWriteBucket, poolID []byte, seriesID uint32, branch Branch,
	index Index, encryptedHash []byte) error {
	usedAddrs := ns.NestedReadWriteBucket(poolID).NestedReadWriteBucket(usedAddrsBucketName)
	bucket, err := usedAddrs.CreateBucketIfNotExists(getUsedAddrBucketID(seriesID, branch))
	if err != nil {
		return newError(ErrDatabase, "failed to store used address hash", err)
	}
	return bucket.Put(uint32ToBytes(uint32(index)), encryptedHash)
}
func getUsedAddrHash(ns walletdb.ReadBucket, poolID []byte, seriesID uint32, branch Branch,
	index Index) []byte {
	usedAddrs := ns.NestedReadBucket(poolID).NestedReadBucket(usedAddrsBucketName)
	bucket := usedAddrs.NestedReadBucket(getUsedAddrBucketID(seriesID, branch))
	if bucket == nil {
		return nil
	}
	return bucket.Get(uint32ToBytes(uint32(index)))
}
func getMaxUsedIdx(ns walletdb.ReadBucket, poolID []byte, seriesID uint32, branch Branch) (Index, error) {
	maxIdx := Index(0)
	usedAddrs := ns.NestedReadBucket(poolID).NestedReadBucket(usedAddrsBucketName)
	bucket := usedAddrs.NestedReadBucket(getUsedAddrBucketID(seriesID, branch))
	if bucket == nil {
		return maxIdx, nil
	}
	err := bucket.ForEach(
		func(k, v []byte) error {
			idx := Index(bytesToUint32(k))
			if idx > maxIdx {
				maxIdx = idx
			}
			return nil
		})
	if err != nil {
		return Index(0), newError(ErrDatabase, "failed to get highest idx of used addresses", err)
	}
	return maxIdx, nil
}
func putPool(ns walletdb.ReadWriteBucket, poolID []byte) error {
	poolBucket, err := ns.CreateBucket(poolID)
	if err != nil {
		return newError(ErrDatabase, fmt.Sprintf("cannot create pool %v", poolID), err)
	}
	_, err = poolBucket.CreateBucket(seriesBucketName)
	if err != nil {
		return newError(ErrDatabase, fmt.Sprintf("cannot create series bucket for pool %v",
			poolID), err)
	}
	_, err = poolBucket.CreateBucket(usedAddrsBucketName)
	if err != nil {
		return newError(ErrDatabase, fmt.Sprintf("cannot create used addrs bucket for pool %v",
			poolID), err)
	}
	_, err = poolBucket.CreateBucket(withdrawalsBucketName)
	if err != nil {
		return newError(
			ErrDatabase, fmt.Sprintf("cannot create withdrawals bucket for pool %v", poolID), err)
	}
	return nil
}
func loadAllSeries(ns walletdb.ReadBucket, poolID []byte) (map[uint32]*dbSeriesRow, error) {
	bucket := ns.NestedReadBucket(poolID).NestedReadBucket(seriesBucketName)
	allSeries := make(map[uint32]*dbSeriesRow)
	err := bucket.ForEach(
		func(k, v []byte) error {
			seriesID := bytesToUint32(k)
			series, err := deserializeSeriesRow(v)
			if err != nil {
				return err
			}
			allSeries[seriesID] = series
			return nil
		})
	if err != nil {
		return nil, err
	}
	return allSeries, nil
}
func existsPool(ns walletdb.ReadBucket, poolID []byte) bool {
	bucket := ns.NestedReadBucket(poolID)
	return bucket != nil
}