github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/transactionpool/persist.go (about)

     1  package transactionpool
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"time"
     8  
     9  	"SiaPrime/build"
    10  	"SiaPrime/modules"
    11  	"SiaPrime/persist"
    12  	"SiaPrime/types"
    13  
    14  	"gitlab.com/NebulousLabs/bolt"
    15  	"gitlab.com/NebulousLabs/errors"
    16  )
    17  
    18  const tpoolSyncRate = time.Minute * 2
    19  
    20  // threadedRegularSync will make sure that sync gets called on the database
    21  // every once in a while.
    22  func (tp *TransactionPool) threadedRegularSync() {
    23  	if err := tp.tg.Add(); err != nil {
    24  		return
    25  	}
    26  	defer tp.tg.Done()
    27  	for {
    28  		select {
    29  		case <-tp.tg.StopChan():
    30  			// A queued AfterStop will close out the db properly.
    31  			return
    32  		case <-time.After(tpoolSyncRate):
    33  			tp.mu.Lock()
    34  			tp.syncDB()
    35  			tp.mu.Unlock()
    36  		}
    37  	}
    38  }
    39  
    40  // syncDB commits the current global transaction and immediately begins a new
    41  // one.
    42  func (tp *TransactionPool) syncDB() {
    43  	// Commit the existing tx.
    44  	err := tp.dbTx.Commit()
    45  	if err != nil {
    46  		tp.log.Severe("ERROR: failed to apply database update:", err)
    47  		tp.dbTx.Rollback()
    48  	}
    49  	// Begin a new tx
    50  	tp.dbTx, err = tp.db.Begin(true)
    51  	if err != nil {
    52  		tp.log.Severe("ERROR: failed to initialize a db transaction:", err)
    53  	}
    54  }
    55  
    56  // resetDB deletes all consensus related persistence from the transaction pool.
    57  func (tp *TransactionPool) resetDB(tx *bolt.Tx) error {
    58  	err := tx.DeleteBucket(bucketConfirmedTransactions)
    59  	if err != nil {
    60  		return err
    61  	}
    62  	err = tp.putRecentBlockID(tx, types.BlockID{})
    63  	if err != nil {
    64  		return err
    65  	}
    66  	err = tp.putRecentConsensusChange(tx, modules.ConsensusChangeBeginning)
    67  	if err != nil {
    68  		return err
    69  	}
    70  	err = tp.putBlockHeight(tx, types.BlockHeight(0))
    71  	if err != nil {
    72  		return err
    73  	}
    74  	_, err = tx.CreateBucket(bucketConfirmedTransactions)
    75  	return err
    76  }
    77  
    78  // initPersist creates buckets in the database
    79  func (tp *TransactionPool) initPersist() error {
    80  	// Create the persist directory if it does not yet exist.
    81  	err := os.MkdirAll(tp.persistDir, 0700)
    82  	if err != nil {
    83  		return err
    84  	}
    85  
    86  	// Create the tpool logger.
    87  	tp.log, err = persist.NewFileLogger(filepath.Join(tp.persistDir, logFile))
    88  	if err != nil {
    89  		return build.ExtendErr("unable to initialize the transaction pool logger", err)
    90  	}
    91  	tp.tg.AfterStop(func() {
    92  		err := tp.log.Close()
    93  		if err != nil {
    94  			fmt.Println("Unable to close the transaction pool logger:", err)
    95  		}
    96  	})
    97  
    98  	// Open the database file.
    99  	tp.db, err = persist.OpenDatabase(dbMetadata, filepath.Join(tp.persistDir, dbFilename))
   100  	if err != nil {
   101  		return err
   102  	}
   103  	tp.tg.AfterStop(func() {
   104  		err := tp.db.Close()
   105  		if err != nil {
   106  			tp.log.Println("Error while closing transaction pool database:", err)
   107  		}
   108  	})
   109  	// Create the global tpool tx that will be used for most persist actions.
   110  	tp.dbTx, err = tp.db.Begin(true)
   111  	if err != nil {
   112  		return build.ExtendErr("unable to begin tpool dbTx", err)
   113  	}
   114  	tp.tg.AfterStop(func() {
   115  		tp.mu.Lock()
   116  		err := tp.dbTx.Commit()
   117  		tp.mu.Unlock()
   118  		if err != nil {
   119  			tp.log.Println("Unable to close transaction properly during shutdown:", err)
   120  		}
   121  	})
   122  	// Spin up the thread that occasionally syncrhonizes the database.
   123  	go tp.threadedRegularSync()
   124  
   125  	// Create the database and get the most recent consensus change.
   126  	var cc modules.ConsensusChangeID
   127  	// Create the database buckets.
   128  	buckets := [][]byte{
   129  		bucketBlockHeight,
   130  		bucketRecentConsensusChange,
   131  		bucketConfirmedTransactions,
   132  		bucketFeeMedian,
   133  	}
   134  	for _, bucket := range buckets {
   135  		_, err := tp.dbTx.CreateBucketIfNotExists(bucket)
   136  		if err != nil {
   137  			return build.ExtendErr("unable to create the tpool buckets", err)
   138  		}
   139  	}
   140  
   141  	// Get the recent consensus change.
   142  	cc, err = tp.getRecentConsensusChange(tp.dbTx)
   143  	if err == errNilConsensusChange {
   144  		err = tp.putRecentConsensusChange(tp.dbTx, modules.ConsensusChangeBeginning)
   145  	}
   146  	if err != nil {
   147  		return build.ExtendErr("unable to initialize the recent consensus change in the tpool", err)
   148  	}
   149  
   150  	// Get the most recent block height
   151  	bh, err := tp.getBlockHeight(tp.dbTx)
   152  	if err != nil {
   153  		tp.log.Println("Block height is reporting as zero, setting up to subscribe from the beginning.")
   154  		err = tp.putBlockHeight(tp.dbTx, types.BlockHeight(0))
   155  		if err != nil {
   156  			return build.ExtendErr("unable to initialize the block height in the tpool", err)
   157  		}
   158  		err = tp.putRecentConsensusChange(tp.dbTx, modules.ConsensusChangeBeginning)
   159  	} else {
   160  		tp.log.Debugln("Transaction pool is loading from height:", bh)
   161  		tp.blockHeight = bh
   162  	}
   163  	if err != nil {
   164  		return build.ExtendErr("unable to initialize the block height in the tpool", err)
   165  	}
   166  
   167  	// Get the fee median data.
   168  	mp, err := tp.getFeeMedian(tp.dbTx)
   169  	if err != nil && err != errNilFeeMedian {
   170  		return build.ExtendErr("unable to load the fee median", err)
   171  	}
   172  	// Just leave the fields empty if no fee median was found. They will be
   173  	// filled out.
   174  	if err != errNilFeeMedian {
   175  		tp.recentMedians = mp.RecentMedians
   176  		tp.recentMedianFee = mp.RecentMedianFee
   177  	}
   178  
   179  	// Subscribe to the consensus set using the most recent consensus change.
   180  	err = tp.consensusSet.ConsensusSetSubscribe(tp, cc, tp.tg.StopChan())
   181  	if err == modules.ErrInvalidConsensusChangeID {
   182  		tp.log.Println("Invalid consensus change loaded; resetting. This can take a while.")
   183  		// Reset and rescan because the consensus set does not recognize the
   184  		// provided consensus change id.
   185  		resetErr := tp.resetDB(tp.dbTx)
   186  		if resetErr != nil {
   187  			return resetErr
   188  		}
   189  		freshScanErr := tp.consensusSet.ConsensusSetSubscribe(tp, modules.ConsensusChangeBeginning, tp.tg.StopChan())
   190  		if freshScanErr != nil {
   191  			return freshScanErr
   192  		}
   193  		tp.tg.OnStop(func() {
   194  			tp.consensusSet.Unsubscribe(tp)
   195  		})
   196  		return nil
   197  	}
   198  	if err != nil {
   199  		return err
   200  	}
   201  	tp.tg.OnStop(func() {
   202  		tp.consensusSet.Unsubscribe(tp)
   203  	})
   204  	return nil
   205  }
   206  
   207  // TransactionConfirmed returns true if the transaction has been seen on the
   208  // blockchain. Note, however, that the block containing the transaction may
   209  // later be invalidated by a reorg.
   210  func (tp *TransactionPool) TransactionConfirmed(id types.TransactionID) (bool, error) {
   211  	if err := tp.tg.Add(); err != nil {
   212  		return false, errors.AddContext(err, "cannot check transaction status, the transaction pool has closed")
   213  	}
   214  	defer tp.tg.Done()
   215  	tp.mu.Lock()
   216  	defer tp.mu.Unlock()
   217  	return tp.transactionConfirmed(tp.dbTx, id), nil
   218  }
   219  
   220  func (tp *TransactionPool) transactionConfirmed(tx *bolt.Tx, id types.TransactionID) bool {
   221  	return tx.Bucket(bucketConfirmedTransactions).Get(id[:]) != nil
   222  }