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 }