Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop #451

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 17 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@

### Features

**This pool is being further developed to provide an easy to use pool for Ethereum miners. This software is functional however an optimised release of the pool is expected soon. Testing and bug submissions are welcome!**
**This pool is no longer supported, expect only casual fixes.**

**Parity client is MANDATORY. Geth is no longer supported.**

* Support for HTTP and Stratum mining
* Detailed block stats with luck percentage and full reward
* Failover geth instances: geth high availability built in
* Parity nodes rpc failover built in
* Modern beautiful Ember.js frontend
* Separate stats for workers: can highlight timed-out workers so miners can perform maintenance of rigs
* JSON-API for stats
Expand All @@ -25,7 +27,7 @@
Dependencies:

* go >= 1.9
* geth or parity
* parity (will not work with geth)
* redis-server >= 2.8.0
* nodejs >= 4 LTS
* nginx
Expand Down Expand Up @@ -131,10 +133,13 @@ otherwise you will get errors on start because of JSON comments.**
// Bind stratum mining socket to this IP:PORT
"listen": "0.0.0.0:8008",
"timeout": "120s",
"maxConn": 8192
"maxConn": 8192,
"tls": false,
"certFile": "/path/to/cert.pem",
"keyFile": "/path/to/key.pem"
},

// Try to get new job from geth in this interval
// Try to get new job from node in this interval
"blockRefreshInterval": "120ms",
"stateUpdateInterval": "3s",
// Require this share difficulty from miners
Expand Down Expand Up @@ -208,10 +213,10 @@ otherwise you will get errors on start because of JSON comments.**
"purgeOnly": false
},

// Check health of each geth node in this interval
// Check health of each node in this interval
"upstreamCheckInterval": "5s",

/* List of geth nodes to poll for new jobs. Pool will try to get work from
/* List of parity nodes to poll for new jobs. Pool will try to get work from
first alive one and check in background for failed to back up.
Current block template of the pool is always cached in RAM indeed.
*/
Expand Down Expand Up @@ -254,9 +259,9 @@ otherwise you will get errors on start because of JSON comments.**
"keepTxFees": false,
// Run unlocker in this interval
"interval": "10m",
// Geth instance node rpc endpoint for unlocking blocks
// Parity node rpc endpoint for unlocking blocks
"daemon": "http://127.0.0.1:8545",
// Rise error if can't reach geth in this amount of time
// Rise error if can't reach parity
"timeout": "10s"
},

Expand All @@ -267,13 +272,13 @@ otherwise you will get errors on start because of JSON comments.**
"requirePeers": 25,
// Run payouts in this interval
"interval": "12h",
// Geth instance node rpc endpoint for payouts processing
// Parity node rpc endpoint for payouts processing
"daemon": "http://127.0.0.1:8545",
// Rise error if can't reach geth in this amount of time
// Rise error if can't reach parity
"timeout": "10s",
// Address with pool balance
"address": "0x0",
// Let geth to determine gas and gasPrice
// Let parity to determine gas and gasPrice
"autoGas": true,
// Gas amount and price for payout tx (advanced users only)
"gas": "21000",
Expand Down Expand Up @@ -303,10 +308,6 @@ I recommend this deployment strategy:
* Don't run payouts and unlocker modules as part of mining node. Create separate configs for both, launch independently and make sure you have a single instance of each module running.
* If `poolFeeAddress` is not specified all pool profit will remain on coinbase address. If it specified, make sure to periodically send some dust back required for payments.

### Alternative Ethereum Implementations

This pool is tested to work with [Ethcore's Parity](https://github.com/ethcore/parity). Mining and block unlocking works, but I am not sure about payouts and suggest to run *official* geth node for payments.

### Credits

Made by sammy007. Licensed under GPLv3.
Expand Down
5 changes: 4 additions & 1 deletion config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
"enabled": true,
"listen": "0.0.0.0:8008",
"timeout": "120s",
"maxConn": 8192
"maxConn": 8192,
"tls": false,
"certFile": "/path/to/cert.pem",
"keyFile": "/path/to/key.pem"
},

"policy": {
Expand Down
9 changes: 7 additions & 2 deletions payouts/unlocker.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ type UnlockerConfig struct {

const minDepth = 16
const byzantiumHardForkHeight = 4370000
const constantinopleHardForkHeight = 7280000

var homesteadReward = math.MustParseBig256("5000000000000000000")
var byzantiumReward = math.MustParseBig256("3000000000000000000")
var constantinopleReward = math.MustParseBig256("2000000000000000000")

// Donate 10% from pool fees to developers
const donationFee = 10.0
Expand Down Expand Up @@ -253,7 +255,7 @@ func (u *BlockUnlocker) unlockPendingBlocks() {
return
}

current, err := u.rpc.GetPendingBlock()
current, err := u.rpc.GetLatestBlock()
if err != nil {
u.halt = true
u.lastFail = err
Expand Down Expand Up @@ -351,7 +353,7 @@ func (u *BlockUnlocker) unlockAndCreditMiners() {
return
}

current, err := u.rpc.GetPendingBlock()
current, err := u.rpc.GetLatestBlock()
if err != nil {
u.halt = true
u.lastFail = err
Expand Down Expand Up @@ -502,6 +504,9 @@ func weiToShannonInt64(wei *big.Rat) int64 {
}

func getConstReward(height int64) *big.Int {
if height >= constantinopleHardForkHeight {
return new(big.Int).Set(constantinopleReward)
}
if height >= byzantiumHardForkHeight {
return new(big.Int).Set(byzantiumReward)
}
Expand Down
12 changes: 10 additions & 2 deletions payouts/unlocker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,22 +108,30 @@ func TestGetByzantiumUncleReward(t *testing.T) {
}
}

func TestGetRewardForUngle(t *testing.T) {
func TestGetRewardForUncle(t *testing.T) {
reward := getRewardForUncle(1).String()
expectedReward := "156250000000000000"
if expectedReward != reward {
t.Errorf("Incorrect uncle bonus for height %v, expected %v vs %v", 1, expectedReward, reward)
}
}

func TestGetByzantiumRewardForUngle(t *testing.T) {
func TestGetByzantiumRewardForUncle(t *testing.T) {
reward := getRewardForUncle(byzantiumHardForkHeight).String()
expectedReward := "93750000000000000"
if expectedReward != reward {
t.Errorf("Incorrect uncle bonus for height %v, expected %v vs %v", byzantiumHardForkHeight, expectedReward, reward)
}
}

func TestGetConstantinopleRewardForUncle(t *testing.T) {
reward := getRewardForUncle(constantinopleHardForkHeight).String()
expectedReward := "62500000000000000"
if expectedReward != reward {
t.Errorf("Incorrect uncle bonus for height %v, expected %v vs %v", constantinopleHardForkHeight, expectedReward, reward)
}
}

func TestMatchCandidate(t *testing.T) {
gethBlock := &rpc.GetBlockReply{Hash: "0x12345A", Nonce: "0x1A"}
parityBlock := &rpc.GetBlockReply{Hash: "0x12345A", SealFields: []string{"0x0A", "0x1A"}}
Expand Down
44 changes: 12 additions & 32 deletions proxy/blocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,37 +47,37 @@ func (b Block) MixDigest() common.Hash { return b.mixDigest }
func (b Block) NumberU64() uint64 { return b.number }

func (s *ProxyServer) fetchBlockTemplate() {
rpc := s.rpc()
r := s.rpc()
t := s.currentBlockTemplate()
pendingReply, height, diff, err := s.fetchPendingBlock()
reply, err := r.GetWork()
if err != nil {
log.Printf("Error while refreshing pending block on %s: %s", rpc.Name, err)
return
}
reply, err := rpc.GetWork()
if err != nil {
log.Printf("Error while refreshing block template on %s: %s", rpc.Name, err)
log.Printf("Error while refreshing block template on %s: %s", r.Name, err)
return
}
// No need to update, we have fresh job
if t != nil && t.Header == reply[0] {
return
}
diff := util.TargetHexToDiff(reply[2])
height, err := strconv.ParseUint(strings.Replace(reply[3], "0x", "", -1), 16, 64)

pendingReply.Difficulty = util.ToHex(s.config.Proxy.Difficulty)
pendingReply := &rpc.GetBlockReplyPart{
Difficulty: util.ToHex(s.config.Proxy.Difficulty),
Number: reply[3],
}

newTemplate := BlockTemplate{
Header: reply[0],
Seed: reply[1],
Target: reply[2],
Height: height,
Difficulty: big.NewInt(diff),
Difficulty: diff,
GetPendingBlockCache: pendingReply,
headers: make(map[string]heightDiffPair),
}
// Copy job backlog and add current one
newTemplate.headers[reply[0]] = heightDiffPair{
diff: util.TargetHexToDiff(reply[2]),
diff: diff,
height: height,
}
if t != nil {
Expand All @@ -88,30 +88,10 @@ func (s *ProxyServer) fetchBlockTemplate() {
}
}
s.blockTemplate.Store(&newTemplate)
log.Printf("New block to mine on %s at height %d / %s", rpc.Name, height, reply[0][0:10])
log.Printf("New block to mine on %s at height %d / %s / %d", r.Name, height, reply[0][0:10], diff)

// Stratum
if s.config.Proxy.Stratum.Enabled {
go s.broadcastNewJobs()
}
}

func (s *ProxyServer) fetchPendingBlock() (*rpc.GetBlockReplyPart, uint64, int64, error) {
rpc := s.rpc()
reply, err := rpc.GetPendingBlock()
if err != nil {
log.Printf("Error while refreshing pending block on %s: %s", rpc.Name, err)
return nil, 0, 0, err
}
blockNumber, err := strconv.ParseUint(strings.Replace(reply.Number, "0x", "", -1), 16, 64)
if err != nil {
log.Println("Can't parse pending block number")
return nil, 0, 0, err
}
blockDiff, err := strconv.ParseInt(strings.Replace(reply.Difficulty, "0x", "", -1), 16, 64)
if err != nil {
log.Println("Can't parse pending block difficulty")
return nil, 0, 0, err
}
return reply, blockNumber, blockDiff, nil
}
11 changes: 7 additions & 4 deletions proxy/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,13 @@ type Proxy struct {
}

type Stratum struct {
Enabled bool `json:"enabled"`
Listen string `json:"listen"`
Timeout string `json:"timeout"`
MaxConn int `json:"maxConn"`
Enabled bool `json:"enabled"`
Listen string `json:"listen"`
Timeout string `json:"timeout"`
MaxConn int `json:"maxConn"`
TLS bool `json:"tls"`
CertFile string `json:"certFile"`
KeyFile string `json:"keyFile"`
}

type Upstream struct {
Expand Down
2 changes: 1 addition & 1 deletion proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ type Session struct {

// Stratum
sync.Mutex
conn *net.TCPConn
conn net.Conn
login string
}

Expand Down
26 changes: 16 additions & 10 deletions proxy/stratum.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package proxy

import (
"bufio"
"crypto/tls"
"encoding/json"
"errors"
"io"
Expand All @@ -17,14 +18,21 @@ const (
)

func (s *ProxyServer) ListenTCP() {
timeout := util.MustParseDuration(s.config.Proxy.Stratum.Timeout)
s.timeout = timeout
s.timeout = util.MustParseDuration(s.config.Proxy.Stratum.Timeout)

addr, err := net.ResolveTCPAddr("tcp", s.config.Proxy.Stratum.Listen)
if err != nil {
log.Fatalf("Error: %v", err)
var err error
var server net.Listener
if s.config.Proxy.Stratum.TLS {
var cert tls.Certificate
cert, err = tls.LoadX509KeyPair(s.config.Proxy.Stratum.CertFile, s.config.Proxy.Stratum.KeyFile)
if err != nil {
log.Fatalln("Error loading certificate:", err)
}
tlsCfg := &tls.Config{Certificates: []tls.Certificate{cert}}
server, err = tls.Listen("tcp", s.config.Proxy.Stratum.Listen, tlsCfg)
} else {
server, err = net.Listen("tcp", s.config.Proxy.Stratum.Listen)
}
server, err := net.ListenTCP("tcp", addr)
if err != nil {
log.Fatalf("Error: %v", err)
}
Expand All @@ -35,12 +43,10 @@ func (s *ProxyServer) ListenTCP() {
n := 0

for {
conn, err := server.AcceptTCP()
conn, err := server.Accept()
if err != nil {
continue
}
conn.SetKeepAlive(true)

ip, _, _ := net.SplitHostPort(conn.RemoteAddr().String())

if s.policy.IsBanned(ip) || !s.policy.ApplyLimitPolicy(ip) {
Expand Down Expand Up @@ -169,7 +175,7 @@ func (cs *Session) sendTCPError(id json.RawMessage, reply *ErrorReply) error {
return errors.New(reply.Message)
}

func (self *ProxyServer) setDeadline(conn *net.TCPConn) {
func (self *ProxyServer) setDeadline(conn net.Conn) {
conn.SetDeadline(time.Now().Add(self.timeout))
}

Expand Down
4 changes: 2 additions & 2 deletions rpc/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ func (r *RPCClient) GetWork() ([]string, error) {
return reply, err
}

func (r *RPCClient) GetPendingBlock() (*GetBlockReplyPart, error) {
rpcResp, err := r.doPost(r.Url, "eth_getBlockByNumber", []interface{}{"pending", false})
func (r *RPCClient) GetLatestBlock() (*GetBlockReplyPart, error) {
rpcResp, err := r.doPost(r.Url, "eth_getBlockByNumber", []interface{}{"latest", false})
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion www/.ember-cli
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@

Setting `disableAnalytics` to true will prevent any data from being sent.
*/
"disableAnalytics": false
"disableAnalytics": true
}
Loading