github.com/deroproject/derosuite@v2.1.6-1.0.20200307070847-0f2e589c7a2b+incompatible/cmd/dero-miner/miner.go (about) 1 package main 2 3 import "io" 4 import "os" 5 import "fmt" 6 import "time" 7 import "crypto/rand" 8 import "sync" 9 import "runtime" 10 import "net" 11 import "net/http" 12 import "math/big" 13 import "path/filepath" 14 import "encoding/hex" 15 import "encoding/binary" 16 import "os/signal" 17 import "sync/atomic" 18 import "strings" 19 import "strconv" 20 21 import "github.com/deroproject/derosuite/config" 22 import "github.com/deroproject/derosuite/globals" 23 24 import "github.com/deroproject/derosuite/crypto" 25 import "github.com/deroproject/derosuite/astrobwt" 26 import "github.com/deroproject/derosuite/structures" 27 import "github.com/deroproject/derosuite/cryptonight" 28 29 import log "github.com/sirupsen/logrus" 30 import "github.com/ybbus/jsonrpc" 31 32 import "github.com/romana/rlog" 33 import "github.com/chzyer/readline" 34 import "github.com/docopt/docopt-go" 35 36 var rpcClient *jsonrpc.RPCClient 37 var netClient *http.Client 38 var mutex sync.RWMutex 39 var job structures.GetBlockTemplate_Result 40 var maxdelay int = 10000 41 var threads int 42 var iterations int = 100 43 var max_pow_size int = 819200 //astrobwt.MAX_LENGTH 44 var wallet_address string 45 var daemon_rpc_address string 46 47 var counter uint64 48 var hash_rate uint64 49 var Difficulty uint64 50 var our_height int64 51 52 var block_counter int 53 54 var command_line string = `dero-miner 55 DERO CPU Miner for AstroBWT. 56 ONE CPU, ONE VOTE. 57 http://wiki.dero.io 58 59 Usage: 60 dero-miner --wallet-address=<wallet_address> [--daemon-rpc-address=<http://127.0.0.1:20206>] [--mining-threads=<threads>] [--max-pow-size=1120] [--testnet] [--debug] 61 dero-miner --bench [--max-pow-size=1120] 62 dero-miner -h | --help 63 dero-miner --version 64 65 Options: 66 -h --help Show this screen. 67 --version Show version. 68 --bench Run benchmark mode. 69 --daemon-rpc-address=<http://127.0.0.1:20206> Miner will connect to daemon RPC on this port. 70 --wallet-address=<wallet_address> This address is rewarded when a block is mined sucessfully. 71 --mining-threads=<threads> Number of CPU threads for mining [default: ` + fmt.Sprintf("%d", runtime.GOMAXPROCS(0)) + `] 72 --max-pow-size=1120 Max amount of PoW size in KiB to mine, some older/newer cpus can increase their work 73 74 Example Mainnet: ./dero-miner-linux-amd64 --wallet-address dERoXHjNHFBabzBCQbBDSqbkLURQyzmPRCLfeFtzRQA3NgVfU4HDbRpZQUKBzq59QU2QLcoAviYQ59FG4bu8T9pZ1woERqciSL --daemon-rpc-address=http://explorer.dero.io:20206 75 Example Testnet: ./dero-miner-linux-amd64 --wallet-address dEToYsDQtFoabzBCQbBDSqbkLURQyzmPRCLfeFtzRQA3NgVfU4HDbRpZQUKBzq59QU2QLcoAviYQ59FG4bu8T9pZ1woEQQstVq --daemon-rpc-address=http://127.0.0.1:30306 76 If daemon running on local machine no requirement of '--daemon-rpc-address' argument. 77 ` 78 var Exit_In_Progress = make(chan bool) 79 80 func main() { 81 82 var err error 83 globals.Init_rlog() 84 globals.Arguments, err = docopt.Parse(command_line, nil, true, config.Version.String(), false) 85 86 if err != nil { 87 log.Fatalf("Error while parsing options err: %s\n", err) 88 } 89 90 // We need to initialize readline first, so it changes stderr to ansi processor on windows 91 92 l, err := readline.NewEx(&readline.Config{ 93 //Prompt: "\033[92mDERO:\033[32m»\033[0m", 94 Prompt: "\033[92mDERO Miner:\033[32m>>>\033[0m ", 95 HistoryFile: filepath.Join(os.TempDir(), "dero_miner_readline.tmp"), 96 AutoComplete: completer, 97 InterruptPrompt: "^C", 98 EOFPrompt: "exit", 99 100 HistorySearchFold: true, 101 FuncFilterInputRune: filterInput, 102 }) 103 if err != nil { 104 panic(err) 105 } 106 defer l.Close() 107 108 // parse arguments and setup testnet mainnet 109 globals.Initialize() // setup network and proxy 110 globals.Logger.Infof("") // a dummy write is required to fully activate logrus 111 112 // all screen output must go through the readline 113 globals.Logger.Out = l.Stdout() 114 115 os.Setenv("RLOG_LOG_LEVEL", "INFO") 116 rlog.UpdateEnv() 117 118 rlog.Infof("Arguments %+v", globals.Arguments) 119 120 globals.Logger.Infof("DERO Atlantis AstroBWT miner : It is an alpha version, use it for testing/evaluations purpose only.") 121 122 globals.Logger.Infof("Copyright 2017-2020 DERO Project. All rights reserved.") 123 globals.Logger.Infof("OS:%s ARCH:%s GOMAXPROCS:%d", runtime.GOOS, runtime.GOARCH, runtime.GOMAXPROCS(0)) 124 globals.Logger.Infof("Version v%s", config.Version.String()) 125 126 if globals.Arguments["--wallet-address"] != nil { 127 addr, err := globals.ParseValidateAddress(globals.Arguments["--wallet-address"].(string)) 128 if err != nil { 129 globals.Logger.Fatalf("Wallet address is invalid: err %s", err) 130 } 131 wallet_address = addr.String() 132 } 133 134 if !globals.Arguments["--testnet"].(bool) { 135 daemon_rpc_address = "http://127.0.0.1:20206" 136 } else { 137 daemon_rpc_address = "http://127.0.0.1:30306" 138 } 139 140 if globals.Arguments["--daemon-rpc-address"] != nil { 141 daemon_rpc_address = globals.Arguments["--daemon-rpc-address"].(string) 142 } 143 144 threads = runtime.GOMAXPROCS(0) 145 if globals.Arguments["--mining-threads"] != nil { 146 if s, err := strconv.Atoi(globals.Arguments["--mining-threads"].(string)); err == nil { 147 threads = s 148 } else { 149 globals.Logger.Fatalf("Mining threads argument cannot be parsed: err %s", err) 150 } 151 152 if threads > runtime.GOMAXPROCS(0) { 153 globals.Logger.Fatalf("Mining threads (%d) is more than available CPUs (%d). This is NOT optimal", threads, runtime.GOMAXPROCS(0)) 154 } 155 } 156 if globals.Arguments["--max-pow-size"] != nil { 157 if s, err := strconv.Atoi(globals.Arguments["--max-pow-size"].(string)); err == nil && s > 200 && s < 100000 { 158 max_pow_size = s*1024 159 } else { 160 globals.Logger.Fatalf("max-pow-size argument cannot be parsed: err %s", err) 161 } 162 } 163 globals.Logger.Infof("max-pow-size limited to %d bytes. Good Luck!!", max_pow_size) 164 165 if globals.Arguments["--bench"].(bool) { 166 167 var wg sync.WaitGroup 168 169 fmt.Printf("%20s %20s %20s %20s %20s \n", "Threads", "Total Time", "Total Iterations", "Time/PoW ", "Hash Rate/Sec") 170 iterations = 500 171 for bench := 1; bench <= threads; bench++ { 172 processor = 0 173 now := time.Now() 174 for i := 0; i < bench; i++ { 175 wg.Add(1) 176 go random_execution(&wg, iterations) 177 } 178 wg.Wait() 179 duration := time.Now().Sub(now) 180 181 fmt.Printf("%20s %20s %20s %20s %20s \n", fmt.Sprintf("%d", bench), fmt.Sprintf("%s", duration), fmt.Sprintf("%d", bench*iterations), 182 fmt.Sprintf("%s", duration/time.Duration(bench*iterations)), fmt.Sprintf("%.1f", float32(time.Second)/(float32(duration/time.Duration(bench*iterations))))) 183 184 } 185 186 os.Exit(0) 187 } 188 189 globals.Logger.Infof("System will mine to \"%s\" with %d threads. Good Luck!!", wallet_address, threads) 190 191 //threads_ptr := flag.Int("threads", runtime.NumCPU(), "No. Of threads") 192 //iterations_ptr := flag.Int("iterations", 20, "No. Of DERO Stereo POW calculated/thread") 193 /*bench_ptr := flag.Bool("bench", false, "run bench with params") 194 daemon_ptr := flag.String("rpc-server-address", "127.0.0.1:18091", "DERO daemon RPC address to get work and submit mined blocks") 195 delay_ptr := flag.Int("delay", 1, "Fetch job every this many seconds") 196 wallet_address := flag.String("wallet-address", "", "Owner of this wallet will receive mining rewards") 197 198 _ = daemon_ptr 199 _ = delay_ptr 200 _ = wallet_address 201 */ 202 203 if threads < 1 || iterations < 1 || threads > 2048 { 204 globals.Logger.Fatalf("Invalid parameters\n") 205 return 206 } 207 208 // This tiny goroutine continuously updates status as required 209 go func() { 210 last_our_height := int64(0) 211 last_best_height := int64(0) 212 last_peer_count := uint64(0) 213 last_topo_height := int64(0) 214 last_mempool_tx_count := 0 215 last_counter := uint64(0) 216 last_counter_time := time.Now() 217 last_mining_state := false 218 219 _ = last_mining_state 220 _ = last_peer_count 221 _ = last_topo_height 222 _ = last_mempool_tx_count 223 224 mining := true 225 for { 226 select { 227 case <-Exit_In_Progress: 228 return 229 default: 230 } 231 232 best_height, best_topo_height := int64(0), int64(0) 233 peer_count := uint64(0) 234 235 mempool_tx_count := 0 236 237 // only update prompt if needed 238 if last_our_height != our_height || last_best_height != best_height || last_counter != counter { 239 // choose color based on urgency 240 color := "\033[33m" // default is green color 241 /*if our_height < best_height { 242 color = "\033[33m" // make prompt yellow 243 } else if our_height > best_height { 244 color = "\033[31m" // make prompt red 245 }*/ 246 247 pcolor := "\033[32m" // default is green color 248 /*if peer_count < 1 { 249 pcolor = "\033[31m" // make prompt red 250 } else if peer_count <= 8 { 251 pcolor = "\033[33m" // make prompt yellow 252 }*/ 253 254 mining_string := "" 255 256 if mining { 257 mining_speed := float64(counter-last_counter) / (float64(uint64(time.Since(last_counter_time))) / 1000000000.0) 258 last_counter = counter 259 last_counter_time = time.Now() 260 switch { 261 case mining_speed > 1000000: 262 mining_string = fmt.Sprintf("MINING @ %.1f MH/s", float32(mining_speed)/1000000.0) 263 case mining_speed > 1000: 264 mining_string = fmt.Sprintf("MINING @ %.1f KH/s", float32(mining_speed)/1000.0) 265 case mining_speed > 0: 266 mining_string = fmt.Sprintf("MINING @ %.0f H/s", mining_speed) 267 } 268 } 269 last_mining_state = mining 270 271 hash_rate_string := "" 272 273 switch { 274 case hash_rate > 1000000000000: 275 hash_rate_string = fmt.Sprintf("%.1f TH/s", float64(hash_rate)/1000000000000.0) 276 case hash_rate > 1000000000: 277 hash_rate_string = fmt.Sprintf("%.1f GH/s", float64(hash_rate)/1000000000.0) 278 case hash_rate > 1000000: 279 hash_rate_string = fmt.Sprintf("%.1f MH/s", float64(hash_rate)/1000000.0) 280 case hash_rate > 1000: 281 hash_rate_string = fmt.Sprintf("%.1f KH/s", float64(hash_rate)/1000.0) 282 case hash_rate > 0: 283 hash_rate_string = fmt.Sprintf("%d H/s", hash_rate) 284 } 285 286 testnet_string := "" 287 if !globals.IsMainnet() { 288 testnet_string = "\033[31m TESTNET" 289 } 290 291 l.SetPrompt(fmt.Sprintf("\033[1m\033[32mDERO Miner: \033[0m"+color+"Height %d "+pcolor+" FOUND_BLOCKS %d \033[32mNW %s %s>%s>>\033[0m ", our_height, block_counter, hash_rate_string, mining_string, testnet_string)) 292 l.Refresh() 293 last_our_height = our_height 294 last_best_height = best_height 295 last_peer_count = peer_count 296 last_mempool_tx_count = mempool_tx_count 297 last_topo_height = best_topo_height 298 } 299 time.Sleep(1 * time.Second) 300 } 301 }() 302 303 l.Refresh() // refresh the prompt 304 305 go func() { 306 var gracefulStop = make(chan os.Signal) 307 signal.Notify(gracefulStop, os.Interrupt) // listen to all signals 308 for { 309 sig := <-gracefulStop 310 fmt.Printf("received signal %s\n", sig) 311 312 if sig.String() == "interrupt" { 313 close(Exit_In_Progress) 314 } 315 } 316 }() 317 318 go increase_delay() 319 for i := 0; i < threads; i++ { 320 go mineblock() 321 } 322 323 go getwork() 324 325 for { 326 line, err := l.Readline() 327 if err == readline.ErrInterrupt { 328 if len(line) == 0 { 329 fmt.Print("Ctrl-C received, Exit in progress\n") 330 close(Exit_In_Progress) 331 os.Exit(0) 332 break 333 } else { 334 continue 335 } 336 } else if err == io.EOF { 337 <-Exit_In_Progress 338 break 339 } 340 341 line = strings.TrimSpace(line) 342 line_parts := strings.Fields(line) 343 344 command := "" 345 if len(line_parts) >= 1 { 346 command = strings.ToLower(line_parts[0]) 347 } 348 349 switch { 350 case line == "help": 351 usage(l.Stderr()) 352 353 case strings.HasPrefix(line, "say"): 354 line := strings.TrimSpace(line[3:]) 355 if len(line) == 0 { 356 log.Println("say what?") 357 break 358 } 359 case command == "version": 360 fmt.Printf("Version %s OS:%s ARCH:%s \n", config.Version.String(), runtime.GOOS, runtime.GOARCH) 361 362 case strings.ToLower(line) == "bye": 363 fallthrough 364 case strings.ToLower(line) == "exit": 365 fallthrough 366 case strings.ToLower(line) == "quit": 367 close(Exit_In_Progress) 368 os.Exit(0) 369 case line == "": 370 default: 371 log.Println("you said:", strconv.Quote(line)) 372 } 373 } 374 375 <-Exit_In_Progress 376 377 return 378 379 } 380 381 func random_execution(wg *sync.WaitGroup, iterations int) { 382 var workbuf [255]byte 383 384 runtime.LockOSThread() 385 //threadaffinity() 386 387 for i := 0; i < iterations; i++ { 388 rand.Read(workbuf[:]) 389 //astrobwt.POW(workbuf[:]) 390 //astrobwt.POW_0alloc(workbuf[:]) 391 _,success := astrobwt.POW_optimized_v1(workbuf[:], max_pow_size) 392 if !success{ 393 i-- 394 } 395 } 396 wg.Done() 397 runtime.UnlockOSThread() 398 } 399 400 func increase_delay() { 401 for { 402 time.Sleep(time.Second) 403 maxdelay++ 404 } 405 } 406 407 // continuously get work 408 func getwork() { 409 410 // create client 411 rpcClient = jsonrpc.NewRPCClient(daemon_rpc_address + "/json_rpc") 412 413 var netTransport = &http.Transport{ 414 Dial: (&net.Dialer{ 415 Timeout: 5 * time.Second, 416 }).Dial, 417 TLSHandshakeTimeout: 5 * time.Second, 418 } 419 420 netClient = &http.Client{ 421 Timeout: time.Second * 5, 422 Transport: netTransport, 423 } 424 425 // execute rpc to service 426 response, err := rpcClient.Call("get_info") 427 if err == nil { 428 globals.Logger.Infof("Connection to RPC server successful \"%s\"", daemon_rpc_address) 429 } else { 430 431 //log.Fatalf("Connection to RPC server Failed err %s", err) 432 globals.Logger.Infof("Connection to RPC server \"%s\".Failed err %s", daemon_rpc_address, err) 433 return 434 } 435 436 for { 437 438 response, err = rpcClient.CallNamed("getblocktemplate", map[string]interface{}{"wallet_address": fmt.Sprintf("%s", wallet_address), "reserve_size": 10}) 439 if err == nil { 440 var block_template structures.GetBlockTemplate_Result 441 err = response.GetObject(&block_template) 442 if err == nil { 443 mutex.Lock() 444 job = block_template 445 maxdelay = 0 446 mutex.Unlock() 447 hash_rate = job.Difficulty / config.BLOCK_TIME_hf4 448 our_height = int64(job.Height) 449 Difficulty = job.Difficulty 450 //fmt.Printf("block_template %+v\n", block_template) 451 } 452 453 } else { 454 globals.Logger.Errorf("Error receiving block template Failed err %s", err) 455 } 456 457 time.Sleep(300 * time.Millisecond) 458 } 459 460 } 461 462 func mineblock() { 463 var diff big.Int 464 var powhash crypto.Hash 465 var work [76]byte 466 var extra_nonce [16]byte 467 nonce_buf := work[39 : 39+4] //since slices are linked, it modifies parent 468 runtime.LockOSThread() 469 threadaffinity() 470 471 iterations_per_loop := uint32(31.0 * float32(astrobwt.MAX_LENGTH) / float32(max_pow_size)) 472 473 var data astrobwt.Data 474 for { 475 mutex.RLock() 476 myjob := job 477 mutex.RUnlock() 478 479 if maxdelay > 10 { 480 time.Sleep(time.Second) 481 continue 482 } 483 484 n, err := hex.Decode(work[:], []byte(myjob.Blockhashing_blob)) 485 if err != nil || n != 76 { 486 time.Sleep(time.Second) 487 globals.Logger.Errorf("Blockwork could not decoded successfully (%s) , err:%s n:%d %+v", myjob.Blockhashing_blob, err, n, myjob) 488 continue 489 } 490 491 rand.Read(extra_nonce[:]) // fill extra nonce with random buffer 492 copy(work[7+32+4:75], extra_nonce[:]) 493 494 diff.SetUint64(myjob.Difficulty) 495 496 if work[0] <= 3 { // check major version 497 for i := uint32(0); i < 20; i++ { 498 atomic.AddUint64(&counter, 1) 499 binary.BigEndian.PutUint32(nonce_buf, i) 500 pow := cryptonight.SlowHash(work[:]) 501 copy(powhash[:], pow[:]) 502 503 if CheckPowHashBig(powhash, &diff) == true { 504 globals.Logger.Infof("Successfully found DERO block at difficulty:%d", myjob.Difficulty) 505 maxdelay = 200 506 block_counter++ 507 508 response, err := rpcClient.Call("submitblock", myjob.Blocktemplate_blob, fmt.Sprintf("%x", work[:])) 509 _ = response 510 _ = err 511 /*fmt.Printf("submitting %+v\n", []string{myjob.Blocktemplate_blob, fmt.Sprintf("%x", work[:])}) 512 fmt.Printf("submit err %s\n", err) 513 fmt.Printf("submit response %s\n", response) 514 */ 515 break 516 517 } 518 } 519 } else { 520 for i := uint32(0); i < iterations_per_loop; i++ { 521 binary.BigEndian.PutUint32(nonce_buf, i) 522 //pow := astrobwt.POW_0alloc(work[:]) 523 pow, success := astrobwt.POW_optimized_v2(work[:],max_pow_size,&data) 524 if !success { 525 continue 526 } 527 atomic.AddUint64(&counter, 1) 528 copy(powhash[:], pow[:]) 529 530 if CheckPowHashBig(powhash, &diff) == true { 531 globals.Logger.Infof("Successfully found DERO block astroblock at difficulty:%d at height %d", myjob.Difficulty, myjob.Height) 532 maxdelay = 200 533 534 block_counter++ 535 536 response, err := rpcClient.Call("submitblock", myjob.Blocktemplate_blob, fmt.Sprintf("%x", work[:])) 537 _ = response 538 _ = err 539 /*fmt.Printf("submitting %+v\n", []string{myjob.Blocktemplate_blob, fmt.Sprintf("%x", work[:])}) 540 fmt.Printf("submit err %s\n", err) 541 fmt.Printf("submit response %s\n", response) 542 */ 543 break 544 545 } 546 } 547 } 548 } 549 } 550 551 func usage(w io.Writer) { 552 io.WriteString(w, "commands:\n") 553 //io.WriteString(w, completer.Tree(" ")) 554 io.WriteString(w, "\t\033[1mhelp\033[0m\t\tthis help\n") 555 io.WriteString(w, "\t\033[1mstatus\033[0m\t\tShow general information\n") 556 io.WriteString(w, "\t\033[1mbye\033[0m\t\tQuit the miner\n") 557 io.WriteString(w, "\t\033[1mversion\033[0m\t\tShow version\n") 558 io.WriteString(w, "\t\033[1mexit\033[0m\t\tQuit the miner\n") 559 io.WriteString(w, "\t\033[1mquit\033[0m\t\tQuit the miner\n") 560 561 } 562 563 var completer = readline.NewPrefixCompleter( 564 readline.PcItem("help"), 565 readline.PcItem("status"), 566 readline.PcItem("version"), 567 readline.PcItem("bye"), 568 readline.PcItem("exit"), 569 readline.PcItem("quit"), 570 ) 571 572 func filterInput(r rune) (rune, bool) { 573 switch r { 574 // block CtrlZ feature 575 case readline.CharCtrlZ: 576 return r, false 577 } 578 return r, true 579 }