github.com/ZuluSpl0it/Sia@v1.3.7/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/NebulousLabs/errors" 15 "github.com/coreos/bbolt" 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 }