// FetchTransactionStore fetches the input transactions referenced by the // passed transaction from the point of view of the end of the main chain. It // also attempts to fetch the transaction itself so the returned TxStore can be // examined for duplicate transactions. // IsValid indicates if the current block on head has had its TxTreeRegular // validated by the stake voters. func (b *BlockChain) FetchTransactionStore(tx *dcrutil.Tx, isValid bool) (TxStore, error) { isSSGen, _ := stake.IsSSGen(tx) // Create a set of needed transactions from the transactions referenced // by the inputs of the passed transaction. Also, add the passed // transaction itself as a way for the caller to detect duplicates. txNeededSet := make(map[chainhash.Hash]struct{}) txNeededSet[*tx.Sha()] = struct{}{} for i, txIn := range tx.MsgTx().TxIn { // Skip all stakebase inputs. if isSSGen && (i == 0) { continue } txNeededSet[txIn.PreviousOutPoint.Hash] = struct{}{} } // Request the input transactions from the point of view of the end of // the main chain without including fully spent transactions in the // results. Fully spent transactions are only needed for chain // reorganization which does not apply here. txStore := fetchTxStoreMain(b.db, txNeededSet, false) topBlock, err := b.getBlockFromHash(b.bestChain.hash) if err != nil { return nil, err } if isValid { connectTxTree(txStore, topBlock, true) } return txStore, nil }
// matchTxAndUpdate returns true if the bloom filter matches data within the // passed transaction, otherwise false is returned. If the filter does match // the passed transaction, it will also update the filter depending on the bloom // update flags set via the loaded filter if needed. // // This function MUST be called with the filter lock held. func (bf *Filter) matchTxAndUpdate(tx *dcrutil.Tx) bool { // Check if the filter matches the hash of the transaction. // This is useful for finding transactions when they appear in a block. matched := bf.matches(tx.Sha().Bytes()) // Check if the filter matches any data elements in the public key // scripts of any of the outputs. When it does, add the outpoint that // matched so transactions which spend from the matched transaction are // also included in the filter. This removes the burden of updating the // filter for this scenario from the client. It is also more efficient // on the network since it avoids the need for another filteradd message // from the client and avoids some potential races that could otherwise // occur. for i, txOut := range tx.MsgTx().TxOut { pushedData, err := txscript.PushedData(txOut.PkScript) if err != nil { continue } for _, data := range pushedData { if !bf.matches(data) { continue } matched = true bf.maybeAddOutpoint(txOut.Version, txOut.PkScript, tx.Sha(), uint32(i), tx.Tree()) break } } // Nothing more to do if a match has already been made. if matched { return true } // At this point, the transaction and none of the data elements in the // public key scripts of its outputs matched. // Check if the filter matches any outpoints this transaction spends or // any any data elements in the signature scripts of any of the inputs. for _, txin := range tx.MsgTx().TxIn { if bf.matchesOutPoint(&txin.PreviousOutPoint) { return true } pushedData, err := txscript.PushedData(txin.SignatureScript) if err != nil { continue } for _, data := range pushedData { if bf.matches(data) { return true } } } return false }
// AddTxOuts adds all outputs in the passed transaction which are not provably // unspendable to the view. When the view already has entries for any of the // outputs, they are simply marked unspent. All fields will be updated for // existing entries since it's possible it has changed during a reorg. func (view *UtxoViewpoint) AddTxOuts(tx *dcrutil.Tx, blockHeight int64, blockIndex uint32) { msgTx := tx.MsgTx() // When there are not already any utxos associated with the transaction, // add a new entry for it to the view. entry := view.LookupEntry(tx.Sha()) if entry == nil { txType := stake.DetermineTxType(msgTx) entry = newUtxoEntry(msgTx.Version, uint32(blockHeight), blockIndex, IsCoinBaseTx(msgTx), msgTx.Expiry != 0, txType) if txType == stake.TxTypeSStx { stakeExtra := make([]byte, serializeSizeForMinimalOutputs(tx)) putTxToMinimalOutputs(stakeExtra, tx) entry.stakeExtra = stakeExtra } view.entries[*tx.Sha()] = entry } else { entry.height = uint32(blockHeight) entry.index = uint32(blockIndex) } entry.modified = true // Loop all of the transaction outputs and add those which are not // provably unspendable. for txOutIdx, txOut := range tx.MsgTx().TxOut { // TODO allow pruning of stake utxs after all other outputs are spent if txscript.IsUnspendable(txOut.Value, txOut.PkScript) { continue } // Update existing entries. All fields are updated because it's // possible (although extremely unlikely) that the existing // entry is being replaced by a different transaction with the // same hash. This is allowed so long as the previous // transaction is fully spent. if output, ok := entry.sparseOutputs[uint32(txOutIdx)]; ok { output.spent = false output.amount = txOut.Value output.scriptVersion = txOut.Version output.pkScript = txOut.PkScript output.compressed = false continue } // Add the unspent transaction output. entry.sparseOutputs[uint32(txOutIdx)] = &utxoOutput{ spent: false, amount: txOut.Value, scriptVersion: txOut.Version, pkScript: txOut.PkScript, compressed: false, } } return }
// FetchUtxoView loads utxo details about the input transactions referenced by // the passed transaction from the point of view of the end of the main chain. // It also attempts to fetch the utxo details for the transaction itself so the // returned view can be examined for duplicate unspent transaction outputs. // // This function is safe for concurrent access however the returned view is NOT. func (b *BlockChain) FetchUtxoView(tx *dcrutil.Tx, treeValid bool) (*UtxoViewpoint, error) { b.chainLock.RLock() defer b.chainLock.RUnlock() // Request the utxos from the point of view of the end of the main // chain. view := NewUtxoViewpoint() if treeValid { view.SetStakeViewpoint(ViewpointPrevValidRegular) block, err := b.fetchBlockFromHash(&b.bestNode.hash) if err != nil { return nil, err } parent, err := b.fetchBlockFromHash(&b.bestNode.header.PrevBlock) if err != nil { return nil, err } err = view.fetchInputUtxos(b.db, block, parent) if err != nil { return nil, err } for i, blockTx := range block.Transactions() { err := view.connectTransaction(blockTx, b.bestNode.height, uint32(i), nil) if err != nil { return nil, err } } } view.SetBestHash(&b.bestNode.hash) // Create a set of needed transactions based on those referenced by the // inputs of the passed transaction. Also, add the passed transaction // itself as a way for the caller to detect duplicates that are not // fully spent. txNeededSet := make(map[chainhash.Hash]struct{}) txNeededSet[*tx.Sha()] = struct{}{} msgTx := tx.MsgTx() isSSGen, _ := stake.IsSSGen(msgTx) if !IsCoinBaseTx(msgTx) { for i, txIn := range msgTx.TxIn { if isSSGen && i == 0 { continue } txNeededSet[txIn.PreviousOutPoint.Hash] = struct{}{} } } err := view.fetchUtxosMain(b.db, txNeededSet) return view, err }
// insertSStx inserts an SStx into the store. func (s *StakeStore) insertSStx(ns walletdb.ReadWriteBucket, sstx *dcrutil.Tx, voteBits stake.VoteBits) error { // If we already have the SStx, no need to // try to include twice. exists := s.checkHashInStore(sstx.Sha()) if exists { log.Tracef("Attempted to insert SStx %v into the stake store, "+ "but the SStx already exists.", sstx.Sha()) return nil } record := &sstxRecord{ sstx, time.Now(), true, voteBits.Bits, voteBits.ExtendedBits, } // Add the SStx to the database. err := putSStxRecord(ns, record, voteBits) if err != nil { return err } // Add the SStx's hash to the internal list in the store. s.addHashToStore(sstx.Sha()) return nil }
// insertSStx inserts an SStx into the store. func (s *StakeStore) insertSStx(sstx *dcrutil.Tx) error { // If we already have the SStx, no need to // try to include twice. exists := s.checkHashInStore(sstx.Sha()) if exists { log.Tracef("Attempted to insert SStx %v into the stake store, "+ "but the SStx already exists.", sstx.Sha()) return nil } record := &sstxRecord{ sstx, time.Now(), } // Add the SStx to the database. err := s.namespace.Update(func(tx walletdb.Tx) error { if putErr := putSStxRecord(tx, record); putErr != nil { return putErr } return nil }) if err != nil { return err } // Add the SStx's hash to the internal list in the store. s.addHashToStore(sstx.Sha()) return nil }
// indexUnconfirmedAddresses modifies the unconfirmed (memory-only) address // index to include mappings for the addresses encoded by the passed public key // script to the transaction. // // This function is safe for concurrent access. func (idx *AddrIndex) indexUnconfirmedAddresses(scriptVersion uint16, pkScript []byte, tx *dcrutil.Tx, isSStx bool) { // The error is ignored here since the only reason it can fail is if the // script fails to parse and it was already validated before being // admitted to the mempool. class, addresses, _, _ := txscript.ExtractPkScriptAddrs(scriptVersion, pkScript, idx.chainParams) if isSStx && class == txscript.NullDataTy { addr, err := stake.AddrFromSStxPkScrCommitment(pkScript, idx.chainParams) if err != nil { // Fail if this fails to decode. It should. return } addresses = append(addresses, addr) } for _, addr := range addresses { // Ignore unsupported address types. addrKey, err := addrToKey(addr, idx.chainParams) if err != nil { continue } // Add a mapping from the address to the transaction. idx.unconfirmedLock.Lock() addrIndexEntry := idx.txnsByAddr[addrKey] if addrIndexEntry == nil { addrIndexEntry = make(map[chainhash.Hash]*dcrutil.Tx) idx.txnsByAddr[addrKey] = addrIndexEntry } addrIndexEntry[*tx.Sha()] = tx // Add a mapping from the transaction to the address. addrsByTxEntry := idx.addrsByTx[*tx.Sha()] if addrsByTxEntry == nil { addrsByTxEntry = make(map[[addrKeySize]byte]struct{}) idx.addrsByTx[*tx.Sha()] = addrsByTxEntry } addrsByTxEntry[addrKey] = struct{}{} idx.unconfirmedLock.Unlock() } }