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 }
func readOutpoint(r io.Reader, o *wire.OutPoint) error { scratch := make([]byte, 4) txid, err := wire.ReadVarBytes(r, 0, 32, "prevout") if err != nil { return err } copy(o.Hash[:], txid) if _, err := r.Read(scratch); err != nil { return err } o.Index = byteOrder.Uint32(scratch) return nil }
// removeOrphan is the internal function which implements the public // RemoveOrphan. See the comment for RemoveOrphan for more details. // // This function MUST be called with the mempool lock held (for writes). func (mp *TxPool) removeOrphan(tx *btcutil.Tx, removeRedeemers bool) { // Nothing to do if passed tx is not an orphan. txHash := tx.Hash() otx, exists := mp.orphans[*txHash] if !exists { return } // Remove the reference from the previous orphan index. for _, txIn := range otx.tx.MsgTx().TxIn { orphans, exists := mp.orphansByPrev[txIn.PreviousOutPoint] if exists { delete(orphans, *txHash) // Remove the map entry altogether if there are no // longer any orphans which depend on it. if len(orphans) == 0 { delete(mp.orphansByPrev, txIn.PreviousOutPoint) } } } // Remove any orphans that redeem outputs from this one if requested. if removeRedeemers { prevOut := wire.OutPoint{Hash: *txHash} for txOutIdx := range tx.MsgTx().TxOut { prevOut.Index = uint32(txOutIdx) for _, orphan := range mp.orphansByPrev[prevOut] { mp.removeOrphan(orphan, true) } } } // Remove the transaction from the orphan pool. delete(mp.orphans, *txHash) }
// Ingest puts a tx into the DB atomically. This can result in a // gain, a loss, or no result. Gain or loss in satoshis is returned. func (ts *TxStore) Ingest(tx *wire.MsgTx, height int32) (uint32, error) { var hits uint32 var err error var nUtxoBytes [][]byte // tx has been OK'd by SPV; check tx sanity utilTx := btcutil.NewTx(tx) // convert for validation // checks basic stuff like there are inputs and ouputs err = blockchain.CheckTransactionSanity(utilTx) if err != nil { return hits, err } // note that you can't check signatures; this is SPV. // 0 conf SPV means pretty much nothing. Anyone can say anything. spentOPs := make([][]byte, len(tx.TxIn)) // before entering into db, serialize all inputs of the ingested tx for i, txin := range tx.TxIn { spentOPs[i], err = outPointToBytes(&txin.PreviousOutPoint) if err != nil { return hits, err } } // go through txouts, and then go through addresses to match // generate PKscripts for all addresses wPKscripts := make([][]byte, len(ts.Adrs)) aPKscripts := make([][]byte, len(ts.Adrs)) for i, _ := range ts.Adrs { // iterate through all our addresses // convert regular address to witness address. (split adrs later) wa, err := btcutil.NewAddressWitnessPubKeyHash( ts.Adrs[i].PkhAdr.ScriptAddress(), ts.Param) if err != nil { return hits, err } wPKscripts[i], err = txscript.PayToAddrScript(wa) if err != nil { return hits, err } aPKscripts[i], err = txscript.PayToAddrScript(ts.Adrs[i].PkhAdr) if err != nil { return hits, err } } cachedSha := tx.TxSha() // iterate through all outputs of this tx, see if we gain for i, out := range tx.TxOut { for j, ascr := range aPKscripts { // detect p2wpkh witBool := false if bytes.Equal(out.PkScript, wPKscripts[j]) { witBool = true } if bytes.Equal(out.PkScript, ascr) || witBool { // new utxo found var newu Utxo // create new utxo and copy into it newu.AtHeight = height newu.KeyIdx = ts.Adrs[j].KeyIdx newu.Value = out.Value newu.IsWit = witBool // copy witness version from pkscript var newop wire.OutPoint newop.Hash = cachedSha newop.Index = uint32(i) newu.Op = newop b, err := newu.ToBytes() if err != nil { return hits, err } nUtxoBytes = append(nUtxoBytes, b) hits++ break // txos can match only 1 script } } } err = ts.StateDB.Update(func(btx *bolt.Tx) error { // get all 4 buckets duf := btx.Bucket(BKTUtxos) // sta := btx.Bucket(BKTState) old := btx.Bucket(BKTStxos) txns := btx.Bucket(BKTTxns) if duf == nil || old == nil || txns == nil { return fmt.Errorf("error: db not initialized") } // iterate through duffel bag and look for matches // this makes us lose money, which is regrettable, but we need to know. for _, nOP := range spentOPs { v := duf.Get(nOP) if v != nil { hits++ // do all this just to figure out value we lost x := make([]byte, len(nOP)+len(v)) copy(x, nOP) copy(x[len(nOP):], v) lostTxo, err := UtxoFromBytes(x) if err != nil { return err } // after marking for deletion, save stxo to old bucket var st Stxo // generate spent txo st.Utxo = lostTxo // assign outpoint st.SpendHeight = height // spent at height st.SpendTxid = cachedSha // spent by txid stxb, err := st.ToBytes() // serialize if err != nil { return err } err = old.Put(nOP, stxb) // write nOP:v outpoint:stxo bytes if err != nil { return err } err = duf.Delete(nOP) if err != nil { return err } } } // done losing utxos, next gain utxos // next add all new utxos to db, this is quick as the work is above for _, ub := range nUtxoBytes { err = duf.Put(ub[:36], ub[36:]) if err != nil { return err } } // if hits is nonzero it's a relevant tx and we should store it var buf bytes.Buffer tx.Serialize(&buf) err = txns.Put(cachedSha.Bytes(), buf.Bytes()) if err != nil { return err } return nil }) return hits, err }
// processOrphans is the internal function which implements the public // ProcessOrphans. See the comment for ProcessOrphans for more details. // // This function MUST be called with the mempool lock held (for writes). func (mp *TxPool) processOrphans(acceptedTx *btcutil.Tx) []*TxDesc { var acceptedTxns []*TxDesc // Start with processing at least the passed transaction. processList := list.New() processList.PushBack(acceptedTx) for processList.Len() > 0 { // Pop the transaction to process from the front of the list. firstElement := processList.Remove(processList.Front()) processItem := firstElement.(*btcutil.Tx) prevOut := wire.OutPoint{Hash: *processItem.Hash()} for txOutIdx := range processItem.MsgTx().TxOut { // Look up all orphans that redeem the output that is // now available. This will typically only be one, but // it could be multiple if the orphan pool contains // double spends. While it may seem odd that the orphan // pool would allow this since there can only possibly // ultimately be a single redeemer, it's important to // track it this way to prevent malicious actors from // being able to purposely constructing orphans that // would otherwise make outputs unspendable. // // Skip to the next available output if there are none. prevOut.Index = uint32(txOutIdx) orphans, exists := mp.orphansByPrev[prevOut] if !exists { continue } // Potentially accept an orphan into the tx pool. for _, tx := range orphans { missing, txD, err := mp.maybeAcceptTransaction( tx, true, true, false) if err != nil { // The orphan is now invalid, so there // is no way any other orphans which // redeem any of its outputs can be // accepted. Remove them. mp.removeOrphan(tx, true) break } // Transaction is still an orphan. Try the next // orphan which redeems this output. if len(missing) > 0 { continue } // Transaction was accepted into the main pool. // // Add it to the list of accepted transactions // that are no longer orphans, remove it from // the orphan pool, and add it to the list of // transactions to process so any orphans that // depend on it are handled too. acceptedTxns = append(acceptedTxns, txD) mp.removeOrphan(tx, false) processList.PushBack(tx) // Only one transaction for this outpoint can be // accepted, so the rest are now double spends // and are removed later. break } } } // Recursively remove any orphans that also redeem any outputs redeemed // by the accepted transactions since those are now definitive double // spends. mp.removeOrphanDoubleSpends(acceptedTx) for _, txD := range acceptedTxns { mp.removeOrphanDoubleSpends(txD.Tx) } return acceptedTxns }
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 -= btcutil.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 btcutil.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 -= btcutil.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) }