// calcMinRelayFee retuns the minimum transaction fee required for the passed // transaction to be accepted into the memory pool and relayed. func calcMinRelayFee(tx *btcutil.Tx) int64 { // Most miners allow a free transaction area in blocks they mine to go // alongside the area used for high-priority transactions as well as // transactions with fees. A transaction size of up to 1000 bytes is // considered safe to go into this section. Further, the minimum fee // calculated below on its own would encourage several small // transactions to avoid fees rather than one single larger transaction // which is more desirable. Therefore, as long as the size of the // transaction does not exceeed 1000 less than the reserved space for // high-priority transactions, don't require a fee for it. serializedLen := int64(tx.MsgTx().SerializeSize()) if serializedLen < (defaultBlockPrioritySize - 1000) { return 0 } // Calculate the minimum fee for a transaction to be allowed into the // mempool and relayed by scaling the base fee (which is the minimum // free transaction relay fee). minTxRelayFee is in Satoshi/KB, so // divide the transaction size by 1000 to convert to kilobytes. Also, // integer division is used so fees only increase on full kilobyte // boundaries. minFee := (1 + serializedLen/1000) * minTxRelayFee // Set the minimum fee to the maximum possible value if the calculated // fee is not in the valid range for monetary amounts. if minFee < 0 || minFee > btcutil.MaxSatoshi { minFee = btcutil.MaxSatoshi } return minFee }
// checkSerializedHeight checks if the signature script in the passed // transaction starts with the serialized block height of wantHeight. func checkSerializedHeight(coinbaseTx *btcutil.Tx, wantHeight int64) error { sigScript := coinbaseTx.MsgTx().TxIn[0].SignatureScript if len(sigScript) < 1 { str := "the coinbase signature script for blocks of " + "version %d or greater must start with the " + "length of the serialized block height" str = fmt.Sprintf(str, serializedHeightVersion) return RuleError(str) } serializedLen := int(sigScript[0]) if len(sigScript[1:]) < serializedLen { str := "the coinbase signature script for blocks of " + "version %d or greater must start with the " + "serialized block height" str = fmt.Sprintf(str, serializedLen) return RuleError(str) } serializedHeightBytes := make([]byte, 8, 8) copy(serializedHeightBytes, sigScript[1:serializedLen+1]) serializedHeight := binary.LittleEndian.Uint64(serializedHeightBytes) if int64(serializedHeight) != wantHeight { str := fmt.Sprintf("the coinbase signature script serialized "+ "block height is %d when %d was expected", serializedHeight, wantHeight) return RuleError(str) } return nil }
func (c *blockTxCollection) txRecordForInserts(tx *btcutil.Tx) *txRecord { if i, ok := c.txIndexes[tx.Index()]; ok { return c.txs[i] } 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 if _, ok := c.unspent[r.Tx().Index()]; ok { c.unspent[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 }
// ValidateTransactionScripts validates the scripts for the passed transaction // using multiple goroutines. func ValidateTransactionScripts(tx *btcutil.Tx, txStore TxStore, flags btcscript.ScriptFlags) error { // Collect all of the transaction inputs and required information for // validation. txIns := tx.MsgTx().TxIn txValItems := make([]*txValidateItem, 0, len(txIns)) for txInIdx, txIn := range txIns { // Skip coinbases. if txIn.PreviousOutpoint.Index == math.MaxUint32 { continue } txVI := &txValidateItem{ txInIndex: txInIdx, txIn: txIn, tx: tx, } txValItems = append(txValItems, txVI) } // Validate all of the inputs. validator := newTxValidator(txStore, flags) if err := validator.Validate(txValItems); err != nil { return err } return nil }
// calcPriority returns a transaction priority given a transaction and the sum // of each of its input values multiplied by their age (# of confirmations). // Thus, the final formula for the priority is: // sum(inputValue * inputAge) / adjustedTxSize func calcPriority(tx *btcutil.Tx, serializedTxSize int, inputValueAge float64) float64 { // In order to encourage spending multiple old unspent transaction // outputs thereby reducing the total set, don't count the constant // overhead for each input as well as enough bytes of the signature // script to cover a pay-to-script-hash redemption with a compressed // pubkey. This makes additional inputs free by boosting the priority // of the transaction accordingly. No more incentive is given to avoid // encouraging gaming future transactions through the use of junk // outputs. This is the same logic used in the reference // implementation. // // The constant overhead for a txin is 41 bytes since the previous // outpoint is 36 bytes + 4 bytes for the sequence + 1 byte the // signature script length. // // A compressed pubkey pay-to-script-hash redemption with a maximum len // signature is of the form: // [OP_DATA_73 <73-byte sig> + OP_DATA_35 + {OP_DATA_33 // <33 byte compresed pubkey> + OP_CHECKSIG}] // // Thus 1 + 73 + 1 + 1 + 33 + 1 = 110 overhead := 0 for _, txIn := range tx.MsgTx().TxIn { // Max inputs + size can't possibly overflow here. overhead += 41 + minInt(110, len(txIn.SignatureScript)) } if overhead >= serializedTxSize { return 0.0 } return inputValueAge / float64(serializedTxSize-overhead) }
// IsFinalizedTransaction determines whether or not a transaction is finalized. func IsFinalizedTransaction(tx *btcutil.Tx, blockHeight int64, blockTime time.Time) bool { msgTx := tx.MsgTx() // Lock time of zero means the transaction is finalized. lockTime := msgTx.LockTime if lockTime == 0 { return true } // The lock time field of a transaction is either a block height at // which the transaction is finalized or a timestamp depending on if the // value is before the lockTimeThreshold. When it is under the // threshold it is a block height. blockTimeOrHeight := int64(0) if lockTime < lockTimeThreshold { blockTimeOrHeight = blockHeight } else { blockTimeOrHeight = blockTime.Unix() } if int64(lockTime) < blockTimeOrHeight { return true } // At this point, the transaction's lock time hasn't occured yet, but // the transaction might still be finalized if the sequence number // for all transaction inputs is maxed out. for _, txIn := range msgTx.TxIn { if txIn.Sequence != math.MaxUint32 { return false } } return true }
// newBlockNotifyCheckTxIn is a helper function to iterate through // each transaction input of a new block and perform any checks and // notify listening frontends when necessary. func (s *rpcServer) newBlockNotifyCheckTxIn(tx *btcutil.Tx) { for wltNtfn, cxt := range s.ws.requests.m { for _, txin := range tx.MsgTx().TxIn { for op, id := range cxt.spentRequests { if txin.PreviousOutpoint != op { continue } reply := &btcjson.Reply{ Result: struct { TxHash string `json:"txhash"` Index uint32 `json:"index"` }{ TxHash: op.Hash.String(), Index: uint32(op.Index), }, Error: nil, Id: &id, } replyBytes, err := json.Marshal(reply) if err != nil { log.Errorf("RPCS: Unable to marshal spent notification: %v", err) continue } wltNtfn <- replyBytes s.ws.requests.RemoveSpentRequest(wltNtfn, &op) } } } }
func (u *unconfirmedStore) findDoubleSpend(tx *btcutil.Tx) *txRecord { for _, input := range tx.MsgTx().TxIn { if r, ok := u.previousOutpoints[input.PreviousOutpoint]; ok { return r } } return nil }
// findPreviousCredits searches for all unspent credits that make up the inputs // for tx. func (s *Store) findPreviousCredits(tx *btcutil.Tx) ([]Credit, error) { type createdCredit struct { credit Credit err error } inputs := tx.MsgTx().TxIn creditChans := make([]chan createdCredit, len(inputs)) for i, txIn := range inputs { creditChans[i] = make(chan createdCredit) go func(i int, op btcwire.OutPoint) { key, ok := s.unspent[op] if !ok { // Does this input spend an unconfirmed output? r, ok := s.unconfirmed.txs[op.Hash] switch { // Not an unconfirmed tx. case !ok: fallthrough // Output isn't a credit. case len(r.credits) <= int(op.Index): fallthrough // Output isn't a credit. case r.credits[op.Index] == nil: fallthrough // Credit already spent. case s.unconfirmed.spentUnconfirmed[op] != nil: close(creditChans[i]) return } t := &TxRecord{BlockTxKey{BlockHeight: -1}, r, s} c := Credit{t, op.Index} creditChans[i] <- createdCredit{credit: c} return } r, err := s.lookupBlockTx(key) if err != nil { creditChans[i] <- createdCredit{err: err} return } t := &TxRecord{key, r, s} c := Credit{t, op.Index} creditChans[i] <- createdCredit{credit: c} }(i, txIn.PreviousOutpoint) } spent := make([]Credit, 0, len(inputs)) for _, c := range creditChans { cc, ok := <-c if !ok { continue } if cc.err != nil { return nil, cc.err } spent = append(spent, cc.credit) } return spent, nil }
// newBlockNotifyCheckTxOut is a helper function to iterate through // each transaction output of a new block and perform any checks and // notify listening frontends when necessary. func (s *rpcServer) newBlockNotifyCheckTxOut(block *btcutil.Block, tx *btcutil.Tx, spent []bool) { for wltNtfn, cxt := range s.ws.requests.m { for i, txout := range tx.MsgTx().TxOut { _, txaddrhash, err := btcscript.ScriptToAddrHash(txout.PkScript) if err != nil { log.Debug("Error getting payment address from tx; dropping any Tx notifications.") break } for addr, id := range cxt.txRequests { if !bytes.Equal(addr[:], txaddrhash) { continue } blkhash, err := block.Sha() if err != nil { log.Error("Error getting block sha; dropping Tx notification.") break } txaddr, err := btcutil.EncodeAddress(txaddrhash, s.server.btcnet) if err != nil { log.Error("Error encoding address; dropping Tx notification.") break } reply := &btcjson.Reply{ Result: struct { Sender string `json:"sender"` Receiver string `json:"receiver"` BlockHash string `json:"blockhash"` Height int64 `json:"height"` TxHash string `json:"txhash"` Index uint32 `json:"index"` Amount int64 `json:"amount"` PkScript string `json:"pkscript"` Spent bool `json:"spent"` }{ Sender: "Unknown", // TODO(jrick) Receiver: txaddr, BlockHash: blkhash.String(), Height: block.Height(), TxHash: tx.Sha().String(), Index: uint32(i), Amount: txout.Value, PkScript: btcutil.Base58Encode(txout.PkScript), Spent: spent[i], }, Error: nil, Id: &id, } replyBytes, err := json.Marshal(reply) if err != nil { log.Errorf("RPCS: Unable to marshal tx notification: %v", err) continue } wltNtfn <- replyBytes } } } }
func (r *txRecord) setDebitsSpends(spends []*BlockOutputKey, tx *btcutil.Tx) error { if r.debits.spends != nil { if *r.tx.Sha() == *tx.Sha() { return ErrDuplicateInsert } return ErrInconsistentStore } r.debits.spends = spends return nil }
func (u *unconfirmedStore) txRecordForInserts(tx *btcutil.Tx) *txRecord { r, ok := u.txs[*tx.Sha()] if !ok { r = &txRecord{tx: tx} u.txs[*tx.Sha()] = r for _, input := range r.Tx().MsgTx().TxIn { u.previousOutpoints[input.PreviousOutpoint] = r } } return r }
// logSkippedDeps logs any dependencies which are also skipped as a result of // skipping a transaction while generating a block template at the trace level. func logSkippedDeps(tx *btcutil.Tx, deps *list.List) { if deps == nil { return } for e := deps.Front(); e != nil; e = e.Next() { item := e.Value.(*txPrioItem) minrLog.Tracef("Skipping tx %s since it depends on %s\n", item.tx.Sha(), tx.Sha()) } }
// checkPoolDoubleSpend checks whether or not the passed transaction is // attempting to spend coins already spent by other transactions in the pool. // Note it does not check for double spends against transactions already in the // main chain. // // This function MUST be called with the mempool lock held (for reads). func (mp *txMemPool) checkPoolDoubleSpend(tx *btcutil.Tx) error { for _, txIn := range tx.MsgTx().TxIn { if txR, exists := mp.outpoints[txIn.PreviousOutpoint]; exists { str := fmt.Sprintf("transaction %v in the pool "+ "already spends the same coins", txR.Sha()) return TxRuleError(str) } } return nil }
// Bitcoin specific type checking func isClassA(tx *btcutil.Tx) bool { mtx := tx.MsgTx() for _, txOut := range mtx.TxOut { _, scriptType := mscutil.GetAddrs(txOut.PkScript) if scriptType == btcscript.MultiSigTy { return false } } // If it wasn't multi sig it's class a return true }
func notifySpentData(n ntfnChan, txhash *btcwire.ShaHash, index uint32, spender *btcutil.Tx) { var buf bytes.Buffer // Ignore Serialize's error, as writing to a bytes.buffer // cannot fail. spender.MsgTx().Serialize(&buf) txStr := hex.EncodeToString(buf.Bytes()) ntfn := btcws.NewTxSpentNtfn(txhash.String(), int(index), txStr) n <- ntfn }
// isNonstandardTransaction determines whether a transaction contains any // scripts which are not one of the standard types. func isNonstandardTransaction(tx *btcutil.Tx) bool { // TODO(davec): Should there be checks for the input signature scripts? // Check all of the output public key scripts for non-standard scripts. for _, txOut := range tx.MsgTx().TxOut { scriptClass := btcscript.GetScriptClass(txOut.PkScript) if scriptClass == btcscript.NonStandardTy { return true } } return false }
// RemoveDoubleSpends removes all transactions which spend outputs spent by the // passed transaction from the memory pool. Removing those transactions then // leads to removing all transactions which rely on them, recursively. This is // necessary when a block is connected to the main chain because the block may // contain transactions which were previously unknown to the memory pool // // This function is safe for concurrent access. func (mp *txMemPool) RemoveDoubleSpends(tx *btcutil.Tx) { // Protect concurrent access. mp.Lock() defer mp.Unlock() for _, txIn := range tx.MsgTx().TxIn { if txRedeemer, ok := mp.outpoints[txIn.PreviousOutpoint]; ok { if !txRedeemer.Sha().IsEqual(tx.Sha()) { mp.removeTransaction(txRedeemer) } } } }
// addTransaction adds the passed transaction to the memory pool. It should // not be called directly as it doesn't perform any validation. This is a // helper for maybeAcceptTransaction. // // This function MUST be called with the mempool lock held (for writes). func (mp *txMemPool) addTransaction(tx *btcutil.Tx, height, fee int64) { // Add the transaction to the pool and mark the referenced outpoints // as spent by the pool. mp.pool[*tx.Sha()] = &TxDesc{ Tx: tx, Added: time.Now(), Height: height, Fee: fee, } for _, txIn := range tx.MsgTx().TxIn { mp.outpoints[txIn.PreviousOutpoint] = tx } }
func (r *txRecord) setCredit(c *credit, index uint32, tx *btcutil.Tx) error { if len(r.credits) <= int(index) { r.credits = extendCredits(r.credits, index) } if r.credits[index] != nil { if *r.tx.Sha() == *tx.Sha() { return ErrDuplicateInsert } return ErrInconsistentStore } r.credits[index] = c return nil }
// newBlockNotifyCheckTxIn is a helper function to iterate through // each transaction input of a new block and perform any checks and // notify listening frontends when necessary. func (s *rpcServer) newBlockNotifyCheckTxIn(tx *btcutil.Tx) { for _, txin := range tx.MsgTx().TxIn { if clist, ok := s.ws.spentNotifications[txin.PreviousOutpoint]; ok { var enext *list.Element for e := clist.Front(); e != nil; e = enext { enext = e.Next() c := e.Value.(walletChan) notifySpentData(c, &txin.PreviousOutpoint.Hash, txin.PreviousOutpoint.Index, tx) s.ws.RemoveSpentRequest(c, &txin.PreviousOutpoint) } } } }
func notifySpentData(wallet walletChan, txhash *btcwire.ShaHash, index uint32, spender *btcutil.Tx) { var buf bytes.Buffer // Ignore Serialize's error, as writing to a bytes.buffer // cannot fail. spender.MsgTx().Serialize(&buf) txStr := hex.EncodeToString(buf.Bytes()) // TODO(jrick): create a new notification in btcws and use that. ntfn := btcws.NewTxSpentNtfn(txhash.String(), int(index), txStr) mntfn, _ := ntfn.MarshalJSON() wallet <- mntfn }
// findPreviousCredits searches for all unspent credits that make up the inputs // for tx. This lookup is very expensive and should be avoided at all costs. func (s *Store) findPreviousCredits(tx *btcutil.Tx) ([]*Credit, error) { unfound := make(map[btcwire.OutPoint]struct{}, len(tx.MsgTx().TxIn)) for _, txIn := range tx.MsgTx().TxIn { unfound[txIn.PreviousOutpoint] = struct{}{} } spent := make([]*Credit, 0, len(unfound)) done: for blockHeight := range s.unspent { b, err := s.lookupBlock(blockHeight) if err != nil { return nil, err } for blockIndex, txIdx := range b.unspent { if uint32(len(b.txs)) <= txIdx { return nil, MissingBlockTxError{ BlockIndex: blockIndex, BlockHeight: blockHeight, } } r := b.txs[txIdx] op := btcwire.OutPoint{Hash: *r.Tx().Sha()} for i, cred := range r.credits { if cred == nil || cred.spentBy != nil { continue } op.Index = uint32(i) if _, ok := unfound[op]; ok { key := BlockTxKey{blockIndex, b.Height} t := &TxRecord{key, r, s} c := &Credit{t, op.Index} spent = append(spent, c) delete(unfound, op) if len(unfound) == 0 { break done } } } } } return spent, 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 *btcutil.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 := btcscript.PushedData(txOut.PkScript) if err != nil { continue } for _, data := range pushedData { if !bf.matches(data) { continue } matched = true bf.maybeAddOutpoint(txOut.PkScript, tx.Sha(), uint32(i)) 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 := btcscript.PushedData(txin.SignatureScript) if err != nil { continue } for _, data := range pushedData { if bf.matches(data) { return true } } } return false }
func (r *txRecord) setCredit(index uint32, change bool, tx *btcutil.Tx) error { if r.credits == nil { r.credits = make([]*credit, 0, len(tx.MsgTx().TxOut)) } for i := uint32(len(r.credits)); i <= index; i++ { r.credits = append(r.credits, nil) } if r.credits[index] != nil { if *r.tx.Sha() == *tx.Sha() { return ErrDuplicateInsert } return ErrInconsistentStore } r.credits[index] = &credit{change: change} return nil }
// Helper function func GetAddrsClassA(tx *btcutil.Tx) []Output { var addrs []Output mtx := tx.MsgTx() for _, txOut := range mtx.TxOut { a, _ := GetAddrs(txOut.PkScript) // There are some bogus transactions out there that don't generate a valid address, skip these outputs if len(a) > 0 { out := Output{Value: txOut.Value, Addr: a[0].Addr} addrs = append(addrs, out) // a is one address guaranteed } } return addrs }
// IsCoinBase determines whether or not a transaction is a coinbase. A coinbase // is a special transaction created by miners that has no inputs. This is // represented in the block chain by a transaction with a single input that has // a previous output transaction index set to the maximum value along with a // zero hash. func IsCoinBase(tx *btcutil.Tx) bool { msgTx := tx.MsgTx() // A coin base must only have one transaction input. if len(msgTx.TxIn) != 1 { return false } // The previous output of a coin base must have a max value index and // a zero hash. prevOut := msgTx.TxIn[0].PreviousOutpoint if prevOut.Index != math.MaxUint32 || !prevOut.Hash.IsEqual(zeroHash) { return false } return true }
// 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. func (b *BlockChain) FetchTransactionStore(tx *btcutil.Tx) (TxStore, error) { // 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[btcwire.ShaHash]struct{}) txNeededSet[*tx.Sha()] = struct{}{} for _, txIn := range tx.MsgTx().TxIn { 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 trasactions in the // results. Fully spent transactions are only needed for chain // reorganization which does not apply here. txStore := fetchTxStoreMain(b.db, txNeededSet, false) return txStore, nil }
// checkInputsStandard performs a series of checks on a transaction's inputs // to ensure they are "standard". A standard transaction input is one that // that consumes the expected number of elements from the stack and that number // is the same as the output script pushes. This help prevent resource // exhaustion attacks by "creative" use of scripts that are super expensive to // process like OP_DUP OP_CHECKSIG OP_DROP repeated a large number of times // followed by a final OP_TRUE. func checkInputsStandard(tx *btcutil.Tx, txStore btcchain.TxStore) error { // NOTE: The reference implementation also does a coinbase check here, // but coinbases have already been rejected prior to calling this // function so no need to recheck. for i, txIn := range tx.MsgTx().TxIn { // It is safe to elide existence and index checks here since // they have already been checked prior to calling this // function. prevOut := txIn.PreviousOutpoint originTx := txStore[prevOut.Hash].Tx.MsgTx() originPkScript := originTx.TxOut[prevOut.Index].PkScript // Calculate stats for the script pair. scriptInfo, err := btcscript.CalcScriptInfo(txIn.SignatureScript, originPkScript, true) if err != nil { str := fmt.Sprintf("transaction input #%d script parse "+ "failure: %v", i, err) return txRuleError(btcwire.RejectNonstandard, str) } // A negative value for expected inputs indicates the script is // non-standard in some way. if scriptInfo.ExpectedInputs < 0 { str := fmt.Sprintf("transaction input #%d expects %d "+ "inputs", i, scriptInfo.ExpectedInputs) return txRuleError(btcwire.RejectNonstandard, str) } // The script pair is non-standard if the number of available // inputs does not match the number of expected inputs. if scriptInfo.NumInputs != scriptInfo.ExpectedInputs { str := fmt.Sprintf("transaction input #%d expects %d "+ "inputs, but referenced output script provides "+ "%d", i, scriptInfo.ExpectedInputs, scriptInfo.NumInputs) return txRuleError(btcwire.RejectNonstandard, str) } } return nil }
// removeTransaction is the internal function which implements the public // RemoveTransaction. See the comment for RemoveTransaction for more details. // // This function MUST be called with the mempool lock held (for writes). func (mp *txMemPool) removeTransaction(tx *btcutil.Tx) { // Remove any transactions which rely on this one. txHash := tx.Sha() for i := uint32(0); i < uint32(len(tx.MsgTx().TxOut)); i++ { outpoint := btcwire.NewOutPoint(txHash, i) if txRedeemer, exists := mp.outpoints[*outpoint]; exists { mp.removeTransaction(txRedeemer) } } // Remove the transaction and mark the referenced outpoints as unspent // by the pool. if txDesc, exists := mp.pool[*txHash]; exists { for _, txIn := range txDesc.Tx.MsgTx().TxIn { delete(mp.outpoints, txIn.PreviousOutpoint) } delete(mp.pool, *txHash) } }