1572 lines
46 KiB
Go
1572 lines
46 KiB
Go
// Copyright (c) 2015 The btcsuite developers
|
|
// Copyright (c) 2015 The Decred developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package wtxmgr
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/lbryio/lbcd/chaincfg/chainhash"
|
|
"github.com/lbryio/lbcd/wire"
|
|
btcutil "github.com/lbryio/lbcutil"
|
|
"github.com/lbryio/lbcwallet/walletdb"
|
|
)
|
|
|
|
// Naming
|
|
//
|
|
// The following variables are commonly used in this file and given
|
|
// reserved names:
|
|
//
|
|
// ns: The namespace bucket for this package
|
|
// b: The primary bucket being operated on
|
|
// k: A single bucket key
|
|
// v: A single bucket value
|
|
// c: A bucket cursor
|
|
// ck: The current cursor key
|
|
// cv: The current cursor value
|
|
//
|
|
// Functions use the naming scheme `Op[Raw]Type[Field]`, which performs the
|
|
// operation `Op` on the type `Type`, optionally dealing with raw keys and
|
|
// values if `Raw` is used. Fetch and extract operations may only need to read
|
|
// some portion of a key or value, in which case `Field` describes the component
|
|
// being returned. The following operations are used:
|
|
//
|
|
// key: return a db key for some data
|
|
// value: return a db value for some data
|
|
// put: insert or replace a value into a bucket
|
|
// fetch: read and return a value
|
|
// read: read a value into an out parameter
|
|
// exists: return the raw (nil if not found) value for some data
|
|
// delete: remove a k/v pair
|
|
// extract: perform an unchecked slice to extract a key or value
|
|
//
|
|
// Other operations which are specific to the types being operated on
|
|
// should be explained in a comment.
|
|
|
|
// Big endian is the preferred byte order, due to cursor scans over integer
|
|
// keys iterating in order.
|
|
var byteOrder = binary.BigEndian
|
|
|
|
// This package makes assumptions that the width of a chainhash.Hash is always
|
|
// 32 bytes. If this is ever changed (unlikely for bitcoin, possible for alts),
|
|
// offsets have to be rewritten. Use a compile-time assertion that this
|
|
// assumption holds true.
|
|
var _ [32]byte = chainhash.Hash{}
|
|
|
|
// Bucket names
|
|
var (
|
|
bucketBlocks = []byte("b")
|
|
bucketTxRecords = []byte("t")
|
|
bucketTxLabels = []byte("l")
|
|
bucketCredits = []byte("c")
|
|
bucketUnspent = []byte("u")
|
|
bucketDebits = []byte("d")
|
|
bucketUnmined = []byte("m")
|
|
bucketUnminedCredits = []byte("mc")
|
|
bucketUnminedInputs = []byte("mi")
|
|
bucketLockedOutputs = []byte("lo")
|
|
)
|
|
|
|
// Root (namespace) bucket keys
|
|
var (
|
|
rootCreateDate = []byte("date")
|
|
rootVersion = []byte("vers")
|
|
rootMinedBalance = []byte("bal")
|
|
)
|
|
|
|
// The root bucket's mined balance k/v pair records the total balance for all
|
|
// unspent credits from mined transactions. This includes immature outputs, and
|
|
// outputs spent by mempool transactions, which must be considered when
|
|
// returning the actual balance for a given number of block confirmations. The
|
|
// value is the amount serialized as a uint64.
|
|
func fetchMinedBalance(ns walletdb.ReadBucket) (btcutil.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 btcutil.Amount(byteOrder.Uint64(v)), nil
|
|
}
|
|
|
|
func putMinedBalance(ns walletdb.ReadWriteBucket, amt btcutil.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
|
|
}
|
|
|
|
// Several data structures are given canonical serialization formats as either
|
|
// keys or values. These common formats allow keys and values to be reused
|
|
// across different buckets.
|
|
//
|
|
// The canonical outpoint serialization format is:
|
|
//
|
|
// [0:32] Trasaction hash (32 bytes)
|
|
// [32:36] Output index (4 bytes)
|
|
//
|
|
// The canonical transaction hash serialization is simply the hash.
|
|
|
|
func canonicalOutPoint(txHash *chainhash.Hash, index uint32) []byte {
|
|
var k [36]byte
|
|
copy(k[:32], txHash[:])
|
|
byteOrder.PutUint32(k[32:36], index)
|
|
return k[:]
|
|
}
|
|
|
|
func readCanonicalOutPoint(k []byte, op *wire.OutPoint) error {
|
|
if len(k) < 36 {
|
|
str := "short canonical outpoint"
|
|
return storeError(ErrData, str, nil)
|
|
}
|
|
copy(op.Hash[:], k)
|
|
op.Index = byteOrder.Uint32(k[32:36])
|
|
return nil
|
|
}
|
|
|
|
// Details regarding blocks are saved as k/v pairs in the blocks bucket.
|
|
// blockRecords are keyed by their height. The value is serialized as such:
|
|
//
|
|
// [0:32] Hash (32 bytes)
|
|
// [32:40] Unix time (8 bytes)
|
|
// [40:44] Number of transaction hashes (4 bytes)
|
|
// [44:] For each transaction hash:
|
|
// Hash (32 bytes)
|
|
|
|
func keyBlockRecord(height int32) []byte {
|
|
k := make([]byte, 4)
|
|
byteOrder.PutUint32(k, uint32(height))
|
|
return k
|
|
}
|
|
|
|
func valueBlockRecord(block *BlockMeta, txHash *chainhash.Hash) []byte {
|
|
v := make([]byte, 76)
|
|
copy(v, block.Hash[:])
|
|
byteOrder.PutUint64(v[32:40], uint64(block.Time.Unix()))
|
|
byteOrder.PutUint32(v[40:44], 1)
|
|
copy(v[44:76], txHash[:])
|
|
return v
|
|
}
|
|
|
|
// appendRawBlockRecord returns a new block record value with a transaction
|
|
// hash appended to the end and an incremented number of transactions.
|
|
func appendRawBlockRecord(v []byte, txHash *chainhash.Hash) ([]byte, error) {
|
|
if len(v) < 44 {
|
|
str := fmt.Sprintf("%s: short read (expected %d bytes, read %d)",
|
|
bucketBlocks, 44, len(v))
|
|
return nil, storeError(ErrData, str, nil)
|
|
}
|
|
newv := append(v[:len(v):len(v)], txHash[:]...)
|
|
n := byteOrder.Uint32(newv[40:44])
|
|
byteOrder.PutUint32(newv[40:44], n+1)
|
|
return newv, nil
|
|
}
|
|
|
|
func putRawBlockRecord(ns walletdb.ReadWriteBucket, k, v []byte) error {
|
|
err := ns.NestedReadWriteBucket(bucketBlocks).Put(k, v)
|
|
if err != nil {
|
|
str := "failed to store block"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func putBlockRecord(ns walletdb.ReadWriteBucket, block *BlockMeta, txHash *chainhash.Hash) error {
|
|
k := keyBlockRecord(block.Height)
|
|
v := valueBlockRecord(block, txHash)
|
|
return putRawBlockRecord(ns, k, v)
|
|
}
|
|
|
|
func fetchBlockTime(ns walletdb.ReadBucket, height int32) (time.Time, error) {
|
|
k := keyBlockRecord(height)
|
|
v := ns.NestedReadBucket(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 existsBlockRecord(ns walletdb.ReadBucket, height int32) (k, v []byte) {
|
|
k = keyBlockRecord(height)
|
|
v = ns.NestedReadBucket(bucketBlocks).Get(k)
|
|
return
|
|
}
|
|
|
|
func readRawBlockRecord(k, v []byte, block *blockRecord) error {
|
|
if len(k) < 4 {
|
|
str := fmt.Sprintf("%s: short key (expected %d bytes, read %d)",
|
|
bucketBlocks, 4, len(k))
|
|
return storeError(ErrData, str, nil)
|
|
}
|
|
if len(v) < 44 {
|
|
str := fmt.Sprintf("%s: short read (expected %d bytes, read %d)",
|
|
bucketBlocks, 44, len(v))
|
|
return storeError(ErrData, str, nil)
|
|
}
|
|
numTransactions := int(byteOrder.Uint32(v[40:44]))
|
|
expectedLen := 44 + chainhash.HashSize*numTransactions
|
|
if len(v) < expectedLen {
|
|
str := fmt.Sprintf("%s: short read (expected %d bytes, read %d)",
|
|
bucketBlocks, expectedLen, len(v))
|
|
return storeError(ErrData, str, nil)
|
|
}
|
|
|
|
block.Height = int32(byteOrder.Uint32(k))
|
|
copy(block.Hash[:], v)
|
|
block.Time = time.Unix(int64(byteOrder.Uint64(v[32:40])), 0)
|
|
block.transactions = make([]chainhash.Hash, numTransactions)
|
|
off := 44
|
|
for i := range block.transactions {
|
|
copy(block.transactions[i][:], v[off:])
|
|
off += chainhash.HashSize
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type blockIterator struct {
|
|
c walletdb.ReadWriteCursor
|
|
seek []byte
|
|
ck []byte
|
|
cv []byte
|
|
elem blockRecord
|
|
err error
|
|
}
|
|
|
|
func makeBlockIterator(ns walletdb.ReadWriteBucket, height int32) blockIterator {
|
|
seek := make([]byte, 4)
|
|
byteOrder.PutUint32(seek, uint32(height))
|
|
c := ns.NestedReadWriteBucket(bucketBlocks).ReadWriteCursor()
|
|
return blockIterator{c: c, seek: seek}
|
|
}
|
|
|
|
func makeReadBlockIterator(ns walletdb.ReadBucket, height int32) blockIterator {
|
|
seek := make([]byte, 4)
|
|
byteOrder.PutUint32(seek, uint32(height))
|
|
c := ns.NestedReadBucket(bucketBlocks).ReadCursor()
|
|
return blockIterator{c: readCursor{c}, seek: seek}
|
|
}
|
|
|
|
// Works just like makeBlockIterator but will initially position the cursor at
|
|
// the last k/v pair. Use this with blockIterator.prev.
|
|
func makeReverseBlockIterator(ns walletdb.ReadWriteBucket) blockIterator {
|
|
seek := make([]byte, 4)
|
|
byteOrder.PutUint32(seek, ^uint32(0))
|
|
c := ns.NestedReadWriteBucket(bucketBlocks).ReadWriteCursor()
|
|
return blockIterator{c: c, seek: seek}
|
|
}
|
|
|
|
func makeReadReverseBlockIterator(ns walletdb.ReadBucket) blockIterator {
|
|
seek := make([]byte, 4)
|
|
byteOrder.PutUint32(seek, ^uint32(0))
|
|
c := ns.NestedReadBucket(bucketBlocks).ReadCursor()
|
|
return blockIterator{c: readCursor{c}, seek: seek}
|
|
}
|
|
|
|
func (it *blockIterator) next() bool {
|
|
if it.c == nil {
|
|
return false
|
|
}
|
|
|
|
if it.ck == nil {
|
|
it.ck, it.cv = it.c.Seek(it.seek)
|
|
} else {
|
|
it.ck, it.cv = it.c.Next()
|
|
}
|
|
if it.ck == nil {
|
|
it.c = nil
|
|
return false
|
|
}
|
|
|
|
err := readRawBlockRecord(it.ck, it.cv, &it.elem)
|
|
if err != nil {
|
|
it.c = nil
|
|
it.err = err
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (it *blockIterator) prev() bool {
|
|
if it.c == nil {
|
|
return false
|
|
}
|
|
|
|
if it.ck == nil {
|
|
it.ck, it.cv = it.c.Seek(it.seek)
|
|
// Seek positions the cursor at the next k/v pair if one with
|
|
// this prefix was not found. If this happened (the prefixes
|
|
// won't match in this case) move the cursor backward.
|
|
//
|
|
// This technically does not correct for multiple keys with
|
|
// matching prefixes by moving the cursor to the last matching
|
|
// key, but this doesn't need to be considered when dealing with
|
|
// block records since the key (and seek prefix) is just the
|
|
// block height.
|
|
if !bytes.HasPrefix(it.ck, it.seek) {
|
|
it.ck, it.cv = it.c.Prev()
|
|
}
|
|
} else {
|
|
it.ck, it.cv = it.c.Prev()
|
|
}
|
|
if it.ck == nil {
|
|
it.c = nil
|
|
return false
|
|
}
|
|
|
|
err := readRawBlockRecord(it.ck, it.cv, &it.elem)
|
|
if err != nil {
|
|
it.c = nil
|
|
it.err = err
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// unavailable until https://github.com/boltdb/bolt/issues/620 is fixed.
|
|
// func (it *blockIterator) delete() error {
|
|
// err := it.c.Delete()
|
|
// if err != nil {
|
|
// str := "failed to delete block record"
|
|
// storeError(ErrDatabase, str, err)
|
|
// }
|
|
// return nil
|
|
// }
|
|
|
|
func (it *blockIterator) reposition(height int32) {
|
|
it.c.Seek(keyBlockRecord(height))
|
|
}
|
|
|
|
func deleteBlockRecord(ns walletdb.ReadWriteBucket, height int32) error {
|
|
k := keyBlockRecord(height)
|
|
return ns.NestedReadWriteBucket(bucketBlocks).Delete(k)
|
|
}
|
|
|
|
// Transaction records are keyed as such:
|
|
//
|
|
// [0:32] Transaction hash (32 bytes)
|
|
// [32:36] Block height (4 bytes)
|
|
// [36:68] Block hash (32 bytes)
|
|
//
|
|
// The leading transaction hash allows to prefix filter for all records with
|
|
// a matching hash. The block height and hash records a particular incidence
|
|
// of the transaction in the blockchain.
|
|
//
|
|
// The record value is serialized as such:
|
|
//
|
|
// [0:8] Received time (8 bytes)
|
|
// [8:] Serialized transaction (varies)
|
|
|
|
func keyTxRecord(txHash *chainhash.Hash, block *Block) []byte {
|
|
k := make([]byte, 68)
|
|
copy(k, txHash[:])
|
|
byteOrder.PutUint32(k[32:36], uint32(block.Height))
|
|
copy(k[36:68], block.Hash[:])
|
|
return k
|
|
}
|
|
|
|
func valueTxRecord(rec *TxRecord) ([]byte, error) {
|
|
var v []byte
|
|
if rec.SerializedTx == nil {
|
|
txSize := rec.MsgTx.SerializeSize()
|
|
v = make([]byte, 8, 8+txSize)
|
|
err := rec.MsgTx.Serialize(bytes.NewBuffer(v[8:]))
|
|
if err != nil {
|
|
str := fmt.Sprintf("unable to serialize transaction %v", rec.Hash)
|
|
return nil, storeError(ErrInput, str, err)
|
|
}
|
|
v = v[:cap(v)]
|
|
} else {
|
|
v = make([]byte, 8+len(rec.SerializedTx))
|
|
copy(v[8:], rec.SerializedTx)
|
|
}
|
|
byteOrder.PutUint64(v, uint64(rec.Received.Unix()))
|
|
return v, nil
|
|
}
|
|
|
|
func putTxRecord(ns walletdb.ReadWriteBucket, rec *TxRecord, block *Block) error {
|
|
k := keyTxRecord(&rec.Hash, block)
|
|
v, err := valueTxRecord(rec)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = ns.NestedReadWriteBucket(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
|
|
}
|
|
|
|
func putRawTxRecord(ns walletdb.ReadWriteBucket, k, v []byte) error {
|
|
err := ns.NestedReadWriteBucket(bucketTxRecords).Put(k, v)
|
|
if err != nil {
|
|
str := fmt.Sprintf("%s: put failed", bucketTxRecords)
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func readRawTxRecord(txHash *chainhash.Hash, v []byte, rec *TxRecord) error {
|
|
if len(v) < 8 {
|
|
str := fmt.Sprintf("%s: short read (expected %d bytes, read %d)",
|
|
bucketTxRecords, 8, len(v))
|
|
return storeError(ErrData, str, nil)
|
|
}
|
|
rec.Hash = *txHash
|
|
rec.Received = time.Unix(int64(byteOrder.Uint64(v)), 0)
|
|
err := rec.MsgTx.Deserialize(bytes.NewReader(v[8:]))
|
|
if err != nil {
|
|
str := fmt.Sprintf("%s: failed to deserialize transaction %v",
|
|
bucketTxRecords, txHash)
|
|
return storeError(ErrData, str, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func readRawTxRecordBlock(k []byte, block *Block) error {
|
|
if len(k) < 68 {
|
|
str := fmt.Sprintf("%s: short key (expected %d bytes, read %d)",
|
|
bucketTxRecords, 68, len(k))
|
|
return storeError(ErrData, str, nil)
|
|
}
|
|
block.Height = int32(byteOrder.Uint32(k[32:36]))
|
|
copy(block.Hash[:], k[36:68])
|
|
return nil
|
|
}
|
|
|
|
func fetchTxRecord(ns walletdb.ReadBucket, txHash *chainhash.Hash, block *Block) (*TxRecord, error) {
|
|
k := keyTxRecord(txHash, block)
|
|
v := ns.NestedReadBucket(bucketTxRecords).Get(k)
|
|
|
|
rec := new(TxRecord)
|
|
err := readRawTxRecord(txHash, v, rec)
|
|
return rec, err
|
|
}
|
|
|
|
// TODO: This reads more than necessary. Pass the pkscript location instead to
|
|
// avoid the wire.MsgTx deserialization.
|
|
func fetchRawTxRecordPkScript(k, v []byte, index uint32) ([]byte, error) {
|
|
var rec TxRecord
|
|
copy(rec.Hash[:], k) // Silly but need an array
|
|
err := readRawTxRecord(&rec.Hash, v, &rec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if int(index) >= len(rec.MsgTx.TxOut) {
|
|
str := "missing transaction output for credit index"
|
|
return nil, storeError(ErrData, str, nil)
|
|
}
|
|
return rec.MsgTx.TxOut[index].PkScript, nil
|
|
}
|
|
|
|
func existsTxRecord(ns walletdb.ReadBucket, txHash *chainhash.Hash, block *Block) (k, v []byte) {
|
|
k = keyTxRecord(txHash, block)
|
|
v = ns.NestedReadBucket(bucketTxRecords).Get(k)
|
|
return
|
|
}
|
|
|
|
func existsRawTxRecord(ns walletdb.ReadBucket, k []byte) (v []byte) {
|
|
return ns.NestedReadBucket(bucketTxRecords).Get(k)
|
|
}
|
|
|
|
func deleteTxRecord(ns walletdb.ReadWriteBucket, txHash *chainhash.Hash, block *Block) error {
|
|
k := keyTxRecord(txHash, block)
|
|
return ns.NestedReadWriteBucket(bucketTxRecords).Delete(k)
|
|
}
|
|
|
|
// 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.ReadBucket, txHash *chainhash.Hash) (k, v []byte) {
|
|
prefix := txHash[:]
|
|
c := ns.NestedReadBucket(bucketTxRecords).ReadCursor()
|
|
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
|
|
}
|
|
|
|
// All transaction credits (outputs) are keyed as such:
|
|
//
|
|
// [0:32] Transaction hash (32 bytes)
|
|
// [32:36] Block height (4 bytes)
|
|
// [36:68] Block hash (32 bytes)
|
|
// [68:72] Output index (4 bytes)
|
|
//
|
|
// The first 68 bytes match the key for the transaction record and may be used
|
|
// as a prefix filter to iterate through all credits in order.
|
|
//
|
|
// The credit value is serialized as such:
|
|
//
|
|
// [0:8] Amount (8 bytes)
|
|
// [8] Flags (1 byte)
|
|
// 0x01: Spent
|
|
// 0x02: Change
|
|
// [9:81] OPTIONAL Debit bucket key (72 bytes)
|
|
// [9:41] Spender transaction hash (32 bytes)
|
|
// [41:45] Spender block height (4 bytes)
|
|
// [45:77] Spender block hash (32 bytes)
|
|
// [77:81] Spender transaction input index (4 bytes)
|
|
//
|
|
// The optional debits key is only included if the credit is spent by another
|
|
// mined debit.
|
|
|
|
func keyCredit(txHash *chainhash.Hash, index uint32, block *Block) []byte {
|
|
k := make([]byte, 72)
|
|
copy(k, txHash[:])
|
|
byteOrder.PutUint32(k[32:36], uint32(block.Height))
|
|
copy(k[36:68], block.Hash[:])
|
|
byteOrder.PutUint32(k[68:72], index)
|
|
return k
|
|
}
|
|
|
|
// valueUnspentCredit creates a new credit value for an unspent credit. All
|
|
// credits are created unspent, and are only marked spent later, so there is no
|
|
// value function to create either spent or unspent credits.
|
|
func valueUnspentCredit(cred *credit) []byte {
|
|
v := make([]byte, 9)
|
|
byteOrder.PutUint64(v, uint64(cred.amount))
|
|
v[8] = cred.flags
|
|
return v
|
|
}
|
|
|
|
func putRawCredit(ns walletdb.ReadWriteBucket, k, v []byte) error {
|
|
err := ns.NestedReadWriteBucket(bucketCredits).Put(k, v)
|
|
if err != nil {
|
|
str := "failed to put credit"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// putUnspentCredit puts a credit record for an unspent credit. It may only be
|
|
// used when the credit is already know to be unspent, or spent by an
|
|
// unconfirmed transaction.
|
|
func putUnspentCredit(ns walletdb.ReadWriteBucket, cred *credit) error {
|
|
k := keyCredit(&cred.outPoint.Hash, cred.outPoint.Index, &cred.block)
|
|
v := valueUnspentCredit(cred)
|
|
return putRawCredit(ns, k, v)
|
|
}
|
|
|
|
func extractRawCreditTxRecordKey(k []byte) []byte {
|
|
return k[0:68]
|
|
}
|
|
|
|
func extractRawCreditIndex(k []byte) uint32 {
|
|
return byteOrder.Uint32(k[68:72])
|
|
}
|
|
|
|
// fetchRawCreditAmount returns the amount of the credit.
|
|
func fetchRawCreditAmount(v []byte) (btcutil.Amount, error) {
|
|
if len(v) < 9 {
|
|
str := fmt.Sprintf("%s: short read (expected %d bytes, read %d)",
|
|
bucketCredits, 9, len(v))
|
|
return 0, storeError(ErrData, str, nil)
|
|
}
|
|
return btcutil.Amount(byteOrder.Uint64(v)), nil
|
|
}
|
|
|
|
// fetchRawCreditAmountSpent returns the amount of the credit and whether the
|
|
// credit is spent.
|
|
func fetchRawCreditAmountSpent(v []byte) (btcutil.Amount, bool, error) {
|
|
if len(v) < 9 {
|
|
str := fmt.Sprintf("%s: short read (expected %d bytes, read %d)",
|
|
bucketCredits, 9, len(v))
|
|
return 0, false, storeError(ErrData, str, nil)
|
|
}
|
|
return btcutil.Amount(byteOrder.Uint64(v)), v[8]&(1<<0) != 0, nil
|
|
}
|
|
|
|
// fetchRawCreditAmountChange returns the amount of the credit and whether the
|
|
// credit is marked as change.
|
|
func fetchRawCreditAmountChange(v []byte) (btcutil.Amount, byte, error) {
|
|
if len(v) < 9 {
|
|
str := fmt.Sprintf("%s: short read (expected %d bytes, read %d)",
|
|
bucketCredits, 9, len(v))
|
|
return 0, 0, storeError(ErrData, str, nil)
|
|
}
|
|
return btcutil.Amount(byteOrder.Uint64(v)), v[8], nil
|
|
}
|
|
|
|
// fetchRawCreditUnspentValue returns the unspent value for a raw credit key.
|
|
// This may be used to mark a credit as unspent.
|
|
func fetchRawCreditUnspentValue(k []byte) ([]byte, error) {
|
|
if len(k) < 72 {
|
|
str := fmt.Sprintf("%s: short key (expected %d bytes, read %d)",
|
|
bucketCredits, 72, len(k))
|
|
return nil, storeError(ErrData, str, nil)
|
|
}
|
|
return k[32:68], 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.ReadWriteBucket, k []byte, spender *indexedIncidence) (btcutil.Amount, error) {
|
|
v := ns.NestedReadBucket(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 btcutil.Amount(byteOrder.Uint64(v[0:8])), putRawCredit(ns, k, v)
|
|
}
|
|
|
|
// 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.ReadWriteBucket, k []byte) (btcutil.Amount, error) {
|
|
b := ns.NestedReadWriteBucket(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 btcutil.Amount(byteOrder.Uint64(v[0:8])), nil
|
|
}
|
|
|
|
func existsCredit(ns walletdb.ReadBucket, txHash *chainhash.Hash, index uint32, block *Block) (k, v []byte) {
|
|
k = keyCredit(txHash, index, block)
|
|
v = ns.NestedReadBucket(bucketCredits).Get(k)
|
|
return
|
|
}
|
|
|
|
func existsRawCredit(ns walletdb.ReadBucket, k []byte) []byte {
|
|
return ns.NestedReadBucket(bucketCredits).Get(k)
|
|
}
|
|
|
|
func deleteRawCredit(ns walletdb.ReadWriteBucket, k []byte) error {
|
|
err := ns.NestedReadWriteBucket(bucketCredits).Delete(k)
|
|
if err != nil {
|
|
str := "failed to delete credit"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// creditIterator allows for in-order iteration of all credit records for a
|
|
// mined transaction.
|
|
//
|
|
// Example usage:
|
|
//
|
|
// prefix := keyTxRecord(txHash, block)
|
|
// it := makeCreditIterator(ns, prefix)
|
|
// for it.next() {
|
|
// // Use it.elem
|
|
// // If necessary, read additional details from it.ck, it.cv
|
|
// }
|
|
// if it.err != nil {
|
|
// // Handle error
|
|
// }
|
|
//
|
|
// The elem's Spent field is not set to true if the credit is spent by an
|
|
// unmined transaction. To check for this case:
|
|
//
|
|
// k := canonicalOutPoint(&txHash, it.elem.Index)
|
|
// it.elem.Spent = existsRawUnminedInput(ns, k) != nil
|
|
type creditIterator struct {
|
|
c walletdb.ReadWriteCursor // Set to nil after final iteration
|
|
prefix []byte
|
|
ck []byte
|
|
cv []byte
|
|
elem CreditRecord
|
|
err error
|
|
}
|
|
|
|
func makeCreditIterator(ns walletdb.ReadWriteBucket, prefix []byte) creditIterator {
|
|
c := ns.NestedReadWriteBucket(bucketCredits).ReadWriteCursor()
|
|
return creditIterator{c: c, prefix: prefix}
|
|
}
|
|
|
|
func makeReadCreditIterator(ns walletdb.ReadBucket, prefix []byte) creditIterator {
|
|
c := ns.NestedReadBucket(bucketCredits).ReadCursor()
|
|
return creditIterator{c: readCursor{c}, prefix: prefix}
|
|
}
|
|
|
|
func (it *creditIterator) readElem() error {
|
|
if len(it.ck) < 72 {
|
|
str := fmt.Sprintf("%s: short key (expected %d bytes, read %d)",
|
|
bucketCredits, 72, len(it.ck))
|
|
return storeError(ErrData, str, nil)
|
|
}
|
|
if len(it.cv) < 9 {
|
|
str := fmt.Sprintf("%s: short read (expected %d bytes, read %d)",
|
|
bucketCredits, 9, len(it.cv))
|
|
return storeError(ErrData, str, nil)
|
|
}
|
|
it.elem.Index = byteOrder.Uint32(it.ck[68:72])
|
|
it.elem.Amount = btcutil.Amount(byteOrder.Uint64(it.cv))
|
|
it.elem.Spent = it.cv[8]&(1<<0) != 0
|
|
it.elem.Change = it.cv[8]&(1<<1) != 0
|
|
return nil
|
|
}
|
|
|
|
func (it *creditIterator) next() bool {
|
|
if it.c == nil {
|
|
return false
|
|
}
|
|
|
|
if it.ck == nil {
|
|
it.ck, it.cv = it.c.Seek(it.prefix)
|
|
} else {
|
|
it.ck, it.cv = it.c.Next()
|
|
}
|
|
if !bytes.HasPrefix(it.ck, it.prefix) {
|
|
it.c = nil
|
|
return false
|
|
}
|
|
|
|
err := it.readElem()
|
|
if err != nil {
|
|
it.err = err
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// The unspent index records all outpoints for mined credits which are not spent
|
|
// by any other mined transaction records (but may be spent by a mempool
|
|
// transaction).
|
|
//
|
|
// Keys are use the canonical outpoint serialization:
|
|
//
|
|
// [0:32] Transaction hash (32 bytes)
|
|
// [32:36] Output index (4 bytes)
|
|
//
|
|
// Values are serialized as such:
|
|
//
|
|
// [0:4] Block height (4 bytes)
|
|
// [4:36] Block hash (32 bytes)
|
|
|
|
func valueUnspent(block *Block) []byte {
|
|
v := make([]byte, 36)
|
|
byteOrder.PutUint32(v, uint32(block.Height))
|
|
copy(v[4:36], block.Hash[:])
|
|
return v
|
|
}
|
|
|
|
func putUnspent(ns walletdb.ReadWriteBucket, outPoint *wire.OutPoint, block *Block) error {
|
|
k := canonicalOutPoint(&outPoint.Hash, outPoint.Index)
|
|
v := valueUnspent(block)
|
|
err := ns.NestedReadWriteBucket(bucketUnspent).Put(k, v)
|
|
if err != nil {
|
|
str := "cannot put unspent"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func putRawUnspent(ns walletdb.ReadWriteBucket, k, v []byte) error {
|
|
err := ns.NestedReadWriteBucket(bucketUnspent).Put(k, v)
|
|
if err != nil {
|
|
str := "cannot put unspent"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func readUnspentBlock(v []byte, block *Block) error {
|
|
if len(v) < 36 {
|
|
str := "short unspent value"
|
|
return storeError(ErrData, str, nil)
|
|
}
|
|
block.Height = int32(byteOrder.Uint32(v))
|
|
copy(block.Hash[:], v[4:36])
|
|
return nil
|
|
}
|
|
|
|
// existsUnspent returns the key for the unspent output and the corresponding
|
|
// key for the credits bucket. If there is no unspent output recorded, the
|
|
// credit key is nil.
|
|
func existsUnspent(ns walletdb.ReadBucket, outPoint *wire.OutPoint) (k, credKey []byte) {
|
|
k = canonicalOutPoint(&outPoint.Hash, outPoint.Index)
|
|
credKey = existsRawUnspent(ns, k)
|
|
return k, credKey
|
|
}
|
|
|
|
// 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.ReadBucket, k []byte) (credKey []byte) {
|
|
if len(k) < 36 {
|
|
return nil
|
|
}
|
|
v := ns.NestedReadBucket(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
|
|
}
|
|
|
|
func deleteRawUnspent(ns walletdb.ReadWriteBucket, k []byte) error {
|
|
err := ns.NestedReadWriteBucket(bucketUnspent).Delete(k)
|
|
if err != nil {
|
|
str := "failed to delete unspent"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// All transaction debits (inputs which spend credits) are keyed as such:
|
|
//
|
|
// [0:32] Transaction hash (32 bytes)
|
|
// [32:36] Block height (4 bytes)
|
|
// [36:68] Block hash (32 bytes)
|
|
// [68:72] Input index (4 bytes)
|
|
//
|
|
// The first 68 bytes match the key for the transaction record and may be used
|
|
// as a prefix filter to iterate through all debits in order.
|
|
//
|
|
// The debit value is serialized as such:
|
|
//
|
|
// [0:8] Amount (8 bytes)
|
|
// [8:80] Credits bucket key (72 bytes)
|
|
// [8:40] Transaction hash (32 bytes)
|
|
// [40:44] Block height (4 bytes)
|
|
// [44:76] Block hash (32 bytes)
|
|
// [76:80] Output index (4 bytes)
|
|
|
|
func keyDebit(txHash *chainhash.Hash, index uint32, block *Block) []byte {
|
|
k := make([]byte, 72)
|
|
copy(k, txHash[:])
|
|
byteOrder.PutUint32(k[32:36], uint32(block.Height))
|
|
copy(k[36:68], block.Hash[:])
|
|
byteOrder.PutUint32(k[68:72], index)
|
|
return k
|
|
}
|
|
|
|
func putDebit(ns walletdb.ReadWriteBucket, txHash *chainhash.Hash, index uint32, amount btcutil.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.NestedReadWriteBucket(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
|
|
}
|
|
|
|
func extractRawDebitCreditKey(v []byte) []byte {
|
|
return v[8:80]
|
|
}
|
|
|
|
// 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.ReadBucket, txHash *chainhash.Hash, index uint32, block *Block) (k, credKey []byte, err error) {
|
|
k = keyDebit(txHash, index, block)
|
|
v := ns.NestedReadBucket(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
|
|
}
|
|
|
|
func deleteRawDebit(ns walletdb.ReadWriteBucket, k []byte) error {
|
|
err := ns.NestedReadWriteBucket(bucketDebits).Delete(k)
|
|
if err != nil {
|
|
str := "failed to delete debit"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// debitIterator allows for in-order iteration of all debit records for a
|
|
// mined transaction.
|
|
//
|
|
// Example usage:
|
|
//
|
|
// prefix := keyTxRecord(txHash, block)
|
|
// it := makeDebitIterator(ns, prefix)
|
|
// for it.next() {
|
|
// // Use it.elem
|
|
// // If necessary, read additional details from it.ck, it.cv
|
|
// }
|
|
// if it.err != nil {
|
|
// // Handle error
|
|
// }
|
|
type debitIterator struct {
|
|
c walletdb.ReadWriteCursor // Set to nil after final iteration
|
|
prefix []byte
|
|
ck []byte
|
|
cv []byte
|
|
elem DebitRecord
|
|
err error
|
|
}
|
|
|
|
func makeDebitIterator(ns walletdb.ReadWriteBucket, prefix []byte) debitIterator {
|
|
c := ns.NestedReadWriteBucket(bucketDebits).ReadWriteCursor()
|
|
return debitIterator{c: c, prefix: prefix}
|
|
}
|
|
|
|
func makeReadDebitIterator(ns walletdb.ReadBucket, prefix []byte) debitIterator {
|
|
c := ns.NestedReadBucket(bucketDebits).ReadCursor()
|
|
return debitIterator{c: readCursor{c}, prefix: prefix}
|
|
}
|
|
|
|
func (it *debitIterator) readElem() error {
|
|
if len(it.ck) < 72 {
|
|
str := fmt.Sprintf("%s: short key (expected %d bytes, read %d)",
|
|
bucketDebits, 72, len(it.ck))
|
|
return storeError(ErrData, str, nil)
|
|
}
|
|
if len(it.cv) < 80 {
|
|
str := fmt.Sprintf("%s: short read (expected %d bytes, read %d)",
|
|
bucketDebits, 80, len(it.cv))
|
|
return storeError(ErrData, str, nil)
|
|
}
|
|
it.elem.Index = byteOrder.Uint32(it.ck[68:72])
|
|
it.elem.Amount = btcutil.Amount(byteOrder.Uint64(it.cv))
|
|
return nil
|
|
}
|
|
|
|
func (it *debitIterator) next() bool {
|
|
if it.c == nil {
|
|
return false
|
|
}
|
|
|
|
if it.ck == nil {
|
|
it.ck, it.cv = it.c.Seek(it.prefix)
|
|
} else {
|
|
it.ck, it.cv = it.c.Next()
|
|
}
|
|
if !bytes.HasPrefix(it.ck, it.prefix) {
|
|
it.c = nil
|
|
return false
|
|
}
|
|
|
|
err := it.readElem()
|
|
if err != nil {
|
|
it.err = err
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// All unmined transactions are saved in the unmined bucket keyed by the
|
|
// transaction hash. The value matches that of mined transaction records:
|
|
//
|
|
// [0:8] Received time (8 bytes)
|
|
// [8:] Serialized transaction (varies)
|
|
|
|
func putRawUnmined(ns walletdb.ReadWriteBucket, k, v []byte) error {
|
|
err := ns.NestedReadWriteBucket(bucketUnmined).Put(k, v)
|
|
if err != nil {
|
|
str := "failed to put unmined record"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func readRawUnminedHash(k []byte, txHash *chainhash.Hash) error {
|
|
if len(k) < 32 {
|
|
str := "short unmined key"
|
|
return storeError(ErrData, str, nil)
|
|
}
|
|
copy(txHash[:], k)
|
|
return nil
|
|
}
|
|
|
|
func existsRawUnmined(ns walletdb.ReadBucket, k []byte) (v []byte) {
|
|
return ns.NestedReadBucket(bucketUnmined).Get(k)
|
|
}
|
|
|
|
func deleteRawUnmined(ns walletdb.ReadWriteBucket, k []byte) error {
|
|
err := ns.NestedReadWriteBucket(bucketUnmined).Delete(k)
|
|
if err != nil {
|
|
str := "failed to delete unmined record"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Unmined transaction credits use the canonical serialization format:
|
|
//
|
|
// [0:32] Transaction hash (32 bytes)
|
|
// [32:36] Output index (4 bytes)
|
|
//
|
|
// The value matches the format used by mined credits, but the spent flag is
|
|
// never set and the optional debit record is never included. The simplified
|
|
// format is thus:
|
|
//
|
|
// [0:8] Amount (8 bytes)
|
|
// [8] Flags (1 byte)
|
|
// 0x02: Change
|
|
|
|
func valueUnminedCredit(amount btcutil.Amount, flags byte) []byte {
|
|
v := make([]byte, 9)
|
|
byteOrder.PutUint64(v, uint64(amount))
|
|
v[8] = flags
|
|
return v
|
|
}
|
|
|
|
func putRawUnminedCredit(ns walletdb.ReadWriteBucket, k, v []byte) error {
|
|
err := ns.NestedReadWriteBucket(bucketUnminedCredits).Put(k, v)
|
|
if err != nil {
|
|
str := "cannot put unmined credit"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func fetchRawUnminedCreditIndex(k []byte) (uint32, error) {
|
|
if len(k) < 36 {
|
|
str := "short unmined credit key"
|
|
return 0, storeError(ErrData, str, nil)
|
|
}
|
|
return byteOrder.Uint32(k[32:36]), nil
|
|
}
|
|
|
|
func fetchRawUnminedCreditAmount(v []byte) (btcutil.Amount, error) {
|
|
if len(v) < 9 {
|
|
str := "short unmined credit value"
|
|
return 0, storeError(ErrData, str, nil)
|
|
}
|
|
return btcutil.Amount(byteOrder.Uint64(v)), nil
|
|
}
|
|
|
|
func fetchRawUnminedCreditAmountChange(v []byte) (btcutil.Amount, byte, error) {
|
|
if len(v) < 9 {
|
|
str := "short unmined credit value"
|
|
return 0, 0, storeError(ErrData, str, nil)
|
|
}
|
|
amt := btcutil.Amount(byteOrder.Uint64(v))
|
|
return amt, v[8], nil
|
|
}
|
|
|
|
func existsRawUnminedCredit(ns walletdb.ReadBucket, k []byte) []byte {
|
|
return ns.NestedReadBucket(bucketUnminedCredits).Get(k)
|
|
}
|
|
|
|
func deleteRawUnminedCredit(ns walletdb.ReadWriteBucket, k []byte) error {
|
|
err := ns.NestedReadWriteBucket(bucketUnminedCredits).Delete(k)
|
|
if err != nil {
|
|
str := "failed to delete unmined credit"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// unminedCreditIterator allows for cursor iteration over all credits, in order,
|
|
// from a single unmined transaction.
|
|
//
|
|
// Example usage:
|
|
//
|
|
// it := makeUnminedCreditIterator(ns, txHash)
|
|
// for it.next() {
|
|
// // Use it.elem, it.ck and it.cv
|
|
// // Optionally, use it.delete() to remove this k/v pair
|
|
// }
|
|
// if it.err != nil {
|
|
// // Handle error
|
|
// }
|
|
//
|
|
// The spentness of the credit is not looked up for performance reasons (because
|
|
// for unspent credits, it requires another lookup in another bucket). If this
|
|
// is needed, it may be checked like this:
|
|
//
|
|
// spent := existsRawUnminedInput(ns, it.ck) != nil
|
|
type unminedCreditIterator struct {
|
|
c walletdb.ReadWriteCursor
|
|
prefix []byte
|
|
ck []byte
|
|
cv []byte
|
|
elem CreditRecord
|
|
err error
|
|
}
|
|
|
|
type readCursor struct {
|
|
walletdb.ReadCursor
|
|
}
|
|
|
|
func (r readCursor) Delete() error {
|
|
str := "failed to delete current cursor item from read-only cursor"
|
|
return storeError(ErrDatabase, str, walletdb.ErrTxNotWritable)
|
|
}
|
|
|
|
func makeUnminedCreditIterator(ns walletdb.ReadWriteBucket, txHash *chainhash.Hash) unminedCreditIterator {
|
|
c := ns.NestedReadWriteBucket(bucketUnminedCredits).ReadWriteCursor()
|
|
return unminedCreditIterator{c: c, prefix: txHash[:]}
|
|
}
|
|
|
|
func makeReadUnminedCreditIterator(ns walletdb.ReadBucket, txHash *chainhash.Hash) unminedCreditIterator {
|
|
c := ns.NestedReadBucket(bucketUnminedCredits).ReadCursor()
|
|
return unminedCreditIterator{c: readCursor{c}, prefix: txHash[:]}
|
|
}
|
|
|
|
func (it *unminedCreditIterator) readElem() error {
|
|
index, err := fetchRawUnminedCreditIndex(it.ck)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
amount, flags, err := fetchRawUnminedCreditAmountChange(it.cv)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
it.elem.Index = index
|
|
it.elem.Amount = amount
|
|
it.elem.Change = (flags & ChangeFlag) > 0
|
|
// Spent intentionally not set
|
|
|
|
return nil
|
|
}
|
|
|
|
func (it *unminedCreditIterator) next() bool {
|
|
if it.c == nil {
|
|
return false
|
|
}
|
|
|
|
if it.ck == nil {
|
|
it.ck, it.cv = it.c.Seek(it.prefix)
|
|
} else {
|
|
it.ck, it.cv = it.c.Next()
|
|
}
|
|
if !bytes.HasPrefix(it.ck, it.prefix) {
|
|
it.c = nil
|
|
return false
|
|
}
|
|
|
|
err := it.readElem()
|
|
if err != nil {
|
|
it.err = err
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// unavailable until https://github.com/boltdb/bolt/issues/620 is fixed.
|
|
// func (it *unminedCreditIterator) delete() error {
|
|
// err := it.c.Delete()
|
|
// if err != nil {
|
|
// str := "failed to delete unmined credit"
|
|
// return storeError(ErrDatabase, str, err)
|
|
// }
|
|
// return nil
|
|
// }
|
|
|
|
func (it *unminedCreditIterator) reposition(txHash *chainhash.Hash, index uint32) {
|
|
it.c.Seek(canonicalOutPoint(txHash, index))
|
|
}
|
|
|
|
// Outpoints spent by unmined transactions are saved in the unmined inputs
|
|
// bucket. This bucket maps between each previous output spent, for both mined
|
|
// and unmined transactions, to the hash of the unmined transaction.
|
|
//
|
|
// The key is serialized as such:
|
|
//
|
|
// [0:32] Transaction hash (32 bytes)
|
|
// [32:36] Output index (4 bytes)
|
|
//
|
|
// The value is serialized as such:
|
|
//
|
|
// [0:32] Transaction hash (32 bytes)
|
|
|
|
// putRawUnminedInput maintains a list of unmined transaction hashes that have
|
|
// spent an outpoint. Each entry in the bucket is keyed by the outpoint being
|
|
// spent.
|
|
func putRawUnminedInput(ns walletdb.ReadWriteBucket, k, v []byte) error {
|
|
spendTxHashes := ns.NestedReadBucket(bucketUnminedInputs).Get(k)
|
|
spendTxHashes = append(spendTxHashes, v...)
|
|
err := ns.NestedReadWriteBucket(bucketUnminedInputs).Put(k, spendTxHashes)
|
|
if err != nil {
|
|
str := "failed to put unmined input"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func existsRawUnminedInput(ns walletdb.ReadBucket, k []byte) (v []byte) {
|
|
return ns.NestedReadBucket(bucketUnminedInputs).Get(k)
|
|
}
|
|
|
|
// fetchUnminedInputSpendTxHashes fetches the list of unmined transactions that
|
|
// spend the serialized outpoint.
|
|
func fetchUnminedInputSpendTxHashes(ns walletdb.ReadBucket, k []byte) []chainhash.Hash {
|
|
rawSpendTxHashes := ns.NestedReadBucket(bucketUnminedInputs).Get(k)
|
|
if rawSpendTxHashes == nil {
|
|
return nil
|
|
}
|
|
|
|
// Each transaction hash is 32 bytes.
|
|
spendTxHashes := make([]chainhash.Hash, 0, len(rawSpendTxHashes)/32)
|
|
for len(rawSpendTxHashes) > 0 {
|
|
var spendTxHash chainhash.Hash
|
|
copy(spendTxHash[:], rawSpendTxHashes[:32])
|
|
spendTxHashes = append(spendTxHashes, spendTxHash)
|
|
rawSpendTxHashes = rawSpendTxHashes[32:]
|
|
}
|
|
|
|
return spendTxHashes
|
|
}
|
|
|
|
// deleteRawUnminedInput removes a spending transaction entry from the list of
|
|
// spending transactions for a given input.
|
|
func deleteRawUnminedInput(ns walletdb.ReadWriteBucket, outPointKey []byte,
|
|
targetSpendHash chainhash.Hash) error {
|
|
|
|
// We'll start by fetching all of the possible spending transactions.
|
|
unminedInputs := ns.NestedReadWriteBucket(bucketUnminedInputs)
|
|
spendHashes := unminedInputs.Get(outPointKey)
|
|
if len(spendHashes) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// We'll iterate through them and pick all the ones that don't match the
|
|
// specified spending transaction.
|
|
var newSpendHashes []byte
|
|
numHashes := len(spendHashes) / 32
|
|
for i, idx := 0, 0; i < numHashes; i, idx = i+1, idx+32 {
|
|
spendHash := spendHashes[idx : idx+32]
|
|
if !bytes.Equal(targetSpendHash[:], spendHash) {
|
|
newSpendHashes = append(newSpendHashes, spendHash...)
|
|
}
|
|
}
|
|
|
|
// If there aren't any entries left after filtering them, then we can
|
|
// remove the record completely. Otherwise, we'll store the filtered
|
|
// records.
|
|
var err error
|
|
if len(newSpendHashes) == 0 {
|
|
err = unminedInputs.Delete(outPointKey)
|
|
} else {
|
|
err = unminedInputs.Put(outPointKey, newSpendHashes)
|
|
}
|
|
if err != nil {
|
|
str := "failed to delete unmined input spend"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// serializeLockedOutput serializes the value of a locked output.
|
|
func serializeLockedOutput(id LockID, expiry time.Time) []byte {
|
|
var v [len(id) + 8]byte
|
|
copy(v[:len(id)], id[:])
|
|
byteOrder.PutUint64(v[len(id):], uint64(expiry.Unix()))
|
|
return v[:]
|
|
}
|
|
|
|
// deserializeLockedOutput deserializes the value of a locked output.
|
|
func deserializeLockedOutput(v []byte) (LockID, time.Time) {
|
|
var id LockID
|
|
copy(id[:], v[:len(id)])
|
|
expiry := time.Unix(int64(byteOrder.Uint64(v[len(id):])), 0)
|
|
return id, expiry
|
|
}
|
|
|
|
// isLockedOutput determines whether an output is locked. If it is, its assigned
|
|
// ID is returned, along with its absolute expiration time. If the output lock
|
|
// exists, but its expiration has been met, then the output is considered
|
|
// unlocked.
|
|
func isLockedOutput(ns walletdb.ReadBucket, op wire.OutPoint,
|
|
timeNow time.Time) (LockID, time.Time, bool) {
|
|
|
|
// The bucket may not exist, indicating that no outputs have ever been
|
|
// locked, so we can just return now.
|
|
lockedOutputs := ns.NestedReadBucket(bucketLockedOutputs)
|
|
if lockedOutputs == nil {
|
|
return LockID{}, time.Time{}, false
|
|
}
|
|
|
|
// Retrieve the output lock, if any, and extract the relevant fields.
|
|
k := canonicalOutPoint(&op.Hash, op.Index)
|
|
v := lockedOutputs.Get(k)
|
|
if v == nil {
|
|
return LockID{}, time.Time{}, false
|
|
}
|
|
lockID, expiry := deserializeLockedOutput(v)
|
|
|
|
// If the output lock has already expired, delete it now.
|
|
if !timeNow.Before(expiry) {
|
|
return LockID{}, time.Time{}, false
|
|
}
|
|
|
|
return lockID, expiry, true
|
|
}
|
|
|
|
// lockOutput creates a lock for `duration` over an output assigned to the `id`,
|
|
// preventing it from becoming eligible for coin selection.
|
|
func lockOutput(ns walletdb.ReadWriteBucket, id LockID, op wire.OutPoint,
|
|
expiry time.Time) error {
|
|
|
|
// Create the corresponding bucket if necessary.
|
|
lockedOutputs, err := ns.CreateBucketIfNotExists(bucketLockedOutputs)
|
|
if err != nil {
|
|
str := "failed to create locked outputs bucket"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
|
|
// Store a mapping of outpoint -> (id, expiry).
|
|
k := canonicalOutPoint(&op.Hash, op.Index)
|
|
v := serializeLockedOutput(id, expiry)
|
|
|
|
if err := lockedOutputs.Put(k, v[:]); err != nil {
|
|
str := fmt.Sprintf("%s: put failed for %v", bucketLockedOutputs,
|
|
op)
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// unlockOutput removes a lock over an output, making it eligible for coin
|
|
// selection if still unspent.
|
|
func unlockOutput(ns walletdb.ReadWriteBucket, op wire.OutPoint) error {
|
|
// The bucket may not exist, indicating that no outputs have ever been
|
|
// locked, so we can just return now.
|
|
lockedOutputs := ns.NestedReadWriteBucket(bucketLockedOutputs)
|
|
if lockedOutputs == nil {
|
|
return nil
|
|
}
|
|
|
|
// Delete the key-value pair representing the output lock.
|
|
k := canonicalOutPoint(&op.Hash, op.Index)
|
|
if err := lockedOutputs.Delete(k); err != nil {
|
|
str := fmt.Sprintf("%s: delete failed for %v",
|
|
bucketLockedOutputs, op)
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// forEachLockedOutput iterates over all existing locked outputs and invokes the
|
|
// callback `f` for each.
|
|
func forEachLockedOutput(ns walletdb.ReadBucket,
|
|
f func(wire.OutPoint, LockID, time.Time)) error {
|
|
|
|
// The bucket may not exist, indicating that no outputs have ever been
|
|
// locked, so we can just return now.
|
|
lockedOutputs := ns.NestedReadBucket(bucketLockedOutputs)
|
|
if lockedOutputs == nil {
|
|
return nil
|
|
}
|
|
|
|
return lockedOutputs.ForEach(func(k, v []byte) error {
|
|
var op wire.OutPoint
|
|
if err := readCanonicalOutPoint(k, &op); err != nil {
|
|
return err
|
|
}
|
|
lockID, expiry := deserializeLockedOutput(v)
|
|
|
|
f(op, lockID, expiry)
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// openStore opens an existing transaction store from the passed namespace.
|
|
func openStore(ns walletdb.ReadBucket) error {
|
|
version, err := fetchVersion(ns)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
latestVersion := getLatestVersion()
|
|
if version < latestVersion {
|
|
str := fmt.Sprintf("a database upgrade is required to upgrade "+
|
|
"wtxmgr from recorded version %d to the latest version %d",
|
|
version, latestVersion)
|
|
return storeError(ErrNeedsUpgrade, str, nil)
|
|
}
|
|
|
|
if version > latestVersion {
|
|
str := fmt.Sprintf("version recorded version %d is newer that "+
|
|
"latest understood version %d", version, latestVersion)
|
|
return storeError(ErrUnknownVersion, str, nil)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// createStore creates the tx store (with the latest db version) in the passed
|
|
// namespace. If a store already exists, ErrAlreadyExists is returned.
|
|
func createStore(ns walletdb.ReadWriteBucket) error {
|
|
// Ensure that nothing currently exists in the namespace bucket.
|
|
ck, cv := ns.ReadCursor().First()
|
|
if ck != nil || cv != nil {
|
|
const str = "namespace is not empty"
|
|
return storeError(ErrAlreadyExists, str, nil)
|
|
}
|
|
|
|
// Write the latest store version.
|
|
if err := putVersion(ns, getLatestVersion()); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Save the creation date of the store.
|
|
var v [8]byte
|
|
byteOrder.PutUint64(v[:], uint64(time.Now().Unix()))
|
|
err := ns.Put(rootCreateDate, v[:])
|
|
if err != nil {
|
|
str := "failed to store database creation time"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
|
|
// Write a zero balance.
|
|
byteOrder.PutUint64(v[:], 0)
|
|
err = ns.Put(rootMinedBalance, v[:])
|
|
if err != nil {
|
|
str := "failed to write zero balance"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
|
|
// Finally, create all of our required descendant buckets.
|
|
return createBuckets(ns)
|
|
}
|
|
|
|
// createBuckets creates all of the descendants buckets required for the
|
|
// transaction store to properly carry its duties.
|
|
func createBuckets(ns walletdb.ReadWriteBucket) error {
|
|
if _, err := ns.CreateBucket(bucketBlocks); err != nil {
|
|
str := "failed to create blocks bucket"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
if _, err := ns.CreateBucket(bucketTxRecords); err != nil {
|
|
str := "failed to create tx records bucket"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
if _, err := ns.CreateBucket(bucketCredits); err != nil {
|
|
str := "failed to create credits bucket"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
if _, err := ns.CreateBucket(bucketDebits); err != nil {
|
|
str := "failed to create debits bucket"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
if _, err := ns.CreateBucket(bucketUnspent); err != nil {
|
|
str := "failed to create unspent bucket"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
if _, err := ns.CreateBucket(bucketUnmined); err != nil {
|
|
str := "failed to create unmined bucket"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
if _, err := ns.CreateBucket(bucketUnminedCredits); err != nil {
|
|
str := "failed to create unmined credits bucket"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
if _, err := ns.CreateBucket(bucketUnminedInputs); err != nil {
|
|
str := "failed to create unmined inputs bucket"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
if _, err := ns.CreateBucket(bucketLockedOutputs); err != nil {
|
|
str := "failed to create locked outputs bucket"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// deleteBuckets deletes all of the descendants buckets required for the
|
|
// transaction store to properly carry its duties.
|
|
func deleteBuckets(ns walletdb.ReadWriteBucket) error {
|
|
if err := ns.DeleteNestedBucket(bucketBlocks); err != nil {
|
|
str := "failed to delete blocks bucket"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
if err := ns.DeleteNestedBucket(bucketTxRecords); err != nil {
|
|
str := "failed to delete tx records bucket"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
if err := ns.DeleteNestedBucket(bucketCredits); err != nil {
|
|
str := "failed to delete credits bucket"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
if err := ns.DeleteNestedBucket(bucketDebits); err != nil {
|
|
str := "failed to delete debits bucket"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
if err := ns.DeleteNestedBucket(bucketUnspent); err != nil {
|
|
str := "failed to delete unspent bucket"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
if err := ns.DeleteNestedBucket(bucketUnmined); err != nil {
|
|
str := "failed to delete unmined bucket"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
if err := ns.DeleteNestedBucket(bucketUnminedCredits); err != nil {
|
|
str := "failed to delete unmined credits bucket"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
if err := ns.DeleteNestedBucket(bucketUnminedInputs); err != nil {
|
|
str := "failed to delete unmined inputs bucket"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
err := ns.DeleteNestedBucket(bucketLockedOutputs)
|
|
if err != nil && err != walletdb.ErrBucketNotFound {
|
|
str := "failed to delete locked outputs bucket"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// putVersion modifies the version of the store to reflect the given version
|
|
// number.
|
|
func putVersion(ns walletdb.ReadWriteBucket, version uint32) error {
|
|
var v [4]byte
|
|
byteOrder.PutUint32(v[:], version)
|
|
if err := ns.Put(rootVersion, v[:]); err != nil {
|
|
str := "failed to store database version"
|
|
return storeError(ErrDatabase, str, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// fetchVersion fetches the current version of the store.
|
|
func fetchVersion(ns walletdb.ReadBucket) (uint32, error) {
|
|
v := ns.Get(rootVersion)
|
|
if len(v) != 4 {
|
|
str := "no transaction store exists in namespace"
|
|
return 0, storeError(ErrNoExists, str, nil)
|
|
}
|
|
|
|
return byteOrder.Uint32(v), nil
|
|
}
|