func (s *BoltDBStore) TransactionsByAddress(addr types.UnlockHash, n int) (txids []types.TransactionID) {
	s.view(func(tx *bolt.Tx) error {
		addrTxnsBucket := tx.Bucket(bucketTxnsAddrIndex).Bucket(addr[:])
		if addrTxnsBucket == nil {
			return nil
		}
		c := addrTxnsBucket.Cursor()
		for k, v := c.Last(); k != nil && len(txids) != n; k, v = c.Prev() {
			var txid types.TransactionID
			copy(txid[:], v)
			txids = append(txids, txid)
		}
		return nil
	})
	return
}
TransactionsByAddress implements Store.
package wallet
import (
	"bytes"
	"encoding/binary"
	"os"
	"time"
	"go.sia.tech/siad/modules"
	"go.sia.tech/siad/types"
	"gitlab.com/NebulousLabs/encoding"
	bolt "go.etcd.io/bbolt"
)
var (
	keyHeight = []byte("keyHeight")
	keyCCID = []byte("keyCCID")
	keySeedIndex = []byte("keySeedIndex")
	bucketMeta = []byte("bucketMeta")
	bucketAddrs = []byte("bucketAddrs")
	bucketOutputs = []byte("bucketOutputs")
	bucketBlockRewards = []byte("bucketBlockRewards")
	bucketFileContracts = []byte("bucketFileContracts")
	bucketMemos = []byte("bucketMemos")
	bucketTxns = []byte("bucketTxns")
	bucketTxnsAddrIndex = []byte("bucketTxnsAddrIndex")
	bucketTxnsRecentIndex = []byte("bucketTxnsRecentIndex")
	bucketLimbo = []byte("bucketLimbo")
	dbBuckets = [][]byte{
		bucketAddrs,
		bucketBlockRewards,
		bucketFileContracts,
		bucketLimbo,
		bucketMemos,
		bucketMeta,
		bucketOutputs,
		bucketTxns,
		bucketTxnsAddrIndex,
		bucketTxnsRecentIndex,
	}
)
type BoltDBStore struct {
	db    *bolt.DB
	addrs map[types.UnlockHash]struct{}
	onErr func(error)
}
func (s *BoltDBStore) view(fn func(*bolt.Tx) error) {
	err := s.db.View(fn)
	if err != nil {
		s.onErr(err)
	}
}
func (s *BoltDBStore) update(fn func(*bolt.Tx) error) {
	err := s.db.Update(fn)
	if err != nil {
		s.onErr(err)
	}
}
func (s *BoltDBStore) ApplyConsensusChange(reverted, applied ProcessedConsensusChange, ccid modules.ConsensusChangeID) {
	s.update(func(tx *bolt.Tx) error {
		for _, o := range reverted.Outputs {
			tx.Bucket(bucketOutputs).Delete(o.ID[:])
		}
		if len(reverted.BlockRewards) > 0 {
			for i := range reverted.BlockRewards {
				c := tx.Bucket(bucketBlockRewards).Cursor()
				for k, v := c.Last(); k != nil; k, v = c.Prev() {
					var br BlockReward
					encoding.Unmarshal(v, &br)
					if br.ID == reverted.BlockRewards[i].ID {
						tx.Bucket(bucketBlockRewards).Delete(k)
						break
					}
				}
			}
		}
		if len(reverted.FileContracts) > 0 {
			for i := range reverted.FileContracts {
				c := tx.Bucket(bucketFileContracts).Cursor()
				for k, v := c.Last(); k != nil; k, v = c.Prev() {
					var fc FileContract
					encoding.Unmarshal(v, &fc)
					if fc.ID == reverted.FileContracts[i].ID && fc.RevisionNumber == reverted.FileContracts[i].RevisionNumber {
						tx.Bucket(bucketFileContracts).Delete(k)
						break
					}
				}
			}
		}
		for _, txn := range reverted.Transactions {
			txid := txn.ID()
			tx.Bucket(bucketTxns).Delete(txid[:])
			tx.Bucket(bucketLimbo).Delete(txid[:])
			c := tx.Bucket(bucketTxnsRecentIndex).Cursor()
			for k, v := c.Last(); k != nil; k, v = c.Prev() {
				if bytes.Equal(v, txid[:]) {
					tx.Bucket(bucketTxnsRecentIndex).Delete(k)
					break
				}
			}
		}
		for addr, txids := range reverted.AddressTransactions {
			addrTxnsBucket := tx.Bucket(bucketTxnsAddrIndex).Bucket(addr[:])
			if addrTxnsBucket == nil {
				continue
			}
			c := addrTxnsBucket.Cursor()
			for k, v := c.Last(); k != nil; k, v = c.Prev() {
				for _, txid := range txids {
					if bytes.Equal(v, txid[:]) {
						addrTxnsBucket.Delete(k)
						break
					}
				}
			}
		}
		seqBytes := make([]byte, 8)
		putSeq := func(b *bolt.Bucket, val []byte) error {
			seq, _ := tx.Bucket(bucketBlockRewards).NextSequence()
			binary.BigEndian.PutUint64(seqBytes, seq)
			return b.Put(seqBytes, val)
		}
		for _, o := range applied.Outputs {
			tx.Bucket(bucketOutputs).Put(o.ID[:], encoding.Marshal(o))
		}
		for _, br := range applied.BlockRewards {
			putSeq(tx.Bucket(bucketBlockRewards), encoding.Marshal(br))
		}
		for _, fc := range applied.FileContracts {
			putSeq(tx.Bucket(bucketFileContracts), encoding.Marshal(fc))
		}
		for _, txn := range applied.Transactions {
			txid := txn.ID()
			tx.Bucket(bucketTxns).Put(txid[:], encoding.Marshal(txn))
			tx.Bucket(bucketLimbo).Delete(txid[:])
			putSeq(tx.Bucket(bucketTxnsRecentIndex), txid[:])
		}
		for addr, txids := range applied.AddressTransactions {
			addrTxnsBucket, _ := tx.Bucket(bucketTxnsAddrIndex).CreateBucketIfNotExists(addr[:])
			for _, txid := range txids {
				putSeq(addrTxnsBucket, txid[:])
			}
		}
		heightBytes := append([]byte(nil), tx.Bucket(bucketMeta).Get(keyHeight)...)
		height := binary.LittleEndian.Uint64(heightBytes) + uint64(applied.BlockCount) - uint64(reverted.BlockCount)
		binary.LittleEndian.PutUint64(heightBytes, height)
		tx.Bucket(bucketMeta).Put(keyHeight, heightBytes)
		tx.Bucket(bucketMeta).Put(keyCCID, ccid[:])
		return nil
	})
}
func (s *BoltDBStore) UnspentOutputs() (outputs []UnspentOutput) {
	s.view(func(tx *bolt.Tx) error {
		c := tx.Bucket(bucketOutputs).Cursor()
		for k, v := c.First(); k != nil; k, v = c.Next() {
			var o UnspentOutput
			encoding.Unmarshal(v, &o)
			outputs = append(outputs, o)
		}
		return nil
	})
	return
}
func (s *BoltDBStore) AddToLimbo(txn types.Transaction) {
	s.update(func(tx *bolt.Tx) error {
		txid := txn.ID()
		if tx.Bucket(bucketLimbo).Get(txid[:]) != nil {
			return nil  
		}
		return tx.Bucket(bucketLimbo).Put(txid[:], encoding.Marshal(LimboTransaction{
			Transaction: txn,
			LimboSince:  time.Now(),
		}))
	})
}
func (s *BoltDBStore) RemoveFromLimbo(id types.TransactionID) {
	s.update(func(tx *bolt.Tx) error {
		return tx.Bucket(bucketLimbo).Delete(id[:])
	})
}
func (s *BoltDBStore) LimboTransactions() (txns []LimboTransaction) {
	s.view(func(tx *bolt.Tx) error {
		c := tx.Bucket(bucketLimbo).Cursor()
		for k, v := c.Last(); k != nil; k, v = c.Prev() {
			var txn LimboTransaction
			encoding.Unmarshal(v, &txn)
			txns = append(txns, txn)
		}
		return nil
	})
	return
}
func (s *BoltDBStore) BlockRewards(n int) (brs []BlockReward) {
	s.view(func(tx *bolt.Tx) error {
		c := tx.Bucket(bucketBlockRewards).Cursor()
		for k, v := c.Last(); k != nil && len(brs) != n; k, v = c.Prev() {
			var br BlockReward
			encoding.Unmarshal(v, &br)
			brs = append(brs, br)
		}
		return nil
	})
	return
}
func (s *BoltDBStore) FileContracts(n int) (fcs []FileContract) {
	s.view(func(tx *bolt.Tx) error {
		c := tx.Bucket(bucketFileContracts).Cursor()
		for k, v := c.Last(); k != nil && len(fcs) != n; k, v = c.Prev() {
			var fc FileContract
			encoding.Unmarshal(v, &fc)
			fcs = append(fcs, fc)
		}
		return nil
	})
	return
}
func (s *BoltDBStore) FileContractHistory(id types.FileContractID) (history []FileContract) {
	s.view(func(tx *bolt.Tx) error {
		c := tx.Bucket(bucketFileContracts).Cursor()
		for k, v := c.Last(); k != nil; k, v = c.Prev() {
			var fc FileContract
			encoding.Unmarshal(v, &fc)
			if fc.ID == id {
				history = append(history, fc)
			}
		}
		return nil
	})
	return
}
func (s *BoltDBStore) Transactions(n int) (txids []types.TransactionID) {
	s.view(func(tx *bolt.Tx) error {
		c := tx.Bucket(bucketTxnsRecentIndex).Cursor()
		for k, v := c.Last(); k != nil && len(txids) != n; k, v = c.Prev() {
			var txid types.TransactionID
			copy(txid[:], v)
			txids = append(txids, txid)
		}
		return nil
	})
	return
}