github.com/trezor/blockbook@v0.4.1-0.20240328132726-e9a08582ee2c/bchain/mempool_bitcoin_type.go (about)

     1  package bchain
     2  
     3  import (
     4  	"encoding/hex"
     5  	"math/big"
     6  	"time"
     7  
     8  	"github.com/golang/glog"
     9  	"github.com/juju/errors"
    10  )
    11  
    12  type chanInputPayload struct {
    13  	tx    *MempoolTx
    14  	index int
    15  }
    16  
    17  // MempoolBitcoinType is mempool handle.
    18  type MempoolBitcoinType struct {
    19  	BaseMempool
    20  	chanTxid            chan string
    21  	chanAddrIndex       chan txidio
    22  	AddrDescForOutpoint AddrDescForOutpointFunc
    23  	golombFilterP       uint8
    24  	filterScripts       string
    25  	useZeroedKey        bool
    26  }
    27  
    28  // NewMempoolBitcoinType creates new mempool handler.
    29  // For now there is no cleanup of sync routines, the expectation is that the mempool is created only once per process
    30  func NewMempoolBitcoinType(chain BlockChain, workers int, subworkers int, golombFilterP uint8, filterScripts string, useZeroedKey bool) *MempoolBitcoinType {
    31  	m := &MempoolBitcoinType{
    32  		BaseMempool: BaseMempool{
    33  			chain:        chain,
    34  			txEntries:    make(map[string]txEntry),
    35  			addrDescToTx: make(map[string][]Outpoint),
    36  		},
    37  		chanTxid:      make(chan string, 1),
    38  		chanAddrIndex: make(chan txidio, 1),
    39  		golombFilterP: golombFilterP,
    40  		filterScripts: filterScripts,
    41  		useZeroedKey:  useZeroedKey,
    42  	}
    43  	for i := 0; i < workers; i++ {
    44  		go func(i int) {
    45  			chanInput := make(chan chanInputPayload, 1)
    46  			chanResult := make(chan *addrIndex, 1)
    47  			for j := 0; j < subworkers; j++ {
    48  				go func(j int) {
    49  					for payload := range chanInput {
    50  						ai := m.getInputAddress(&payload)
    51  						chanResult <- ai
    52  					}
    53  				}(j)
    54  			}
    55  			for txid := range m.chanTxid {
    56  				io, golombFilter, ok := m.getTxAddrs(txid, chanInput, chanResult)
    57  				if !ok {
    58  					io = []addrIndex{}
    59  				}
    60  				m.chanAddrIndex <- txidio{txid, io, golombFilter}
    61  			}
    62  		}(i)
    63  	}
    64  	glog.Info("mempool: starting with ", workers, "*", subworkers, " sync workers")
    65  	return m
    66  }
    67  
    68  func (m *MempoolBitcoinType) getInputAddress(payload *chanInputPayload) *addrIndex {
    69  	var addrDesc AddressDescriptor
    70  	var value *big.Int
    71  	vin := &payload.tx.Vin[payload.index]
    72  	if vin.Txid == "" {
    73  		// cannot get address from empty input txid (for example in Litecoin mweb)
    74  		return nil
    75  	}
    76  	if m.AddrDescForOutpoint != nil {
    77  		addrDesc, value = m.AddrDescForOutpoint(Outpoint{vin.Txid, int32(vin.Vout)})
    78  	}
    79  	if addrDesc == nil {
    80  		itx, err := m.chain.GetTransactionForMempool(vin.Txid)
    81  		if err != nil {
    82  			glog.Error("cannot get transaction ", vin.Txid, ": ", err)
    83  			return nil
    84  		}
    85  		if int(vin.Vout) >= len(itx.Vout) {
    86  			glog.Error("Vout len in transaction ", vin.Txid, " ", len(itx.Vout), " input.Vout=", vin.Vout)
    87  			return nil
    88  		}
    89  		addrDesc, err = m.chain.GetChainParser().GetAddrDescFromVout(&itx.Vout[vin.Vout])
    90  		if err != nil {
    91  			glog.Error("error in addrDesc in ", vin.Txid, " ", vin.Vout, ": ", err)
    92  			return nil
    93  		}
    94  		value = &itx.Vout[vin.Vout].ValueSat
    95  	}
    96  	vin.AddrDesc = addrDesc
    97  	vin.ValueSat = *value
    98  	return &addrIndex{string(addrDesc), ^int32(vin.Vout)}
    99  
   100  }
   101  
   102  func (m *MempoolBitcoinType) computeGolombFilter(mtx *MempoolTx, tx *Tx) string {
   103  	gf, _ := NewGolombFilter(m.golombFilterP, m.filterScripts, mtx.Txid, m.useZeroedKey)
   104  	if gf == nil || !gf.Enabled {
   105  		return ""
   106  	}
   107  	for _, vin := range mtx.Vin {
   108  		gf.AddAddrDesc(vin.AddrDesc, tx)
   109  	}
   110  	for _, vout := range mtx.Vout {
   111  		b, err := hex.DecodeString(vout.ScriptPubKey.Hex)
   112  		if err == nil {
   113  			gf.AddAddrDesc(b, tx)
   114  		}
   115  	}
   116  	fb := gf.Compute()
   117  	return hex.EncodeToString(fb)
   118  }
   119  
   120  func (m *MempoolBitcoinType) getTxAddrs(txid string, chanInput chan chanInputPayload, chanResult chan *addrIndex) ([]addrIndex, string, bool) {
   121  	tx, err := m.chain.GetTransactionForMempool(txid)
   122  	if err != nil {
   123  		glog.Error("cannot get transaction ", txid, ": ", err)
   124  		return nil, "", false
   125  	}
   126  	glog.V(2).Info("mempool: gettxaddrs ", txid, ", ", len(tx.Vin), " inputs")
   127  	mtx := m.txToMempoolTx(tx)
   128  	io := make([]addrIndex, 0, len(tx.Vout)+len(tx.Vin))
   129  	for _, output := range tx.Vout {
   130  		addrDesc, err := m.chain.GetChainParser().GetAddrDescFromVout(&output)
   131  		if err != nil {
   132  			glog.Error("error in addrDesc in ", txid, " ", output.N, ": ", err)
   133  			continue
   134  		}
   135  		if len(addrDesc) > 0 {
   136  			io = append(io, addrIndex{string(addrDesc), int32(output.N)})
   137  		}
   138  		if m.OnNewTxAddr != nil {
   139  			m.OnNewTxAddr(tx, addrDesc)
   140  		}
   141  	}
   142  	dispatched := 0
   143  	for i := range tx.Vin {
   144  		input := &tx.Vin[i]
   145  		if input.Coinbase != "" {
   146  			continue
   147  		}
   148  		payload := chanInputPayload{mtx, i}
   149  	loop:
   150  		for {
   151  			select {
   152  			// store as many processed results as possible
   153  			case ai := <-chanResult:
   154  				if ai != nil {
   155  					io = append(io, *ai)
   156  				}
   157  				dispatched--
   158  			// send input to be processed
   159  			case chanInput <- payload:
   160  				dispatched++
   161  				break loop
   162  			}
   163  		}
   164  	}
   165  	for i := 0; i < dispatched; i++ {
   166  		ai := <-chanResult
   167  		if ai != nil {
   168  			io = append(io, *ai)
   169  		}
   170  	}
   171  	var golombFilter string
   172  	if m.golombFilterP > 0 {
   173  		golombFilter = m.computeGolombFilter(mtx, tx)
   174  	}
   175  	if m.OnNewTx != nil {
   176  		m.OnNewTx(mtx)
   177  	}
   178  	return io, golombFilter, true
   179  }
   180  
   181  // Resync gets mempool transactions and maps outputs to transactions.
   182  // Resync is not reentrant, it should be called from a single thread.
   183  // Read operations (GetTransactions) are safe.
   184  func (m *MempoolBitcoinType) Resync() (int, error) {
   185  	start := time.Now()
   186  	glog.V(1).Info("mempool: resync")
   187  	txs, err := m.chain.GetMempoolTransactions()
   188  	if err != nil {
   189  		return 0, err
   190  	}
   191  	glog.V(2).Info("mempool: resync ", len(txs), " txs")
   192  	onNewEntry := func(txid string, entry txEntry) {
   193  		if len(entry.addrIndexes) > 0 {
   194  			m.mux.Lock()
   195  			m.txEntries[txid] = entry
   196  			for _, si := range entry.addrIndexes {
   197  				m.addrDescToTx[si.addrDesc] = append(m.addrDescToTx[si.addrDesc], Outpoint{txid, si.n})
   198  			}
   199  			m.mux.Unlock()
   200  		}
   201  	}
   202  	txsMap := make(map[string]struct{}, len(txs))
   203  	dispatched := 0
   204  	txTime := uint32(time.Now().Unix())
   205  	// get transaction in parallel using goroutines created in NewUTXOMempool
   206  	for _, txid := range txs {
   207  		txsMap[txid] = struct{}{}
   208  		_, exists := m.txEntries[txid]
   209  		if !exists {
   210  		loop:
   211  			for {
   212  				select {
   213  				// store as many processed transactions as possible
   214  				case tio := <-m.chanAddrIndex:
   215  					onNewEntry(tio.txid, txEntry{tio.io, txTime, tio.filter})
   216  					dispatched--
   217  				// send transaction to be processed
   218  				case m.chanTxid <- txid:
   219  					dispatched++
   220  					break loop
   221  				}
   222  			}
   223  		}
   224  	}
   225  	for i := 0; i < dispatched; i++ {
   226  		tio := <-m.chanAddrIndex
   227  		onNewEntry(tio.txid, txEntry{tio.io, txTime, tio.filter})
   228  	}
   229  
   230  	for txid, entry := range m.txEntries {
   231  		if _, exists := txsMap[txid]; !exists {
   232  			m.mux.Lock()
   233  			m.removeEntryFromMempool(txid, entry)
   234  			m.mux.Unlock()
   235  		}
   236  	}
   237  	glog.Info("mempool: resync finished in ", time.Since(start), ", ", len(m.txEntries), " transactions in mempool")
   238  	return len(m.txEntries), nil
   239  }
   240  
   241  // GetTxidFilterEntries returns all mempool entries with golomb filter from
   242  func (m *MempoolBitcoinType) GetTxidFilterEntries(filterScripts string, fromTimestamp uint32) (MempoolTxidFilterEntries, error) {
   243  	if m.filterScripts != filterScripts {
   244  		return MempoolTxidFilterEntries{}, errors.Errorf("Unsupported script filter %s", filterScripts)
   245  	}
   246  	m.mux.Lock()
   247  	entries := make(map[string]string)
   248  	for txid, entry := range m.txEntries {
   249  		if entry.filter != "" && entry.time >= fromTimestamp {
   250  			entries[txid] = entry.filter
   251  		}
   252  	}
   253  	m.mux.Unlock()
   254  	return MempoolTxidFilterEntries{entries, m.useZeroedKey}, nil
   255  }