func (c *blockTxCollection) txRecordForInserts(tx *btcutil.Tx) *txRecord { if i, ok := c.txIndexes[tx.Index()]; ok { return c.txs[i] } log.Infof("Inserting transaction %v from block %d", tx.Sha(), c.Height) record := &txRecord{tx: tx} // If this new transaction record cannot be appended to the end of the // txs slice (which would disobey ordering transactions by their block // index), reslice and update the block's map of block indexes to txs // slice indexes. if len(c.txs) > 0 && c.txs[len(c.txs)-1].Tx().Index() > tx.Index() { i := uint32(len(c.txs)) for i != 0 && c.txs[i-1].Tx().Index() >= tx.Index() { i-- } detached := c.txs[i:] c.txs = append(c.txs[:i], record) c.txIndexes[tx.Index()] = i for i, r := range detached { newIndex := uint32(i + len(c.txs)) c.txIndexes[r.Tx().Index()] = newIndex } c.txs = append(c.txs, detached...) } else { c.txIndexes[tx.Index()] = uint32(len(c.txs)) c.txs = append(c.txs, record) } return record }
// NotifyForTxOuts iterates through all outputs of a tx, performing any // necessary notifications for wallets. If a non-nil block is passed, // additional block information is passed with the notifications. func (s *rpcServer) NotifyForTxOuts(tx *btcutil.Tx, block *btcutil.Block) { // Nothing to do if nobody is listening for transaction notifications. if len(s.ws.txNotifications) == 0 { return } for i, txout := range tx.MsgTx().TxOut { _, addrs, _, err := btcscript.ExtractPkScriptAddrs( txout.PkScript, s.server.btcnet) if err != nil { continue } for _, addr := range addrs { // Only support pay-to-pubkey-hash right now. if _, ok := addr.(*btcutil.AddressPubKeyHash); !ok { continue } encodedAddr := addr.EncodeAddress() if idlist, ok := s.ws.txNotifications[encodedAddr]; ok { for e := idlist.Front(); e != nil; e = e.Next() { n := e.Value.(ntfnChan) ntfn := &btcws.ProcessedTxNtfn{ Receiver: encodedAddr, TxID: tx.Sha().String(), TxOutIndex: uint32(i), Amount: txout.Value, PkScript: hex.EncodeToString(txout.PkScript), // TODO(jrick): hardcoding unspent is WRONG and needs // to be either calculated from other block txs, or dropped. Spent: false, } if block != nil { blkhash, err := block.Sha() if err != nil { rpcsLog.Error("Error getting block sha; dropping Tx notification") break } ntfn.BlockHeight = int32(block.Height()) ntfn.BlockHash = blkhash.String() ntfn.BlockIndex = tx.Index() ntfn.BlockTime = block.MsgBlock().Header.Timestamp.Unix() } else { ntfn.BlockHeight = -1 ntfn.BlockIndex = -1 } n <- ntfn } } } } }
// InsertTx records a transaction as belonging to a wallet's transaction // history. If block is nil, the transaction is considered unspent, and the // transaction's index must be unset. Otherwise, the transaction index must be // set if a non-nil block is set. // // The transaction record is returned. Credits and debits may be added to the // transaction by calling methods on the TxRecord. func (s *Store) InsertTx(tx *btcutil.Tx, block *Block) (*TxRecord, error) { // The receive time will be the earlier of now and the block time // (if any). received := time.Now() // Verify that the index of the transaction within the block is // set if a block is set, and unset if there is no block. index := tx.Index() switch { case index == btcutil.TxIndexUnknown && block != nil: return nil, errors.New("transaction block index unset") case index != btcutil.TxIndexUnknown && block == nil: return nil, errors.New("transaction block index set") } // Simply create or return the transaction record if this transaction // is unconfirmed. if block == nil { r := s.unconfirmed.txRecordForInserts(tx) r.received = received return &TxRecord{BlockTxKey{BlockHeight: -1}, r, s}, nil } // Check if block records already exist for this tx. If so, // we're done. key := BlockTxKey{index, block.Height} record, err := s.lookupBlockTx(key) switch err.(type) { case MissingValueError: // handle it later case nil: // Verify that the txs actually match. if *record.tx.Sha() != *tx.Sha() { return nil, ErrInconsistentStore } return &TxRecord{key, record, s}, nil } // If the exact tx (not a double spend) is already included but // unconfirmed, move it to a block. if r, ok := s.unconfirmed.txs[*tx.Sha()]; ok { r.Tx().SetIndex(tx.Index()) if err := s.moveMinedTx(r, block); err != nil { return nil, err } return &TxRecord{key, r, s}, nil } // If this transaction is not already saved unconfirmed, remove all // unconfirmed transactions that are now invalidated due to being a // double spend. This also handles killing unconfirmed transaction // spend chains if any other unconfirmed transactions spend outputs // of the removed double spend. if err := s.removeDoubleSpends(tx); err != nil { return nil, err } r := s.blockTxRecordForInserts(tx, block) if r.received.IsZero() { if !block.Time.IsZero() && block.Time.Before(received) { received = block.Time } r.received = received } return &TxRecord{key, r, s}, nil }