github.com/nebulouslabs/sia@v1.3.7/modules/miner/miner.go (about) 1 // Package miner is responsible for creating and submitting siacoin blocks 2 package miner 3 4 import ( 5 "errors" 6 "fmt" 7 "sync" 8 "time" 9 10 "github.com/NebulousLabs/Sia/build" 11 "github.com/NebulousLabs/Sia/crypto" 12 "github.com/NebulousLabs/Sia/modules" 13 "github.com/NebulousLabs/Sia/persist" 14 siasync "github.com/NebulousLabs/Sia/sync" 15 "github.com/NebulousLabs/Sia/types" 16 ) 17 18 var ( 19 // BlockMemory is the maximum number of blocks the miner will store 20 // Blocks take up to 2 megabytes of memory, which is why this number is 21 // limited. 22 BlockMemory = build.Select(build.Var{ 23 Standard: 50, 24 Dev: 10, 25 Testing: 5, 26 }).(int) 27 28 errNilCS = errors.New("miner cannot use a nil consensus set") 29 errNilTpool = errors.New("miner cannot use a nil transaction pool") 30 errNilWallet = errors.New("miner cannot use a nil wallet") 31 32 // HeaderMemory is the number of previous calls to 'header' 33 // that are remembered. Additionally, 'header' will only poll for a 34 // new block every 'headerMemory / blockMemory' times it is 35 // called. This reduces the amount of memory used, but comes at the cost of 36 // not always having the most recent transactions. 37 HeaderMemory = build.Select(build.Var{ 38 Standard: 10000, 39 Dev: 500, 40 Testing: 50, 41 }).(int) 42 43 // MaxSourceBlockAge is the maximum amount of time that is allowed to 44 // elapse between generating source blocks. 45 MaxSourceBlockAge = build.Select(build.Var{ 46 Standard: 30 * time.Second, 47 Dev: 5 * time.Second, 48 Testing: 1 * time.Second, 49 }).(time.Duration) 50 ) 51 52 // splitSet defines a transaction set that can be added components-wise to a 53 // block. It's split because it doesn't necessarily represent the full set 54 // prpovided by the transaction pool. Splits can be sorted so that the largest 55 // and most valuable sets can be selected when picking transactions. 56 type splitSet struct { 57 averageFee types.Currency 58 size uint64 59 transactions []types.Transaction 60 } 61 62 type splitSetID int 63 64 // Miner struct contains all variables the miner needs 65 // in order to create and submit blocks. 66 type Miner struct { 67 // Module dependencies. 68 cs modules.ConsensusSet 69 tpool modules.TransactionPool 70 wallet modules.Wallet 71 72 // BlockManager variables. Becaues blocks are large, one block is used to 73 // make many headers which can be used by miners. Headers include an 74 // arbitrary data transaction (appended to the block) to make the merkle 75 // roots unique (preventing miners from doing redundant work). Every N 76 // requests or M seconds, a new block is used to create headers. 77 // 78 // Only 'blocksMemory' blocks are kept in memory at a time, which 79 // keeps ram usage reasonable. Miners may request many headers in parallel, 80 // and thus may be working on different blocks. When they submit the solved 81 // header to the block manager, the rest of the block needs to be found in 82 // a lookup. 83 blockMem map[types.BlockHeader]*types.Block // Mappings from headers to the blocks they are derived from. 84 arbDataMem map[types.BlockHeader][crypto.EntropySize]byte // Mappings from the headers to their unique arb data. 85 headerMem []types.BlockHeader // A circular list of headers that have been given out from the api recently. 86 sourceBlock *types.Block // The block from which new headers for mining are created. 87 sourceBlockTime time.Time // How long headers have been using the same block (different from 'recent block'). 88 memProgress int // The index of the most recent header used in headerMem. 89 90 // Transaction pool variables. 91 fullSets map[modules.TransactionSetID][]int 92 blockMapHeap *mapHeap 93 overflowMapHeap *mapHeap 94 setCounter int 95 splitSets map[splitSetID]*splitSet 96 splitSetIDFromTxID map[types.TransactionID]splitSetID 97 unsolvedBlockIndex map[types.TransactionID]int 98 99 // CPUMiner variables. 100 miningOn bool // indicates if the miner is supposed to be running 101 mining bool // indicates if the miner is actually running 102 hashRate int64 // indicates hashes per second 103 104 // Utils 105 log *persist.Logger 106 mu sync.RWMutex 107 persist persistence 108 persistDir string 109 // tg signals the Miner's goroutines to shut down and blocks until all 110 // goroutines have exited before returning from Close(). 111 tg siasync.ThreadGroup 112 } 113 114 // startupRescan will rescan the blockchain in the event that the miner 115 // persistence layer has become desynchronized from the consensus persistence 116 // layer. This might happen if a user replaces any of the folders with backups 117 // or deletes any of the folders. 118 func (m *Miner) startupRescan() error { 119 // Reset all of the variables that have relevance to the consensus set. The 120 // operations are wrapped by an anonymous function so that the locking can 121 // be handled using a defer statement. 122 err := func() error { 123 m.mu.Lock() 124 defer m.mu.Unlock() 125 126 m.log.Println("Performing a miner rescan.") 127 m.persist.RecentChange = modules.ConsensusChangeBeginning 128 m.persist.Height = 0 129 m.persist.Target = types.Target{} 130 return m.saveSync() 131 }() 132 if err != nil { 133 return err 134 } 135 136 // Subscribe to the consensus set. This is a blocking call that will not 137 // return until the miner has fully caught up to the current block. 138 err = m.cs.ConsensusSetSubscribe(m, modules.ConsensusChangeBeginning, m.tg.StopChan()) 139 if err != nil { 140 return err 141 } 142 m.tg.OnStop(func() { 143 m.cs.Unsubscribe(m) 144 }) 145 return nil 146 } 147 148 // New returns a ready-to-go miner that is not mining. 149 func New(cs modules.ConsensusSet, tpool modules.TransactionPool, w modules.Wallet, persistDir string) (*Miner, error) { 150 // Create the miner and its dependencies. 151 if cs == nil { 152 return nil, errNilCS 153 } 154 if tpool == nil { 155 return nil, errNilTpool 156 } 157 if w == nil { 158 return nil, errNilWallet 159 } 160 161 // Assemble the miner. The miner is assembled without an address because 162 // the wallet is likely not unlocked yet. The miner will grab an address 163 // after the miner is unlocked (this must be coded manually for each 164 // function that potentially requires the miner to have an address. 165 m := &Miner{ 166 cs: cs, 167 tpool: tpool, 168 wallet: w, 169 170 blockMem: make(map[types.BlockHeader]*types.Block), 171 arbDataMem: make(map[types.BlockHeader][crypto.EntropySize]byte), 172 headerMem: make([]types.BlockHeader, HeaderMemory), 173 174 fullSets: make(map[modules.TransactionSetID][]int), 175 splitSets: make(map[splitSetID]*splitSet), 176 blockMapHeap: &mapHeap{ 177 selectID: make(map[splitSetID]*mapElement), 178 data: nil, 179 minHeap: true, 180 }, 181 overflowMapHeap: &mapHeap{ 182 selectID: make(map[splitSetID]*mapElement), 183 data: nil, 184 minHeap: false, 185 }, 186 splitSetIDFromTxID: make(map[types.TransactionID]splitSetID), 187 unsolvedBlockIndex: make(map[types.TransactionID]int), 188 189 persistDir: persistDir, 190 } 191 192 err := m.initPersist() 193 if err != nil { 194 return nil, errors.New("miner persistence startup failed: " + err.Error()) 195 } 196 197 err = m.cs.ConsensusSetSubscribe(m, m.persist.RecentChange, m.tg.StopChan()) 198 if err == modules.ErrInvalidConsensusChangeID { 199 // Perform a rescan of the consensus set if the change id is not found. 200 // The id will only be not found if there has been desynchronization 201 // between the miner and the consensus package. 202 err = m.startupRescan() 203 if err != nil { 204 return nil, errors.New("miner startup failed - rescanning failed: " + err.Error()) 205 } 206 } else if err != nil { 207 return nil, errors.New("miner subscription failed: " + err.Error()) 208 } 209 m.tg.OnStop(func() { 210 m.cs.Unsubscribe(m) 211 }) 212 213 m.tpool.TransactionPoolSubscribe(m) 214 m.tg.OnStop(func() { 215 m.tpool.Unsubscribe(m) 216 }) 217 218 // Save after synchronizing with consensus 219 err = m.saveSync() 220 if err != nil { 221 return nil, errors.New("miner could not save during startup: " + err.Error()) 222 } 223 224 return m, nil 225 } 226 227 // Close terminates all ongoing processes involving the miner, enabling garbage 228 // collection. 229 func (m *Miner) Close() error { 230 if err := m.tg.Stop(); err != nil { 231 return err 232 } 233 234 m.mu.Lock() 235 defer m.mu.Unlock() 236 237 m.cs.Unsubscribe(m) 238 239 var errs []error 240 if err := m.saveSync(); err != nil { 241 errs = append(errs, fmt.Errorf("save failed: %v", err)) 242 } 243 if err := m.log.Close(); err != nil { 244 errs = append(errs, fmt.Errorf("log.Close failed: %v", err)) 245 } 246 return build.JoinErrors(errs, "; ") 247 } 248 249 // checkAddress checks that the miner has an address, fetching an address from 250 // the wallet if not. 251 func (m *Miner) checkAddress() error { 252 addrs, err := m.wallet.AllAddresses() 253 if err != nil { 254 return err 255 } 256 hasAddr := false 257 for _, addr := range addrs { 258 if m.persist.Address == addr { 259 hasAddr = true 260 break 261 } 262 } 263 if m.persist.Address != (types.UnlockHash{}) && hasAddr { 264 return nil 265 } 266 uc, err := m.wallet.NextAddress() 267 if err != nil { 268 return err 269 } 270 m.persist.Address = uc.UnlockHash() 271 return nil 272 } 273 274 // BlocksMined returns the number of good blocks and stale blocks that have 275 // been mined by the miner. 276 func (m *Miner) BlocksMined() (goodBlocks, staleBlocks int) { 277 if err := m.tg.Add(); err != nil { 278 build.Critical(err) 279 } 280 defer m.tg.Done() 281 282 m.mu.Lock() 283 defer m.mu.Unlock() 284 285 for _, blockID := range m.persist.BlocksFound { 286 if m.cs.InCurrentPath(blockID) { 287 goodBlocks++ 288 } else { 289 staleBlocks++ 290 } 291 } 292 return 293 }