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  }