github.com/dominant-strategies/go-quai@v0.28.2/consensus/progpow/progpow.go (about) 1 package progpow 2 3 import ( 4 "errors" 5 "fmt" 6 "math/big" 7 "math/rand" 8 "os" 9 "path/filepath" 10 "reflect" 11 "runtime" 12 "strconv" 13 "sync" 14 "time" 15 "unsafe" 16 17 "github.com/dominant-strategies/go-quai/common" 18 "github.com/dominant-strategies/go-quai/common/hexutil" 19 "github.com/dominant-strategies/go-quai/consensus" 20 "github.com/dominant-strategies/go-quai/log" 21 "github.com/dominant-strategies/go-quai/metrics" 22 "github.com/dominant-strategies/go-quai/rpc" 23 mmap "github.com/edsrzf/mmap-go" 24 "github.com/hashicorp/golang-lru/simplelru" 25 ) 26 27 var ( 28 // sharedProgpow is a full instance that can be shared between multiple users. 29 sharedProgpow *Progpow 30 // algorithmRevision is the data structure version used for file naming. 31 algorithmRevision = 1 32 // dumpMagic is a dataset dump header to sanity check a data dump. 33 dumpMagic = []uint32{0xbaddcafe, 0xfee1dead} 34 ) 35 36 var ErrInvalidDumpMagic = errors.New("invalid dump magic") 37 38 func init() { 39 sharedConfig := Config{ 40 PowMode: ModeNormal, 41 } 42 sharedProgpow = New(sharedConfig, nil, false) 43 } 44 45 // isLittleEndian returns whether the local system is running in little or big 46 // endian byte order. 47 func isLittleEndian() bool { 48 n := uint32(0x01020304) 49 return *(*byte)(unsafe.Pointer(&n)) == 0x04 50 } 51 52 // memoryMap tries to memory map a file of uint32s for read only access. 53 func memoryMap(path string, lock bool) (*os.File, mmap.MMap, []uint32, error) { 54 file, err := os.OpenFile(path, os.O_RDONLY, 0644) 55 if err != nil { 56 return nil, nil, nil, err 57 } 58 mem, buffer, err := memoryMapFile(file, false) 59 if err != nil { 60 file.Close() 61 return nil, nil, nil, err 62 } 63 for i, magic := range dumpMagic { 64 if buffer[i] != magic { 65 mem.Unmap() 66 file.Close() 67 return nil, nil, nil, ErrInvalidDumpMagic 68 } 69 } 70 if lock { 71 if err := mem.Lock(); err != nil { 72 mem.Unmap() 73 file.Close() 74 return nil, nil, nil, err 75 } 76 } 77 return file, mem, buffer[len(dumpMagic):], err 78 } 79 80 // memoryMapFile tries to memory map an already opened file descriptor. 81 func memoryMapFile(file *os.File, write bool) (mmap.MMap, []uint32, error) { 82 // Try to memory map the file 83 flag := mmap.RDONLY 84 if write { 85 flag = mmap.RDWR 86 } 87 mem, err := mmap.Map(file, flag, 0) 88 if err != nil { 89 return nil, nil, err 90 } 91 // Yay, we managed to memory map the file, here be dragons 92 header := *(*reflect.SliceHeader)(unsafe.Pointer(&mem)) 93 header.Len /= 4 94 header.Cap /= 4 95 96 return mem, *(*[]uint32)(unsafe.Pointer(&header)), nil 97 } 98 99 // memoryMapAndGenerate tries to memory map a temporary file of uint32s for write 100 // access, fill it with the data from a generator and then move it into the final 101 // path requested. 102 func memoryMapAndGenerate(path string, size uint64, lock bool, generator func(buffer []uint32)) (*os.File, mmap.MMap, []uint32, error) { 103 // Ensure the data folder exists 104 if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { 105 return nil, nil, nil, err 106 } 107 // Create a huge temporary empty file to fill with data 108 temp := path + "." + strconv.Itoa(rand.Int()) 109 110 dump, err := os.Create(temp) 111 if err != nil { 112 return nil, nil, nil, err 113 } 114 if err = dump.Truncate(int64(len(dumpMagic))*4 + int64(size)); err != nil { 115 return nil, nil, nil, err 116 } 117 // Memory map the file for writing and fill it with the generator 118 mem, buffer, err := memoryMapFile(dump, true) 119 if err != nil { 120 dump.Close() 121 return nil, nil, nil, err 122 } 123 copy(buffer, dumpMagic) 124 125 data := buffer[len(dumpMagic):] 126 generator(data) 127 128 if err := mem.Unmap(); err != nil { 129 return nil, nil, nil, err 130 } 131 if err := dump.Close(); err != nil { 132 return nil, nil, nil, err 133 } 134 if err := os.Rename(temp, path); err != nil { 135 return nil, nil, nil, err 136 } 137 return memoryMap(path, lock) 138 } 139 140 // Mode defines the type and amount of PoW verification a progpow engine makes. 141 type Mode uint 142 143 const ( 144 ModeNormal Mode = iota 145 ModeShared 146 ModeTest 147 ModeFake 148 ModeFullFake 149 ) 150 151 // Config are the configuration parameters of the progpow. 152 type Config struct { 153 PowMode Mode 154 155 CacheDir string 156 CachesInMem int 157 CachesOnDisk int 158 CachesLockMmap bool 159 DurationLimit *big.Int 160 GasCeil uint64 161 MinDifficulty *big.Int 162 163 // When set, notifications sent by the remote sealer will 164 // be block header JSON objects instead of work package arrays. 165 NotifyFull bool 166 167 Log *log.Logger `toml:"-"` 168 } 169 170 // Progpow is a proof-of-work consensus engine using the blake3 hash algorithm 171 type Progpow struct { 172 config Config 173 174 caches *lru // In memory caches to avoid regenerating too often 175 176 // Mining related fields 177 rand *rand.Rand // Properly seeded random source for nonces 178 threads int // Number of threads to mine on if mining 179 update chan struct{} // Notification channel to update mining parameters 180 hashrate metrics.Meter // Meter tracking the average hashrate 181 remote *remoteSealer 182 183 // The fields below are hooks for testing 184 shared *Progpow // Shared PoW verifier to avoid cache regeneration 185 fakeFail uint64 // Block number which fails PoW check even in fake mode 186 fakeDelay time.Duration // Time delay to sleep for before returning from verify 187 188 lock sync.Mutex // Ensures thread safety for the in-memory caches and mining fields 189 closeOnce sync.Once // Ensures exit channel will not be closed twice. 190 } 191 192 // New creates a full sized progpow PoW scheme and starts a background thread for 193 // remote mining, also optionally notifying a batch of remote services of new work 194 // packages. 195 func New(config Config, notify []string, noverify bool) *Progpow { 196 if config.Log == nil { 197 config.Log = &log.Log 198 } 199 if config.CachesInMem <= 0 { 200 config.Log.Warn("One ethash cache must always be in memory", "requested", config.CachesInMem) 201 config.CachesInMem = 1 202 } 203 if config.CacheDir != "" && config.CachesOnDisk > 0 { 204 config.Log.Info("Disk storage enabled for ethash caches", "dir", config.CacheDir, "count", config.CachesOnDisk) 205 } 206 progpow := &Progpow{ 207 config: config, 208 caches: newlru("cache", config.CachesInMem, newCache), 209 update: make(chan struct{}), 210 hashrate: metrics.NewMeterForced(), 211 } 212 if config.PowMode == ModeShared { 213 progpow.shared = sharedProgpow 214 } 215 progpow.remote = startRemoteSealer(progpow, notify, noverify) 216 return progpow 217 } 218 219 // NewTester creates a small sized progpow PoW scheme useful only for testing 220 // purposes. 221 func NewTester(notify []string, noverify bool) *Progpow { 222 return New(Config{PowMode: ModeTest}, notify, noverify) 223 } 224 225 // NewFaker creates a progpow consensus engine with a fake PoW scheme that accepts 226 // all blocks' seal as valid, though they still have to conform to the Quai 227 // consensus rules. 228 func NewFaker() *Progpow { 229 return &Progpow{ 230 config: Config{ 231 PowMode: ModeFake, 232 Log: &log.Log, 233 }, 234 } 235 } 236 237 // NewFakeFailer creates a progpow consensus engine with a fake PoW scheme that 238 // accepts all blocks as valid apart from the single one specified, though they 239 // still have to conform to the Quai consensus rules. 240 func NewFakeFailer(fail uint64) *Progpow { 241 return &Progpow{ 242 config: Config{ 243 PowMode: ModeFake, 244 Log: &log.Log, 245 }, 246 fakeFail: fail, 247 } 248 } 249 250 // NewFakeDelayer creates a progpow consensus engine with a fake PoW scheme that 251 // accepts all blocks as valid, but delays verifications by some time, though 252 // they still have to conform to the Quai consensus rules. 253 func NewFakeDelayer(delay time.Duration) *Progpow { 254 return &Progpow{ 255 config: Config{ 256 PowMode: ModeFake, 257 Log: &log.Log, 258 }, 259 fakeDelay: delay, 260 } 261 } 262 263 // NewFullFaker creates an progpow consensus engine with a full fake scheme that 264 // accepts all blocks as valid, without checking any consensus rules whatsoever. 265 func NewFullFaker() *Progpow { 266 return &Progpow{ 267 config: Config{ 268 PowMode: ModeFullFake, 269 Log: &log.Log, 270 }, 271 } 272 } 273 274 // NewShared creates a full sized progpow PoW shared between all requesters running 275 // in the same process. 276 func NewShared() *Progpow { 277 return &Progpow{shared: sharedProgpow} 278 } 279 280 // Close closes the exit channel to notify all backend threads exiting. 281 func (progpow *Progpow) Close() error { 282 progpow.closeOnce.Do(func() { 283 // Short circuit if the exit channel is not allocated. 284 if progpow.remote == nil { 285 return 286 } 287 close(progpow.remote.requestExit) 288 <-progpow.remote.exitCh 289 }) 290 return nil 291 } 292 293 // lru tracks caches or datasets by their last use time, keeping at most N of them. 294 type lru struct { 295 what string 296 new func(epoch uint64) interface{} 297 mu sync.Mutex 298 // Items are kept in a LRU cache, but there is a special case: 299 // We always keep an item for (highest seen epoch) + 1 as the 'future item'. 300 cache *simplelru.LRU 301 future uint64 302 futureItem interface{} 303 } 304 305 // newlru create a new least-recently-used cache for either the verification caches 306 // or the mining datasets. 307 func newlru(what string, maxItems int, new func(epoch uint64) interface{}) *lru { 308 if maxItems <= 0 { 309 maxItems = 1 310 } 311 cache, _ := simplelru.NewLRU(maxItems, func(key, value interface{}) { 312 log.Trace("Evicted ethash "+what, "epoch", key) 313 }) 314 return &lru{what: what, new: new, cache: cache} 315 } 316 317 // get retrieves or creates an item for the given epoch. The first return value is always 318 // non-nil. The second return value is non-nil if lru thinks that an item will be useful in 319 // the near future. 320 func (lru *lru) get(epoch uint64) (item, future interface{}) { 321 lru.mu.Lock() 322 defer lru.mu.Unlock() 323 324 // Get or create the item for the requested epoch. 325 item, ok := lru.cache.Get(epoch) 326 if !ok { 327 if lru.future > 0 && lru.future == epoch { 328 item = lru.futureItem 329 } else { 330 log.Trace("Requiring new ethash "+lru.what, "epoch", epoch) 331 item = lru.new(epoch) 332 } 333 lru.cache.Add(epoch, item) 334 } 335 // Update the 'future item' if epoch is larger than previously seen. 336 if epoch < maxEpoch-1 && lru.future < epoch+1 { 337 log.Trace("Requiring new future ethash "+lru.what, "epoch", epoch+1) 338 future = lru.new(epoch + 1) 339 lru.future = epoch + 1 340 lru.futureItem = future 341 } 342 return item, future 343 } 344 345 // cache wraps an ethash cache with some metadata to allow easier concurrent use. 346 type cache struct { 347 epoch uint64 // Epoch for which this cache is relevant 348 dump *os.File // File descriptor of the memory mapped cache 349 mmap mmap.MMap // Memory map itself to unmap before releasing 350 cache []uint32 // The actual cache data content (may be memory mapped) 351 cDag []uint32 // The cDag used by progpow. May be nil 352 once sync.Once // Ensures the cache is generated only once 353 } 354 355 // newCache creates a new ethash verification cache and returns it as a plain Go 356 // interface to be usable in an LRU cache. 357 func newCache(epoch uint64) interface{} { 358 return &cache{epoch: epoch} 359 } 360 361 // generate ensures that the cache content is generated before use. 362 func (c *cache) generate(dir string, limit int, lock bool, test bool) { 363 c.once.Do(func() { 364 size := cacheSize(c.epoch*epochLength + 1) 365 seed := seedHash(c.epoch*epochLength + 1) 366 if test { 367 size = 1024 368 } 369 // If we don't store anything on disk, generate and return. 370 if dir == "" { 371 c.cache = make([]uint32, size/4) 372 generateCache(c.cache, c.epoch, seed) 373 c.cDag = make([]uint32, progpowCacheWords) 374 generateCDag(c.cDag, c.cache, c.epoch) 375 return 376 } 377 // Disk storage is needed, this will get fancy 378 var endian string 379 if !isLittleEndian() { 380 endian = ".be" 381 } 382 path := filepath.Join(dir, fmt.Sprintf("cache-R%d-%x%s", algorithmRevision, seed[:8], endian)) 383 logger := log.New("epoch") 384 385 // We're about to mmap the file, ensure that the mapping is cleaned up when the 386 // cache becomes unused. 387 runtime.SetFinalizer(c, (*cache).finalizer) 388 389 // Try to load the file from disk and memory map it 390 var err error 391 c.dump, c.mmap, c.cache, err = memoryMap(path, lock) 392 if err == nil { 393 logger.Debug("Loaded old ethash cache from disk") 394 c.cDag = make([]uint32, progpowCacheWords) 395 generateCDag(c.cDag, c.cache, c.epoch) 396 return 397 } 398 logger.Debug("Failed to load old ethash cache", "err", err) 399 400 // No previous cache available, create a new cache file to fill 401 c.dump, c.mmap, c.cache, err = memoryMapAndGenerate(path, size, lock, func(buffer []uint32) { generateCache(buffer, c.epoch, seed) }) 402 if err != nil { 403 logger.Error("Failed to generate mapped ethash cache", "err", err) 404 405 c.cache = make([]uint32, size/4) 406 generateCache(c.cache, c.epoch, seed) 407 } 408 c.cDag = make([]uint32, progpowCacheWords) 409 generateCDag(c.cDag, c.cache, c.epoch) 410 // Iterate over all previous instances and delete old ones 411 for ep := int(c.epoch) - limit; ep >= 0; ep-- { 412 seed := seedHash(uint64(ep)*epochLength + 1) 413 path := filepath.Join(dir, fmt.Sprintf("cache-R%d-%x%s", algorithmRevision, seed[:8], endian)) 414 os.Remove(path) 415 } 416 }) 417 } 418 419 // finalizer unmaps the memory and closes the file. 420 func (c *cache) finalizer() { 421 if c.mmap != nil { 422 c.mmap.Unmap() 423 c.dump.Close() 424 c.mmap, c.dump = nil, nil 425 } 426 } 427 428 // cache tries to retrieve a verification cache for the specified block number 429 // by first checking against a list of in-memory caches, then against caches 430 // stored on disk, and finally generating one if none can be found. 431 func (progpow *Progpow) cache(block uint64) *cache { 432 epoch := block / epochLength 433 currentI, futureI := progpow.caches.get(epoch) 434 current := currentI.(*cache) 435 436 // Wait for generation finish. 437 current.generate(progpow.config.CacheDir, progpow.config.CachesOnDisk, progpow.config.CachesLockMmap, progpow.config.PowMode == ModeTest) 438 439 // If we need a new future cache, now's a good time to regenerate it. 440 if futureI != nil { 441 future := futureI.(*cache) 442 go future.generate(progpow.config.CacheDir, progpow.config.CachesOnDisk, progpow.config.CachesLockMmap, progpow.config.PowMode == ModeTest) 443 } 444 return current 445 } 446 447 // Threads returns the number of mining threads currently enabled. This doesn't 448 // necessarily mean that mining is running! 449 func (progpow *Progpow) Threads() int { 450 progpow.lock.Lock() 451 defer progpow.lock.Unlock() 452 453 return progpow.threads 454 } 455 456 // SetThreads updates the number of mining threads currently enabled. Calling 457 // this method does not start mining, only sets the thread count. If zero is 458 // specified, the miner will use all cores of the machine. Setting a thread 459 // count below zero is allowed and will cause the miner to idle, without any 460 // work being done. 461 func (progpow *Progpow) SetThreads(threads int) { 462 progpow.lock.Lock() 463 defer progpow.lock.Unlock() 464 465 if progpow.shared != nil { 466 // If we're running a shared PoW, set the thread count on that instead 467 progpow.shared.SetThreads(threads) 468 } else { 469 // Update the threads and ping any running seal to pull in any changes 470 progpow.threads = threads 471 select { 472 case progpow.update <- struct{}{}: 473 default: 474 } 475 } 476 } 477 478 // Hashrate implements PoW, returning the measured rate of the search invocations 479 // per second over the last minute. 480 // Note the returned hashrate includes local hashrate, but also includes the total 481 // hashrate of all remote miner. 482 func (progpow *Progpow) Hashrate() float64 { 483 // Short circuit if we are run the progpow in normal/test mode. 484 if progpow.config.PowMode != ModeNormal && progpow.config.PowMode != ModeTest { 485 return progpow.hashrate.Rate1() 486 } 487 var res = make(chan uint64, 1) 488 489 select { 490 case progpow.remote.fetchRateCh <- res: 491 case <-progpow.remote.exitCh: 492 // Return local hashrate only if progpow is stopped. 493 return progpow.hashrate.Rate1() 494 } 495 496 // Gather total submitted hash rate of remote sealers. 497 return progpow.hashrate.Rate1() + float64(<-res) 498 } 499 500 // SubmitHashrate can be used for remote miners to submit their hash rate. 501 // This enables the node to report the combined hash rate of all miners 502 // which submit work through this node. 503 // 504 // It accepts the miner hash rate and an identifier which must be unique 505 // between nodes. 506 func (progpow *Progpow) SubmitHashrate(rate hexutil.Uint64, id common.Hash) bool { 507 if progpow.remote == nil { 508 return false 509 } 510 511 var done = make(chan struct{}, 1) 512 select { 513 case progpow.remote.submitRateCh <- &hashrate{done: done, rate: uint64(rate), id: id}: 514 case <-progpow.remote.exitCh: 515 return false 516 } 517 518 // Block until hash rate submitted successfully. 519 <-done 520 return true 521 } 522 523 // APIs implements consensus.Engine, returning the user facing RPC APIs. 524 func (progpow *Progpow) APIs(chain consensus.ChainHeaderReader) []rpc.API { 525 // In order to ensure backward compatibility, we exposes progpow RPC APIs 526 // to both eth and progpow namespaces. 527 return []rpc.API{ 528 { 529 Namespace: "eth", 530 Version: "1.0", 531 Service: &API{progpow}, 532 Public: true, 533 }, 534 { 535 Namespace: "progpow", 536 Version: "1.0", 537 Service: &API{progpow}, 538 Public: true, 539 }, 540 } 541 }