// BenchmarkIsCoinBaseTx performs a simple benchmark against the IsCoinBaseTx // function. func BenchmarkIsCoinBaseTx(b *testing.B) { tx := Block100000.Transactions[1] b.ResetTimer() for i := 0; i < b.N; i++ { blockchain.IsCoinBaseTx(tx) } }
// mockCredits decodes the given txHex and returns the outputs with // the given indices as eligible inputs. func mockCredits(t *testing.T, txHex string, indices []uint32) []wtxmgr.Credit { serialized, err := hex.DecodeString(txHex) if err != nil { t.Fatal(err) } utx, err := coinutil.NewTxFromBytes(serialized) if err != nil { t.Fatal(err) } tx := utx.MsgTx() isCB := blockchain.IsCoinBaseTx(tx) now := time.Now() eligible := make([]wtxmgr.Credit, len(indices)) c := wtxmgr.Credit{ OutPoint: wire.OutPoint{Hash: *utx.Sha()}, BlockMeta: wtxmgr.BlockMeta{ Block: wtxmgr.Block{Height: -1}, }, } for i, idx := range indices { c.OutPoint.Index = idx c.Amount = coinutil.Amount(tx.TxOut[idx].Value) c.PkScript = tx.TxOut[idx].PkScript c.Received = now c.FromCoinBase = isCB eligible[i] = c } return eligible }
// RecvCategory returns the category of received credit outputs from a // transaction record. The passed block chain height is used to distinguish // immature from mature coinbase outputs. // // TODO: This is intended for use by the RPC server and should be moved out of // this package at a later time. func RecvCategory(details *wtxmgr.TxDetails, syncHeight int32) CreditCategory { if blockchain.IsCoinBaseTx(&details.MsgTx) { if confirmed(blockchain.CoinbaseMaturity, details.Block.Height, syncHeight) { return CreditGenerate } return CreditImmature } return CreditReceive }
// ListTransactions creates a object that may be marshalled to a response result // for a listtransactions RPC. // // TODO: This should be moved out of this package into the main package's // rpcserver.go, along with everything that requires this. func ListTransactions(details *wtxmgr.TxDetails, syncHeight int32, net *chaincfg.Params) []btcjson.ListTransactionsResult { var ( blockHashStr string blockTime int64 confirmations int64 ) if details.Block.Height != -1 { blockHashStr = details.Block.Hash.String() blockTime = details.Block.Time.Unix() confirmations = int64(confirms(details.Block.Height, syncHeight)) } results := []btcjson.ListTransactionsResult{} txHashStr := details.Hash.String() received := details.Received.Unix() generated := blockchain.IsCoinBaseTx(&details.MsgTx) recvCat := RecvCategory(details, syncHeight).String() send := len(details.Debits) != 0 // Fee can only be determined if every input is a debit. var feeF64 float64 if len(details.Debits) == len(details.MsgTx.TxIn) { var debitTotal coinutil.Amount for _, deb := range details.Debits { debitTotal += deb.Amount } var outputTotal coinutil.Amount for _, output := range details.MsgTx.TxOut { outputTotal += coinutil.Amount(output.Value) } // Note: The actual fee is debitTotal - outputTotal. However, // this RPC reports negative numbers for fees, so the inverse // is calculated. feeF64 = (outputTotal - debitTotal).ToBTC() } outputs: for i, output := range details.MsgTx.TxOut { // Determine if this output is a credit, and if so, determine // its spentness. var isCredit bool var spentCredit bool for _, cred := range details.Credits { if cred.Index == uint32(i) { // Change outputs are ignored. if cred.Change { continue outputs } isCredit = true spentCredit = cred.Spent break } } var address string _, addrs, _, _ := txscript.ExtractPkScriptAddrs(output.PkScript, net) if len(addrs) == 1 { address = addrs[0].EncodeAddress() } amountF64 := coinutil.Amount(output.Value).ToBTC() result := btcjson.ListTransactionsResult{ // Fields left zeroed: // InvolvesWatchOnly // Account // BlockIndex // // Fields set below: // Category // Amount // Fee Address: address, Vout: uint32(i), Confirmations: confirmations, Generated: generated, BlockHash: blockHashStr, BlockTime: blockTime, TxID: txHashStr, WalletConflicts: []string{}, Time: received, TimeReceived: received, } // Add a received/generated/immature result if this is a credit. // If the output was spent, create a second result under the // send category with the inverse of the output amount. It is // therefore possible that a single output may be included in // the results set zero, one, or two times. // // Since credits are not saved for outputs that are not // controlled by this wallet, all non-credits from transactions // with debits are grouped under the send category. if send || spentCredit { result.Category = "send" result.Amount = -amountF64 result.Fee = &feeF64 results = append(results, result) } if isCredit { result.Category = recvCat result.Amount = amountF64 result.Fee = nil results = append(results, result) } } return results }
func (s *Store) balance(ns walletdb.Bucket, minConf int32, syncHeight int32) (coinutil.Amount, error) { bal, err := fetchMinedBalance(ns) if err != nil { return 0, err } // Subtract the balance for each credit that is spent by an unmined // transaction. var op wire.OutPoint var block Block err = ns.Bucket(bucketUnspent).ForEach(func(k, v []byte) error { err := readCanonicalOutPoint(k, &op) if err != nil { return err } err = readUnspentBlock(v, &block) if err != nil { return err } if existsRawUnminedInput(ns, k) != nil { _, v := existsCredit(ns, &op.Hash, op.Index, &block) amt, err := fetchRawCreditAmount(v) if err != nil { return err } bal -= amt } return nil }) if err != nil { if _, ok := err.(Error); ok { return 0, err } str := "failed iterating unspent outputs" return 0, storeError(ErrDatabase, str, err) } // Decrement the balance for any unspent credit with less than // minConf confirmations and any (unspent) immature coinbase credit. stopConf := minConf if blockchain.CoinbaseMaturity > stopConf { stopConf = blockchain.CoinbaseMaturity } lastHeight := syncHeight - stopConf blockIt := makeReverseBlockIterator(ns) for blockIt.prev() { block := &blockIt.elem if block.Height < lastHeight { break } for i := range block.transactions { txHash := &block.transactions[i] rec, err := fetchTxRecord(ns, txHash, &block.Block) if err != nil { return 0, err } numOuts := uint32(len(rec.MsgTx.TxOut)) for i := uint32(0); i < numOuts; i++ { // Avoid double decrementing the credit amount // if it was already removed for being spent by // an unmined tx. opKey := canonicalOutPoint(txHash, i) if existsRawUnminedInput(ns, opKey) != nil { continue } _, v := existsCredit(ns, txHash, i, &block.Block) if v == nil { continue } amt, spent, err := fetchRawCreditAmountSpent(v) if err != nil { return 0, err } if spent { continue } confs := syncHeight - block.Height + 1 if confs < minConf || (blockchain.IsCoinBaseTx(&rec.MsgTx) && confs < blockchain.CoinbaseMaturity) { bal -= amt } } } } if blockIt.err != nil { return 0, blockIt.err } // If unmined outputs are included, increment the balance for each // output that is unspent. if minConf == 0 { err = ns.Bucket(bucketUnminedCredits).ForEach(func(k, v []byte) error { if existsRawUnminedInput(ns, k) != nil { // Output is spent by an unmined transaction. // Skip to next unmined credit. return nil } amount, err := fetchRawUnminedCreditAmount(v) if err != nil { return err } bal += amount return nil }) if err != nil { if _, ok := err.(Error); ok { return 0, err } str := "failed to iterate over unmined credits bucket" return 0, storeError(ErrDatabase, str, err) } } return bal, nil }
func (s *Store) unspentOutputs(ns walletdb.Bucket) ([]Credit, error) { var unspent []Credit var op wire.OutPoint var block Block err := ns.Bucket(bucketUnspent).ForEach(func(k, v []byte) error { err := readCanonicalOutPoint(k, &op) if err != nil { return err } if existsRawUnminedInput(ns, k) != nil { // Output is spent by an unmined transaction. // Skip this k/v pair. return nil } err = readUnspentBlock(v, &block) if err != nil { return err } blockTime, err := fetchBlockTime(ns, block.Height) if err != nil { return err } // TODO(jrick): reading the entire transaction should // be avoidable. Creating the credit only requires the // output amount and pkScript. rec, err := fetchTxRecord(ns, &op.Hash, &block) if err != nil { return err } txOut := rec.MsgTx.TxOut[op.Index] cred := Credit{ OutPoint: op, BlockMeta: BlockMeta{ Block: block, Time: blockTime, }, Amount: coinutil.Amount(txOut.Value), PkScript: txOut.PkScript, Received: rec.Received, FromCoinBase: blockchain.IsCoinBaseTx(&rec.MsgTx), } unspent = append(unspent, cred) return nil }) if err != nil { if _, ok := err.(Error); ok { return nil, err } str := "failed iterating unspent bucket" return nil, storeError(ErrDatabase, str, err) } err = ns.Bucket(bucketUnminedCredits).ForEach(func(k, v []byte) error { if existsRawUnminedInput(ns, k) != nil { // Output is spent by an unmined transaction. // Skip to next unmined credit. return nil } err := readCanonicalOutPoint(k, &op) if err != nil { return err } // TODO(jrick): Reading/parsing the entire transaction record // just for the output amount and script can be avoided. recVal := existsRawUnmined(ns, op.Hash[:]) var rec TxRecord err = readRawTxRecord(&op.Hash, recVal, &rec) if err != nil { return err } txOut := rec.MsgTx.TxOut[op.Index] cred := Credit{ OutPoint: op, BlockMeta: BlockMeta{ Block: Block{Height: -1}, }, Amount: coinutil.Amount(txOut.Value), PkScript: txOut.PkScript, Received: rec.Received, FromCoinBase: blockchain.IsCoinBaseTx(&rec.MsgTx), } unspent = append(unspent, cred) return nil }) if err != nil { if _, ok := err.(Error); ok { return nil, err } str := "failed iterating unmined credits bucket" return nil, storeError(ErrDatabase, str, err) } return unspent, nil }
func (s *Store) rollback(ns walletdb.Bucket, height int32) error { minedBalance, err := fetchMinedBalance(ns) if err != nil { return err } // Keep track of all credits that were removed from coinbase // transactions. After detaching all blocks, if any transaction record // exists in unmined that spends these outputs, remove them and their // spend chains. // // It is necessary to keep these in memory and fix the unmined // transactions later since blocks are removed in increasing order. var coinBaseCredits []wire.OutPoint it := makeBlockIterator(ns, height) for it.next() { b := &it.elem log.Infof("Rolling back %d transactions from block %v height %d", len(b.transactions), b.Hash, b.Height) for i := range b.transactions { txHash := &b.transactions[i] recKey := keyTxRecord(txHash, &b.Block) recVal := existsRawTxRecord(ns, recKey) var rec TxRecord err = readRawTxRecord(txHash, recVal, &rec) if err != nil { return err } err = deleteTxRecord(ns, txHash, &b.Block) if err != nil { return err } // Handle coinbase transactions specially since they are // not moved to the unconfirmed store. A coinbase cannot // contain any debits, but all credits should be removed // and the mined balance decremented. if blockchain.IsCoinBaseTx(&rec.MsgTx) { op := wire.OutPoint{Hash: rec.Hash} for i, output := range rec.MsgTx.TxOut { k, v := existsCredit(ns, &rec.Hash, uint32(i), &b.Block) if v == nil { continue } op.Index = uint32(i) coinBaseCredits = append(coinBaseCredits, op) unspentKey, credKey := existsUnspent(ns, &op) if credKey != nil { minedBalance -= coinutil.Amount(output.Value) err = deleteRawUnspent(ns, unspentKey) if err != nil { return err } } err = deleteRawCredit(ns, k) if err != nil { return err } } continue } err = putRawUnmined(ns, txHash[:], recVal) if err != nil { return err } // For each debit recorded for this transaction, mark // the credit it spends as unspent (as long as it still // exists) and delete the debit. The previous output is // recorded in the unconfirmed store for every previous // output, not just debits. for i, input := range rec.MsgTx.TxIn { prevOut := &input.PreviousOutPoint prevOutKey := canonicalOutPoint(&prevOut.Hash, prevOut.Index) err = putRawUnminedInput(ns, prevOutKey, rec.Hash[:]) if err != nil { return err } // If this input is a debit, remove the debit // record and mark the credit that it spent as // unspent, incrementing the mined balance. debKey, credKey, err := existsDebit(ns, &rec.Hash, uint32(i), &b.Block) if err != nil { return err } if debKey == nil { continue } // unspendRawCredit does not error in case the // no credit exists for this key, but this // behavior is correct. Since blocks are // removed in increasing order, this credit // may have already been removed from a // previously removed transaction record in // this rollback. var amt coinutil.Amount amt, err = unspendRawCredit(ns, credKey) if err != nil { return err } err = deleteRawDebit(ns, debKey) if err != nil { return err } // If the credit was previously removed in the // rollback, the credit amount is zero. Only // mark the previously spent credit as unspent // if it still exists. if amt == 0 { continue } unspentVal, err := fetchRawCreditUnspentValue(credKey) if err != nil { return err } minedBalance += amt err = putRawUnspent(ns, prevOutKey, unspentVal) if err != nil { return err } } // For each detached non-coinbase credit, move the // credit output to unmined. If the credit is marked // unspent, it is removed from the utxo set and the // mined balance is decremented. // // TODO: use a credit iterator for i, output := range rec.MsgTx.TxOut { k, v := existsCredit(ns, &rec.Hash, uint32(i), &b.Block) if v == nil { continue } amt, change, err := fetchRawCreditAmountChange(v) if err != nil { return err } outPointKey := canonicalOutPoint(&rec.Hash, uint32(i)) unminedCredVal := valueUnminedCredit(amt, change) err = putRawUnminedCredit(ns, outPointKey, unminedCredVal) if err != nil { return err } err = deleteRawCredit(ns, k) if err != nil { return err } credKey := existsRawUnspent(ns, outPointKey) if credKey != nil { minedBalance -= coinutil.Amount(output.Value) err = deleteRawUnspent(ns, outPointKey) if err != nil { return err } } } } err = it.delete() if err != nil { return err } } if it.err != nil { return it.err } for _, op := range coinBaseCredits { opKey := canonicalOutPoint(&op.Hash, op.Index) unminedKey := existsRawUnminedInput(ns, opKey) if unminedKey != nil { unminedVal := existsRawUnmined(ns, unminedKey) var unminedRec TxRecord copy(unminedRec.Hash[:], unminedKey) // Silly but need an array err = readRawTxRecord(&unminedRec.Hash, unminedVal, &unminedRec) if err != nil { return err } log.Debugf("Transaction %v spends a removed coinbase "+ "output -- removing as well", unminedRec.Hash) err = s.removeConflict(ns, &unminedRec) if err != nil { return err } } } return putMinedBalance(ns, minedBalance) }