query.go raw

   1  package wtxmgr
   2  
   3  import (
   4  	"fmt"
   5  	"github.com/p9c/p9/pkg/amt"
   6  	
   7  	"github.com/p9c/p9/pkg/chainhash"
   8  	"github.com/p9c/p9/pkg/walletdb"
   9  )
  10  
  11  // CreditRecord contains metadata regarding a transaction credit for a known transaction. Further details may be looked
  12  // up by indexing a wire.MsgTx.TxOut with the Index field.
  13  type CreditRecord struct {
  14  	Amount amt.Amount
  15  	Index  uint32
  16  	Spent  bool
  17  	Change bool
  18  }
  19  
  20  // DebitRecord contains metadata regarding a transaction debit for a known transaction. Further details may be looked up
  21  // by indexing a wire.MsgTx.TxIn with the Index field.
  22  type DebitRecord struct {
  23  	Amount amt.Amount
  24  	Index  uint32
  25  }
  26  
  27  // TxDetails is intended to provide callers with access to rich details regarding a relevant transaction and which
  28  // inputs and outputs are credit or debits.
  29  type TxDetails struct {
  30  	TxRecord
  31  	Block   BlockMeta
  32  	Credits []CreditRecord
  33  	Debits  []DebitRecord
  34  }
  35  
  36  // minedTxDetails fetches the TxDetails for the mined transaction with hash txHash and the passed tx record key and
  37  // value.
  38  func (s *Store) minedTxDetails(ns walletdb.ReadBucket, txHash *chainhash.Hash, recKey, recVal []byte) (
  39  	*TxDetails,
  40  	error,
  41  ) {
  42  	var details TxDetails
  43  	// Parse transaction record k/v, lookup the full block record for the block time, and read all matching credits,
  44  	// debits.
  45  	e := readRawTxRecord(txHash, recVal, &details.TxRecord)
  46  	if e != nil {
  47  		return nil, e
  48  	}
  49  	e = readRawTxRecordBlock(recKey, &details.Block.Block)
  50  	if e != nil {
  51  		return nil, e
  52  	}
  53  	details.Block.Time, e = fetchBlockTime(ns, details.Block.Height)
  54  	if e != nil {
  55  		return nil, e
  56  	}
  57  	credIter := makeReadCreditIterator(ns, recKey)
  58  	for credIter.next() {
  59  		if int(credIter.elem.Index) >= len(details.MsgTx.TxOut) {
  60  			str := "saved credit index exceeds number of outputs"
  61  			return nil, storeError(ErrData, str, nil)
  62  		}
  63  		// The credit iterator does not record whether this credit was spent by an unmined transaction, so check that
  64  		// here.
  65  		if !credIter.elem.Spent {
  66  			k := canonicalOutPoint(txHash, credIter.elem.Index)
  67  			spent := existsRawUnminedInput(ns, k) != nil
  68  			credIter.elem.Spent = spent
  69  		}
  70  		details.Credits = append(details.Credits, credIter.elem)
  71  	}
  72  	if credIter.err != nil {
  73  		return nil, credIter.err
  74  	}
  75  	debIter := makeReadDebitIterator(ns, recKey)
  76  	for debIter.next() {
  77  		if int(debIter.elem.Index) >= len(details.MsgTx.TxIn) {
  78  			str := "saved debit index exceeds number of inputs"
  79  			return nil, storeError(ErrData, str, nil)
  80  		}
  81  		details.Debits = append(details.Debits, debIter.elem)
  82  	}
  83  	return &details, debIter.err
  84  }
  85  
  86  // unminedTxDetails fetches the TxDetails for the unmined transaction with the hash txHash and the passed unmined record
  87  // value.
  88  func (s *Store) unminedTxDetails(ns walletdb.ReadBucket, txHash *chainhash.Hash, v []byte) (*TxDetails, error) {
  89  	details := TxDetails{
  90  		Block: BlockMeta{Block: Block{Height: -1}},
  91  	}
  92  	e := readRawTxRecord(txHash, v, &details.TxRecord)
  93  	if e != nil {
  94  		return nil, e
  95  	}
  96  	it := makeReadUnminedCreditIterator(ns, txHash)
  97  	for it.next() {
  98  		if int(it.elem.Index) >= len(details.MsgTx.TxOut) {
  99  			str := "saved credit index exceeds number of outputs"
 100  			return nil, storeError(ErrData, str, nil)
 101  		}
 102  		// Set the Spent field since this is not done by the iterator.
 103  		it.elem.Spent = existsRawUnminedInput(ns, it.ck) != nil
 104  		details.Credits = append(details.Credits, it.elem)
 105  	}
 106  	if it.err != nil {
 107  		return nil, it.err
 108  	}
 109  	// Debit records are not saved for unmined transactions. Instead, they must be looked up for each transaction input
 110  	// manually. There are two kinds of previous credits that may be debited by an unmined transaction: mined unspent
 111  	// outputs (which remain marked unspent even when spent by an unmined transaction), and credits from other unmined
 112  	// transactions. Both situations must be considered.
 113  	for i, output := range details.MsgTx.TxIn {
 114  		opKey := canonicalOutPoint(
 115  			&output.PreviousOutPoint.Hash,
 116  			output.PreviousOutPoint.Index,
 117  		)
 118  		credKey := existsRawUnspent(ns, opKey)
 119  		if credKey != nil {
 120  			v := existsRawCredit(ns, credKey)
 121  			amount, e := fetchRawCreditAmount(v)
 122  			if e != nil {
 123  				return nil, e
 124  			}
 125  			details.Debits = append(
 126  				details.Debits, DebitRecord{
 127  					Amount: amount,
 128  					Index:  uint32(i),
 129  				},
 130  			)
 131  			continue
 132  		}
 133  		v := existsRawUnminedCredit(ns, opKey)
 134  		if v == nil {
 135  			continue
 136  		}
 137  		amount, e := fetchRawCreditAmount(v)
 138  		if e != nil {
 139  			return nil, e
 140  		}
 141  		details.Debits = append(
 142  			details.Debits, DebitRecord{
 143  				Amount: amount,
 144  				Index:  uint32(i),
 145  			},
 146  		)
 147  	}
 148  	return &details, nil
 149  }
 150  
 151  // TxDetails looks up all recorded details regarding a transaction with some hash. In case of a hash collision, the most
 152  // recent transaction with a matching hash is returned.
 153  //
 154  // Not finding a transaction with this hash is not an error. In this case, a nil TxDetails is returned.
 155  func (s *Store) TxDetails(ns walletdb.ReadBucket, txHash *chainhash.Hash) (*TxDetails, error) {
 156  	// First, check whether there exists an unmined transaction with this hash. Use it if found.
 157  	v := existsRawUnmined(ns, txHash[:])
 158  	if v != nil {
 159  		return s.unminedTxDetails(ns, txHash, v)
 160  	}
 161  	// Otherwise, if there exists a mined transaction with this matching hash, skip over to the newest and begin
 162  	// fetching all details.
 163  	k, v := latestTxRecord(ns, txHash)
 164  	if v == nil {
 165  		// not found
 166  		return nil, nil
 167  	}
 168  	return s.minedTxDetails(ns, txHash, k, v)
 169  }
 170  
 171  // UniqueTxDetails looks up all recorded details for a transaction recorded mined in some particular block, or an
 172  // unmined transaction if block is nil.
 173  //
 174  // Not finding a transaction with this hash from this block is not an error. In this case, a nil TxDetails is returned.
 175  func (s *Store) UniqueTxDetails(
 176  	ns walletdb.ReadBucket, txHash *chainhash.Hash,
 177  	block *Block,
 178  ) (*TxDetails, error) {
 179  	if block == nil {
 180  		v := existsRawUnmined(ns, txHash[:])
 181  		if v == nil {
 182  			return nil, nil
 183  		}
 184  		return s.unminedTxDetails(ns, txHash, v)
 185  	}
 186  	k, v := existsTxRecord(ns, txHash, block)
 187  	if v == nil {
 188  		return nil, nil
 189  	}
 190  	return s.minedTxDetails(ns, txHash, k, v)
 191  }
 192  
 193  // rangeUnminedTransactions executes the function f with TxDetails for every unmined transaction. f is not executed if
 194  // no unmined transactions exist. DBError returns from f (if any) are propigated to the caller. Returns true (signaling
 195  // breaking out of a RangeTransactions) iff f executes and returns true.
 196  func (s *Store) rangeUnminedTransactions(
 197  	ns walletdb.ReadBucket,
 198  	f func([]TxDetails) (bool, error),
 199  ) (bool, error) {
 200  	T.Ln("rangeUnminedTransactions")
 201  	var details []TxDetails
 202  	e := ns.NestedReadBucket(bucketUnmined).ForEach(
 203  		func(k, v []byte) (e error) {
 204  			// D.Ln("k", k, "v", v)
 205  			if len(k) < 32 {
 206  				str := fmt.Sprintf("%s: short key (expected %d bytes, read %d)", bucketUnmined, 32, len(k))
 207  				return storeError(ErrData, str, nil)
 208  			}
 209  			var txHash chainhash.Hash
 210  			copy(txHash[:], k)
 211  			detail, e := s.unminedTxDetails(ns, &txHash, v)
 212  			if e != nil {
 213  				return e
 214  			}
 215  			// Because the key was created while foreach-ing over the bucket, it should be impossible for unminedTxDetails
 216  			// to ever successfully return a nil details struct.
 217  			details = append(details, *detail)
 218  			return nil
 219  		},
 220  	)
 221  	if e == nil && len(details) > 0 {
 222  		return f(details)
 223  	}
 224  	return false, e
 225  }
 226  
 227  // rangeBlockTransactions executes the function f with TxDetails for every block between heights begin and end (reverse
 228  // order when end > begin) until f returns true, or the transactions from block is processed. Returns true iff f
 229  // executes and returns true.
 230  func (s *Store) rangeBlockTransactions(
 231  	ns walletdb.ReadBucket, begin, end int32,
 232  	f func([]TxDetails) (bool, error),
 233  ) (bool, error) {
 234  	T.Ln("rangeBlockTransactions", begin, end)
 235  	// Mempool height is considered a high bound.
 236  	if begin < 0 {
 237  		begin = int32(^uint32(0) >> 1)
 238  	}
 239  	if end < 0 {
 240  		end = int32(^uint32(0) >> 1)
 241  	}
 242  	T.Ln("begin", begin, "end", end)
 243  	var blockIter blockIterator
 244  	var advance func(*blockIterator) bool
 245  	if begin < end {
 246  		// Iterate in forwards order
 247  		blockIter = makeReadBlockIterator(ns, begin)
 248  		advance = func(it *blockIterator) bool {
 249  			if !it.next() {
 250  				D.Ln("end of blocks")
 251  				return false
 252  			}
 253  			return it.elem.Height <= end
 254  		}
 255  	} else {
 256  		// Iterate in backwards order, from begin -> end.
 257  		blockIter = makeReadBlockIterator(ns, begin)
 258  		advance = func(it *blockIterator) bool {
 259  			if !it.prev() {
 260  				return false
 261  			}
 262  			return end <= it.elem.Height
 263  		}
 264  	}
 265  	var details []TxDetails
 266  	for advance(&blockIter) {
 267  		block := &blockIter.elem
 268  		if cap(details) < len(block.transactions) {
 269  			details = make([]TxDetails, 0, len(block.transactions))
 270  		} else {
 271  			details = details[:0]
 272  		}
 273  		for _, txHash := range block.transactions {
 274  			k := keyTxRecord(&txHash, &block.Block)
 275  			v := existsRawTxRecord(ns, k)
 276  			if v == nil {
 277  				// T.F("missing transaction %v for block %v", txHash, block.Height)
 278  				// str := fmt.Sprintf("missing transaction %v for block %v", txHash, block.Height)
 279  				// return false, storeError(ErrData, str, nil)
 280  				// deleteTxRecord(ns, )
 281  			} else {
 282  				detail := TxDetails{
 283  					Block: BlockMeta{
 284  						Block: block.Block,
 285  						Time:  block.Time,
 286  					},
 287  				}
 288  				e := readRawTxRecord(&txHash, v, &detail.TxRecord)
 289  				if e != nil {
 290  					return false, e
 291  				}
 292  				credIter := makeReadCreditIterator(ns, k)
 293  				for credIter.next() {
 294  					if int(credIter.elem.Index) >= len(detail.MsgTx.TxOut) {
 295  						str := "saved credit index exceeds number of outputs"
 296  						return false, storeError(ErrData, str, nil)
 297  					}
 298  					// The credit iterator does not record whether this credit was spent by an unmined transaction, so check
 299  					// that here.
 300  					if !credIter.elem.Spent {
 301  						k = canonicalOutPoint(&txHash, credIter.elem.Index)
 302  						spent := existsRawUnminedInput(ns, k) != nil
 303  						credIter.elem.Spent = spent
 304  					}
 305  					detail.Credits = append(detail.Credits, credIter.elem)
 306  				}
 307  				if credIter.err != nil {
 308  					return false, credIter.err
 309  				}
 310  				debIter := makeReadDebitIterator(ns, k)
 311  				for debIter.next() {
 312  					if int(debIter.elem.Index) >= len(detail.MsgTx.TxIn) {
 313  						str := "saved debit index exceeds number of inputs"
 314  						return false, storeError(ErrData, str, nil)
 315  					}
 316  					detail.Debits = append(detail.Debits, debIter.elem)
 317  				}
 318  				if debIter.err != nil {
 319  					return false, debIter.err
 320  				}
 321  				details = append(details, detail)
 322  			}
 323  		}
 324  		// Every block record must have at least one transaction, so it
 325  		// is safe to call f.
 326  		brk, e := f(details)
 327  		if e != nil || brk {
 328  			return brk, e
 329  		}
 330  	}
 331  	return false, blockIter.err
 332  }
 333  
 334  // RangeTransactions runs the function f on all transaction details between blocks on the best chain over the height
 335  // range [begin,end]. The special height -1 may be used to also include unmined transactions. If the end height comes
 336  // before the begin height, blocks are iterated in reverse order and unmined transactions (if any) are processed first.
 337  //
 338  // The function f may return an error which, if non-nil, is propagated to the caller. Additionally, a boolean return
 339  // value allows exiting the function early without reading any additional transactions early when true.
 340  //
 341  // All calls to f are guaranteed to be passed a slice with more than zero elements. The slice may be reused for multiple
 342  // blocks, so it is not safe to use it after the loop iteration it was acquired.
 343  func (s *Store) RangeTransactions(
 344  	ns walletdb.ReadBucket, begin, end int32,
 345  	f func([]TxDetails) (bool, error),
 346  ) error {
 347  	T.Ln("RangeTransactions")
 348  	var addedUnmined, brk bool
 349  	var e error
 350  	if begin < 0 {
 351  		brk, e = s.rangeUnminedTransactions(ns, f)
 352  		if e != nil || brk {
 353  			return e
 354  		}
 355  		addedUnmined = true
 356  	}
 357  	if brk, e = s.rangeBlockTransactions(ns, begin, end, f); E.Chk(e) {
 358  	}
 359  	if e == nil && !brk && !addedUnmined && end < 0 {
 360  		_, e = s.rangeUnminedTransactions(ns, f)
 361  	}
 362  	return e
 363  }
 364  
 365  // PreviousPkScripts returns a slice of previous output scripts for each credit output this transaction record debits
 366  // from.
 367  func (s *Store) PreviousPkScripts(ns walletdb.ReadBucket, rec *TxRecord, block *Block) ([][]byte, error) {
 368  	var pkScripts [][]byte
 369  	if block == nil {
 370  		for _, input := range rec.MsgTx.TxIn {
 371  			prevOut := &input.PreviousOutPoint
 372  			// Input may spend a previous unmined output, a mined output (which would still be marked unspent), or
 373  			// neither.
 374  			v := existsRawUnmined(ns, prevOut.Hash[:])
 375  			if v != nil {
 376  				// Ensure a credit exists for this unmined transaction before including the output script.
 377  				k := canonicalOutPoint(&prevOut.Hash, prevOut.Index)
 378  				if existsRawUnminedCredit(ns, k) == nil {
 379  					continue
 380  				}
 381  				pkScript, e := fetchRawTxRecordPkScript(
 382  					prevOut.Hash[:], v, prevOut.Index,
 383  				)
 384  				if e != nil {
 385  					return nil, e
 386  				}
 387  				pkScripts = append(pkScripts, pkScript)
 388  				continue
 389  			}
 390  			_, credKey := existsUnspent(ns, prevOut)
 391  			if credKey != nil {
 392  				k := extractRawCreditTxRecordKey(credKey)
 393  				v = existsRawTxRecord(ns, k)
 394  				pkScript, e := fetchRawTxRecordPkScript(
 395  					k, v,
 396  					prevOut.Index,
 397  				)
 398  				if e != nil {
 399  					return nil, e
 400  				}
 401  				pkScripts = append(pkScripts, pkScript)
 402  				continue
 403  			}
 404  		}
 405  		return pkScripts, nil
 406  	}
 407  	recKey := keyTxRecord(&rec.Hash, block)
 408  	it := makeReadDebitIterator(ns, recKey)
 409  	for it.next() {
 410  		credKey := extractRawDebitCreditKey(it.cv)
 411  		index := extractRawCreditIndex(credKey)
 412  		k := extractRawCreditTxRecordKey(credKey)
 413  		v := existsRawTxRecord(ns, k)
 414  		pkScript, e := fetchRawTxRecordPkScript(k, v, index)
 415  		if e != nil {
 416  			return nil, e
 417  		}
 418  		pkScripts = append(pkScripts, pkScript)
 419  	}
 420  	if it.err != nil {
 421  		return nil, it.err
 422  	}
 423  	return pkScripts, nil
 424  }
 425