gitlab.com/jokerrs1/Sia@v1.3.2/modules/transactionpool/persist.go (about)

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