github.com/aychain/blockbook@v0.1.1-0.20181121092459-6d1fc7e07c5b/bchain/mempool_utxo.go (about) 1 package bchain 2 3 import ( 4 "sync" 5 "time" 6 7 "github.com/golang/glog" 8 ) 9 10 // addrIndex and outpoint are used also in non utxo mempool 11 type addrIndex struct { 12 addrDesc string 13 n int32 14 } 15 16 type outpoint struct { 17 txid string 18 vout int32 19 } 20 21 type txidio struct { 22 txid string 23 io []addrIndex 24 } 25 26 // UTXOMempool is mempool handle. 27 type UTXOMempool struct { 28 chain BlockChain 29 mux sync.Mutex 30 txToInputOutput map[string][]addrIndex 31 addrDescToTx map[string][]outpoint 32 chanTxid chan string 33 chanAddrIndex chan txidio 34 onNewTxAddr OnNewTxAddrFunc 35 } 36 37 // NewUTXOMempool creates new mempool handler. 38 // For now there is no cleanup of sync routines, the expectation is that the mempool is created only once per process 39 func NewUTXOMempool(chain BlockChain, workers int, subworkers int) *UTXOMempool { 40 m := &UTXOMempool{ 41 chain: chain, 42 chanTxid: make(chan string, 1), 43 chanAddrIndex: make(chan txidio, 1), 44 } 45 for i := 0; i < workers; i++ { 46 go func(i int) { 47 chanInput := make(chan outpoint, 1) 48 chanResult := make(chan *addrIndex, 1) 49 for j := 0; j < subworkers; j++ { 50 go func(j int) { 51 for input := range chanInput { 52 ai := m.getInputAddress(input) 53 chanResult <- ai 54 } 55 }(j) 56 } 57 for txid := range m.chanTxid { 58 io, ok := m.getTxAddrs(txid, chanInput, chanResult) 59 if !ok { 60 io = []addrIndex{} 61 } 62 m.chanAddrIndex <- txidio{txid, io} 63 } 64 }(i) 65 } 66 glog.Info("mempool: starting with ", workers, "*", subworkers, " sync workers") 67 return m 68 } 69 70 // GetTransactions returns slice of mempool transactions for given address 71 func (m *UTXOMempool) GetTransactions(address string) ([]string, error) { 72 parser := m.chain.GetChainParser() 73 addrDesc, err := parser.GetAddrDescFromAddress(address) 74 if err != nil { 75 return nil, err 76 } 77 return m.GetAddrDescTransactions(addrDesc) 78 } 79 80 // GetAddrDescTransactions returns slice of mempool transactions for given address descriptor 81 func (m *UTXOMempool) GetAddrDescTransactions(addrDesc AddressDescriptor) ([]string, error) { 82 m.mux.Lock() 83 defer m.mux.Unlock() 84 outpoints := m.addrDescToTx[string(addrDesc)] 85 txs := make([]string, 0, len(outpoints)) 86 for _, o := range outpoints { 87 txs = append(txs, o.txid) 88 } 89 return txs, nil 90 } 91 92 func (m *UTXOMempool) updateMappings(newTxToInputOutput map[string][]addrIndex, newAddrDescToTx map[string][]outpoint) { 93 m.mux.Lock() 94 defer m.mux.Unlock() 95 m.txToInputOutput = newTxToInputOutput 96 m.addrDescToTx = newAddrDescToTx 97 } 98 99 func (m *UTXOMempool) getInputAddress(input outpoint) *addrIndex { 100 itx, err := m.chain.GetTransactionForMempool(input.txid) 101 if err != nil { 102 glog.Error("cannot get transaction ", input.txid, ": ", err) 103 return nil 104 } 105 if int(input.vout) >= len(itx.Vout) { 106 glog.Error("Vout len in transaction ", input.txid, " ", len(itx.Vout), " input.Vout=", input.vout) 107 return nil 108 } 109 addrDesc, err := m.chain.GetChainParser().GetAddrDescFromVout(&itx.Vout[input.vout]) 110 if err != nil { 111 glog.Error("error in addrDesc in ", input.txid, " ", input.vout, ": ", err) 112 return nil 113 } 114 return &addrIndex{string(addrDesc), ^input.vout} 115 116 } 117 118 func (m *UTXOMempool) getTxAddrs(txid string, chanInput chan outpoint, chanResult chan *addrIndex) ([]addrIndex, bool) { 119 tx, err := m.chain.GetTransactionForMempool(txid) 120 if err != nil { 121 glog.Error("cannot get transaction ", txid, ": ", err) 122 return nil, false 123 } 124 glog.V(2).Info("mempool: gettxaddrs ", txid, ", ", len(tx.Vin), " inputs") 125 io := make([]addrIndex, 0, len(tx.Vout)+len(tx.Vin)) 126 for _, output := range tx.Vout { 127 addrDesc, err := m.chain.GetChainParser().GetAddrDescFromVout(&output) 128 if err != nil { 129 glog.Error("error in addrDesc in ", txid, " ", output.N, ": ", err) 130 continue 131 } 132 if len(addrDesc) > 0 { 133 io = append(io, addrIndex{string(addrDesc), int32(output.N)}) 134 } 135 if m.onNewTxAddr != nil { 136 m.onNewTxAddr(tx.Txid, addrDesc, true) 137 } 138 } 139 dispatched := 0 140 for _, input := range tx.Vin { 141 if input.Coinbase != "" { 142 continue 143 } 144 o := outpoint{input.Txid, int32(input.Vout)} 145 loop: 146 for { 147 select { 148 // store as many processed results as possible 149 case ai := <-chanResult: 150 if ai != nil { 151 io = append(io, *ai) 152 } 153 dispatched-- 154 // send input to be processed 155 case chanInput <- o: 156 dispatched++ 157 break loop 158 } 159 } 160 } 161 for i := 0; i < dispatched; i++ { 162 ai := <-chanResult 163 if ai != nil { 164 io = append(io, *ai) 165 } 166 } 167 return io, true 168 } 169 170 // Resync gets mempool transactions and maps outputs to transactions. 171 // Resync is not reentrant, it should be called from a single thread. 172 // Read operations (GetTransactions) are safe. 173 func (m *UTXOMempool) Resync(onNewTxAddr OnNewTxAddrFunc) (int, error) { 174 start := time.Now() 175 glog.V(1).Info("mempool: resync") 176 m.onNewTxAddr = onNewTxAddr 177 txs, err := m.chain.GetMempool() 178 if err != nil { 179 return 0, err 180 } 181 glog.V(2).Info("mempool: resync ", len(txs), " txs") 182 // allocate slightly larger capacity of the maps 183 newTxToInputOutput := make(map[string][]addrIndex, len(m.txToInputOutput)+5) 184 newAddrDescToTx := make(map[string][]outpoint, len(m.addrDescToTx)+5) 185 dispatched := 0 186 onNewData := func(txid string, io []addrIndex) { 187 if len(io) > 0 { 188 newTxToInputOutput[txid] = io 189 for _, si := range io { 190 newAddrDescToTx[si.addrDesc] = append(newAddrDescToTx[si.addrDesc], outpoint{txid, si.n}) 191 } 192 } 193 } 194 // get transaction in parallel using goroutines created in NewUTXOMempool 195 for _, txid := range txs { 196 io, exists := m.txToInputOutput[txid] 197 if !exists { 198 loop: 199 for { 200 select { 201 // store as many processed transactions as possible 202 case tio := <-m.chanAddrIndex: 203 onNewData(tio.txid, tio.io) 204 dispatched-- 205 // send transaction to be processed 206 case m.chanTxid <- txid: 207 dispatched++ 208 break loop 209 } 210 } 211 } else { 212 onNewData(txid, io) 213 } 214 } 215 for i := 0; i < dispatched; i++ { 216 tio := <-m.chanAddrIndex 217 onNewData(tio.txid, tio.io) 218 } 219 m.updateMappings(newTxToInputOutput, newAddrDescToTx) 220 m.onNewTxAddr = nil 221 glog.Info("mempool: resync finished in ", time.Since(start), ", ", len(m.txToInputOutput), " transactions in mempool") 222 return len(m.txToInputOutput), nil 223 }