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  }