├── network_test.go ├── .gitignore ├── main_test.go ├── LICENSE ├── README.md ├── network.go ├── kernel.go ├── miner_test.go ├── main.go └── miner.go /network_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | gominer 26 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | func TestExcludedDevices(t *testing.T) { 6 | testSet := []struct { 7 | deviceID int 8 | excludedGPUs string 9 | excluded bool 10 | }{{ 11 | deviceID: 1, 12 | excludedGPUs: "", 13 | excluded: false, 14 | }, 15 | { 16 | deviceID: 2, 17 | excludedGPUs: "2", 18 | excluded: true, 19 | }, 20 | { 21 | deviceID: 2, 22 | excludedGPUs: "3,2", 23 | excluded: true, 24 | }, 25 | { 26 | deviceID: 1, 27 | excludedGPUs: "2,3", 28 | excluded: false, 29 | }, 30 | { 31 | deviceID: 1, 32 | excludedGPUs: "0", 33 | excluded: false, 34 | }, 35 | } 36 | for _, test := range testSet { 37 | result := deviceExcludedForMining(test.deviceID, test.excludedGPUs) 38 | if result != test.excluded { 39 | t.Error(test) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Rob Van Mieghem 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gominer 2 | GPU miner for siacoin in go 3 | 4 | All available opencl capable GPU's are detected and used in parallel. 5 | 6 | 7 | ## Installation from source 8 | 9 | ### Prerequisites 10 | * go version 1.4.2 or above (earlier version might work or not), check with `go version` 11 | * opencl libraries on the library path 12 | * gcc 13 | 14 | ``` 15 | go get github.com/robvanmieghem/gominer 16 | ``` 17 | 18 | ## Run 19 | ``` 20 | gominer 21 | ``` 22 | 23 | Usage: 24 | ``` 25 | -H string 26 | siad host and port (default "localhost:9980") 27 | -Q string 28 | Query string 29 | -I int 30 | Intensity (default 28) 31 | -E string 32 | Exclude GPU's: comma separated list of devicenumbers 33 | -cpu 34 | If set, also use the CPU for mining, only GPU's are used by default 35 | -v Show version and exit 36 | ``` 37 | 38 | See what intensity gives you the best hashrate. 39 | 40 | ## FAQ 41 | 42 | - *ERROR fetching work - Get http://localhost:9980/miner/headerforwork: dial tcp 127.0.0.1:9980: connection refused* 43 | 44 | Make sure `siad` is running 45 | 46 | - What is `siad`? 47 | 48 | Check the sia documentation 49 | 50 | - I don't know how to set up siad or the sia UI wallet, how do I do that? 51 | 52 | Check the sia documentation. 53 | 54 | - *You have to help me set up mining SIA* 55 | 56 | No I don't 57 | 58 | - *Can you log in on my machine to configure my mining setup?* 59 | 60 | No 61 | 62 | - I don't know how to get it working, can you help me? 63 | 64 | I get this question at least once a day. Seriously, you can not expect me to set up and support everyone's mining equipment. 65 | 66 | - I don't know how to get it working, can you help me please please please ? 67 | 68 | Everyone has his price, make me an offer I can't refuse so I don't have to continue answering `no` to this question. 69 | 70 | ## Support development 71 | 72 | If you really want to, you can support the gominer development: 73 | 74 | SIA: 79b9089439218734192db7016f07dc5a0e2a95e873992dd782a1e1306b2c44e116e1d8ded910 75 | 76 | BTC: 3QrmVRLU2JZKHiLdATud2vvL9b376wbmKU 77 | -------------------------------------------------------------------------------- /network.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | ) 10 | 11 | //HeaderReporter defines the required method a SIA client or pool client should implement for miners to be able to report solved headers 12 | type HeaderReporter interface { 13 | //SubmitHeader reports a solved header 14 | SubmitHeader(header []byte) (err error) 15 | } 16 | 17 | // SiadClient is used to connect to siad 18 | type SiadClient struct { 19 | siadurl string 20 | } 21 | 22 | // NewSiadClient creates a new SiadClient given a 'host:port' connectionstring 23 | func NewSiadClient(connectionstring string, querystring string) *SiadClient { 24 | s := SiadClient{} 25 | s.siadurl = "http://" + connectionstring + "/miner/header?" + querystring 26 | return &s 27 | } 28 | 29 | func decodeMessage(resp *http.Response) (msg string, err error) { 30 | buf, err := ioutil.ReadAll(resp.Body) 31 | if err != nil { 32 | return 33 | } 34 | var data struct{Message string `json:"message"`} 35 | if err = json.Unmarshal(buf, &data); err == nil { 36 | msg = data.Message 37 | } 38 | return 39 | } 40 | 41 | //GetHeaderForWork fetches new work from the SIA daemon 42 | func (sc *SiadClient) GetHeaderForWork() (target, header []byte, err error) { 43 | client := &http.Client{} 44 | 45 | req, err := http.NewRequest("GET", sc.siadurl, nil) 46 | if err != nil { 47 | return 48 | } 49 | 50 | req.Header.Add("User-Agent", "Sia-Agent") 51 | resp, err := client.Do(req) 52 | if err != nil { 53 | return 54 | } 55 | defer resp.Body.Close() 56 | switch resp.StatusCode { 57 | case 200: 58 | case 400: 59 | msg, errd := decodeMessage(resp) 60 | if errd != nil { 61 | err = fmt.Errorf("Status code %d", resp.StatusCode) 62 | } else { 63 | err = fmt.Errorf("Status code %d, message: %s", resp.StatusCode, msg) 64 | } 65 | return 66 | default: 67 | err = fmt.Errorf("Status code %d", resp.StatusCode) 68 | return 69 | } 70 | buf, err := ioutil.ReadAll(resp.Body) 71 | if err != nil { 72 | return 73 | } 74 | 75 | if len(buf) < 112 { 76 | err = fmt.Errorf("Invalid response, only received %d bytes", len(buf)) 77 | return 78 | } 79 | 80 | target = buf[:32] 81 | header = buf[32:112] 82 | 83 | return 84 | } 85 | 86 | //SubmitHeader reports a solved header to the SIA daemon 87 | func (sc *SiadClient) SubmitHeader(header []byte) (err error) { 88 | req, err := http.NewRequest("POST", sc.siadurl, bytes.NewReader(header)) 89 | if err != nil { 90 | return 91 | } 92 | 93 | req.Header.Add("User-Agent", "Sia-Agent") 94 | 95 | client := &http.Client{} 96 | resp, err := client.Do(req) 97 | if err != nil { 98 | return 99 | } 100 | switch resp.StatusCode { 101 | case 204: 102 | default: 103 | msg, errd := decodeMessage(resp) 104 | if errd != nil { 105 | err = fmt.Errorf("Status code %d", resp.StatusCode) 106 | } else { 107 | err = fmt.Errorf("%s", msg) 108 | } 109 | return 110 | } 111 | return 112 | } 113 | -------------------------------------------------------------------------------- /kernel.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const kernelSource = ` 4 | 5 | inline static uint2 ror64(const uint2 x, const uint y) 6 | { 7 | return (uint2)(((x).x>>y)^((x).y<<(32-y)),((x).y>>y)^((x).x<<(32-y))); 8 | } 9 | 10 | inline static uint2 ror64_2(const uint2 x, const uint y) 11 | { 12 | return (uint2)(((x).y>>(y-32))^((x).x<<(64-y)),((x).x>>(y-32))^((x).y<<(64-y))); 13 | } 14 | 15 | 16 | __constant static const uchar blake2b_sigma[12][16] = { 17 | { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } , 18 | { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } , 19 | { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 } , 20 | { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 } , 21 | { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 } , 22 | { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 } , 23 | { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 } , 24 | { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 } , 25 | { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 } , 26 | { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 } , 27 | { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } , 28 | { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } }; 29 | 30 | // Target is passed in via headerIn[32 - 29] 31 | __kernel void nonceGrind(__global ulong *headerIn, __global ulong *nonceOut) { 32 | ulong target = headerIn[4]; 33 | ulong m[16] = { headerIn[0], headerIn[1], 34 | headerIn[2], headerIn[3], 35 | (ulong)get_global_id(0), headerIn[5], 36 | headerIn[6], headerIn[7], 37 | headerIn[8], headerIn[9], 0, 0, 0, 0, 0, 0 }; 38 | 39 | ulong v[16] = { 0x6a09e667f2bdc928, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, 40 | 0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179, 41 | 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, 42 | 0x510e527fade68281, 0x9b05688c2b3e6c1f, 0xe07c265404be4294, 0x5be0cd19137e2179 }; 43 | 44 | 45 | 46 | #define G(r,i,a,b,c,d) \ 47 | a = a + b + m[ blake2b_sigma[r][2*i] ]; \ 48 | ((uint2*)&d)[0] = ((uint2*)&d)[0].yx ^ ((uint2*)&a)[0].yx; \ 49 | c = c + d; \ 50 | ((uint2*)&b)[0] = ror64( ((uint2*)&b)[0] ^ ((uint2*)&c)[0], 24U); \ 51 | a = a + b + m[ blake2b_sigma[r][2*i+1] ]; \ 52 | ((uint2*)&d)[0] = ror64( ((uint2*)&d)[0] ^ ((uint2*)&a)[0], 16U); \ 53 | c = c + d; \ 54 | ((uint2*)&b)[0] = ror64_2( ((uint2*)&b)[0] ^ ((uint2*)&c)[0], 63U); 55 | 56 | 57 | #define ROUND(r) \ 58 | G(r,0,v[ 0],v[ 4],v[ 8],v[12]); \ 59 | G(r,1,v[ 1],v[ 5],v[ 9],v[13]); \ 60 | G(r,2,v[ 2],v[ 6],v[10],v[14]); \ 61 | G(r,3,v[ 3],v[ 7],v[11],v[15]); \ 62 | G(r,4,v[ 0],v[ 5],v[10],v[15]); \ 63 | G(r,5,v[ 1],v[ 6],v[11],v[12]); \ 64 | G(r,6,v[ 2],v[ 7],v[ 8],v[13]); \ 65 | G(r,7,v[ 3],v[ 4],v[ 9],v[14]); 66 | 67 | ROUND( 0 ); 68 | ROUND( 1 ); 69 | ROUND( 2 ); 70 | ROUND( 3 ); 71 | ROUND( 4 ); 72 | ROUND( 5 ); 73 | ROUND( 6 ); 74 | ROUND( 7 ); 75 | ROUND( 8 ); 76 | ROUND( 9 ); 77 | ROUND( 10 ); 78 | ROUND( 11 ); 79 | #undef G 80 | #undef ROUND 81 | 82 | if (as_ulong(as_uchar8(0x6a09e667f2bdc928 ^ v[0] ^ v[8]).s76543210) < target) { 83 | *nonceOut = m[4]; 84 | return; 85 | } 86 | } 87 | ` 88 | -------------------------------------------------------------------------------- /miner_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | "math" 7 | "testing" 8 | 9 | "github.com/robvanmieghem/go-opencl/cl" 10 | ) 11 | 12 | var provenSolutions = []struct { 13 | height int 14 | hash string 15 | workHeader []byte 16 | offset int 17 | submittedHeader []byte 18 | intensity int 19 | }{ 20 | { 21 | height: 56206, 22 | hash: "00000000000006418b86014ff54b457f52665b428d5af57e80b0b7ec84c706e5", 23 | workHeader: []byte{0, 0, 0, 0, 0, 0, 26, 158, 25, 209, 169, 53, 113, 22, 90, 11, 72, 7, 222, 103, 247, 244, 163, 156, 158, 5, 53, 126, 186, 215, 88, 48, 45, 32, 0, 0, 0, 0, 0, 0, 20, 25, 103, 87, 0, 0, 0, 0, 218, 189, 84, 137, 247, 169, 197, 113, 213, 120, 125, 148, 92, 197, 47, 212, 250, 153, 114, 53, 199, 209, 183, 97, 28, 242, 206, 120, 191, 202, 34, 9}, 24 | offset: 5 * int(math.Exp2(float64(28))), 25 | submittedHeader: []byte{0, 0, 0, 0, 0, 0, 26, 158, 25, 209, 169, 53, 113, 22, 90, 11, 72, 7, 222, 103, 247, 244, 163, 156, 158, 5, 53, 126, 186, 215, 88, 48, 88, 47, 107, 95, 0, 0, 0, 0, 20, 25, 103, 87, 0, 0, 0, 0, 218, 189, 84, 137, 247, 169, 197, 113, 213, 120, 125, 148, 92, 197, 47, 212, 250, 153, 114, 53, 199, 209, 183, 97, 28, 242, 206, 120, 191, 202, 34, 9}, 26 | intensity: 28, 27 | }, 28 | { 29 | height: 57653, 30 | hash: "00000000000001ccac64b49a9ebc69c6046a93f4d32d8f8f6967c8f487ed8cec", 31 | workHeader: []byte{0, 0, 0, 0, 0, 0, 6, 72, 174, 217, 105, 206, 174, 59, 150, 117, 251, 55, 209, 192, 241, 37, 35, 184, 2, 194, 253, 173, 207, 249, 114, 1, 62, 26, 0, 0, 0, 0, 0, 0, 41, 7, 115, 87, 0, 0, 0, 0, 56, 56, 181, 217, 76, 24, 251, 231, 137, 4, 166, 20, 40, 53, 77, 36, 148, 23, 138, 146, 2, 199, 168, 122, 71, 162, 44, 150, 144, 2, 198, 67}, 32 | offset: 805306368, 33 | submittedHeader: []byte{0, 0, 0, 0, 0, 0, 6, 72, 174, 217, 105, 206, 174, 59, 150, 117, 251, 55, 209, 192, 241, 37, 35, 184, 2, 194, 253, 173, 207, 249, 114, 1, 7, 235, 26, 63, 0, 0, 0, 0, 41, 7, 115, 87, 0, 0, 0, 0, 56, 56, 181, 217, 76, 24, 251, 231, 137, 4, 166, 20, 40, 53, 77, 36, 148, 23, 138, 146, 2, 199, 168, 122, 71, 162, 44, 150, 144, 2, 198, 67}, 34 | intensity: 28, 35 | }, 36 | } 37 | 38 | func TestMine(t *testing.T) { 39 | platforms, err := cl.GetPlatforms() 40 | if err != nil { 41 | log.Panic(err) 42 | } 43 | 44 | var clDevice *cl.Device 45 | for _, platform := range platforms { 46 | platormDevices, err := cl.GetDevices(platform, devicesTypesForMining) 47 | if err != nil { 48 | log.Fatalln(err) 49 | } 50 | for _, device := range platormDevices { 51 | log.Println(device.Type(), "-", device.Name()) 52 | clDevice = device 53 | } 54 | } 55 | 56 | workChannel := make(chan *MiningWork, len(provenSolutions)+1) 57 | 58 | for _, provenSolution := range provenSolutions { 59 | workChannel <- &MiningWork{provenSolution.workHeader, provenSolution.offset} 60 | } 61 | close(workChannel) 62 | var hashRateReportsChannel = make(chan *HashRateReport, len(provenSolutions)+1) 63 | validator := newSubmittedHeaderValidator(len(provenSolutions)) 64 | miner := &Miner{ 65 | clDevice: clDevice, 66 | minerID: 0, 67 | hashRateReports: hashRateReportsChannel, 68 | GlobalItemSize: int(math.Exp2(float64(28))), 69 | miningWorkChannel: workChannel, 70 | siad: validator, 71 | } 72 | miner.mine() 73 | validator.validate(t) 74 | } 75 | 76 | func newSubmittedHeaderValidator(capacity int) (v *submittedHeaderValidator) { 77 | v = &submittedHeaderValidator{} 78 | v.submittedHeaders = make(chan []byte, capacity) 79 | return 80 | } 81 | 82 | type submittedHeaderValidator struct { 83 | submittedHeaders chan []byte 84 | } 85 | 86 | //SubmitHeader stores solved so they can later be validated after the testrun 87 | func (v *submittedHeaderValidator) SubmitHeader(header []byte) (err error) { 88 | v.submittedHeaders <- header 89 | return 90 | } 91 | 92 | func (v *submittedHeaderValidator) validate(t *testing.T) { 93 | if len(v.submittedHeaders) != len(provenSolutions) { 94 | t.Fatal("Wrong number of headers reported") 95 | } 96 | for _, provenSolution := range provenSolutions { 97 | submittedHeader := <-v.submittedHeaders 98 | if !bytes.Equal(submittedHeader, provenSolution.submittedHeader) { 99 | t.Error("Mismatch\nExpected header: ", provenSolution.submittedHeader, "\nSubmitted header: ", submittedHeader) 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "math" 8 | "os" 9 | "strconv" 10 | "strings" 11 | "time" 12 | 13 | "github.com/robvanmieghem/go-opencl/cl" 14 | ) 15 | 16 | //Version is the released version string of gominer 17 | var Version = "0.5-Dev" 18 | 19 | var intensity = 28 20 | var devicesTypesForMining = cl.DeviceTypeGPU 21 | 22 | const maxUint = ^uint(0) 23 | const maxInt = int(maxUint >> 1) 24 | 25 | func createWork(siad *SiadClient, miningWorkChannel chan *MiningWork, secondsOfWorkPerRequestedHeader int, globalItemSize int) { 26 | for { 27 | timeBeforeGettingWork := time.Now() 28 | target, header, err := siad.GetHeaderForWork() 29 | 30 | if err != nil { 31 | log.Println("ERROR fetching work -", err) 32 | time.Sleep(1000 * time.Millisecond) 33 | continue 34 | } 35 | //copy target to header 36 | for i := 0; i < 8; i++ { 37 | header[i+32] = target[7-i] 38 | } 39 | //Fill the workchannel with work for the requested number of secondsOfWorkPerRequestedHeader 40 | // If the GetHeaderForWork call took too long, it might be that no work is generated at all 41 | for i := 0; i*globalItemSize < (maxInt - globalItemSize); i++ { 42 | if time.Since(timeBeforeGettingWork) < time.Second*time.Duration(secondsOfWorkPerRequestedHeader) { 43 | miningWorkChannel <- &MiningWork{header, i * globalItemSize} 44 | } else { 45 | if i == 0 { 46 | log.Println("ERROR: Getting work took longer then", secondsOfWorkPerRequestedHeader, "seconds - No work generated") 47 | } 48 | break 49 | } 50 | } 51 | } 52 | } 53 | 54 | func main() { 55 | printVersion := flag.Bool("v", false, "Show version and exit") 56 | useCPU := flag.Bool("cpu", false, "If set, also use the CPU for mining, only GPU's are used by default") 57 | flag.IntVar(&intensity, "I", intensity, "Intensity") 58 | siadHost := flag.String("H", "localhost:9980", "siad host and port") 59 | secondsOfWorkPerRequestedHeader := flag.Int("S", 10, "Time between calls to siad") 60 | excludedGPUs := flag.String("E", "", "Exclude GPU's: comma separated list of devicenumbers") 61 | queryString := flag.String("Q", "", "Query string") 62 | flag.Parse() 63 | 64 | if *printVersion { 65 | fmt.Println("gominer version", Version) 66 | os.Exit(0) 67 | } 68 | 69 | siad := NewSiadClient(*siadHost, *queryString) 70 | 71 | if *useCPU { 72 | devicesTypesForMining = cl.DeviceTypeAll 73 | } 74 | globalItemSize := int(math.Exp2(float64(intensity))) 75 | 76 | platforms, err := cl.GetPlatforms() 77 | if err != nil { 78 | log.Panic(err) 79 | } 80 | 81 | clDevices := make([]*cl.Device, 0, 4) 82 | for _, platform := range platforms { 83 | log.Println("Platform", platform.Name()) 84 | platormDevices, err := cl.GetDevices(platform, devicesTypesForMining) 85 | if err != nil { 86 | log.Println(err) 87 | } 88 | log.Println(len(platormDevices), "device(s) found:") 89 | for i, device := range platormDevices { 90 | log.Println(i, "-", device.Type(), "-", device.Name()) 91 | clDevices = append(clDevices, device) 92 | } 93 | } 94 | 95 | nrOfMiningDevices := len(clDevices) 96 | 97 | if nrOfMiningDevices == 0 { 98 | log.Println("No suitable opencl devices found") 99 | os.Exit(1) 100 | } 101 | 102 | //Start fetching work 103 | workChannel := make(chan *MiningWork, nrOfMiningDevices*4) 104 | go createWork(siad, workChannel, *secondsOfWorkPerRequestedHeader, globalItemSize) 105 | 106 | //Start mining routines 107 | var hashRateReportsChannel = make(chan *HashRateReport, nrOfMiningDevices*10) 108 | for i, device := range clDevices { 109 | if deviceExcludedForMining(i, *excludedGPUs) { 110 | continue 111 | } 112 | miner := &Miner{ 113 | clDevice: device, 114 | minerID: i, 115 | hashRateReports: hashRateReportsChannel, 116 | miningWorkChannel: workChannel, 117 | GlobalItemSize: globalItemSize, 118 | siad: siad, 119 | } 120 | go miner.mine() 121 | } 122 | 123 | //Start printing out the hashrates of the different gpu's 124 | hashRateReports := make([]float64, nrOfMiningDevices) 125 | for { 126 | //No need to print at every hashreport, we have time 127 | for i := 0; i < nrOfMiningDevices; i++ { 128 | report := <-hashRateReportsChannel 129 | hashRateReports[report.MinerID] = report.HashRate 130 | } 131 | fmt.Print("\r") 132 | var totalHashRate float64 133 | for minerID, hashrate := range hashRateReports { 134 | fmt.Printf("%d-%.1f ", minerID, hashrate) 135 | totalHashRate += hashrate 136 | } 137 | fmt.Printf("Total: %.1f MH/s ", totalHashRate) 138 | 139 | } 140 | } 141 | 142 | //deviceExcludedForMining checks if the device is in the exclusion list 143 | func deviceExcludedForMining(deviceID int, excludedGPUs string) bool { 144 | excludedGPUList := strings.Split(excludedGPUs, ",") 145 | for _, excludedGPU := range excludedGPUList { 146 | if strconv.Itoa(deviceID) == excludedGPU { 147 | return true 148 | } 149 | } 150 | return false 151 | } 152 | -------------------------------------------------------------------------------- /miner.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | "github.com/robvanmieghem/go-opencl/cl" 8 | ) 9 | 10 | //HashRateReport is sent from the mining routines for giving combined information as output 11 | type HashRateReport struct { 12 | MinerID int 13 | HashRate float64 14 | } 15 | 16 | //MiningWork is sent to the mining routines and defines what ranges should be searched for a matching nonce 17 | type MiningWork struct { 18 | Header []byte 19 | Offset int 20 | } 21 | 22 | // Miner actually mines :-) 23 | type Miner struct { 24 | clDevice *cl.Device 25 | minerID int 26 | hashRateReports chan *HashRateReport 27 | miningWorkChannel chan *MiningWork 28 | GlobalItemSize int 29 | siad HeaderReporter 30 | } 31 | 32 | func (miner *Miner) mine() { 33 | log.Println(miner.minerID, "- Initializing", miner.clDevice.Type(), "-", miner.clDevice.Name()) 34 | 35 | context, err := cl.CreateContext([]*cl.Device{miner.clDevice}) 36 | if err != nil { 37 | log.Fatalln(miner.minerID, "-", err) 38 | } 39 | defer context.Release() 40 | 41 | commandQueue, err := context.CreateCommandQueue(miner.clDevice, 0) 42 | if err != nil { 43 | log.Fatalln(miner.minerID, "-", err) 44 | } 45 | defer commandQueue.Release() 46 | 47 | program, err := context.CreateProgramWithSource([]string{kernelSource}) 48 | if err != nil { 49 | log.Fatalln(miner.minerID, "-", err) 50 | } 51 | defer program.Release() 52 | 53 | err = program.BuildProgram([]*cl.Device{miner.clDevice}, "") 54 | if err != nil { 55 | log.Fatalln(miner.minerID, "-", err) 56 | } 57 | 58 | kernel, err := program.CreateKernel("nonceGrind") 59 | if err != nil { 60 | log.Fatalln(miner.minerID, "-", err) 61 | } 62 | defer kernel.Release() 63 | 64 | blockHeaderObj, err := context.CreateEmptyBuffer(cl.MemReadOnly, 80) 65 | if err != nil { 66 | log.Fatalln(miner.minerID, "-", err) 67 | } 68 | defer blockHeaderObj.Release() 69 | kernel.SetArgBuffer(0, blockHeaderObj) 70 | 71 | nonceOutObj, err := context.CreateEmptyBuffer(cl.MemReadWrite, 8) 72 | if err != nil { 73 | log.Fatalln(miner.minerID, "-", err) 74 | } 75 | defer nonceOutObj.Release() 76 | kernel.SetArgBuffer(1, nonceOutObj) 77 | 78 | localItemSize, err := kernel.WorkGroupSize(miner.clDevice) 79 | if err != nil { 80 | log.Fatalln(miner.minerID, "- WorkGroupSize failed -", err) 81 | } 82 | 83 | log.Println(miner.minerID, "- Global item size:", miner.GlobalItemSize, "(Intensity", intensity, ")", "- Local item size:", localItemSize) 84 | 85 | log.Println(miner.minerID, "- Initialized ", miner.clDevice.Type(), "-", miner.clDevice.Name()) 86 | 87 | nonceOut := make([]byte, 8, 8) 88 | if _, err = commandQueue.EnqueueWriteBufferByte(nonceOutObj, true, 0, nonceOut, nil); err != nil { 89 | log.Fatalln(miner.minerID, "-", err) 90 | } 91 | for { 92 | start := time.Now() 93 | var work *MiningWork 94 | continueMining := true 95 | select { 96 | case work, continueMining = <-miner.miningWorkChannel: 97 | default: 98 | log.Println(miner.minerID, "-", "No work ready") 99 | work, continueMining = <-miner.miningWorkChannel 100 | log.Println(miner.minerID, "-", "Continuing") 101 | } 102 | if !continueMining { 103 | log.Println("Halting miner ", miner.minerID) 104 | break 105 | } 106 | //Copy input to kernel args 107 | if _, err = commandQueue.EnqueueWriteBufferByte(blockHeaderObj, true, 0, work.Header, nil); err != nil { 108 | log.Fatalln(miner.minerID, "-", err) 109 | } 110 | 111 | //Run the kernel 112 | if _, err = commandQueue.EnqueueNDRangeKernel(kernel, []int{work.Offset}, []int{miner.GlobalItemSize}, []int{localItemSize}, nil); err != nil { 113 | log.Fatalln(miner.minerID, "-", err) 114 | } 115 | //Get output 116 | if _, err = commandQueue.EnqueueReadBufferByte(nonceOutObj, true, 0, nonceOut, nil); err != nil { 117 | log.Fatalln(miner.minerID, "-", err) 118 | } 119 | //Check if match found 120 | if nonceOut[0] != 0 || nonceOut[1] != 0 || nonceOut[2] != 0 || nonceOut[3] != 0 || nonceOut[4] != 0 || nonceOut[5] != 0 || nonceOut[6] != 0 || nonceOut[7] != 0 { 121 | log.Println(miner.minerID, "-", "Yay, solution found!") 122 | if nonceOut[0] == 0 { 123 | log.Println(miner.minerID, "-", "Solution found with a nonce that started with 0...") 124 | } 125 | // Copy nonce to a new header. 126 | header := append([]byte(nil), work.Header...) 127 | for i := 0; i < 8; i++ { 128 | header[i+32] = nonceOut[i] 129 | } 130 | go func() { 131 | if err := miner.siad.SubmitHeader(header); err != nil { 132 | log.Println(miner.minerID, "- Error submitting solution -", err) 133 | } 134 | }() 135 | log.Println("Work header:", work.Header) 136 | log.Println("Offset:", work.Offset) 137 | log.Println("Submitted header:", header) 138 | 139 | //Clear the output since it is dirty now 140 | nonceOut = make([]byte, 8, 8) 141 | if _, err = commandQueue.EnqueueWriteBufferByte(nonceOutObj, true, 0, nonceOut, nil); err != nil { 142 | log.Fatalln(miner.minerID, "-", err) 143 | } 144 | } 145 | 146 | hashRate := float64(miner.GlobalItemSize) / (time.Since(start).Seconds() * 1000000) 147 | miner.hashRateReports <- &HashRateReport{miner.minerID, hashRate} 148 | } 149 | 150 | } 151 | --------------------------------------------------------------------------------