github.com/cerberus-wallet/blockbook@v0.3.2/bchain/mempool_bitcoin_type.go (about) 1 package bchain 2 3 import ( 4 "time" 5 6 "github.com/golang/glog" 7 ) 8 9 // MempoolBitcoinType is mempool handle. 10 type MempoolBitcoinType struct { 11 BaseMempool 12 chanTxid chan string 13 chanAddrIndex chan txidio 14 AddrDescForOutpoint AddrDescForOutpointFunc 15 } 16 17 // NewMempoolBitcoinType creates new mempool handler. 18 // For now there is no cleanup of sync routines, the expectation is that the mempool is created only once per process 19 func NewMempoolBitcoinType(chain BlockChain, workers int, subworkers int) *MempoolBitcoinType { 20 m := &MempoolBitcoinType{ 21 BaseMempool: BaseMempool{ 22 chain: chain, 23 txEntries: make(map[string]txEntry), 24 addrDescToTx: make(map[string][]Outpoint), 25 }, 26 chanTxid: make(chan string, 1), 27 chanAddrIndex: make(chan txidio, 1), 28 } 29 for i := 0; i < workers; i++ { 30 go func(i int) { 31 chanInput := make(chan Outpoint, 1) 32 chanResult := make(chan *addrIndex, 1) 33 for j := 0; j < subworkers; j++ { 34 go func(j int) { 35 for input := range chanInput { 36 ai := m.getInputAddress(input) 37 chanResult <- ai 38 } 39 }(j) 40 } 41 for txid := range m.chanTxid { 42 io, ok := m.getTxAddrs(txid, chanInput, chanResult) 43 if !ok { 44 io = []addrIndex{} 45 } 46 m.chanAddrIndex <- txidio{txid, io} 47 } 48 }(i) 49 } 50 glog.Info("mempool: starting with ", workers, "*", subworkers, " sync workers") 51 return m 52 } 53 54 func (m *MempoolBitcoinType) getInputAddress(input Outpoint) *addrIndex { 55 var addrDesc AddressDescriptor 56 if m.AddrDescForOutpoint != nil { 57 addrDesc = m.AddrDescForOutpoint(input) 58 } 59 if addrDesc == nil { 60 itx, err := m.chain.GetTransactionForMempool(input.Txid) 61 if err != nil { 62 glog.Error("cannot get transaction ", input.Txid, ": ", err) 63 return nil 64 } 65 if int(input.Vout) >= len(itx.Vout) { 66 glog.Error("Vout len in transaction ", input.Txid, " ", len(itx.Vout), " input.Vout=", input.Vout) 67 return nil 68 } 69 addrDesc, err = m.chain.GetChainParser().GetAddrDescFromVout(&itx.Vout[input.Vout]) 70 if err != nil { 71 glog.Error("error in addrDesc in ", input.Txid, " ", input.Vout, ": ", err) 72 return nil 73 } 74 } 75 return &addrIndex{string(addrDesc), ^input.Vout} 76 77 } 78 79 func (m *MempoolBitcoinType) getTxAddrs(txid string, chanInput chan Outpoint, chanResult chan *addrIndex) ([]addrIndex, bool) { 80 tx, err := m.chain.GetTransactionForMempool(txid) 81 if err != nil { 82 glog.Error("cannot get transaction ", txid, ": ", err) 83 return nil, false 84 } 85 glog.V(2).Info("mempool: gettxaddrs ", txid, ", ", len(tx.Vin), " inputs") 86 io := make([]addrIndex, 0, len(tx.Vout)+len(tx.Vin)) 87 for _, output := range tx.Vout { 88 addrDesc, err := m.chain.GetChainParser().GetAddrDescFromVout(&output) 89 if err != nil { 90 glog.Error("error in addrDesc in ", txid, " ", output.N, ": ", err) 91 continue 92 } 93 if len(addrDesc) > 0 { 94 io = append(io, addrIndex{string(addrDesc), int32(output.N)}) 95 } 96 if m.OnNewTxAddr != nil { 97 m.OnNewTxAddr(tx, addrDesc) 98 } 99 } 100 dispatched := 0 101 for _, input := range tx.Vin { 102 if input.Coinbase != "" { 103 continue 104 } 105 o := Outpoint{input.Txid, int32(input.Vout)} 106 loop: 107 for { 108 select { 109 // store as many processed results as possible 110 case ai := <-chanResult: 111 if ai != nil { 112 io = append(io, *ai) 113 } 114 dispatched-- 115 // send input to be processed 116 case chanInput <- o: 117 dispatched++ 118 break loop 119 } 120 } 121 } 122 for i := 0; i < dispatched; i++ { 123 ai := <-chanResult 124 if ai != nil { 125 io = append(io, *ai) 126 } 127 } 128 return io, true 129 } 130 131 // Resync gets mempool transactions and maps outputs to transactions. 132 // Resync is not reentrant, it should be called from a single thread. 133 // Read operations (GetTransactions) are safe. 134 func (m *MempoolBitcoinType) Resync() (int, error) { 135 start := time.Now() 136 glog.V(1).Info("mempool: resync") 137 txs, err := m.chain.GetMempoolTransactions() 138 if err != nil { 139 return 0, err 140 } 141 glog.V(2).Info("mempool: resync ", len(txs), " txs") 142 onNewEntry := func(txid string, entry txEntry) { 143 if len(entry.addrIndexes) > 0 { 144 m.mux.Lock() 145 m.txEntries[txid] = entry 146 for _, si := range entry.addrIndexes { 147 m.addrDescToTx[si.addrDesc] = append(m.addrDescToTx[si.addrDesc], Outpoint{txid, si.n}) 148 } 149 m.mux.Unlock() 150 } 151 } 152 txsMap := make(map[string]struct{}, len(txs)) 153 dispatched := 0 154 txTime := uint32(time.Now().Unix()) 155 // get transaction in parallel using goroutines created in NewUTXOMempool 156 for _, txid := range txs { 157 txsMap[txid] = struct{}{} 158 _, exists := m.txEntries[txid] 159 if !exists { 160 loop: 161 for { 162 select { 163 // store as many processed transactions as possible 164 case tio := <-m.chanAddrIndex: 165 onNewEntry(tio.txid, txEntry{tio.io, txTime}) 166 dispatched-- 167 // send transaction to be processed 168 case m.chanTxid <- txid: 169 dispatched++ 170 break loop 171 } 172 } 173 } 174 } 175 for i := 0; i < dispatched; i++ { 176 tio := <-m.chanAddrIndex 177 onNewEntry(tio.txid, txEntry{tio.io, txTime}) 178 } 179 180 for txid, entry := range m.txEntries { 181 if _, exists := txsMap[txid]; !exists { 182 m.mux.Lock() 183 m.removeEntryFromMempool(txid, entry) 184 m.mux.Unlock() 185 } 186 } 187 glog.Info("mempool: resync finished in ", time.Since(start), ", ", len(m.txEntries), " transactions in mempool") 188 return len(m.txEntries), nil 189 }