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 }