[
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 etherchain-org\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "README.md",
    "content": "# Introduction\nWe have decided to open source our implementation of pooled mining for Ethereum. This software is not a complete mining pool. It only takes care of work distribution and share validation; valid shares are stored into a local database (LevelDB). Reward calculation and payments are not yet implemented but it should be possible to connect this software some of the  existing open source mining pools.\n\n# Performance\nWhile the current implementation in go might not be the most effective one, the pool was able to process ~600 workers at 30% CPU utilization (1 core) and 70MB RAM usage.\n\n# Supported clients\nThe pool has been tested successfully with both the go Ethereum client (geth) and the cpp Ethereum client (eth).\n\n# Pull requests & possible optimizations\nIf you find any issues with the pool software please feel free to issue a pull request.\n\nIf you want to improve the pool, implementing the connection to geth via IPC instead of HTTP would be a good start.\n\n# Setup guide (Ubuntu 14.04)\n* Install go according to https://github.com/ethereum/go-ethereum/wiki/Installing-Go#ubuntu-1404\n* Put the pool.go file into your gopath\n* Run go get to download the dependencies\n* Adjust the ports to match your environment (poolPort and ethereumPort)\n* Start your Ethereum client & enable RPC\n* Run go build pool.go\n* Start the pool server ./pool\n* Point your miner to http://ip:port/miner/\\<account\\>.\\<worker\\>/\\<hashrate\\>\n\n# Donations\nDonations are always welcome:\n\nBTC: 37rfj6oPJmnEDHTnUxvsUEmF4CnqofgWJr\n\nETH: 0xc5d2dd8b399b67d857ed6d91bbe26f0702f7cd34\n"
  },
  {
    "path": "pool.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"math/big\"\n\t\"net/http\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/ethereum/ethash\"\n\t\"github.com/ethereum/go-ethereum/common\"\n\n\t\"github.com/gorilla/mux\"\n\n\t\"github.com/syndtr/goleveldb/leveldb\"\n)\n\nvar levelDB *leveldb.DB\n\nvar currWork *ResponseArray = nil\n\nvar pendingBlockNumber uint64 = 0\nvar pendingBlockDifficulty *big.Int\n\nvar invalidRequest = `{\n  \"id\":64,\n  \"jsonrpc\": \"2.0\",\n  \"result\": false,\n  \"error\": \"invalid request\"\n}`\n\nvar okRequest = `{\n  \"id\":64,\n  \"jsonrpc\": \"2.0\",\n  \"result\": true\n}`\n\nvar pow256 = common.BigPow(2, 256)\n\nvar hasher = ethash.New()\n\nvar poolPort = \"5082\"\nvar ethereumPort = \"8545\" //8545 = geth, 8080 = eth (requires dev branch when using eth client)\n\nvar logInfo *log.Logger\nvar logError *log.Logger\n\ntype ResponseArray struct {\n\tId      int           `json:\"id\"`\n\tJsonrpc string        `json:\"jsonrpc\"`\n\tResult  []interface{} `json:\"result\"`\n}\n\ntype ResponseJSON struct {\n\tId      int                    `json:\"id\"`\n\tJsonrpc string                 `json:\"jsonrpc\"`\n\tResult  map[string]interface{} `json:\"result\"`\n}\n\ntype ResponseBool struct {\n\tId      int    `json:\"id\"`\n\tJsonrpc string `json:\"jsonrpc\"`\n\tResult  bool   `json:\"result\"`\n}\n\ntype Request struct {\n\tId      int           `json:\"id\"`\n\tJsonrpc string        `json:\"jsonrpc\"`\n\tMethod  string        `json:\"method\"`\n\tParams  []interface{} `json:\"params\"`\n}\n\ntype block struct {\n\tdifficulty  *big.Int\n\thashNoNonce common.Hash\n\tnonce       uint64\n\tmixDigest   common.Hash\n\tnumber      uint64\n}\n\nfunc (b block) Difficulty() *big.Int     { return b.difficulty }\nfunc (b block) HashNoNonce() common.Hash { return b.hashNoNonce }\nfunc (b block) Nonce() uint64            { return b.nonce }\nfunc (b block) MixDigest() common.Hash   { return b.mixDigest }\nfunc (b block) NumberU64() uint64        { return b.number }\n\nfunc main() {\n\t// Set up logging\n\tlogInfo = log.New(os.Stderr, \"INFO: \", log.Ldate|log.Ltime)\n\tlogError = log.New(os.Stderr, \"ERROR: \", log.Ldate|log.Ltime)\n\tlogInfo.Println(\"Welcome to ethpool 2.0\")\n\tlogInfo.Println(\"Pool port is\", poolPort)\n\tlogInfo.Println(\"Point your miners to: http://<ip>:\" + poolPort + \"/miner/{miner}/{difficulty}\")\n\n\t// Open the share database\n\tvar err error\n\n\tlevelDB, err = leveldb.OpenFile(\"~/ethpool_shares.db\", nil)\n\tif err != nil {\n\t\tlogError.Println(\"Unable to open leveldb connection:\", err)\n\t\treturn\n\t}\n\tdefer levelDB.Close()\n\n\tgo updateWork()\n\tgo updatePendingBlock()\n\tgo submitShares()\n\t// names := []interface{}{\"pending\", false}\n\n\t// pb := callJSON(\"eth_getBlockByNumber\", names)\n\t// fmt.Println(pb.Result[\"number\"])\n\n\t// fmt.Scanln()\n\tr := mux.NewRouter()\n\tr.HandleFunc(\"/miner/{miner}/{difficulty}\", handleMiner)\n\thttp.Handle(\"/\", r)\n\tlog.Fatal(http.ListenAndServe(\":5082\", nil))\n}\n\nfunc handleMiner(rw http.ResponseWriter, req *http.Request) {\n\n\tvars := mux.Vars(req)\n\n\tminerDifficulty, err := strconv.ParseFloat(vars[\"difficulty\"], 64)\n\tif err != nil {\n\t\tlogError.Println(\"Invalid difficulty provided: \" + vars[\"difficulty\"])\n\t\tminerDifficulty = 5 // Set a fixed difficulty (5MH/s) in this case\n\t\t// fmt.Fprint(rw, getErrorResponse(\"Invalid difficulty provided: \"+vars[\"difficulty\"]))\n\t\t// return\n\t}\n\tminerAdjustedDifficulty := int64(minerDifficulty * 1000000 * 100)\n\n\tminerArray := strings.Split(vars[\"miner\"], \".\")\n\n\tif len(minerArray) == 0 || len(minerArray) > 2 {\n\t\tlogError.Println(\"Invalid miner & worker provided: \" + vars[\"miner\"])\n\t\tfmt.Fprint(rw, getErrorResponse(\"Invalid miner & worker provided: \"+vars[\"miner\"]))\n\t\treturn\n\t}\n\n\tminer := strings.Replace(minerArray[0], \"0x\", \"\", -1)\n\tworker := \"default\"\n\n\tif len(minerArray) == 2 {\n\t\tworker = minerArray[1]\n\t}\n\n\tif len(miner) != 40 {\n\t\tlogError.Println(\"Invalid ethereum address provided: 0x\" + miner)\n\t\tfmt.Fprint(rw, getErrorResponse(\"Invalid ethereum address provided: 0x\"+miner))\n\t\treturn\n\t}\n\n\tdecoder := json.NewDecoder(req.Body)\n\tvar t Request\n\terr = decoder.Decode(&t)\n\tif err != nil {\n\t\tlogError.Println(\"Invalid JSON request: \", err)\n\t\tfmt.Fprint(rw, getErrorResponse(\"Invalid JSON request\"))\n\t\treturn\n\t}\n\n\tif t.Method == \"eth_getWork\" {\n\t\tdifficulty := big.NewInt(minerAdjustedDifficulty)\n\t\t// Send the response\n\t\tfmt.Fprint(rw, getWorkPackage(difficulty))\n\t} else if t.Method == \"eth_submitHashrate\" {\n\t\tfmt.Fprint(rw, okRequest)\n\t} else if t.Method == \"eth_submitWork\" {\n\t\tparamsOrig := t.Params[:]\n\n\t\thashNoNonce := t.Params[1].(string)\n\t\tnonce, err := strconv.ParseUint(strings.Replace(t.Params[0].(string), \"0x\", \"\", -1), 16, 64)\n\t\tif err != nil {\n\t\t\tlogError.Println(\"Invalid nonce provided: \", err)\n\t\t\tfmt.Fprint(rw, getErrorResponse(\"Invalid nonce provided\"))\n\t\t\treturn\n\t\t}\n\n\t\tmixDigest := t.Params[2].(string)\n\n\t\tmyBlock := block{\n\t\t\tnumber:      pendingBlockNumber,\n\t\t\thashNoNonce: common.HexToHash(hashNoNonce),\n\t\t\tdifficulty:  big.NewInt(minerAdjustedDifficulty),\n\t\t\tnonce:       nonce,\n\t\t\tmixDigest:   common.HexToHash(mixDigest),\n\t\t}\n\n\t\tmyBlockRealDiff := block{\n\t\t\tnumber:      pendingBlockNumber,\n\t\t\thashNoNonce: common.HexToHash(hashNoNonce),\n\t\t\tdifficulty:  pendingBlockDifficulty,\n\t\t\tnonce:       nonce,\n\t\t\tmixDigest:   common.HexToHash(mixDigest),\n\t\t}\n\n\t\tif hasher.Verify(myBlock) {\n\t\t\t//fmt.Println(\"Share is valid\")\n\t\t\tif hasher.Verify(myBlockRealDiff) {\n\t\t\t\tsubmitWork(paramsOrig)\n\t\t\t\tlogInfo.Println(\"###########################################################################\")\n\t\t\t\tlogInfo.Println(\"################################Block found################################\")\n\t\t\t\tlogInfo.Println(\"###########################################################################\")\n\t\t\t}\n\n\t\t\tshare := `MIX:` + mixDigest + `|MINER:\"` + miner + `|DIFFICULTY:` + strconv.FormatInt(minerAdjustedDifficulty, 10) + `|WORKER:` + worker\n\t\t\tlogInfo.Println(\"Miner\", miner, \".\", worker, \"found valid share (Diff:\", minerAdjustedDifficulty, \"Mix:\", mixDigest, \"Hash:\", hashNoNonce, \"Nonce:\", nonce, \")\")\n\t\t\terr = levelDB.Put([]byte(mixDigest), []byte(share), nil)\n\n\t\t\tif err != nil {\n\t\t\t\tlogError.Println(\"Error inserting share into database:\", err)\n\t\t\t}\n\t\t} else {\n\t\t\tlogError.Println(\"Miner\", miner, \"provided invalid share\")\n\t\t\tfmt.Fprint(rw, getErrorResponse(\"Provided PoW solution is invalid!\"))\n\t\t}\n\t\tfmt.Fprint(rw, okRequest)\n\t} else {\n\t\tlogError.Println(\"Method \" + t.Method + \" not implemented!\")\n\t\tfmt.Fprint(rw, getErrorResponse(\"Method \"+t.Method+\" not implemented!\"))\n\t}\n}\n\nfunc getWorkPackage(difficulty *big.Int) string {\n\n\tif currWork == nil {\n\t\treturn getErrorResponse(\"Current work unavailable\")\n\t}\n\n\t// Our response object\n\tresponse := &ResponseArray{\n\t\tId:      currWork.Id,\n\t\tJsonrpc: currWork.Jsonrpc,\n\t\tResult:  currWork.Result[:],\n\t}\n\n\t// Calculte requested difficulty\n\tdiff := new(big.Int).Div(pow256, difficulty)\n\tdiffBytes := string(common.ToHex(diff.Bytes()))\n\n\t// Adjust the difficulty for the miner\n\tresponse.Result[2] = diffBytes\n\n\t// Convert respone object to JSON\n\tb, _ := json.Marshal(response)\n\n\treturn string(b)\n\n}\n\nfunc updateWork() {\n\tfor true {\n\t\tcurrWorkNew, err := callArray(\"eth_getWork\", []interface{}{})\n\n\t\tif err == nil {\n\t\t\tcurrWork = currWorkNew\n\t\t} else {\n\t\t\tcurrWork = nil\n\t\t}\n\n\t\t// fmt.Println(\"Current work\", currWork.Result[0])\n\t\ttime.Sleep(time.Millisecond * 100)\n\t}\n}\n\nfunc submitWork(params []interface{}) {\n\tresult, err := callBool(\"eth_submitWork\", params)\n\tif err == nil {\n\t\tlogInfo.Println(result.Result)\n\t}\n}\n\nfunc submitShares() {\n\tfor true {\n\t\titer := levelDB.NewIterator(nil, nil)\n\t\tfor iter.Next() {\n\t\t\tkey := iter.Key()\n\t\t\tvalue := iter.Value()\n\t\t\t_ = value\n\t\t\tlogInfo.Println(\"Do smth with the share (e.g. send to pool database):\", string(key))\n\t\t}\n\t\titer.Release()\n\t\terr := iter.Error()\n\t\tif err != nil {\n\t\t\tlogError.Println(\"Error itarating shares:\", err)\n\t\t}\n\t\ttime.Sleep(time.Second * 10)\n\t}\n}\n\nfunc updatePendingBlock() {\n\tparams := []interface{}{\"pending\", false}\n\n\tfor true {\n\t\tblock, err := callJSON(\"eth_getBlockByNumber\", params)\n\t\tif err == nil {\n\t\t\tblockNbr, err := strconv.ParseUint(strings.Replace(block.Result[\"number\"].(string), \"0x\", \"\", -1), 16, 64)\n\t\t\tif err == nil {\n\t\t\t\tpendingBlockNumber = blockNbr\n\t\t\t}\n\n\t\t\tblockDiff, err := strconv.ParseInt(strings.Replace(block.Result[\"difficulty\"].(string), \"0x\", \"\", -1), 16, 64)\n\t\t\tif err == nil {\n\t\t\t\tpendingBlockDifficulty = big.NewInt(blockDiff)\n\t\t\t\t// logInfo.Println(\"Pending block difficulty:\", pendingBlockDifficulty)\n\t\t\t}\n\t\t}\n\t\ttime.Sleep(time.Millisecond * 100)\n\t}\n}\n\nfunc callArray(method string, params []interface{}) (*ResponseArray, error) {\n\turl := \"http://localhost:\" + ethereumPort\n\tjsonReq := &Request{\n\t\tId:      1,\n\t\tJsonrpc: \"2.0\",\n\t\tMethod:  method,\n\t\tParams:  params,\n\t}\n\treqJSON, _ := json.Marshal(jsonReq)\n\t// fmt.Println(string(reqJSON))\n\treq, err := http.NewRequest(\"POST\", url, bytes.NewBuffer(reqJSON))\n\n\tif err != nil {\n\t\tlogError.Println(\"Could not create POST request\", err)\n\t\treturn nil, errors.New(\"Could not create POST request\")\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tclient := &http.Client{}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\tlogError.Println(\"Could not send POST request to Ethereum client\", err)\n\t\treturn nil, errors.New(\"Could not send POST request to Ethereum client\")\n\t}\n\n\tdefer resp.Body.Close()\n\tbody, _ := ioutil.ReadAll(resp.Body)\n\n\t// fmt.Println(string(body))\n\tres := &ResponseArray{}\n\n\tif err := json.Unmarshal(body, res); err != nil {\n\t\tlogError.Println(\"Ethereum client returned unexpected data\", err)\n\t\treturn nil, errors.New(\"Ethereum client returned unexpected data\")\n\t}\n\n\t// fmt.Println(\"done\")\n\treturn res, nil\n}\n\nfunc callBool(method string, params []interface{}) (*ResponseBool, error) {\n\turl := \"http://localhost:\" + ethereumPort\n\tjsonReq := &Request{\n\t\tId:      1,\n\t\tJsonrpc: \"2.0\",\n\t\tMethod:  method,\n\t\tParams:  params,\n\t}\n\treqJSON, _ := json.Marshal(jsonReq)\n\t// fmt.Println(string(reqJSON))\n\treq, err := http.NewRequest(\"POST\", url, bytes.NewBuffer(reqJSON))\n\n\tif err != nil {\n\t\tlogError.Println(\"Could not create POST request\", err)\n\t\treturn nil, errors.New(\"Could not create POST request\")\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tclient := &http.Client{}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\tlogError.Println(\"Could not send POST request to Ethereum client\", err)\n\t\treturn nil, errors.New(\"Could not send POST request to Ethereum client\")\n\t}\n\n\tdefer resp.Body.Close()\n\tbody, _ := ioutil.ReadAll(resp.Body)\n\n\t// fmt.Println(string(body))\n\tres := &ResponseBool{}\n\n\tif err := json.Unmarshal(body, res); err != nil {\n\t\tlogError.Println(\"Ethereum client returned unexpected data\", err)\n\t\treturn nil, errors.New(\"Ethereum client returned unexpected data\")\n\t}\n\n\t// fmt.Println(\"done\")\n\treturn res, nil\n}\n\nfunc callJSON(method string, params []interface{}) (*ResponseJSON, error) {\n\turl := \"http://localhost:\" + ethereumPort\n\tjsonReq := &Request{\n\t\tId:      1,\n\t\tJsonrpc: \"2.0\",\n\t\tMethod:  method,\n\t\tParams:  params,\n\t}\n\treqJSON, _ := json.Marshal(jsonReq)\n\t// fmt.Println(string(reqJSON))\n\treq, err := http.NewRequest(\"POST\", url, bytes.NewBuffer(reqJSON))\n\n\tif err != nil {\n\t\tlogError.Println(\"Could not create POST request\", err)\n\t\treturn nil, errors.New(\"Could not create POST request\")\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tclient := &http.Client{}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\tlogError.Println(\"Could not send POST request to Ethereum client\", err)\n\t\treturn nil, errors.New(\"Could not send POST request to Ethereum client\")\n\t}\n\n\tdefer resp.Body.Close()\n\tbody, _ := ioutil.ReadAll(resp.Body)\n\n\t// fmt.Println(string(body))\n\tres := &ResponseJSON{}\n\n\tif err := json.Unmarshal(body, res); err != nil {\n\t\tlogError.Println(\"Ethereum client returned unexpected data\", err)\n\t\treturn nil, errors.New(\"Ethereum client returned unexpected data\")\n\t}\n\n\t// fmt.Println(\"done\")\n\treturn res, nil\n}\n\nfunc getErrorResponse(errorMsg string) string {\n\treturn `{\n    \"id\":64,\n    \"jsonrpc\": \"2.0\",\n    \"result\": false,\n    \"error\": \"` + errorMsg + `\"\n  }`\n}\n"
  }
]