gitlab.com/aquachain/aquachain@v1.17.16-rc3.0.20221018032414-e3ddf1e1c055/cmd/aquaminer/main.go (about)

     1  // Copyright 2018 The aquachain Authors
     2  // This file is part of aquachain.
     3  //
     4  // aquachain is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // aquachain is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU General Public License
    15  // along with aquachain. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // aquaminer command is a aquachain reference miner
    18  package main
    19  
    20  import (
    21  	"context"
    22  	crand "crypto/rand"
    23  	"encoding/binary"
    24  	"encoding/hex"
    25  	"flag"
    26  	"fmt"
    27  	"log"
    28  	"math"
    29  	"math/big"
    30  	mrand "math/rand"
    31  	"os"
    32  	"runtime"
    33  	"strings"
    34  	"time"
    35  
    36  	"github.com/aerth/tgun"
    37  	"gitlab.com/aquachain/aquachain/cmd/utils"
    38  	"gitlab.com/aquachain/aquachain/common"
    39  	"gitlab.com/aquachain/aquachain/core/types"
    40  	"gitlab.com/aquachain/aquachain/crypto"
    41  	"gitlab.com/aquachain/aquachain/opt/aquaclient"
    42  	rpc "gitlab.com/aquachain/aquachain/rpc/rpcclient"
    43  )
    44  
    45  const version = "0.9.3"
    46  
    47  // EmptyMixDigest is sent when submitting work since HF5
    48  var EmptyMixDigest = common.BytesToHash(make([]byte, common.HashLength))
    49  
    50  var (
    51  	maxproc        = flag.Int("t", runtime.NumCPU(), "number of miners to spawn")
    52  	farm           = flag.String("F", "http://localhost:8543", "rpc server to mine to")
    53  	showVersion    = flag.Bool("version", false, "show version and exit")
    54  	autoworkername = flag.Bool("autoname", false, "adds random worker name to pool url")
    55  	benching       = flag.Bool("B", false, "offline benchmark mode")
    56  	debug          = flag.Bool("d", false, "debug mode")
    57  	benchversion   = flag.Uint64("v", 2, "hash version (benchmarking only)")
    58  	nonceseed      = flag.Int64("seed", 1, "nonce seed multiplier")
    59  	refresh        = flag.Duration("r", time.Second*3, "seconds to wait between asking for more work")
    60  	proxypath      = flag.String("prx", "", "example: socks5://192.168.1.3:1080 or 'tor' for localhost:9050")
    61  	seedflag       = flag.String("s", "", "hash once and exit")
    62  )
    63  
    64  // big numbers
    65  var bigOne = big.NewInt(1)
    66  var oneLsh256 = new(big.Int).Lsh(bigOne, 256)
    67  
    68  // bench work taken from a testnet work load
    69  var benchdiff = new(big.Int).SetBytes(common.HexToHash("0x08637bd05af6c69b5a63f9a49c2c1b10fd7e45803cd141a6937d1fe64f54").Bytes())
    70  var benchwork = common.HexToHash("0xd3b5f1b47f52fdc72b1dab0b02ab352442487a1d3a43211bc4f0eb5f092403fc")
    71  
    72  type workload struct {
    73  	job     common.Hash
    74  	target  *big.Int
    75  	version uint64
    76  	err     error
    77  }
    78  
    79  type doneworkload struct {
    80  	job   common.Hash
    81  	nonce uint64
    82  }
    83  
    84  type worker struct {
    85  	newwork chan workload
    86  }
    87  
    88  func main() {
    89  	fmt.Fprintln(os.Stderr, "aquaminer", version)
    90  	fmt.Fprintln(os.Stderr, "source code: https://gitlab.com/aquachain/aquachain/blob/master/cmd/aquaminer/main.go")
    91  	flag.Parse()
    92  	if *showVersion {
    93  		os.Exit(0)
    94  	}
    95  	if *seedflag != "" {
    96  		algo := *benchversion
    97  		input, err := hex.DecodeString(*seedflag)
    98  		if err != nil {
    99  			panic(err.Error())
   100  		}
   101  		seed := input
   102  		fmt.Printf("%02x\n", seed)
   103  		hashed := crypto.VersionHash(byte(algo), seed)
   104  		fmt.Println(common.ToHex(hashed))
   105  		os.Exit(0)
   106  
   107  	}
   108  
   109  	if !*debug {
   110  		fmt.Println("not showing h/s, use -d flag to print")
   111  	}
   112  	runtime.GOMAXPROCS(*maxproc)
   113  	runtime.GOMAXPROCS(*maxproc)
   114  
   115  	// get a random nonceseed
   116  	if *nonceseed == 1 {
   117  		seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64))
   118  		if err != nil {
   119  			if err != nil {
   120  				utils.Fatalf("rand err: %v", err)
   121  			}
   122  		}
   123  		*nonceseed = seed.Int64()
   124  	}
   125  	fmt.Println("rand seed:", *nonceseed)
   126  
   127  	// multiply nonceseed by 'now' so machines can share the same nonceseed
   128  	mrand.Seed(time.Now().UTC().Unix() * *nonceseed)
   129  
   130  	var (
   131  		workers    = []*worker{}
   132  		getnewwork = time.Tick(*refresh)
   133  		maxProc    = *maxproc
   134  		client     = &aquaclient.Client{}
   135  	)
   136  
   137  	if *autoworkername {
   138  		if !strings.HasSuffix(*farm, "/") {
   139  			*farm = *farm + "/"
   140  		}
   141  		*farm = *farm + fmt.Sprintf("%x", *nonceseed)[:6]
   142  		fmt.Println("auto:", *farm)
   143  	}
   144  
   145  	if !*benching {
   146  		// make http client
   147  		tgunner := &tgun.Client{
   148  			Proxy: *proxypath,
   149  		}
   150  		httpClient, err := tgunner.HTTPClient()
   151  		if err != nil {
   152  			utils.Fatalf("dial err: %v", err)
   153  		}
   154  
   155  		// make rpc client
   156  		rpcclient, err := rpc.DialHTTPCustom(*farm, httpClient, map[string]string{"User-Agent": "aquaminer/" + version})
   157  		if err != nil {
   158  			utils.Fatalf("dial err: %v", err)
   159  		}
   160  
   161  		// wrap with aquaclient
   162  		client = aquaclient.NewClient(rpcclient)
   163  	} else {
   164  		fmt.Println("OFFLINE MODE")
   165  		<-time.After(time.Second)
   166  	}
   167  
   168  	var (
   169  		donework     = make(chan doneworkload)
   170  		forcenewwork = make(chan struct{}, 100)
   171  		ctx          = context.Background()
   172  		cachework    = common.Hash{}
   173  	)
   174  
   175  	// spawn miners
   176  	for i := 0; i < maxProc*2; i++ {
   177  		w := new(worker)
   178  		w.newwork = make(chan workload, 4) // new work incoming channel
   179  
   180  		workers = append(workers, w)
   181  		var workername string
   182  		if maxProc == 1 {
   183  			workername = fmt.Sprintf("%x", *nonceseed)[:6]
   184  		} else {
   185  			workername = fmt.Sprintf("%x", i)
   186  		}
   187  		go miner(workername, donework, *benching, w.newwork)
   188  	}
   189  
   190  	runtime.LockOSThread()
   191  	for {
   192  		select {
   193  		case <-getnewwork:
   194  			forcenewwork <- struct{}{}
   195  		case <-forcenewwork:
   196  			if *debug {
   197  				log.Println("fetching new work")
   198  			}
   199  			work, target, algo, err := refreshWork(ctx, client, *benching)
   200  			if err != nil {
   201  				log.Println("Error fetching new work from pool:", err)
   202  			}
   203  			if work == cachework {
   204  				continue // dont send already known work
   205  			}
   206  			cachework = work
   207  			log.Printf("Begin new work: %s (difficulty: %v) algo %v\n", work.Hex(), big2diff(target), algo)
   208  			for i := range workers {
   209  				workers[i].newwork <- workload{work, target, algo, err}
   210  			}
   211  		case gotdone := <-donework:
   212  			log.Printf("submitting nonce: %x", gotdone.nonce)
   213  			blknonce := types.EncodeNonce(gotdone.nonce)
   214  			if client.SubmitWork(ctx, blknonce, gotdone.job, EmptyMixDigest) {
   215  				log.Printf("good nonce: %x", gotdone.nonce)
   216  			} else {
   217  				// there was an error when we send the work. lets get a totally
   218  				log.Printf("bad nonce: %x", gotdone.nonce)
   219  				forcenewwork <- struct{}{}
   220  			}
   221  		}
   222  	}
   223  
   224  }
   225  
   226  // courtesy function to display difficulty for humans
   227  func big2diff(large *big.Int) uint64 {
   228  	if large == nil {
   229  		return 0
   230  	}
   231  	denominator := new(big.Int).Add(large, bigOne)
   232  	return new(big.Int).Div(oneLsh256, denominator).Uint64()
   233  
   234  }
   235  
   236  // fetch work from a rpc client
   237  func refreshWork(ctx context.Context, client *aquaclient.Client, benchmarking bool) (common.Hash, *big.Int, uint64, error) {
   238  	if benchmarking {
   239  		return benchwork, benchdiff, *benchversion, nil
   240  	}
   241  	work, err := client.GetWork(ctx)
   242  	if err != nil {
   243  		return common.Hash{}, benchdiff, 0, fmt.Errorf("getwork err: %v\ncheck address, pool url, and/or local rpc", err)
   244  	}
   245  	target := new(big.Int).SetBytes(common.HexToHash(work[2]).Bytes())
   246  	headerVersion := new(big.Int).SetBytes(common.HexToHash(work[1]).Bytes()).Uint64()
   247  
   248  	// set header version manually for before hf8
   249  	if headerVersion == 0 || headerVersion > 4 {
   250  		headerVersion = 2
   251  	}
   252  	return common.HexToHash(work[0]), target, headerVersion, nil
   253  }
   254  
   255  // single miner loop
   256  func miner(label string, doneworkchan chan doneworkload, offline bool, getworkchan <-chan workload) {
   257  
   258  	var (
   259  		second   = time.Tick(*refresh)
   260  		fps      = 0.00
   261  		workHash common.Hash
   262  		target   *big.Int
   263  		err      error
   264  		algo     uint64
   265  	)
   266  
   267  	// remember original nonce
   268  	ononce := mrand.Uint64()
   269  	nonce := ononce
   270  
   271  	for {
   272  
   273  		// accept new work if available
   274  		select {
   275  		case newwork := <-getworkchan:
   276  			workHash = newwork.job
   277  			target = newwork.target
   278  			algo = newwork.version
   279  			err = newwork.err
   280  		default:
   281  		}
   282  
   283  		// error fetching work, wait one second and see if theres more work
   284  		if err != nil {
   285  			log.Println("error getting work:", err)
   286  			<-time.After(time.Second)
   287  			continue
   288  		}
   289  
   290  		// difficulty isnt set. wait one second for more work.
   291  		if target == nil {
   292  			log.Println(label, "waiting for work...")
   293  			<-time.After(time.Second)
   294  			continue
   295  		}
   296  
   297  		// count h/s
   298  		if *debug {
   299  			fps++
   300  			select {
   301  			case <-second:
   302  				log.Printf("( %s %2.0fH/s (algo #%v)", label, fps/(*refresh).Seconds(), algo)
   303  				fps = 0
   304  			default:
   305  			}
   306  		}
   307  
   308  		// increment nonce
   309  		nonce++
   310  
   311  		// real actual hashing!
   312  		seed := make([]byte, 40)
   313  		copy(seed, workHash.Bytes())
   314  		binary.LittleEndian.PutUint64(seed[32:], nonce)
   315  		result := common.BytesToHash(crypto.VersionHash(byte(algo), seed))
   316  		// check difficulty of result
   317  		if diff := new(big.Int).SetBytes(result.Bytes()); diff.Cmp(target) <= 0 {
   318  			if offline {
   319  				continue
   320  			}
   321  			// submit the nonce, with the original job
   322  			doneworkchan <- doneworkload{workHash, nonce}
   323  		}
   324  	}
   325  }