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