gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/miningpool/miningpool.go (about) 1 // Package pool is an implementation of the pool module, and is responsible for 2 // creating a mining pool, accepting incoming potential block solutions and 3 // rewarding the submitters proportionally for their shares. 4 package pool 5 6 import ( 7 "bytes" 8 "database/sql" 9 "encoding/binary" 10 "encoding/hex" 11 "errors" 12 "fmt" 13 "math/rand" 14 "net" 15 "path/filepath" 16 "sync/atomic" 17 "time" 18 19 "github.com/sasha-s/go-deadlock" 20 21 "gitlab.com/NebulousLabs/threadgroup" 22 "gitlab.com/SiaPrime/SiaPrime/build" 23 "gitlab.com/SiaPrime/SiaPrime/config" 24 "gitlab.com/SiaPrime/SiaPrime/crypto" 25 "gitlab.com/SiaPrime/SiaPrime/modules" 26 "gitlab.com/SiaPrime/SiaPrime/persist" 27 "gitlab.com/SiaPrime/SiaPrime/types" 28 29 // blank to load the sql driver for mysql 30 _ "github.com/go-sql-driver/mysql" 31 ) 32 33 var ( 34 // persistMetadata is the header that gets written to the persist file, and is 35 // used to recognize other persist files. 36 persistMetadata = persist.Metadata{ 37 Header: "Sia Pool", 38 Version: "0.0.1", 39 } 40 41 // errPoolClosed gets returned when a call is rejected due to the pool 42 // having been closed. 43 errPoolClosed = errors.New("call is disabled because the pool is closed") 44 45 // Nil dependency errors. 46 errNilCS = errors.New("pool cannot use a nil consensus state") 47 errNilTpool = errors.New("pool cannot use a nil transaction pool") 48 errNilGW = errors.New("pool cannot use a nil gateway") 49 // errNilWallet = errors.New("pool cannot use a nil wallet") 50 51 // Required settings to run pool 52 errNoAddressSet = errors.New("pool operators address must be set") 53 54 running bool // indicates if the mining pool is actually running 55 hashRate int64 // indicates hashes per second 56 // HeaderMemory is the number of previous calls to 'header' 57 // that are remembered. Additionally, 'header' will only poll for a 58 // new block every 'headerMemory / blockMemory' times it is 59 // called. This reduces the amount of memory used, but comes at the cost of 60 // not always having the most recent transactions. 61 HeaderMemory = build.Select(build.Var{ 62 Standard: 10000, 63 Dev: 500, 64 Testing: 50, 65 }).(int) 66 67 // BlockMemory is the maximum number of blocks the miner will store 68 // Blocks take up to 2 megabytes of memory, which is why this number is 69 // limited. 70 BlockMemory = build.Select(build.Var{ 71 Standard: 50, 72 Dev: 10, 73 Testing: 5, 74 }).(int) 75 76 // MaxSourceBlockAge is the maximum amount of time that is allowed to 77 // elapse between generating source blocks. 78 MaxSourceBlockAge = build.Select(build.Var{ 79 Standard: 30 * time.Second, 80 Dev: 5 * time.Second, 81 Testing: 1 * time.Second, 82 }).(time.Duration) 83 84 // ShiftDuration is how often we commit mining data to persistent 85 // storage when a block hasn't been found. 86 ShiftDuration = build.Select(build.Var{ 87 Standard: 20 * time.Second, 88 Dev: 20 * time.Second, 89 Testing: 1 * time.Second, 90 }).(time.Duration) 91 ) 92 93 // splitSet defines a transaction set that can be added componenet-wise to a 94 // block. It's split because it doesn't necessarily represent the full set 95 // prpovided by the transaction pool. Splits can be sorted so that the largest 96 // and most valuable sets can be selected when picking transactions. 97 type splitSet struct { 98 averageFee types.Currency 99 size uint64 100 transactions []types.Transaction 101 } 102 103 type splitSetID int 104 105 // A Pool contains all the fields necessary for storing status for clients and 106 // performing the evaluation and rewarding on submitted shares 107 type Pool struct { 108 // BlockManager variables. Becaues blocks are large, one block is used to 109 // make many headers which can be used by miners. Headers include an 110 // arbitrary data transaction (appended to the block) to make the merkle 111 // roots unique (preventing miners from doing redundant work). Every N 112 // requests or M seconds, a new block is used to create headers. 113 // 114 // Only 'blocksMemory' blocks are kept in memory at a time, which 115 // keeps ram usage reasonable. Miners may request many headers in parallel, 116 // and thus may be working on different blocks. When they submit the solved 117 // header to the block manager, the rest of the block needs to be found in 118 // a lookup. 119 blockMem map[types.BlockHeader]*types.Block // Mappings from headers to the blocks they are derived from. 120 blockTxns *txnList // list of transactions that are supposed to be solved in the next block 121 arbDataMem map[types.BlockHeader][crypto.EntropySize]byte // Mappings from the headers to their unique arb data. 122 headerMem []types.BlockHeader // A circular list of headers that have been given out from the api recently. 123 sourceBlock types.Block // The block from which new headers for mining are created. 124 sourceBlockTime time.Time // How long headers have been using the same block (different from 'recent block'). 125 memProgress int // The index of the most recent header used in headerMem. 126 127 // Transaction pool variables. 128 fullSets map[modules.TransactionSetID][]int 129 blockMapHeap *mapHeap 130 overflowMapHeap *mapHeap 131 setCounter int 132 splitSets map[splitSetID]*splitSet 133 134 // Dependencies. 135 cs modules.ConsensusSet 136 tpool modules.TransactionPool 137 wallet modules.Wallet 138 gw modules.Gateway 139 dependencies 140 modules.StorageManager 141 142 // Pool ACID fields - these fields need to be updated in serial, ACID 143 // transactions. 144 announceConfirmed bool 145 secretKey crypto.SecretKey 146 // Pool transient fields - these fields are either determined at startup or 147 // otherwise are not critical to always be correct. 148 workingStatus modules.PoolWorkingStatus 149 connectabilityStatus modules.PoolConnectabilityStatus 150 151 // Utilities. 152 sqldb *sql.DB 153 listener net.Listener 154 log *persist.Logger 155 yiilog *persist.Logger 156 mu deadlock.RWMutex 157 dbConnectionMu deadlock.RWMutex 158 persistDir string 159 port string 160 tg threadgroup.ThreadGroup 161 persist persistence 162 dispatcher *Dispatcher 163 stratumID uint64 164 shiftID uint64 165 shiftChan chan bool 166 shiftTimestamp time.Time 167 clients map[string]*Client //client name to client pointer mapping 168 169 clientSetupMutex deadlock.Mutex 170 runningMutex deadlock.RWMutex 171 running bool 172 } 173 174 // startupRescan will rescan the blockchain in the event that the pool 175 // persistence layer has become desynchronized from the consensus persistence 176 // layer. This might happen if a user replaces any of the folders with backups 177 // or deletes any of the folders. 178 func (p *Pool) startupRescan() error { 179 // Reset all of the variables that have relevance to the consensus set. The 180 // operations are wrapped by an anonymous function so that the locking can 181 // be handled using a defer statement. 182 err := func() error { 183 // p.log.Debugf("Waiting to lock pool\n") 184 p.mu.Lock() 185 defer func() { 186 // p.log.Debugf("Unlocking pool\n") 187 p.mu.Unlock() 188 }() 189 190 p.log.Println("Performing a pool rescan.") 191 p.persist.SetRecentChange(modules.ConsensusChangeBeginning) 192 p.persist.SetBlockHeight(0) 193 p.persist.SetTarget(types.Target{}) 194 return p.saveSync() 195 }() 196 if err != nil { 197 return err 198 } 199 200 // Subscribe to the consensus set. This is a blocking call that will not 201 // return until the pool has fully caught up to the current block. 202 err = p.cs.ConsensusSetSubscribe(p, modules.ConsensusChangeBeginning, p.tg.StopChan()) 203 if err != nil { 204 return err 205 } 206 p.tg.OnStop(func() error { 207 p.cs.Unsubscribe(p) 208 return nil 209 }) 210 return nil 211 } 212 213 func (p *Pool) monitorShifts() { 214 p.shiftChan = make(chan bool, 1) 215 p.tg.Add() 216 defer p.tg.Done() 217 for { 218 select { 219 case <-p.shiftChan: 220 case <-time.After(ShiftDuration): 221 case <-p.tg.StopChan(): 222 return 223 } 224 p.log.Debugf("Shift change - end of shift %d\n", p.shiftID) 225 atomic.AddUint64(&p.shiftID, 1) 226 p.dispatcher.mu.RLock() 227 // TODO: switch to batched insert 228 for _, h := range p.dispatcher.handlers { 229 h.mu.RLock() 230 s := h.s.Shift() 231 h.mu.RUnlock() 232 go func(savingShift *Shift) { 233 if savingShift != nil { 234 savingShift.SaveShift() 235 } 236 }(s) 237 sh := p.newShift(h.s.CurrentWorker) 238 h.s.addShift(sh) 239 } 240 p.dispatcher.mu.RUnlock() 241 } 242 } 243 244 func (p *Pool) startServer() { 245 p.log.Printf(" Waiting for consensus synchronization...\n") 246 select { 247 // if we've received the stop message before this can even be spun up, just exit 248 case <-p.tg.StopChan(): 249 return 250 default: 251 } 252 p.tg.Add() 253 defer p.tg.Done() 254 for { 255 if p.cs.Synced() { 256 // If we're immediately synced upon subscription AND we never got ProcessConsensusChange 257 // calls (this happens when we start the server and our most recent change was the latest 258 // block in a synced chain), we will need to look at the top of the chain to set up our 259 // source block. So let's just always do that upon first sync. 260 finalBlock := p.cs.CurrentBlock() 261 parentID := finalBlock.ID() 262 p.mu.Lock() 263 p.newSourceBlock() 264 p.sourceBlock.ParentID = parentID 265 p.sourceBlock.Timestamp, _ = p.cs.MinimumValidChildTimestamp(parentID) 266 p.mu.Unlock() 267 268 p.log.Printf(" Starting Stratum Server\n") 269 270 port := fmt.Sprintf("%d", p.InternalSettings().PoolNetworkPort) 271 go p.dispatcher.ListenHandlers(port) 272 p.tg.OnStop(func() error { 273 if p.dispatcher.ln == nil { 274 //panic(errors.New("network not opened yet")) 275 } else { 276 p.dispatcher.ln.Close() 277 } 278 return nil 279 }) 280 return 281 } 282 time.Sleep(100 * time.Millisecond) 283 } 284 } 285 286 // newPool returns an initialized Pool, taking a set of dependencies as input. 287 // By making the dependencies an argument of the 'new' call, the pool can be 288 // mocked such that the dependencies can return unexpected errors or unique 289 // behaviors during testing, enabling easier testing of the failure modes of 290 // the Pool. 291 func newPool(dependencies dependencies, cs modules.ConsensusSet, tpool modules.TransactionPool, gw modules.Gateway, wallet modules.Wallet, persistDir string, initConfig config.MiningPoolConfig) (*Pool, error) { 292 // Check that all the dependencies were provided. 293 if cs == nil { 294 return nil, errNilCS 295 } 296 if tpool == nil { 297 return nil, errNilTpool 298 } 299 if gw == nil { 300 return nil, errNilGW 301 } 302 303 // Create the pool object. 304 p := &Pool{ 305 cs: cs, 306 tpool: tpool, 307 gw: gw, 308 wallet: wallet, 309 dependencies: dependencies, 310 311 blockMem: make(map[types.BlockHeader]*types.Block), 312 blockTxns: newTxnList(), 313 arbDataMem: make(map[types.BlockHeader][crypto.EntropySize]byte), 314 headerMem: make([]types.BlockHeader, HeaderMemory), 315 316 fullSets: make(map[modules.TransactionSetID][]int), 317 splitSets: make(map[splitSetID]*splitSet), 318 blockMapHeap: &mapHeap{ 319 selectID: make(map[splitSetID]*mapElement), 320 data: nil, 321 minHeap: true, 322 }, 323 overflowMapHeap: &mapHeap{ 324 selectID: make(map[splitSetID]*mapElement), 325 data: nil, 326 minHeap: false, 327 }, 328 329 persistDir: persistDir, 330 stratumID: rand.Uint64(), 331 clients: make(map[string]*Client), 332 } 333 var err error 334 335 // Create the perist directory if it does not yet exist. 336 err = dependencies.mkdirAll(p.persistDir, 0700) 337 if err != nil { 338 return nil, err 339 } 340 341 // Initialize the logger, and set up the stop call that will close the 342 // logger. 343 // fmt.Println("log path:", filepath.Join(p.persistDir, logFile)) 344 p.log, err = dependencies.newLogger(filepath.Join(p.persistDir, logFile)) 345 if err != nil { 346 return nil, err 347 } 348 p.yiilog, err = dependencies.newLogger(filepath.Join(p.persistDir, yiilogFile)) 349 if err != nil { 350 return nil, err 351 } 352 p.tg.AfterStop(func() error { 353 err = p.log.Close() 354 if err != nil { 355 // State of the logger is uncertain, a Println will have to 356 // suffice. 357 fmt.Println("Error when closing the logger:", err) 358 } 359 return err 360 }) 361 362 // Load the prior persistence structures, and configure the pool to save 363 // before shutting down. 364 err = p.load() 365 if err != nil { 366 return nil, err 367 } 368 p.setPoolSettings(initConfig) 369 370 p.tg.AfterStop(func() error { 371 p.mu.Lock() 372 err = p.saveSync() 373 p.mu.Unlock() 374 if err != nil { 375 p.log.Println("Could not save pool upon shutdown:", err) 376 } 377 return err 378 }) 379 380 p.newDbConnection() 381 382 // clean old worker records for this stratum server just in case we didn't 383 // shutdown cleanly 384 err = p.DeleteAllWorkerRecords() 385 if err != nil { 386 return nil, errors.New("Failed to clean database: " + err.Error()) 387 } 388 389 p.tg.OnStop(func() error { 390 p.DeleteAllWorkerRecords() 391 p.sqldb.Close() 392 return nil 393 }) 394 395 // grab our consensus set data 396 err = p.cs.ConsensusSetSubscribe(p, p.persist.RecentChange, p.tg.StopChan()) 397 if err == modules.ErrInvalidConsensusChangeID { 398 // Perform a rescan of the consensus set if the change id is not found. 399 // The id will only be not found if there has been desynchronization 400 // between the miner and the consensus package. 401 err = p.startupRescan() 402 if err != nil { 403 return nil, errors.New("mining pool startup failed - rescanning failed: " + err.Error()) 404 } 405 } else if err != nil { 406 return nil, errors.New("mining pool subscription failed: " + err.Error()) 407 } 408 409 // spin up a go routine to handle shift changes. 410 go p.monitorShifts() 411 412 p.tg.OnStop(func() error { 413 p.cs.Unsubscribe(p) 414 return nil 415 }) 416 417 p.tpool.TransactionPoolSubscribe(p) 418 p.tg.OnStop(func() error { 419 p.tpool.Unsubscribe(p) 420 return nil 421 }) 422 423 p.runningMutex.Lock() 424 p.dispatcher = &Dispatcher{handlers: make(map[string]*Handler), mu: deadlock.RWMutex{}, p: p} 425 p.dispatcher.log, _ = dependencies.newLogger(filepath.Join(p.persistDir, "stratum.log")) 426 p.running = true 427 p.runningMutex.Unlock() 428 429 go p.startServer() 430 431 return p, nil 432 } 433 434 // New returns an initialized Pool. 435 func New(cs modules.ConsensusSet, tpool modules.TransactionPool, gw modules.Gateway, wallet modules.Wallet, persistDir string, initConfig config.MiningPoolConfig) (*Pool, error) { 436 return newPool(productionDependencies{}, cs, tpool, gw, wallet, persistDir, initConfig) 437 } 438 439 // Close shuts down the pool. 440 func (p *Pool) Close() error { 441 p.log.Println("Closing pool") 442 //defer func () {}() 443 return p.tg.Stop() 444 } 445 446 // SetInternalSettings updates the pool's internal PoolInternalSettings object. 447 func (p *Pool) SetInternalSettings(settings modules.PoolInternalSettings) error { 448 p.mu.Lock() 449 defer p.mu.Unlock() 450 451 err := p.tg.Add() 452 if err != nil { 453 return err 454 } 455 defer p.tg.Done() 456 457 // The pool should not be open for business if it does not have an 458 // unlock hash. 459 err = p.checkAddress() 460 if err != nil { 461 return errors.New("internal settings not updated, no operator wallet set: " + err.Error()) 462 } 463 464 p.persist.SetSettings(settings) 465 p.persist.SetRevisionNumber(p.persist.GetRevisionNumber() + 1) 466 467 err = p.saveSync() 468 if err != nil { 469 return errors.New("internal settings updated, but failed saving to disk: " + err.Error()) 470 } 471 return nil 472 } 473 474 // InternalSettings returns the settings of a pool. 475 func (p *Pool) InternalSettings() modules.PoolInternalSettings { 476 return p.persist.GetSettings() 477 } 478 479 // checkAddress checks that the miner has an address, fetching an address from 480 // the wallet if not. 481 func (p *Pool) checkAddress() error { 482 if p.InternalSettings().PoolWallet == (types.UnlockHash{}) { 483 return errNoAddressSet 484 } 485 return nil 486 } 487 488 // Client returns the client with the specified name that has been stored in 489 // memory 490 func (p *Pool) Client(name string) *Client { 491 p.mu.RLock() 492 defer p.mu.RUnlock() 493 494 return p.clients[name] 495 } 496 497 // AddClient stores the client with the specified name into memory 498 func (p *Pool) AddClient(c *Client) { 499 p.mu.Lock() 500 defer p.mu.Unlock() 501 502 p.clients[c.Name()] = c 503 504 } 505 506 // newStratumID returns a function pointer to a unique ID generator used 507 // for more the unique IDs within the Stratum protocol 508 func (p *Pool) newStratumID() (f func() uint64) { 509 f = func() uint64 { 510 // p.log.Debugf("Waiting to lock pool\n") 511 p.mu.Lock() 512 defer func() { 513 // p.log.Debugf("Unlocking pool\n") 514 p.mu.Unlock() 515 }() 516 atomic.AddUint64(&p.stratumID, 1) 517 return p.stratumID 518 } 519 return 520 } 521 522 func (p *Pool) coinB1() types.Transaction { 523 s := fmt.Sprintf("\000 Software: siad-miningpool-module v%d.%02d\nPool name: \"%s\" \000", MajorVersion, MinorVersion, p.InternalSettings().PoolName) 524 if ((len(modules.PrefixNonSia[:]) + len(s)) % 2) != 0 { 525 // odd length, add extra null 526 s = s + "\000" 527 } 528 cb := make([]byte, len(modules.PrefixNonSia[:])+len(s)) // represents the bytes appended later 529 n := copy(cb, modules.PrefixNonSia[:]) 530 copy(cb[n:], s) 531 return types.Transaction{ 532 ArbitraryData: [][]byte{cb}, 533 } 534 } 535 536 func (p *Pool) coinB1Txn() string { 537 coinbaseTxn := p.coinB1() 538 buf := new(bytes.Buffer) 539 coinbaseTxn.MarshalSiaNoSignatures(buf) 540 b := buf.Bytes() 541 // extranonce1 and extranonce2 are 4 bytes each, and they will be part of the 542 // arbitrary transaction via the arbitrary data field. when the arbitrary data 543 // field is marshalled, the length of the arbitrary data must be specified. thus we 544 // leave 8 bytes for the necessary 2 extranonce fields. The position here is determined 545 // by the format specified in the MarshalSiaNoSignatures function. 546 binary.LittleEndian.PutUint64(b[72:87], binary.LittleEndian.Uint64(b[72:87])+8) 547 return hex.EncodeToString(b) 548 } 549 550 func (p *Pool) coinB2() string { 551 return "0000000000000000" 552 } 553 554 // NumConnections returns the number of tcp connections from clients the pool 555 // currently has open 556 func (p *Pool) NumConnections() int { 557 p.runningMutex.RLock() 558 defer p.runningMutex.RUnlock() 559 if p.running { 560 return p.dispatcher.NumConnections() 561 } 562 return 0 563 } 564 565 // NumConnectionsOpened returns the total number of tcp connections from clients the 566 // pool has opened since startup 567 func (p *Pool) NumConnectionsOpened() uint64 { 568 p.runningMutex.RLock() 569 defer p.runningMutex.RUnlock() 570 if p.running { 571 return p.dispatcher.NumConnectionsOpened() 572 } 573 return 0 574 }