github.com/bchainhub/blockbook@v0.3.2/db/bulkconnect.go (about)

     1  package db
     2  
     3  import (
     4  	"blockbook/bchain"
     5  	"time"
     6  
     7  	"github.com/golang/glog"
     8  	"github.com/tecbot/gorocksdb"
     9  )
    10  
    11  // bulk connect
    12  // in bulk mode the data are cached and stored to db in batches
    13  // it speeds up the import in two ways:
    14  // 1) balances and txAddresses are modified several times during the import, there is a chance that the modifications are done before write to DB
    15  // 2) rocksdb seems to handle better fewer larger batches than continuous stream of smaller batches
    16  
    17  type bulkAddresses struct {
    18  	bi        BlockInfo
    19  	addresses addressesMap
    20  }
    21  
    22  // BulkConnect is used to connect blocks in bulk, faster but if interrupted inconsistent way
    23  type BulkConnect struct {
    24  	d                  *RocksDB
    25  	chainType          bchain.ChainType
    26  	bulkAddresses      []bulkAddresses
    27  	bulkAddressesCount int
    28  	txAddressesMap     map[string]*TxAddresses
    29  	balances           map[string]*AddrBalance
    30  	addressContracts   map[string]*AddrContracts
    31  	height             uint32
    32  }
    33  
    34  const (
    35  	maxBulkAddresses          = 80000
    36  	maxBulkTxAddresses        = 500000
    37  	partialStoreAddresses     = maxBulkTxAddresses / 10
    38  	maxBulkBalances           = 700000
    39  	partialStoreBalances      = maxBulkBalances / 10
    40  	maxBulkAddrContracts      = 1200000
    41  	partialStoreAddrContracts = maxBulkAddrContracts / 10
    42  )
    43  
    44  // InitBulkConnect initializes bulk connect and switches DB to inconsistent state
    45  func (d *RocksDB) InitBulkConnect() (*BulkConnect, error) {
    46  	b := &BulkConnect{
    47  		d:                d,
    48  		chainType:        d.chainParser.GetChainType(),
    49  		txAddressesMap:   make(map[string]*TxAddresses),
    50  		balances:         make(map[string]*AddrBalance),
    51  		addressContracts: make(map[string]*AddrContracts),
    52  	}
    53  	if err := d.SetInconsistentState(true); err != nil {
    54  		return nil, err
    55  	}
    56  	glog.Info("rocksdb: bulk connect init, db set to inconsistent state")
    57  	return b, nil
    58  }
    59  
    60  func (b *BulkConnect) storeTxAddresses(wb *gorocksdb.WriteBatch, all bool) (int, int, error) {
    61  	var txm map[string]*TxAddresses
    62  	var sp int
    63  	if all {
    64  		txm = b.txAddressesMap
    65  		b.txAddressesMap = make(map[string]*TxAddresses)
    66  	} else {
    67  		txm = make(map[string]*TxAddresses)
    68  		for k, a := range b.txAddressesMap {
    69  			// store all completely spent transactions, they will not be modified again
    70  			r := true
    71  			for _, o := range a.Outputs {
    72  				if o.Spent == false {
    73  					r = false
    74  					break
    75  				}
    76  			}
    77  			if r {
    78  				txm[k] = a
    79  				delete(b.txAddressesMap, k)
    80  			}
    81  		}
    82  		sp = len(txm)
    83  		// store some other random transactions if necessary
    84  		if len(txm) < partialStoreAddresses {
    85  			for k, a := range b.txAddressesMap {
    86  				txm[k] = a
    87  				delete(b.txAddressesMap, k)
    88  				if len(txm) >= partialStoreAddresses {
    89  					break
    90  				}
    91  			}
    92  		}
    93  	}
    94  	if err := b.d.storeTxAddresses(wb, txm); err != nil {
    95  		return 0, 0, err
    96  	}
    97  	return len(txm), sp, nil
    98  }
    99  
   100  func (b *BulkConnect) parallelStoreTxAddresses(c chan error, all bool) {
   101  	defer close(c)
   102  	start := time.Now()
   103  	wb := gorocksdb.NewWriteBatch()
   104  	defer wb.Destroy()
   105  	count, sp, err := b.storeTxAddresses(wb, all)
   106  	if err != nil {
   107  		c <- err
   108  		return
   109  	}
   110  	if err := b.d.db.Write(b.d.wo, wb); err != nil {
   111  		c <- err
   112  		return
   113  	}
   114  	glog.Info("rocksdb: height ", b.height, ", stored ", count, " (", sp, " spent) txAddresses, ", len(b.txAddressesMap), " remaining, done in ", time.Since(start))
   115  	c <- nil
   116  }
   117  
   118  func (b *BulkConnect) storeBalances(wb *gorocksdb.WriteBatch, all bool) (int, error) {
   119  	var bal map[string]*AddrBalance
   120  	if all {
   121  		bal = b.balances
   122  		b.balances = make(map[string]*AddrBalance)
   123  	} else {
   124  		bal = make(map[string]*AddrBalance)
   125  		// store some random balances
   126  		for k, a := range b.balances {
   127  			bal[k] = a
   128  			delete(b.balances, k)
   129  			if len(bal) >= partialStoreBalances {
   130  				break
   131  			}
   132  		}
   133  	}
   134  	if err := b.d.storeBalances(wb, bal); err != nil {
   135  		return 0, err
   136  	}
   137  	return len(bal), nil
   138  }
   139  
   140  func (b *BulkConnect) parallelStoreBalances(c chan error, all bool) {
   141  	defer close(c)
   142  	start := time.Now()
   143  	wb := gorocksdb.NewWriteBatch()
   144  	defer wb.Destroy()
   145  	count, err := b.storeBalances(wb, all)
   146  	if err != nil {
   147  		c <- err
   148  		return
   149  	}
   150  	if err := b.d.db.Write(b.d.wo, wb); err != nil {
   151  		c <- err
   152  		return
   153  	}
   154  	glog.Info("rocksdb: height ", b.height, ", stored ", count, " balances, ", len(b.balances), " remaining, done in ", time.Since(start))
   155  	c <- nil
   156  }
   157  
   158  func (b *BulkConnect) storeBulkAddresses(wb *gorocksdb.WriteBatch) error {
   159  	for _, ba := range b.bulkAddresses {
   160  		if err := b.d.storeAddresses(wb, ba.bi.Height, ba.addresses); err != nil {
   161  			return err
   162  		}
   163  		if err := b.d.writeHeight(wb, ba.bi.Height, &ba.bi, opInsert); err != nil {
   164  			return err
   165  		}
   166  	}
   167  	b.bulkAddressesCount = 0
   168  	b.bulkAddresses = b.bulkAddresses[:0]
   169  	return nil
   170  }
   171  
   172  func (b *BulkConnect) connectBlockBitcoinType(block *bchain.Block, storeBlockTxs bool) error {
   173  	addresses := make(addressesMap)
   174  	if err := b.d.processAddressesBitcoinType(block, addresses, b.txAddressesMap, b.balances); err != nil {
   175  		return err
   176  	}
   177  	var storeAddressesChan, storeBalancesChan chan error
   178  	var sa bool
   179  	if len(b.txAddressesMap) > maxBulkTxAddresses || len(b.balances) > maxBulkBalances {
   180  		sa = true
   181  		if len(b.txAddressesMap)+partialStoreAddresses > maxBulkTxAddresses {
   182  			storeAddressesChan = make(chan error)
   183  			go b.parallelStoreTxAddresses(storeAddressesChan, false)
   184  		}
   185  		if len(b.balances)+partialStoreBalances > maxBulkBalances {
   186  			storeBalancesChan = make(chan error)
   187  			go b.parallelStoreBalances(storeBalancesChan, false)
   188  		}
   189  	}
   190  	b.bulkAddresses = append(b.bulkAddresses, bulkAddresses{
   191  		bi: BlockInfo{
   192  			Hash:   block.Hash,
   193  			Time:   block.Time,
   194  			Txs:    uint32(len(block.Txs)),
   195  			Size:   uint32(block.Size),
   196  			Height: block.Height,
   197  		},
   198  		addresses: addresses,
   199  	})
   200  	b.bulkAddressesCount += len(addresses)
   201  	// open WriteBatch only if going to write
   202  	if sa || b.bulkAddressesCount > maxBulkAddresses || storeBlockTxs {
   203  		start := time.Now()
   204  		wb := gorocksdb.NewWriteBatch()
   205  		defer wb.Destroy()
   206  		bac := b.bulkAddressesCount
   207  		if sa || b.bulkAddressesCount > maxBulkAddresses {
   208  			if err := b.storeBulkAddresses(wb); err != nil {
   209  				return err
   210  			}
   211  		}
   212  		if storeBlockTxs {
   213  			if err := b.d.storeAndCleanupBlockTxs(wb, block); err != nil {
   214  				return err
   215  			}
   216  		}
   217  		if err := b.d.db.Write(b.d.wo, wb); err != nil {
   218  			return err
   219  		}
   220  		if bac > b.bulkAddressesCount {
   221  			glog.Info("rocksdb: height ", b.height, ", stored ", bac, " addresses, done in ", time.Since(start))
   222  		}
   223  	}
   224  	if storeAddressesChan != nil {
   225  		if err := <-storeAddressesChan; err != nil {
   226  			return err
   227  		}
   228  	}
   229  	if storeBalancesChan != nil {
   230  		if err := <-storeBalancesChan; err != nil {
   231  			return err
   232  		}
   233  	}
   234  	return nil
   235  }
   236  
   237  func (b *BulkConnect) storeAddressContracts(wb *gorocksdb.WriteBatch, all bool) (int, error) {
   238  	var ac map[string]*AddrContracts
   239  	if all {
   240  		ac = b.addressContracts
   241  		b.addressContracts = make(map[string]*AddrContracts)
   242  	} else {
   243  		ac = make(map[string]*AddrContracts)
   244  		// store some random address contracts
   245  		for k, a := range b.addressContracts {
   246  			ac[k] = a
   247  			delete(b.addressContracts, k)
   248  			if len(ac) >= partialStoreAddrContracts {
   249  				break
   250  			}
   251  		}
   252  	}
   253  	if err := b.d.storeAddressContracts(wb, ac); err != nil {
   254  		return 0, err
   255  	}
   256  	return len(ac), nil
   257  }
   258  
   259  func (b *BulkConnect) parallelStoreAddressContracts(c chan error, all bool) {
   260  	defer close(c)
   261  	start := time.Now()
   262  	wb := gorocksdb.NewWriteBatch()
   263  	defer wb.Destroy()
   264  	count, err := b.storeAddressContracts(wb, all)
   265  	if err != nil {
   266  		c <- err
   267  		return
   268  	}
   269  	if err := b.d.db.Write(b.d.wo, wb); err != nil {
   270  		c <- err
   271  		return
   272  	}
   273  	glog.Info("rocksdb: height ", b.height, ", stored ", count, " addressContracts, ", len(b.addressContracts), " remaining, done in ", time.Since(start))
   274  	c <- nil
   275  }
   276  
   277  func (b *BulkConnect) connectBlockEthereumType(block *bchain.Block, storeBlockTxs bool) error {
   278  	addresses := make(addressesMap)
   279  	blockTxs, err := b.d.processAddressesEthereumType(block, addresses, b.addressContracts)
   280  	if err != nil {
   281  		return err
   282  	}
   283  	var storeAddrContracts chan error
   284  	var sa bool
   285  	if len(b.addressContracts) > maxBulkAddrContracts {
   286  		sa = true
   287  		storeAddrContracts = make(chan error)
   288  		go b.parallelStoreAddressContracts(storeAddrContracts, false)
   289  	}
   290  	b.bulkAddresses = append(b.bulkAddresses, bulkAddresses{
   291  		bi: BlockInfo{
   292  			Hash:   block.Hash,
   293  			Time:   block.Time,
   294  			Txs:    uint32(len(block.Txs)),
   295  			Size:   uint32(block.Size),
   296  			Height: block.Height,
   297  		},
   298  		addresses: addresses,
   299  	})
   300  	b.bulkAddressesCount += len(addresses)
   301  	// open WriteBatch only if going to write
   302  	if sa || b.bulkAddressesCount > maxBulkAddresses || storeBlockTxs {
   303  		start := time.Now()
   304  		wb := gorocksdb.NewWriteBatch()
   305  		defer wb.Destroy()
   306  		bac := b.bulkAddressesCount
   307  		if sa || b.bulkAddressesCount > maxBulkAddresses {
   308  			if err := b.storeBulkAddresses(wb); err != nil {
   309  				return err
   310  			}
   311  		}
   312  		if storeBlockTxs {
   313  			if err := b.d.storeAndCleanupBlockTxsEthereumType(wb, block, blockTxs); err != nil {
   314  				return err
   315  			}
   316  		}
   317  		if err := b.d.db.Write(b.d.wo, wb); err != nil {
   318  			return err
   319  		}
   320  		if bac > b.bulkAddressesCount {
   321  			glog.Info("rocksdb: height ", b.height, ", stored ", bac, " addresses, done in ", time.Since(start))
   322  		}
   323  	}
   324  	if storeAddrContracts != nil {
   325  		if err := <-storeAddrContracts; err != nil {
   326  			return err
   327  		}
   328  	}
   329  	return nil
   330  }
   331  
   332  // ConnectBlock connects block in bulk mode
   333  func (b *BulkConnect) ConnectBlock(block *bchain.Block, storeBlockTxs bool) error {
   334  	b.height = block.Height
   335  	if b.chainType == bchain.ChainBitcoinType {
   336  		return b.connectBlockBitcoinType(block, storeBlockTxs)
   337  	} else if b.chainType == bchain.ChainEthereumType {
   338  		return b.connectBlockEthereumType(block, storeBlockTxs)
   339  	}
   340  	// for default is to connect blocks in non bulk mode
   341  	return b.d.ConnectBlock(block)
   342  }
   343  
   344  // Close flushes the cached data and switches DB from inconsistent state open
   345  // after Close, the BulkConnect cannot be used
   346  func (b *BulkConnect) Close() error {
   347  	glog.Info("rocksdb: bulk connect closing")
   348  	start := time.Now()
   349  	var storeTxAddressesChan, storeBalancesChan, storeAddressContractsChan chan error
   350  	if b.chainType == bchain.ChainBitcoinType {
   351  		storeTxAddressesChan = make(chan error)
   352  		go b.parallelStoreTxAddresses(storeTxAddressesChan, true)
   353  		storeBalancesChan = make(chan error)
   354  		go b.parallelStoreBalances(storeBalancesChan, true)
   355  	} else if b.chainType == bchain.ChainEthereumType {
   356  		storeAddressContractsChan = make(chan error)
   357  		go b.parallelStoreAddressContracts(storeAddressContractsChan, true)
   358  	}
   359  	wb := gorocksdb.NewWriteBatch()
   360  	defer wb.Destroy()
   361  	bac := b.bulkAddressesCount
   362  	if err := b.storeBulkAddresses(wb); err != nil {
   363  		return err
   364  	}
   365  	if err := b.d.db.Write(b.d.wo, wb); err != nil {
   366  		return err
   367  	}
   368  	glog.Info("rocksdb: height ", b.height, ", stored ", bac, " addresses, done in ", time.Since(start))
   369  	if storeTxAddressesChan != nil {
   370  		if err := <-storeTxAddressesChan; err != nil {
   371  			return err
   372  		}
   373  	}
   374  	if storeBalancesChan != nil {
   375  		if err := <-storeBalancesChan; err != nil {
   376  			return err
   377  		}
   378  	}
   379  	if storeAddressContractsChan != nil {
   380  		if err := <-storeAddressContractsChan; err != nil {
   381  			return err
   382  		}
   383  	}
   384  	var err error
   385  	b.d.is.BlockTimes, err = b.d.loadBlockTimes()
   386  	if err != nil {
   387  		return err
   388  	}
   389  
   390  	if err := b.d.SetInconsistentState(false); err != nil {
   391  		return err
   392  	}
   393  	glog.Info("rocksdb: bulk connect closed, db set to open state")
   394  	b.d = nil
   395  	return nil
   396  }