// GetDoubleSpends takes a transaction and compares it with // all transactions in the db. It returns a slice of all txids in the db // which are double spent by the received tx. func CheckDoubleSpends( argTx *wire.MsgTx, txs []*wire.MsgTx) ([]*wire.ShaHash, error) { var dubs []*wire.ShaHash // slice of all double-spent txs argTxid := argTx.TxSha() for _, compTx := range txs { compTxid := compTx.TxSha() // check if entire tx is dup if argTxid.IsEqual(&compTxid) { return nil, fmt.Errorf("tx %s is dup", argTxid.String()) } // not dup, iterate through inputs of argTx for _, argIn := range argTx.TxIn { // iterate through inputs of compTx for _, compIn := range compTx.TxIn { if OutPointsEqual( argIn.PreviousOutPoint, compIn.PreviousOutPoint) { // found double spend dubs = append(dubs, &compTxid) break // back to argIn loop } } } } return dubs, nil }
// NewTxRecordFromMsgTx creates a new transaction record that may be inserted // into the store. func NewTxRecordFromMsgTx(msgTx *wire.MsgTx, received time.Time) (*TxRecord, error) { buf := bytes.NewBuffer(make([]byte, 0, msgTx.SerializeSize())) err := msgTx.Serialize(buf) if err != nil { str := "failed to serialize transaction" return nil, storeError(ErrInput, str, err) } rec := &TxRecord{ MsgTx: *msgTx, Received: received, SerializedTx: buf.Bytes(), Hash: msgTx.TxSha(), } return rec, nil }
func (s *SPVCon) NewOutgoingTx(tx *wire.MsgTx) error { txid := tx.TxSha() // assign height of zero for txs we create err := s.TS.AddTxid(&txid, 0) if err != nil { return err } _, err = s.TS.Ingest(tx, 0) // our own tx; don't keep track of false positives if err != nil { return err } // make an inv message instead of a tx message to be polite iv1 := wire.NewInvVect(wire.InvTypeWitnessTx, &txid) invMsg := wire.NewMsgInv() err = invMsg.AddInvVect(iv1) if err != nil { return err } s.outMsgQueue <- invMsg return nil }
// TxToString prints out some info about a transaction. for testing / debugging func TxToString(tx *wire.MsgTx) string { str := fmt.Sprintf("size %d vsize %d wsize %d locktime %d txid %s\n", tx.SerializeSize(), blockchain.GetTxVirtualSize(btcutil.NewTx(tx)), tx.SerializeSize(), tx.LockTime, tx.TxSha().String()) for i, in := range tx.TxIn { str += fmt.Sprintf("Input %d spends %s\n", i, in.PreviousOutPoint.String()) str += fmt.Sprintf("\tSigScript: %x\n", in.SignatureScript) for j, wit := range in.Witness { str += fmt.Sprintf("\twitness %d: %x\n", j, wit) } } for i, out := range tx.TxOut { if out != nil { str += fmt.Sprintf("output %d script: %x amt: %d\n", i, out.PkScript, out.Value) } else { str += fmt.Sprintf("output %d nil (WARNING)\n", i) } } return str }
// TxHandler takes in transaction messages that come in from either a request // after an inv message or after a merkle block message. func (s *SPVCon) TxHandler(m *wire.MsgTx) { s.TS.OKMutex.Lock() height, ok := s.TS.OKTxids[m.TxSha()] s.TS.OKMutex.Unlock() if !ok { log.Printf("Tx %s unknown, will not ingest\n", m.TxSha().String()) return } // check for double spends // allTxs, err := s.TS.GetAllTxs() // if err != nil { // log.Printf("Can't get txs from db: %s", err.Error()) // return // } // dubs, err := CheckDoubleSpends(m, allTxs) // if err != nil { // log.Printf("CheckDoubleSpends error: %s", err.Error()) // return // } // if len(dubs) > 0 { // for i, dub := range dubs { // fmt.Printf("dub %d known tx %s and new tx %s are exclusive!!!\n", // i, dub.String(), m.TxSha().String()) // } // } utilTx := btcutil.NewTx(m) if !s.HardMode || s.TS.localFilter.MatchTxAndUpdate(utilTx) { hits, err := s.TS.Ingest(m, height) if err != nil { log.Printf("Incoming Tx error: %s\n", err.Error()) return } if hits == 0 && !s.HardMode { log.Printf("tx %s had no hits, filter false positive.", m.TxSha().String()) s.fPositives <- 1 // add one false positive to chan return } log.Printf("tx %s ingested and matches %d utxo/adrs.", m.TxSha().String(), hits) } }
// 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 }