diff --git a/cmd/root.go b/cmd/root.go
index 0588cd5..a45e8c3 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -203,7 +203,7 @@ var testMiner = &cobra.Command{
ctx, cancel := context.WithCancel(context.Background())
exit.GlobalExitHandler.AddCancel(cancel)
- client, err := stratum.NewClient("user", "miner", "password", "invitecode", "payoutaddress", config.CompiledInVersion)
+ client, err := stratum.NewClient("user", "miner", "password", "invitecode", "payoutaddress", config.CompiledInVersion, nil)
if err != nil {
panic(err)
}
diff --git a/go.mod b/go.mod
index 136a323..2ed0659 100644
--- a/go.mod
+++ b/go.mod
@@ -7,10 +7,10 @@ require (
github.com/Factom-Asset-Tokens/factom v0.0.0-20191120022136-7bf60a31a324
github.com/andybalholm/cascadia v1.1.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect
- github.com/aws/aws-sdk-go v1.25.12 // indirect
github.com/cenkalti/backoff v2.1.1+incompatible
github.com/disintegration/imaging v1.6.1 // indirect
github.com/dustin/go-humanize v1.0.0
+ github.com/ethereum/go-ethereum v1.9.11
github.com/fatih/color v1.7.0 // indirect
github.com/gorilla/rpc v1.2.0
github.com/gorilla/sessions v1.2.0 // indirect
@@ -18,10 +18,11 @@ require (
github.com/jinzhu/configor v1.1.1 // indirect
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a // indirect
github.com/jinzhu/gorm v1.9.11
+ github.com/kardianos/service v1.0.0
github.com/mattn/go-colorable v0.1.4 // indirect
github.com/mattn/go-isatty v0.0.10 // indirect
github.com/microcosm-cc/bluemonday v1.0.2 // indirect
- github.com/pegnet/LXRHash v0.0.0-20200205233914-cceb516c4b7f
+ github.com/pegnet/LXRHash v0.0.0-20200611040256-b33327b51c91
github.com/pegnet/pegnet v0.5.0
github.com/pegnet/pegnetd v0.1.2-0.20191011183044-5eca2d08a5e8
github.com/prometheus/client_golang v1.0.0
@@ -56,4 +57,6 @@ require (
go.uber.org/atomic v1.4.0
go.uber.org/ratelimit v0.1.0
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
+ golang.org/x/sync v0.0.0-20190423024810-112230192c58
+ golang.org/x/sys v0.0.0-20191008105621-543471e840be
)
diff --git a/go.sum b/go.sum
index 265fd6e..d6c0a22 100644
--- a/go.sum
+++ b/go.sum
@@ -16,6 +16,19 @@ github.com/AdamSLevy/jsonrpc2/v12 v12.0.1/go.mod h1:UUmIu8A7Sjw+yI0tIc/iGQHoSu/7
github.com/AdamSLevy/jsonrpc2/v13 v13.0.1 h1:hQ5rfCFBQrXQNtvP+2mtH6EmsUAEN61ZF1n8UGVNis0=
github.com/AdamSLevy/jsonrpc2/v13 v13.0.1/go.mod h1:8QsYqGKdPEim+n/j9KFTYB2tIj250f044Vxh2XRKhP4=
github.com/AdamSLevy/sqlitechangeset v0.0.0-20190925183646-3ddb70fb709d/go.mod h1:kQNmmf+2gf3uGKHt0LS4guxdp4Ay44SXA4+Is8/Gxm8=
+github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4=
+github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc=
+github.com/Azure/azure-storage-blob-go v0.7.0/go.mod h1:f9YQKtsG1nMisotuTPpO0tjNuEjKRYAcJU8/ydDI++4=
+github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
+github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
+github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc=
+github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
+github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g=
+github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
+github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
+github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM=
+github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
+github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885 h1:rfy2fwMrOZPqQgjsH7VCreGMSPzcvqQrfjeSs8nf+sY=
@@ -76,20 +89,27 @@ github.com/FactomProject/serveridentity v0.0.0-20180611231115-cf42d2aa8deb/go.mo
github.com/FactomProject/web v0.1.0 h1:M136UkW3h/V8hH7h+s7qqm3GGSrKGKyXi6p94Z/AgPw=
github.com/FactomProject/web v0.1.0/go.mod h1:wiLhlN8amF4dalkSy+u75C3xsXaesSHy2cph6b/8PrI=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
+github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
+github.com/VictoriaMetrics/fastcache v1.5.3/go.mod h1:+jv9Ckb+za/P1ZRg/sulP5Ni1v49daAVERr0H3CuscE=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alexandrevicenzi/go-sse v0.0.0-20190531224209-805eefa457e7 h1:HF2BoTaOEJzRl1WV+epnYM+kGi4DD87mVyQlWbkVLgA=
github.com/alexandrevicenzi/go-sse v0.0.0-20190531224209-805eefa457e7/go.mod h1:BLBuvd1uY9dCX660zu1fzsmr0Cqt3VPqK1e5fPfV6wc=
+github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
+github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847 h1:rtI0fD4oG/8eVokGVPYJEW1F88p1ZNgXiEIs9thEE4A=
+github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/aws/aws-sdk-go v1.25.12 h1:a4h2FxoUJq9h+hajSE/dsRiqoOniIh6BkzhxMjkepzY=
github.com/aws/aws-sdk-go v1.25.12/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@@ -97,12 +117,17 @@ github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4Yn
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
+github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ=
github.com/btcsuitereleases/btcutil v0.0.0-20150612230727-f2b1058a8255 h1:2Dd/81Xn+6DGPIV01YTt9mNV1li0kM1dk62cE3YDU44=
github.com/btcsuitereleases/btcutil v0.0.0-20150612230727-f2b1058a8255/go.mod h1:cUeoYJcc2EfS9DIrDrJ44AjirCbgkmThYeFu/yEddxs=
github.com/cenkalti/backoff v2.1.1+incompatible h1:tKJnvO2kl0zmb/jA5UKAt4VoEVw1qxKWjE/Bpp46npY=
github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
+github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/cespare/xxhash/v2 v2.0.1-0.20190104013014-3767db7a7e18/go.mod h1:HD5P3vAIAh+Y2GAxg0PrPN1P8WkepXGpjbUPDHJqqKM=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9/go.mod h1:1MxXX1Ux4x6mqPmjkUgTP1CdXIBXKX7T+Jk9Gxrmx+U=
github.com/cmars/basen v0.0.0-20150613233007-fe3947df716e h1:0XBUw73chJ1VYSsfvcPvVT7auykAJce9FpRr10L6Qhw=
github.com/cmars/basen v0.0.0-20150613233007-fe3947df716e/go.mod h1:P13beTBKr5Q18lJe1rIoLUqjM+CB1zYrRg44ZqGuQSA=
github.com/codegangsta/cli v1.20.0 h1:iX1FXEgwzd5+XN6wk5cVHOGQj6Q3Dcp20lUeS4lHNTw=
@@ -119,9 +144,12 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea h1:j4317fAZh7X6GqbFowYdYdI0L9bwxL07jyPZIdepyZ0=
+github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3 h1:tkum0XDgfR0jcVVXuTsYv/erY2NnEDqwRojbxR1rBYA=
github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
@@ -129,17 +157,27 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/disintegration/imaging v1.6.1 h1:JnBbK6ECIZb1NsWIikP9pd8gIlTIRx7fuDNpU9fsxOE=
github.com/disintegration/imaging v1.6.1/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ=
+github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
+github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/dop251/goja v0.0.0-20200106141417-aaec0e7bde29/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
+github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
+github.com/elastic/gosigar v0.8.1-0.20180330100440-37f05ff46ffa/go.mod h1:cdorVVzy1fhmEqmtgqkoE3bYtCfSCkVyjTyCIo22xvs=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
+github.com/ethereum/go-ethereum v1.9.11 h1:Z0jugPDfuI5qsPY1XgBGVwikpdFK/ANqP7MrYvkmk+A=
+github.com/ethereum/go-ethereum v1.9.11/go.mod h1:7oC0Ni6dosMv5pxMigm6s0hN8g4haJMBnqmmo0D9YfQ=
+github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-ini/ini v1.44.0 h1:8+SRbfpRFlIunpSum4BEf1ClTtVjOgKzgBv9pHFkI6w=
@@ -147,8 +185,11 @@ github.com/go-ini/ini v1.44.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3I
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
+github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -163,6 +204,7 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2-0.20190517061210-b285ee9cfc6c/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
@@ -173,6 +215,7 @@ github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
@@ -189,8 +232,11 @@ github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYb
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 h1:giknQ4mEuDFmmHSrGcbargOuLHQGtywqo4mheITex54=
+github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gosimple/slug v1.8.0 h1:qOwP2zm20LpQQnVkabt6sFVLaTd1CIvPa3j9uzZzHPM=
github.com/gosimple/slug v1.8.0/go.mod h1:ER78kgg1Mv0NQGlXiDe57DpCyfbNywXXZ9mIorhxAf0=
+github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
@@ -203,6 +249,7 @@ github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-plugin v1.0.1 h1:4OtAfUGbnKC6yS48p0CtMX2oFYtzFZVv6rok3cRWgnE=
github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
+github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
@@ -212,8 +259,11 @@ github.com/howeyc/fsnotify v0.9.0 h1:0gtV5JmOKH4A8SsFxG2BczSeXWWPvcMT0euZt5gDAxY
github.com/howeyc/fsnotify v0.9.0/go.mod h1:41HzSPxBGeFRQKEEwgh49TRw/nKBsYZ2cF1OzPjSJsA=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/huin/goupnp v0.0.0-20161224104101-679507af18f3/go.mod h1:MZ2ZmwcBpvOoJ22IJsc7va19ZwoheaBk43rKg12SKag=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY=
+github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jinzhu/configor v1.1.1 h1:gntDP+ffGhs7aJ0u8JvjCDts2OsxsI7bnz3q+jC+hSY=
github.com/jinzhu/configor v1.1.1/go.mod h1:nX89/MOmDba7ZX7GCyU/VIaQ2Ar2aizBl2d3JLF/rDc=
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o=
@@ -232,7 +282,11 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU=
+github.com/kardianos/service v1.0.0 h1:HgQS3mFfOlyntWX8Oke98JcJLqt1DBcHR4kxShpYef0=
+github.com/kardianos/service v1.0.0/go.mod h1:8CzDhVuCuugtsHyZoTvsOBuvonN/UDBvl0kH+BUxvbo=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -244,15 +298,22 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
+github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
+github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
+github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
@@ -268,10 +329,14 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0=
+github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E=
github.com/nightlyone/lockfile v0.0.0-20180618180623-0ad87eef1443/go.mod h1:JbxfV1Iifij2yhRjXai0oFrbpxszXHRx1E5RuM26o4Y=
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
+github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
@@ -279,7 +344,9 @@ github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
+github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34=
github.com/pegnet/LXR256 v0.0.0-20190721001507-5e925f415fa2/go.mod h1:11Z6s/PoxMH3ON6Kh+2MxNFdaq9op2sgvIAqg52d/3I=
github.com/pegnet/LXRHash v0.0.0-20190913134745-ca3b8b65a729 h1:LNv5Hz+Ydu+CzU+DAD5cGe81zkcvd1tCCKmyhVtpnuY=
github.com/pegnet/LXRHash v0.0.0-20190913134745-ca3b8b65a729/go.mod h1:0zBp9GFy9F77zuQbPkAmdUBRiptMaJ1V96eVFdMnXZA=
@@ -297,9 +364,11 @@ github.com/pegnet/pegnetd v0.1.2-0.20191011183044-5eca2d08a5e8 h1:V41j01pVUgCRG+
github.com/pegnet/pegnetd v0.1.2-0.20191011183044-5eca2d08a5e8/go.mod h1:ArfRaVXzKqOyBQMm4onNh1g17LGN9IEhQs4JcpJH8YM=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.2.1/go.mod h1:6gapUrK/U1TAN7ciCoNRIdVC5sbdBTUh1DKN0g6uH7E=
@@ -322,6 +391,7 @@ github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/qor/admin v0.0.0-20190906081516-5593fc33b920 h1:5EOWduHGAYb1pdtcdUF2xjOwIUXQLY+XCswBw4AYLQg=
github.com/qor/admin v0.0.0-20190906081516-5593fc33b920/go.mod h1:Sm5kX+Hkq1LKiFyqZJLnncUg8dWM/2roOEiy98NOUzA=
@@ -362,12 +432,17 @@ github.com/qor/validations v0.0.0-20171228122639-f364bca61b46/go.mod h1:UJsA0Auv
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
+github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
@@ -378,6 +453,7 @@ github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:s
github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spaolacci/murmur3 v1.0.1-0.20190317074736-539464a789e9/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
@@ -391,6 +467,9 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
+github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q=
+github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw=
+github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@@ -400,6 +479,7 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
+github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA=
github.com/theplant/cldr v0.0.0-20190423050709-9f76f7ce4ee8 h1:di0cR5qqo2DllBMwmP75kZpUX6dAXhsn1O2dshQfMaA=
github.com/theplant/cldr v0.0.0-20190423050709-9f76f7ce4ee8/go.mod h1:MIL7SmF8wRAYDn+JexczVRUiJXTCi4VbQavsCKWKwXI=
github.com/theplant/htmltestingutils v0.0.0-20190423050759-0e06de7b6967 h1:yPrgtU8bj7Q/XbXgjjmngZtOhsUufBAraruNwxv/eXM=
@@ -408,10 +488,13 @@ github.com/theplant/testingutils v0.0.0-20190603093022-26d8b4d95c61 h1:757/ruZNg
github.com/theplant/testingutils v0.0.0-20190603093022-26d8b4d95c61/go.mod h1:p22Q3Bg5ML+hdI3QSQkB/pZ2+CjfOnGugoQIoyE2Ub8=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs=
github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 h1:3SVOIvH7Ae1KRYyQWRjXWJEA9sS/c/pjvH++55Gr648=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
+github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
+github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
@@ -455,6 +538,7 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -463,6 +547,7 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -472,10 +557,12 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190911201528-7ad0cfa0b7b5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU=
@@ -521,16 +608,22 @@ gopkg.in/gcfg.v1 v1.2.3 h1:m8OOJ4ccYHnx2f4gQwpno8nAX5OGOh7RLaaz0pj3Ogs=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
+gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
+gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns=
gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/mining/miner.go b/mining/miner.go
index 485620c..e871360 100644
--- a/mining/miner.go
+++ b/mining/miner.go
@@ -10,9 +10,6 @@ import (
"fmt"
"math"
"math/rand"
- "os"
- "strconv"
- "sync"
"time"
lxr "github.com/pegnet/LXRHash"
@@ -20,27 +17,10 @@ import (
"go.uber.org/ratelimit"
)
-// LX holds an instance of lxrhash
-var LX lxr.LXRHash
-var lxInitializer sync.Once
-
-// The init function for LX is expensive. So we should explicitly call the init if we intend
-// to use it. Make the init call idempotent
-func InitLX() {
- lxInitializer.Do(func() {
- // This code will only be executed ONCE, no matter how often you call it
- LX.Verbose(true)
- if size, err := strconv.Atoi(os.Getenv("LXRBITSIZE")); err == nil && size >= 8 && size <= 30 {
- LX.Init(0xfafaececfafaecec, uint64(size), 256, 5)
- } else {
- LX.Init(lxr.Seed, lxr.MapSizeBits, lxr.HashSize, lxr.Passes)
- }
- })
-}
-
const (
_ = iota
BatchCommand
+ CurrentHashRate
NewNoncePrefix
NewOPRHash
ResetRecords
@@ -83,6 +63,9 @@ type PegnetMiner struct {
// Used to compute difficulties
ComputeDifficulty func(oprhash, nonce []byte) (difficulty uint64)
+
+ // A handle for the LXR hash
+ sharedLXR *lxr.LXRHash
}
type oprMiningState struct {
@@ -150,9 +133,8 @@ func (p *PegnetMiner) ResetNonce() {
p.resetStatic()
}
-func NewPegnetMiner(id uint32, commands chan *MinerCommand, successes chan *Winner) *PegnetMiner {
+func NewPegnetMiner(id uint32, commands chan *MinerCommand, successes chan *Winner, sharedLXR *lxr.LXRHash) *PegnetMiner {
p := new(PegnetMiner)
- InitLX()
p.ID = id
p.PersonalID = id
p.commands = commands
@@ -163,11 +145,19 @@ func NewPegnetMiner(id uint32, commands chan *MinerCommand, successes chan *Winn
p.ResetNonce()
p.MiningState.stats = NewSingleMinerStats(p.PersonalID)
- p.ComputeDifficulty = ComputeDifficulty
+ p.ComputeDifficulty = wrapComputeDifficulty(sharedLXR)
+ p.sharedLXR = sharedLXR
return p
}
+func (p *PegnetMiner) Close() {
+ p.paused = true
+ // Throw away the references to sharedLXR
+ p.ComputeDifficulty = nil
+ p.sharedLXR = nil
+}
+
// SetFakeHashRate sets the miner to "fake" a hashrate. All targets are invalid
// The rate is in hashes/s
func (p *PegnetMiner) SetFakeHashRate(rate int) {
@@ -229,7 +219,7 @@ func (p *PegnetMiner) MineBatch(ctx context.Context, batchsize int) {
}
var results [][]byte
- results = LX.HashParallel(p.MiningState.static, batch)
+ results = p.sharedLXR.HashParallel(p.MiningState.static, batch)
for i := range results {
// do something with the result here
// nonce = batch[i]
@@ -336,6 +326,14 @@ func (p *PegnetMiner) HandleCommand(c *MinerCommand) {
for _, c := range commands {
p.HandleCommand(c)
}
+ case CurrentHashRate:
+ currentStats := *p.MiningState.stats
+ currentStats.Stop = time.Now()
+ w := c.Data.(chan *SingleMinerStats)
+ select {
+ case w <- ¤tStats:
+ default:
+ }
case NewNoncePrefix:
p.ID = c.Data.(uint32)
p.ResetNonce()
@@ -379,6 +377,25 @@ func (p *PegnetMiner) waitForResume(ctx context.Context) {
}
}
+func wrapComputeDifficulty(sharedLXR *lxr.LXRHash) func([]byte, []byte) uint64 {
+ if sharedLXR == nil {
+ log.Fatal("Cannot calculate difficulty with nil LXRHash")
+ }
+ return func(oprhash, nonce []byte) (difficulty uint64) {
+ no := make([]byte, len(oprhash)+len(nonce))
+ i := copy(no, oprhash)
+ copy(no[i:], nonce)
+ b := sharedLXR.Hash(no)
+
+ // The high eight bytes of the hash(hash(entry.Content) + nonce) is the difficulty.
+ // Because we don't have a difficulty bar, we can define difficulty as the greatest
+ // value, rather than the minimum value. Our bar is the greatest difficulty found
+ // within a 10 minute period. We compute difficulty as Big Endian.
+ return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 |
+ uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56
+ }
+}
+
// CommandBuilder just let's me use building syntax to build commands
type CommandBuilder struct {
command *MinerCommand
@@ -392,6 +409,11 @@ func BuildCommand() *CommandBuilder {
return c
}
+func (b *CommandBuilder) CurrentHashRate(w chan *SingleMinerStats) *CommandBuilder {
+ b.commands = append(b.commands, &MinerCommand{Command: CurrentHashRate, Data: w})
+ return b
+}
+
func (b *CommandBuilder) SubmitStats(w chan *SingleMinerStats) *CommandBuilder {
b.commands = append(b.commands, &MinerCommand{Command: SubmitStats, Data: w})
return b
@@ -430,17 +452,3 @@ func (b *CommandBuilder) Build() *MinerCommand {
b.command.Data = b.commands
return b.command
}
-
-func ComputeDifficulty(oprhash, nonce []byte) (difficulty uint64) {
- no := make([]byte, len(oprhash)+len(nonce))
- i := copy(no, oprhash)
- copy(no[i:], nonce)
- b := LX.Hash(no)
-
- // The high eight bytes of the hash(hash(entry.Content) + nonce) is the difficulty.
- // Because we don't have a difficulty bar, we can define difficulty as the greatest
- // value, rather than the minimum value. Our bar is the greatest difficulty found
- // within a 10 minute period. We compute difficulty as Big Endian.
- return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 |
- uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56
-}
diff --git a/prosper-miner/README.md b/prosper-miner/README.md
index e609607..4b43e06 100644
--- a/prosper-miner/README.md
+++ b/prosper-miner/README.md
@@ -55,4 +55,13 @@ Flags:
-s, --poolhost string URL to connect to the pool (default "localhost:1234")
-u, --user string Username to log into the mining pool
-```
\ No newline at end of file
+```
+
+# Environment variables
+
+The `prosper-miner` program can use the following environment variables:
+
+
+ - PROSPERPOOL_CONFIG
+ - When the PROSPERPOOL_CONFIG variable is set,
prosper-miner will use the configuration file identified PROSPERPOOL_CONFIG unless the --config argument is passed.
+
diff --git a/prosper-miner/feedcmd.go b/prosper-miner/feedcmd.go
new file mode 100644
index 0000000..4a56875
--- /dev/null
+++ b/prosper-miner/feedcmd.go
@@ -0,0 +1,90 @@
+package main
+
+import (
+ "bufio"
+ "context"
+ "fmt"
+ "os"
+
+ "github.com/ethereum/go-ethereum/rpc"
+ log "github.com/sirupsen/logrus"
+ "github.com/spf13/cobra"
+)
+
+var feedCmd = &cobra.Command{
+ Use: "feed",
+ Short: "Show notifications from the miner service",
+ Run: func(cmd *cobra.Command, args []string) {
+ ctx := context.Background()
+ clientpipe, err := rpc.DialIPC(ctx, NamedPipeName)
+ if err != nil {
+ log.Fatal("Failed to connect to the named pipe")
+ }
+ defer clientpipe.Close()
+ hashRateChannel := make(chan float64)
+ submissionChannel := make(chan int)
+ statusChannel := make(chan *MiningStatus)
+ subscriptions := map[*rpc.ClientSubscription]bool{}
+
+ subRate, err := clientpipe.Subscribe(ctx, "mining", hashRateChannel, "hashRateSubscription")
+ if err != nil {
+ log.WithError(err).Fatal("Failed to subscribe to mining:hashRateSubscription")
+ }
+ subscriptions[subRate] = true
+ subStatus, err := clientpipe.Subscribe(ctx, "mining", statusChannel, "statusSubscription")
+ if err != nil {
+ log.WithError(err).Fatal("Failed to subscribe to mining:statusSubscription")
+ }
+ subscriptions[subStatus] = true
+ subSubmission, err := clientpipe.Subscribe(ctx, "mining", submissionChannel, "submissionSubscription")
+ if err != nil {
+ log.WithError(err).Fatal("Failed to subscribe to mining:submissionSubscription")
+ }
+ subscriptions[subSubmission] = true
+
+ go func() {
+ for {
+ var status *MiningStatus
+ select {
+ case hashRate := <-hashRateChannel:
+ fmt.Printf("Current hash rate: %f\n", hashRate)
+ case status = <-statusChannel:
+ if status.IsConnected {
+ fmt.Printf("Connected to %s\n", status.PoolHostAndPort)
+ } else {
+ fmt.Print("Disconnected from pool host\n")
+ }
+ status = nil
+ case <-submissionChannel:
+ fmt.Println("A share was submitted")
+ case <-subRate.Err():
+ fmt.Println("Hash rate subscription ended")
+ delete(subscriptions, subRate)
+ if len(subscriptions) == 0 {
+ os.Exit(1)
+ }
+ case <-subStatus.Err():
+ fmt.Println("Status subscription ended")
+ delete(subscriptions, subStatus)
+ if len(subscriptions) == 0 {
+ os.Exit(1)
+ }
+ case <-subSubmission.Err():
+ fmt.Println("Submission subscription ended")
+ delete(subscriptions, subSubmission)
+ if len(subscriptions) == 0 {
+ os.Exit(1)
+ }
+ }
+ }
+ }()
+
+ // Allow the user to exit by pressing Enter
+ fmt.Println("Press Enter to terminate the feed")
+ keyboardReader := bufio.NewReader(os.Stdin)
+ keyboardReader.ReadString('\n')
+ subRate.Unsubscribe()
+ subSubmission.Unsubscribe()
+ return
+ },
+}
diff --git a/prosper-miner/main.go b/prosper-miner/main.go
index 6d74c89..1ea9753 100644
--- a/prosper-miner/main.go
+++ b/prosper-miner/main.go
@@ -19,9 +19,12 @@ import (
"github.com/FactomWyomingEntity/prosper-pool/loghelp"
"github.com/FactomWyomingEntity/prosper-pool/profile"
"github.com/FactomWyomingEntity/prosper-pool/stratum"
+ "github.com/kardianos/service"
"github.com/pegnet/pegnet/modules/factoidaddress"
log "github.com/sirupsen/logrus"
+ "github.com/spf13/afero"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
"github.com/spf13/viper"
"golang.org/x/crypto/ssh/terminal"
)
@@ -33,6 +36,7 @@ const (
ConfigUserName = "miner.username"
ConfigMinerName = "miner.minerid"
)
+const UserConfigFilePath = "$HOME/.prosper/prosper-miner.toml"
var rxEmail = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
@@ -60,6 +64,9 @@ func init() {
rootCmd.Flags().IntP("miners", "t", runtime.NumCPU(), "Number of mining threads")
rootCmd.AddCommand(properties)
+ rootCmd.AddCommand(feedCmd)
+ rootCmd.AddCommand(rpcCmd)
+ rootCmd.AddCommand(serviceCmd)
}
// Pool entry point
@@ -79,6 +86,10 @@ var rootCmd = &cobra.Command{
Short: "Launch miner to communicate with the prosper mining pool.",
PreRunE: OpenConfig,
Run: func(cmd *cobra.Command, args []string) {
+ if !service.Interactive() {
+ runMinerService()
+ return
+ }
ctx, cancel := context.WithCancel(context.Background())
exit.GlobalExitHandler.AddCancel(cancel)
keyboardReader := bufio.NewReader(os.Stdin)
@@ -125,7 +136,8 @@ var rootCmd = &cobra.Command{
return
}
- client, err := stratum.NewClient(username, minerid, password, invitecode, payoutaddress, config.CompiledInVersion)
+ notifications := stratum.NewNotificationChannels()
+ client, err := stratum.NewClient(username, minerid, password, invitecode, payoutaddress, config.CompiledInVersion, notifications)
if err != nil {
panic(err)
}
@@ -158,10 +170,6 @@ var rootCmd = &cobra.Command{
log.Infof("Username: %s, MinerID: %s", username, minerid)
log.Infof("Using %d threads", miners)
- exit.GlobalExitHandler.AddExit(func() error {
- return client.Close()
- })
-
err = client.Connect(viper.GetString(ConfigHost))
if err != nil {
panic(err)
@@ -170,6 +178,7 @@ var rootCmd = &cobra.Command{
client.Handshake()
go func() {
+ stopFeed := make(chan int)
for {
userCommand, _ := keyboardReader.ReadString('\n')
words := strings.Fields(userCommand)
@@ -185,6 +194,11 @@ var rootCmd = &cobra.Command{
if len(words) > 1 {
client.SuggestTarget(words[1])
}
+ case "startfeed":
+ fmt.Println("Use 'stopfeed' to stop")
+ go startFeed(stopFeed, notifications)
+ case "stopfeed":
+ stopFeed <- 1
default:
fmt.Println("Client command not supported: ", words[0])
}
@@ -196,29 +210,22 @@ var rootCmd = &cobra.Command{
},
}
+// OpenConfig finds and loads the Prosper Miner configuration file.
func OpenConfig(cmd *cobra.Command, args []string) error {
initLogger(cmd)
- closeHandle()
-
- configPath, _ := cmd.Flags().GetString("config")
- configCustom := true
- if configPath == "" {
- if runtime.GOOS == "windows" {
- u, err := user.Current()
- if err == nil {
- _ = os.Setenv("HOME", u.HomeDir)
- }
- }
- configPath = "$HOME/.prosper/prosper-miner.toml" // Default
- configCustom = false
+ handleSigInt()
+ fs := afero.NewOsFs()
+ viper.SetFs(fs)
+
+ if service.Interactive() {
+ ensureEnvHomeIsSet()
}
+ path, configCustom, err := selectConfigFile(cmd.Flags(), fs)
if pro, _ := cmd.Flags().GetBool("profile"); pro {
go profile.StartProfiler(false, 6050) // Only localhost, on 6050
}
- path := os.ExpandEnv(configPath)
-
dir := filepath.Dir(path)
name := filepath.Base(path)
viper.AddConfigPath(dir)
@@ -226,7 +233,7 @@ func OpenConfig(cmd *cobra.Command, args []string) error {
ext := filepath.Ext(name)
viper.SetConfigName(strings.TrimSuffix(name, ext))
- info, err := os.Stat(path)
+ info, err := fs.Stat(path)
exists := info != nil && !os.IsNotExist(err)
// Set default config values
@@ -241,7 +248,7 @@ func OpenConfig(cmd *cobra.Command, args []string) error {
log.Infof("Writing config to file at %s", path)
// Make the config
dir := filepath.Dir(path)
- err := os.MkdirAll(dir, 0777)
+ err := fs.MkdirAll(dir, 0777)
if err != nil {
return fmt.Errorf("failed to make config path: %s", err.Error())
}
@@ -272,8 +279,8 @@ func GenerateMinerID() string {
return NewRandomName(time.Now().UnixNano()).Haikunate()
}
-func closeHandle() {
- // Catch ctl+c
+func handleSigInt() {
+ // Catch ctrl + C (SIGINT)
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt)
go func() {
@@ -287,6 +294,15 @@ func closeHandle() {
}()
}
+func ensureEnvHomeIsSet() {
+ if runtime.GOOS == "windows" {
+ u, err := user.Current()
+ if err == nil {
+ _ = os.Setenv("HOME", u.HomeDir)
+ }
+ }
+}
+
func initLogger(cmd *cobra.Command) {
logLvl, _ := cmd.Flags().GetString("log")
switch strings.ToLower(logLvl) {
@@ -306,3 +322,53 @@ func initLogger(cmd *cobra.Command) {
log.StandardLogger().Hooks.Add(&loghelp.ContextHook{})
}
+
+// This function looks for a configuration file in the following locations:
+//
+// 1. If the user has specified a path on the command line, the
+// specified path is given first priority.
+// 2. If the program is being run interactively, a
+// configuration file at $HOME/.prosper/prosper-miner.toml
+// is given second priority.
+// 3. The path returned by the getSystemConfigFilePath function
+// —see the minerconfig_$GOOS.go file for your platform—is
+// given lowest priority.
+func selectConfigFile(flags *pflag.FlagSet, fs afero.Fs) (filePath string, fileSpecified bool, err error) {
+ configFlag, err := flags.GetString("config")
+ if err == nil {
+ // Specified config file path is first priority…
+ if configFlag != "" {
+ return configFlag, true, nil
+ } else {
+ // …otherwise, look for the file
+ _, homeExists := os.LookupEnv("HOME")
+ filePath := os.ExpandEnv(UserConfigFilePath)
+ filePathExists, err := afero.Exists(fs, filePath)
+ if err != nil {
+ return "", false, err
+ }
+ if !homeExists || !filePathExists {
+ filePath, err = getSystemConfigFilePath()
+ if err != nil {
+ return "", false, err
+ }
+ }
+ return filePath, false, nil
+ }
+ } else {
+ return "", false, err
+ }
+}
+
+func startFeed(stop chan int, nc *stratum.NotificationChannels) {
+ for {
+ select {
+ case <-stop:
+ return
+ case i := <-nc.HashRateChannel:
+ fmt.Printf("Current hash rate: %.2f\n", i)
+ case <-nc.SubmissionChannel:
+ fmt.Println("A share was submitted")
+ }
+ }
+}
diff --git a/prosper-miner/main_test.go b/prosper-miner/main_test.go
new file mode 100644
index 0000000..fe7db91
--- /dev/null
+++ b/prosper-miner/main_test.go
@@ -0,0 +1,101 @@
+package main
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/spf13/afero"
+ "github.com/spf13/pflag"
+)
+
+func TestSelectConfigFile01(t *testing.T) {
+ var path = "/does/not/exist"
+ var flags = &pflag.FlagSet{}
+ initFlagsForTesting(flags)
+ flags.Parse([]string{"--config", path})
+ fs := fsForTesting()
+
+ configFilePath, configFileSpecified, err := selectConfigFile(flags, fs)
+
+ if configFilePath != path || !configFileSpecified || err != nil {
+ t.Errorf("Test of specified config file path %s failed", configFilePath)
+ }
+}
+
+func TestSelectConfigFile02(t *testing.T) {
+ var path = "C:\\Users\\user\\config.file"
+ var flags = &pflag.FlagSet{}
+ initFlagsForTesting(flags)
+ flags.Parse([]string{"-c", path})
+ fs := fsForTesting()
+
+ configFilePath, configFileSpecified, err := selectConfigFile(flags, fs)
+
+ if configFilePath != path || !configFileSpecified || err != nil {
+ t.Errorf("Test of specified config file path %s failed", configFilePath)
+ }
+}
+
+func TestSelectConfigFile03(t *testing.T) {
+ var flags = &pflag.FlagSet{}
+ initFlagsForTesting(flags)
+ flags.Parse([]string{})
+ ensureEnvHomeIsSetForTesting()
+ fs := fsForTesting()
+ userConfigFilePath := makeConfigFileForTesting(fs, UserConfigFilePath)
+
+ configFilePath, configFileSpecified, err := selectConfigFile(flags, fs)
+
+ if configFilePath != userConfigFilePath {
+ t.Errorf("TestSelectConfigFile03 return incorrect config file path %s", configFilePath)
+ } else if configFileSpecified {
+ t.Errorf("TestSelectConfigFile03 incorrectly reports that the config file path was specified by the user")
+ } else if err != nil {
+ t.Errorf("TestSelectConfigFile03 failed: %s", err)
+ }
+}
+
+func TestSelectConfigFile04(t *testing.T) {
+ var flags = &pflag.FlagSet{}
+ initFlagsForTesting(flags)
+ flags.Parse([]string{})
+ ensureEnvHomeIsSetForTesting()
+ fs := fsForTesting()
+ systemPath, err := getSystemConfigFilePath()
+ if err != nil {
+ t.Errorf("Failed getting system config file path: %s", err)
+ }
+ makeConfigFileForTesting(fs, systemPath)
+
+ configFilePath, configFileSpecified, err := selectConfigFile(flags, fs)
+
+ if configFilePath != systemPath {
+ t.Errorf("TestSelectConfigFile04 return incorrect config file path %s", configFilePath)
+ } else if configFileSpecified {
+ t.Errorf("TestSelectConfigFile04 incorrectly reports that the config file path was specified by the user")
+ } else if err != nil {
+ t.Errorf("TestSelectConfigFile04 failed: %s", err)
+ }
+}
+
+func initFlagsForTesting(flags *pflag.FlagSet) {
+ flags.StringP("config", "c", "", "config path location")
+}
+
+func fsForTesting() afero.Fs {
+ return afero.NewMemMapFs()
+}
+
+func ensureEnvHomeIsSetForTesting() {
+ homeDir := "C:/Users/user"
+ os.Setenv("HOME", homeDir)
+}
+
+func makeConfigFileForTesting(fs afero.Fs, configFilePath string) string {
+ cfp := os.ExpandEnv(configFilePath)
+ configFileDir, _ := filepath.Split(cfp)
+ fs.MkdirAll(configFileDir, 0755)
+ afero.WriteFile(fs, cfp, []byte("config file"), 0644)
+ return cfp
+}
diff --git a/prosper-miner/minerconfig.go b/prosper-miner/minerconfig.go
new file mode 100644
index 0000000..1480266
--- /dev/null
+++ b/prosper-miner/minerconfig.go
@@ -0,0 +1,142 @@
+package main
+
+import (
+ "errors"
+ "os"
+ "os/user"
+ "path/filepath"
+ "regexp"
+ "strings"
+
+ "github.com/kardianos/service"
+ log "github.com/sirupsen/logrus"
+ "github.com/spf13/viper"
+)
+
+const (
+ ConfigHashTableDirectoryKey = "lxrhash.tabledir"
+ ConfigEmailAddressKey = "miner.username"
+ ConfigMinerIdKey = "miner.minerid"
+ ConfigConcurrentMinersKey = "miner.threads"
+ ConfigPoolHostAndPortKey = "pool.host"
+)
+
+// This does not validate that the port is less than 65536.
+var rxHostAndPort = regexp.MustCompile(":[1-6]?[0-9]{1,4}$")
+
+// Configuration values for the miner
+type minerConfig struct {
+ emailaddress string
+ hashtabledirectory string
+ minerid string
+ concurrentminers int
+ poolhostandport string
+}
+
+// Load the configuration values from prosper-miner.toml
+func getMinerConfig(path string) (*minerConfig, error) {
+ var err error
+ path, err = getConfigFilePath(path)
+ dir := filepath.Dir(path)
+ name := filepath.Base(path)
+ extension := filepath.Ext(name)
+
+ if hashtabledir, err := getDefaultHashTableDirectory(); err == nil {
+ viper.SetDefault(ConfigHashTableDirectoryKey, hashtabledir)
+ } else {
+ return nil, err
+ }
+
+ viper.AddConfigPath(dir)
+ viper.SetConfigName(strings.TrimSuffix(name, extension))
+
+ statinfo, err := os.Stat(path)
+ if err != nil || statinfo == nil || os.IsNotExist(err) {
+ log.WithFields(log.Fields{"configFilePath": path}).Error("Configuration file could not be read")
+ return nil, errors.New("Configuration file could not be read");
+ }
+
+ err = viper.ReadInConfig()
+ if err != nil {
+ log.WithFields(log.Fields{"configFilePath": path}).Error("Failed to read configuration")
+ return nil, err
+ }
+
+ mc := &minerConfig{}
+ mc.hashtabledirectory = viper.GetString(ConfigHashTableDirectoryKey)
+ mc.emailaddress = viper.GetString(ConfigEmailAddressKey)
+ mc.minerid = viper.GetString(ConfigMinerIdKey)
+ mc.concurrentminers = viper.GetInt(ConfigConcurrentMinersKey)
+ mc.poolhostandport = viper.GetString(ConfigPoolHostAndPortKey)
+
+ // Validate configuration
+ if !strings.Contains(mc.emailaddress, "@") {
+ log.WithFields(log.Fields{ConfigEmailAddressKey: mc.emailaddress}).Errorf("%s does not contain '@'", ConfigEmailAddressKey)
+ return nil, errors.New(ConfigEmailAddressKey + " should be an e-mail address and must contain '@'")
+ }
+ if mc.minerid == "" {
+ log.WithFields(log.Fields{ConfigMinerIdKey: mc.minerid}).Errorf("%s is an empty string", ConfigMinerIdKey)
+ return nil, errors.New(ConfigMinerIdKey + " must not be an empty string")
+ }
+ if mc.concurrentminers < 1 {
+ log.WithFields(log.Fields{ConfigConcurrentMinersKey: mc.concurrentminers}).Errorf("%s must be a positive value", ConfigConcurrentMinersKey)
+ return nil, errors.New(ConfigConcurrentMinersKey + " must be a positive value")
+ }
+ if !rxHostAndPort.MatchString(mc.poolhostandport) {
+ log.WithFields(log.Fields{ConfigPoolHostAndPortKey: mc.poolhostandport}).Errorf("%s must contain a port number", ConfigPoolHostAndPortKey)
+ return nil, errors.New(ConfigPoolHostAndPortKey + " must contain a port number")
+ }
+
+ return mc, nil
+}
+
+func getConfigFilePath(path string) (string, error) {
+ // First, use the path passed, presuming it is from
+ // the user. If no path is passed, use the path set
+ // in the PROSPERPOOL_CONFIG environment variable. If
+ // that value is empty, get the default path for the
+ // operating system.
+ var err error
+ if path == "" {
+ path = os.Getenv("PROSPERPOOL_CONFIG")
+ if path == "" {
+ if service.Interactive() {
+ var err error
+ path, err = getUserConfigFilePath()
+ if err != nil {
+ return "", err
+ }
+ if _, err = os.Stat(path); os.IsNotExist(err) {
+ path, err = getSystemConfigFilePath()
+ if err != nil {
+ return "", err
+ }
+ log.WithFields(log.Fields{"configFilePath": path}).Debug("Using system config file")
+ } else {
+ log.WithFields(log.Fields{"configFilePath": path}).Debug("Using user's config file")
+ }
+ } else {
+ path, err = getSystemConfigFilePath()
+ if err != nil {
+ return "", err
+ }
+ log.WithFields(log.Fields{"configFilePath": path}).Debug("Using system config file")
+ }
+ } else {
+ log.WithFields(log.Fields{"configFilePath": path}).Debug("Using PROSPERPOOL_CONFIG")
+ }
+ } else {
+ log.WithFields(log.Fields{"configFilePath": path}).Debug("Using user-specified configuration file")
+ }
+ return path, nil
+}
+
+func getUserConfigFilePath() (string, error) {
+ currentUser, err := user.Current()
+ if err == nil {
+ path := filepath.Join(currentUser.HomeDir, ".prosper", "prosper-miner.toml")
+ log.WithFields(log.Fields{"configFilePath": path}).Debug("Using user's default configuration file")
+ return path, nil
+ }
+ return "", err
+}
diff --git a/prosper-miner/minerconfig_bsd.go b/prosper-miner/minerconfig_bsd.go
new file mode 100644
index 0000000..1becc16
--- /dev/null
+++ b/prosper-miner/minerconfig_bsd.go
@@ -0,0 +1,11 @@
+// +build freebsd netbsd openbsd
+
+package main
+
+func getDefaultHashTableDirectory() (string, error) {
+ return "/var/db/LXRHash", nil
+}
+
+func getSystemConfigFilePath() (string, error) {
+ return "/etc/prosper-pool/prosper-pool.toml", nil
+}
diff --git a/prosper-miner/minerconfig_darwin.go b/prosper-miner/minerconfig_darwin.go
new file mode 100644
index 0000000..b76865a
--- /dev/null
+++ b/prosper-miner/minerconfig_darwin.go
@@ -0,0 +1,24 @@
+// +build darwin
+
+package main
+
+import (
+ "path/filepath"
+)
+
+const bundleIdentifier = "io.prosperpool.ProsperMiner"
+const lxrhashBundleIdentifier = "org.pegnet.LXRHash"
+
+func getDefaultHashTableDirectory() (string, error) {
+
+ path := filepath.Join("/Library", "Application Support", lxrhashBundleIdentifier)
+ return path, nil
+}
+
+func getSystemConfigFilePath() (string, error) {
+
+ configFile := "prosper-miner.toml"
+
+ path := filepath.Join("/Library", "Application Support", bundleIdentifier, configFile)
+ return path, nil
+}
diff --git a/prosper-miner/minerconfig_linux.go b/prosper-miner/minerconfig_linux.go
new file mode 100644
index 0000000..9422a0b
--- /dev/null
+++ b/prosper-miner/minerconfig_linux.go
@@ -0,0 +1,11 @@
+// +build linux
+
+package main
+
+func getDefaultHashTableDirectory() (string, error) {
+ return "/var/lib/LXRHash", nil
+}
+
+func getSystemConfigFilePath() (string, error) {
+ return "/etc/prosper-pool/prosper-pool.toml", nil
+}
diff --git a/prosper-miner/minerconfig_windows.go b/prosper-miner/minerconfig_windows.go
new file mode 100644
index 0000000..929e73c
--- /dev/null
+++ b/prosper-miner/minerconfig_windows.go
@@ -0,0 +1,30 @@
+// +build windows
+
+package main
+
+import (
+ "path/filepath"
+
+ "golang.org/x/sys/windows"
+ log "github.com/sirupsen/logrus"
+)
+
+func getDefaultHashTableDirectory() (string, error) {
+ pdpath, err := windows.KnownFolderPath(windows.FOLDERID_ProgramData, 0)
+ if err != nil {
+ log.Error("Unable to find the ProgramData folder")
+ return "", err
+ }
+ path := filepath.Join(pdpath, "LXRHash")
+ return path, nil
+}
+
+func getSystemConfigFilePath() (string, error) {
+ pdpath, err := windows.KnownFolderPath(windows.FOLDERID_ProgramData, 0)
+ if err != nil {
+ log.Error("Unable to find the ProgramData folder")
+ return "", err
+ }
+ path := filepath.Join(pdpath, "Prosper Pool", "prosper-miner.toml")
+ return path, nil
+}
diff --git a/prosper-miner/minerservice.go b/prosper-miner/minerservice.go
new file mode 100644
index 0000000..2ebe7fe
--- /dev/null
+++ b/prosper-miner/minerservice.go
@@ -0,0 +1,159 @@
+package main
+
+import (
+ "time"
+
+ "github.com/kardianos/service"
+ log "github.com/sirupsen/logrus"
+)
+
+type program struct {
+ mining Mining
+ logger service.Logger
+}
+
+func (p *program) Start (s service.Service) error {
+ go p.run()
+ return nil
+}
+
+// Run the miner service
+//
+// Miner service states:
+// 1. No configuration, not mining.
+// a. Transitions to 2. by JSON-RPC request to start mining.
+// 2. Seeking configuration, not mining.
+// a. Transitions to 1. by JSON-RPC request to stop mining.
+// b. Transitions to self by looking for, but not finding a configuration.
+// c. Transitions to 3. by finding a configuration.
+// d. Transitions to 5. by service termination.
+// 3. Configured, not mining.
+// a. Transitions to self by failing to initialize the miner
+// b. Transitions to 4. by initializing the miner
+// c. Transitions to 5. by service termination.
+// 4. Configured, mining.
+// a. Transitions to 1. by JSON-RPC request to stop mining.
+// b. Transitions to 2. by repeated network errors.
+// c. Transitions to 2. by failing to connect to pool.
+// d. Transitions to 5. by service termination.
+// 5. Stopped.
+// a. Transitions to 2. by starting the service.
+//
+// The miner service starts in state 2.
+//
+// First, the Mining object is created. It holds all shared data. Next,
+// startRPC() runs the JSON-RPC server. Finally, the main loop/state machine
+// is run.
+func (p *program) run() {
+ networkErrors := 0
+ mining := NewMining()
+ startRPCServer(mining)
+ log.WithFields(log.Fields{"platform": service.Platform()}).Info("Service started")
+ for {
+ if !mining.IsRunning() {
+ // State 1: Not running
+ p.logger.Info("State 1")
+ if mining.HasMinerConfig() {
+ log.Warn("Miner configuration is not nil while not running")
+ }
+ if mining.HasStratumClient() {
+ log.Warn("Stratum client is not nil while not running")
+ }
+ time.Sleep(time.Second)
+ } else if !mining.HasMinerConfig() {
+ // State 2: Seeking configuration
+ p.logger.Info("State 2")
+ if !mining.IsRunning() {
+ log.Warn("Mining is not running while seeking configuration")
+ }
+ if mining.HasStratumClient() {
+ log.Warn("Stratum client is not nil while seeking configuration")
+ }
+ if err := mining.GetMinerConfig(); err != nil {
+ time.Sleep(time.Second)
+ }
+ } else if !mining.HasStratumClient() {
+ // State 3: Initialize miners
+ p.logger.Info("State 3")
+ if !mining.IsRunning() {
+ log.Warn("Mining is not running while starting to mine")
+ }
+ if !mining.HasMinerConfig() {
+ log.Warn("Miner configuration is not found while starting to mine")
+ }
+ if err := mining.InitializeMiners(); err != nil {
+ time.Sleep(time.Second)
+ }
+ } else if mining.IsReadyToMine() {
+ // State 4: Mining
+ // As long as mining is happening, this thread stays in
+ // mining.MineUntilStopped(). Control is through the
+ // JSON-RPC server or through a Stop command to the
+ // service.
+ p.logger.Info("State 4")
+ if err := mining.MineUntilStopped(); err != nil {
+ p.logger.Error(err)
+ networkErrors++
+ if networkErrors > 5 {
+ mining.Reset()
+ networkErrors = 0
+ }
+ time.Sleep(time.Second)
+ }
+ } else {
+ p.logger.Error("Unknown state")
+ // Unknown state: This is a bug. Log it and stop.
+ log.WithFields(log.Fields{"mining.running": mining.running, "mining.mc": mining.mc, "mining.client": mining.client, "mining.disconnect": mining.disconnect}).Fatal("Mining service entered unexpected state")
+ }
+ }
+}
+
+func (p *program) Stop(s service.Service) error {
+ log.Info("Stopping")
+ p.mining.Stop()
+ return nil
+}
+
+func getMinerService() (service.Service, service.Logger, error) {
+ svcOptions := make(service.KeyValue)
+ svcOptions["Restart"] = "on-success"
+ svcOptions["SuccessExitStatus"] = "1 2 8 SIGKILL"
+ svcConfig := &service.Config{
+ Name: "ProsperPoolMinerService",
+ DisplayName: "Prosper Pool Miner Service",
+ Description: "Prosper Pool PegNet miners",
+ Option: svcOptions,
+ }
+
+ prg := &program{}
+ s, err := service.New(prg, svcConfig)
+ if err != nil {
+ return nil, nil, err
+ }
+ errs := make(chan error, 5)
+ logger, err := s.Logger(errs)
+ if err != nil {
+ log.Fatal(err)
+ }
+ prg.logger = logger
+/* go func() {
+ for {
+ err := <-errs
+ if err != nil {
+ log.Error(err)
+ }
+ }
+ }() */
+ return s, logger, nil
+}
+
+func runMinerService() {
+ s, logger, err := getMinerService()
+ if err != nil {
+ log.Fatal(err)
+ }
+ err = s.Run()
+ if err != nil {
+ logger.Error(err)
+ }
+}
diff --git a/prosper-miner/mining.go b/prosper-miner/mining.go
new file mode 100644
index 0000000..0b26079
--- /dev/null
+++ b/prosper-miner/mining.go
@@ -0,0 +1,245 @@
+package main
+
+import (
+ "context"
+ "errors"
+ "sync"
+ "time"
+
+ "github.com/FactomWyomingEntity/prosper-pool/config"
+ "github.com/FactomWyomingEntity/prosper-pool/exit"
+ "github.com/FactomWyomingEntity/prosper-pool/stratum"
+ log "github.com/sirupsen/logrus"
+)
+
+type Mining struct {
+ running bool
+ mc *minerConfig
+ notificationChannels *stratum.NotificationChannels
+ blocksSubmitted uint64
+ client *stratum.Client
+ connectedAt *time.Time
+ disconnect context.CancelFunc
+ statusChannel chan *MiningStatus
+ sync.RWMutex
+}
+
+func NewMining () *Mining {
+ log.Trace("NewMining")
+ mining := &Mining{}
+ mining.running = true
+ mining.notificationChannels = stratum.NewNotificationChannels()
+ mining.statusChannel = make(chan *MiningStatus)
+ return mining
+}
+
+func (m *Mining) GetHashRateChannel() (<-chan float64) {
+ if m.notificationChannels != nil {
+ return m.notificationChannels.HashRateChannel
+ } else {
+ return nil
+ }
+}
+
+func (m *Mining) GetMinerConfig() error {
+ log.Trace("Mining.GetMinerConfig")
+ m.Lock()
+ defer m.Unlock()
+ var err error = nil
+ m.mc, err = getMinerConfig("")
+ return err
+}
+
+type MiningStatus struct {
+ IsConnected bool `json:"isConnected"`
+ IsRunning bool `json:"isRunning"`
+ PoolHostAndPort string `json:"poolHostAndPort,omitempty"`
+ DurationConnected uint64 `json:"durationConnected,omitempty"`
+ BlocksSubmitted uint64 `json:"blocksSubmitted,omitempty"`
+}
+
+func (m *Mining) GetStatus() *MiningStatus {
+ log.Trace("Mining.GetStatus")
+ m.RLock()
+ defer m.RUnlock()
+ return m.getStatusWhileRLocked()
+}
+
+func (m *Mining) GetStatusChannel() (<-chan *MiningStatus) {
+ log.Trace("Mining.GetStatusChannel")
+ return m.statusChannel
+}
+
+func (m *Mining) GetSubmissionChannel() (<-chan int) {
+ log.Trace("Mining.GetSubmissionChannel")
+ if m.notificationChannels != nil {
+ return m.notificationChannels.SubmissionChannel
+ } else {
+ return nil
+ }
+}
+
+func (m *Mining) HasMinerConfig() bool {
+ m.RLock()
+ defer m.RUnlock()
+ return m.mc != nil
+}
+
+func (m *Mining) HasStratumClient() bool {
+ m.RLock()
+ defer m.RUnlock()
+ return m.client != nil
+}
+
+func (m *Mining) InitializeMiners () error {
+ log.Trace("Mining.InitializeMiners")
+ if !m.HasMinerConfig() {
+ log.Error("Cannot start mining without miner configuration")
+ return errors.New("Miner configuration is missing")
+ }
+ m.RLock()
+ initialize := m.client == nil
+ m.RUnlock()
+ if initialize {
+ var err error = nil
+ // Create the Stratum client
+ // No need to pass the password, invitation code or payout address.
+ // The service should not handle the sign-up process.
+ m.Lock()
+ m.client, err = stratum.NewClient(m.mc.emailaddress, m.mc.minerid, "", "", "", config.CompiledInVersion, m.notificationChannels)
+ concurrentminers := m.mc.concurrentminers
+ hashtabledirectory := m.mc.hashtabledirectory
+ m.Unlock()
+ if err != nil {
+ m.RLock()
+ log.WithFields(log.Fields{ConfigEmailAddressKey: m.mc.emailaddress, ConfigMinerIdKey: m.mc.minerid, "CompiledInVersion": config.CompiledInVersion}).Debug("Failed to create a Stratum client")
+ m.RUnlock()
+ log.Error(err)
+ return errors.New("Failed to create new Stratum client")
+ }
+ m.client.InitMiners(concurrentminers, hashtabledirectory)
+ }
+ return nil
+}
+
+func (m *Mining) IsReadyToMine() bool {
+ m.RLock()
+ defer m.RUnlock()
+ return m.running == true && m.mc != nil && m.client != nil
+}
+
+func (m *Mining) IsRunning() bool {
+ m.RLock()
+ defer m.RUnlock()
+ return m.running
+}
+
+func (m *Mining) MineUntilStopped() error {
+ log.Trace("Mining.MineUntilStopped")
+ ctx, cancel := context.WithCancel(context.Background())
+ exit.GlobalExitHandler.AddCancel(cancel)
+ m.Lock()
+ poolhostandport := m.mc.poolhostandport
+ m.disconnect = cancel
+ m.Unlock()
+ m.client.RunMiners(ctx)
+ exit.GlobalExitHandler.AddExit(func() error {
+ m.Lock()
+ m.resetWhileLocked()
+ m.Unlock()
+ return nil
+ })
+ err := m.client.Connect(poolhostandport)
+ if err == nil {
+ m.Lock()
+ t := time.Now()
+ m.connectedAt = &t
+ m.sendStatusNotificationWhileRLocked()
+ m.Unlock()
+ } else {
+ log.WithFields(log.Fields{ConfigPoolHostAndPortKey: poolhostandport}).Debug("Failed to connect to the pool host")
+ log.Error(err)
+ m.Lock()
+ m.disconnect()
+ m.disconnect = nil
+ m.Unlock()
+ return err
+ }
+ m.client.Handshake()
+ err = m.client.Listen(ctx)
+ m.Lock()
+ cancel()
+ m.disconnect = nil
+ m.connectedAt = nil
+ m.sendStatusNotificationWhileRLocked()
+ m.Unlock()
+ return err
+}
+
+func (m *Mining) Reset() {
+ log.Trace("Mining.Reset")
+ m.Lock()
+ m.resetWhileLocked()
+ m.Unlock()
+}
+
+func (m *Mining) Start() {
+ log.Trace("Mining.Start")
+ m.Lock()
+ defer m.Unlock()
+ m.running = true
+}
+
+func (m *Mining) Stop() {
+ log.Trace("Mining.Stop")
+ m.Lock()
+ defer m.Unlock()
+ m.running = false
+ m.resetWhileLocked()
+}
+
+// private methods
+
+func (m *Mining) sendStatusNotificationWhileRLocked() {
+ log.Trace("Mining.sendStatusNotificationWhileRLocked")
+ if m.statusChannel != nil {
+ // Notify status listeners. Do nothing if no goroutines are listening.
+ select {
+ case m.statusChannel <- m.getStatusWhileRLocked():
+ default:
+ }
+ }
+}
+
+func (m *Mining) getStatusWhileRLocked() *MiningStatus {
+ log.Trace("Mining.getStatusWhileRLocked")
+ var status = &MiningStatus{}
+ status.IsRunning = m.running
+ status.IsConnected = m.connectedAt != nil
+ if status.IsConnected {
+ status.PoolHostAndPort = m.client.RemoteAddr()
+ status.DurationConnected = uint64(time.Since(*m.connectedAt).Seconds())
+ status.BlocksSubmitted = m.client.TotalSuccesses() + m.blocksSubmitted
+ } else {
+ status.BlocksSubmitted = m.blocksSubmitted
+ }
+ return status
+}
+
+func (m *Mining) resetWhileLocked() {
+ log.Trace("Mining.resetWhileLocked")
+ if m.disconnect != nil {
+ log.Info("Disconnecting")
+ m.disconnect()
+ m.disconnect = nil
+ }
+ if m.client != nil {
+ successes := m.client.TotalSuccesses()
+ log.WithFields(log.Fields{"successes": successes}).Debug("Adding blocksSubmitted")
+ m.blocksSubmitted += m.client.TotalSuccesses()
+ // No need to call m.client.Close here. Cancelling the context
+ // in MineUntilStopped will call it.
+ }
+ m.mc = nil
+ m.client = nil
+}
diff --git a/prosper-miner/protocol.md b/prosper-miner/protocol.md
new file mode 100644
index 0000000..d42c871
--- /dev/null
+++ b/prosper-miner/protocol.md
@@ -0,0 +1,230 @@
+# Prosper Pool Miner Socket Protocol
+
+The Prosper Pool miner opens a socket when run. This socket provides methods for control of the miner and notifications about the status of the miner and mining.
+
+The miner runs a JSON-RPC 2.0 server on the socket. All methods use the "mining" namespace.
+
+This document describes the Prosper Pool Miner Socket Protocol, version 0.1.
+
+## Methods
+
+### getStatus
+
+The `getStatus` method reports the current status of the miner. The method requires no arguments and returns a JSON object with the following keys:
+
+
+ - isRunning
+ - _bool_: always present. This value indicates whether the miner is configured to mine if possible.
+ - isConnected
+ - _bool_: always present. This value indicates whether the miner is connected to the pool server.
+ - poolHostAndPort
+ - _string_: present when the miner is connected to the pool server. This value indicates the IP address and the port of the remote server, separated by a colon. IPv6 addresses are enclosed in square brackets.
+ - durationConnected
+ - _uint64_: present when the miner is connected to the pool server. This value indicates seconds since the connection was last established. This value resets to zero when a new connection is established.
+ - blocksSubmitted
+ - _uint64_: always present. This value is a count of shares submitted to the pool server since the miner started running. It does not reset to zero when a new connection is established. This value resets to zero when the miner process terminates.
+ - statusCode
+ - _uint64_: always present. This value indicates the miner's state in greater detail.
+
+
+Possible `statusCode` values include:
+
+
+ - 101
+ - The miner is starting up.
+ - 102
+ - The miner is idle.
+ - 103
+ - The miner is generating the LXRHash table file.
+ - 104
+ - The miner is attempting to connect to the pool server.
+ - 105
+ - The miner is connected and mining.
+ - -101
+ - The miner is cannot find a configuration file.
+ - -102
+ - The miner is unable to read the configuration file.
+ - -103
+ - The miner configuration is invalid.
+ - -104
+ - The miner is unable to write the configuration file.
+ - -105
+ - The miner is unable to read the LXRHash table file.
+ - -106
+ - The miner is unable to write the LXRHash table file.
+
+
+#### Examples
+
+This example shows the response from a miner that has run for a short time and submitted a single share to the pool server. The miner was then stopped.
+
+```
+>> {"jsonrpc": "2.0", "id": 4, "method": "mining_getStatus"}
+<< {"jsonrpc": "2.0", "id": 4, "result": {"isRunning": false, "isConnected": false, "blocksSubmitted": 1}}
+```
+
+This example shows the response from a miner that has been running for a short time and is currently connected to the pool server.
+
+```
+>> {"jsonrpc": "2.0", "id": 6, "method": "mining_getStatus"}
+<< {"jsonrpc": "2.0", "id": 6, "result": {"isRunning": true, "isConnected": true, "poolHostAndPort": "3.233.176.186:1234", "durationConnected": 73, "blocksSubmitted": 4}}
+```
+
+### register
+
+The `register` method registers a new account with the mining pool. The method requires several arguments:
+
+
+ - emailAddress
+ - _string_: the e-mail address for the account, which functions as a username
+ - minerId
+ - _string_: an identifier for the miner which is running the command. This is used to identify which miner belonging to an account has submitted which shares.
+ - password
+ - _string_: the password for this account, used to access the account interface at https://prosperpool.io/
+ - inviteCode
+ - _string_: an invitation code, required to register during the beta period. See the Getting Started Guide at https://prosperpool.io for instructions on obtaining an invitation code.
+ - payoutAddress
+ - _string_: a Factoid address used for payouts. This address should be one which the user controls.
+
+
+The `register` method parameters are passed to `func NewClient(…)` in `../stratum/client.go`.
+
+The `register` method returns a _bool_ indicating success. Failure returns a [JSON-RPC error][json-rpc error specification] object with one of the following codes:
+
+
+ - -301
+ - The e-mail address is invalid.
+ - -302
+ - The password is invalid.
+ - -303
+ - The invite code is invalid.
+ - -304
+ - The payout address is invalid.
+
+
+#### Examples
+
+Do not use the e-mail addresses, passwords, invite codes or Factoid addresses shown in these examples. You will be sorry if you do.
+
+This example shows successful registration.
+
+```
+>> {"jsonrpc": "2.0", "id": 1, "method": "mining_register", params: ["user@example.com", "desktop", "sweetly_sings-my33rose", "49uE1bxYmtZjLemZhTeXOuUTh8Ee", "FA38eZQPdMN3oRQ6b1QG14kbQGVEkkaGEhtZDyQtWUDoydjfjvTU"]}
+<< {"jsonrpc": "2.0", "id": 1, "result": {"success": true}}
+```
+
+This example shows failed registration because the e-mail address is invalid.
+
+```
+>> {"jsonrpc": "2.0", "id": 1, "method": "mining_register", params: ["user2example.com", "desktop", "!noisy9392FLOWERS!", "49uE1bxYmtZjLemZhTeXOuUTh8Ee", "FA38eZQPdMN3oRQ6b1QG14kbQGVEkkaGEhtZDyQtWUDoydjfjvTU"]}
+<< {"jsonrpc": "2.0", "id": 1, "result": {"success": false, "errorMessage": "Please correct the e-mail address.", "validationError": {"emailAddress": "The e-mail address does not contain an at sign (@)."}}
+```
+
+This example shows failed registration because the invite code is empty.
+
+```
+>> {"jsonrpc": "2.0", "id": 1, "method": "mining_register", params: ["user@example.com", "desktop", "tehD415y_protests", "", "FA38eZQPdMN3oRQ6b1QG14kbQGVEkkaGEhtZDyQtWUDoydjfjvTU"]}
+<< {"jsonrpc": "2.0", "id": 1, "result": {"success": false, "errorMessage": "Please provide an invite code.", "validationError": {"inviteCode": "The invite code is empty."}}
+```
+
+### start
+
+The `start` method requests that the miner begin mining. The method requires no arguments and returns a _bool_, `true`, to indicate that the request has been received.
+
+#### Example
+
+This example shows a request that the miner start mining.
+
+```
+>> {"jsonrpc": "2.0", "id": 42, "method": "mining_start"}
+<< {"jsonrpc": "2.0", "id": 42, "result": true}
+```
+
+### stop
+
+The `start` method requests that the miner stop mining. The method requires no arguments and returns a _bool_, `true` to indicate that the request has been received.
+
+#### Example
+
+This example shows a request that the miner stop mining.
+
+```
+>> {"jsonrpc": "2.0", "id": 43, "method": "mining_stop"}
+<< {"jsonrpc": "2.0", "id": 43, "result": true}
+```
+
+### subscribe
+
+The `subscribe` method is used to request notifications. The method requires one argument, a _string_ indicating the desired source of the notifications, and returns a _string_, identifying the active subscription. The following sources are supported:
+
+- [hashRateSubscription](#hashratesubscription)
+- [statusSubscription](#statussubscription)
+- [submissionSubscription](#submissionsubscription)
+
+If the request fails because the subscription does not exist, [a JSON-RPC error][json-rpc error specification] is returned, in keeping with the [JSON-RPC Specification][json-rpc specification]. The error will have a `code` of -32601, as implemented in `rpc/errors.go` of the [go-ethereum project][go-ethereum].
+
+#### Examples
+
+This example shows a successful subscription to the `hashRateSubscription` notification source.
+
+```
+>> {"jsonrpc": "2.0", "id": 201, "method": "mining_subscribe", "params": ["hashRateSubscription"]}
+<< {"jsonrpc":"2.0","id":201,"result":"0xeaf8c028180b1b0eb3e8577f25d84e89"}
+…
+<< {"jsonrpc":"2.0","method":"mining_subscription","params":{"subscription":"0xeaf8c028180b1b0eb3e8577f25d84e89","result":4217.888140275206}}
+```
+
+This example shows an unsuccessful subscription to a non-existant notification source.
+
+```
+>> {"jsonrpc": "2.0", "id": 201, "method": "mining_subscribe", "params": ["doesNotExistSubscription"]}
+<< {"jsonrpc":"2.0","id":201,"error":{"code":-32601,"message":"no \"doesNotExistSubscription\" subscription in mining namespace"}}
+```
+
+### unsubscribe
+
+The `unsubscribe` method is used by a client to cancel a subscription to notifications. The method requires one argument, a _string_ identifying the active subscription to be canceled, and returns a _bool_, indicating the success (_true_) of the request. If the request fails because the subscription does not exist, [a JSON-RPC error][json-rpc error specification] is returned, in keeping with the [JSON-RPC Specification][json-rpc specification]. The error will have a `code` of -32600, as implemented in `rpc/errors.go` of the [go-ethereum project][go-ethereum].
+
+#### Examples
+
+This example shows a successful subscription and the subsequent cancelation of that subscription.
+
+```
+>> {"jsonrpc": "2.0", "id": 201, "method": "mining_subscribe", "params": ["hashRateSubscription"]}
+<< {"jsonrpc":"2.0","id":201,"result":"0xeaf8c028180b1b0eb3e8577f25d84e89"}
+…
+<< {"jsonrpc":"2.0","method":"mining_subscription","params":{"subscription":"0xeaf8c028180b1b0eb3e8577f25d84e89","result":4217.888140275206}}
+<< {"jsonrpc":"2.0","method":"mining_subscription","params":{"subscription":"0xeaf8c028180b1b0eb3e8577f25d84e89","result":4214.794689984841}}
+…
+>> {"jsonrpc": "2.0", "id": 202, "method": "mining_unsubscribe", "params": ["0xeaf8c028180b1b0eb3e8577f25d84e89"]}
+<< {"jsonrpc":"2.0","id":202,"result":true}
+```
+
+This example shows the unsuccessful cancelation of a non-existant subscription.
+
+```
+>> {"jsonrpc": "2.0", "id": 201, "method": "mining_unsubscribe", "params": ["0x123456789abcdef0123456789abcdef0"]}
+<< {"jsonrpc":"2.0","id":201,"error":{"code":-32000,"message":"subscription not found"}}
+```
+
+The error code is defined in `rpc/errors.go` of the [go-ethereum project][go-ethereum].
+
+## Notifications
+
+Clients subscribe to notifications using the ``subscribe`` method. Clients may cancel a subscription with the ``unsubscribe`` method. Subscriptions are automatically canceled when the client disconnects from the server.
+
+### hashRateSubscription
+
+The `hashRateSubscription` notification source reports the miner's current hash rate as a _JSON number_(in hashes per second) at a regular interval. The interval is currently ten seconds and is defined in `func (c *Client) ReportHashRate()` in `../stratum/client.go`.
+
+### statusSubscription
+
+The `statusSubscription` notification source reports changes to the miner's mining status. Specifically, notifications will be provided when the miner connects to the pool server and when the miner becomes disconnected from the pool server. The status object is of the same type as the return value for the [getStatus](#getstatus) method.
+
+### submissionSubscription
+
+The `submissionSubscription` notification source reports the miner submitting a share to the pool server. One notification is made for each share.
+
+[json-rpc error specification]: https://www.jsonrpc.org/specification#error_object
+[json-rpc specification]: https://www.jsonrpc.org/specification
+[go-ethereum]: https://github.com/ethereum/go-ethereum
diff --git a/prosper-miner/rpccmd.go b/prosper-miner/rpccmd.go
new file mode 100644
index 0000000..2a09575
--- /dev/null
+++ b/prosper-miner/rpccmd.go
@@ -0,0 +1,49 @@
+package main
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/ethereum/go-ethereum/rpc"
+ log "github.com/sirupsen/logrus"
+ "github.com/spf13/cobra"
+)
+
+var rpcCmd = &cobra.Command{
+ Use: "rpc",
+ Short: "Control the miner service with RPC",
+ Args: cobra.ExactValidArgs(1),
+ ValidArgs: []string{"getStatus", "isRunning", "start", "stop"},
+ Run: func(cmd *cobra.Command, args []string) {
+ method := args[0]
+ ctx := context.Background()
+ clientpipe, err := rpc.DialIPC(ctx, NamedPipeName)
+ defer clientpipe.Close()
+ if err != nil {
+ log.Fatal("Failed to connect to the named pipe")
+ }
+ switch method {
+ case "getStatus":
+ var result MiningStatus
+ if err := clientpipe.Call(&result, "mining_getStatus"); err != nil {
+ log.WithError(err).Fatal("Failed to call mining_getStatus")
+ }
+ fmt.Println("mining_getStatus:")
+ fmt.Printf("\tisRunning:\t%t\n", result.IsRunning)
+ fmt.Printf("\tisConnected:\t%t\n", result.IsConnected)
+ fmt.Printf("\tpoolHostAndPort:\t%s\n", result.PoolHostAndPort)
+ fmt.Printf("\tdurationConnected:\t%d\n", result.DurationConnected)
+ fmt.Printf("\tblocksSubmitted:\t%d\n", result.BlocksSubmitted)
+ case "isRunning":
+ var result bool
+ if err := clientpipe.Call(&result, "mining_isRunning"); err != nil {
+ log.WithError(err).Fatal("Failed to call mining_isRunning")
+ }
+ fmt.Printf("mining_isRunning: %t\n", result)
+ case "stop":
+ clientpipe.Call(nil, "mining_stop")
+ case "start":
+ clientpipe.Call(nil, "mining_start")
+ }
+ },
+}
diff --git a/prosper-miner/rpcserver.go b/prosper-miner/rpcserver.go
new file mode 100644
index 0000000..da08f6c
--- /dev/null
+++ b/prosper-miner/rpcserver.go
@@ -0,0 +1,336 @@
+package main
+
+import (
+ "context"
+ "errors"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/rpc"
+ log "github.com/sirupsen/logrus"
+)
+
+var NamedPipeName = `\\.\pipe\ProsperPoolService`
+
+type hashRateChannel <-chan float64
+type hashRateSubscriptionMap map[*hashRateSubscription]bool
+type statusChannel <-chan *MiningStatus
+type statusSubscriptionMap map[*statusSubscription]bool
+type submissionChannel <-chan int
+type submissionSubscriptionMap map[*submissionSubscription]bool
+
+type MinerRPCService struct {
+ m *Mining
+ hashRateSubscriptions hashRateSubscriptionMap
+ statusSubscriptions statusSubscriptionMap
+ submissionSubscriptions submissionSubscriptionMap
+ sync.RWMutex
+}
+
+type hashRateSubscription struct {
+ subscription *rpc.Subscription
+ notifier *rpc.Notifier
+ subscriptions *hashRateSubscriptionMap
+ channel hashRateChannel
+}
+
+type statusSubscription struct {
+ subscription *rpc.Subscription
+ notifier *rpc.Notifier
+ subscriptions *statusSubscriptionMap
+ channel statusChannel
+}
+
+type submissionSubscription struct {
+ subscription *rpc.Subscription
+ notifier *rpc.Notifier
+ subscriptions *submissionSubscriptionMap
+ channel submissionChannel
+}
+
+// RPC methods
+
+// Exposed as mining_getStatus
+func (s MinerRPCService) GetStatus() *MiningStatus {
+ var status *MiningStatus
+ status = s.m.GetStatus()
+ return status
+}
+
+// Exposed as mining_isRunning
+func (s MinerRPCService) IsRunning() bool {
+ return s.m.IsRunning()
+}
+
+// Exposed as mining_start
+func (s MinerRPCService) Start() {
+ s.m.Start()
+}
+
+// Exposed as mining_stop
+func (s MinerRPCService) Stop() {
+ s.m.Stop()
+}
+
+// Subscriptions
+//
+// Each subscription has a channel that notifies the service that there
+// is a new event. This event needs to be turned into a call to
+// notifier.Notify() for each subscribed client.
+
+func (s MinerRPCService) HashRateSubscription(ctx context.Context) (*rpc.Subscription, error) {
+ log.Trace("MinerRPCService.HashRateSubscription")
+ notifier, subscription, err := getNotifierAndSubscription(ctx)
+ if err != nil {
+ return nil, err
+ }
+ log.Debug("Adding hash rate subscription")
+ s.addHashRateSubscription(subscription, notifier)
+
+ return subscription, nil
+}
+
+func (s MinerRPCService) StatusSubscription(ctx context.Context) (*rpc.Subscription, error) {
+ log.Trace("MinerRPCService.StatusSubscription")
+ notifier, subscription, err := getNotifierAndSubscription(ctx)
+ if err != nil {
+ return nil, err
+ }
+ log.Debug("Adding status subscription")
+ s.addStatusSubscription(subscription, notifier)
+
+ return subscription, nil
+}
+
+func (s MinerRPCService) SubmissionSubscription(ctx context.Context) (*rpc.Subscription, error) {
+ log.Trace("MinerRPCService.SubmissionSubscription")
+ notifier, subscription, err := getNotifierAndSubscription(ctx)
+ if err != nil {
+ return nil, err
+ }
+ log.Debug("Adding submission subscription")
+ s.addSubmissionSubscription(subscription, notifier)
+
+ return subscription, nil
+}
+
+// Private methods
+
+func (s MinerRPCService) addHashRateSubscription(subscription *rpc.Subscription, notifier *rpc.Notifier) error {
+ log.Trace("MinerRPCService.addHashRateSubscription")
+ sub := &hashRateSubscription{subscription, notifier, &s.hashRateSubscriptions, s.m.GetHashRateChannel()}
+ if sub.channel != nil {
+ s.Lock()
+ (*sub.subscriptions)[sub] = true
+ s.Unlock()
+ log.Debug("Starting hash rate monitor")
+ go s.hashRateSubscriptionMonitor(sub)
+ if len(*sub.subscriptions) == 1 {
+ log.Debug("Starting hash rate worker")
+ go s.hashRateSubscriptionWorker(sub.channel)
+ }
+ } else {
+ return errors.New("Unable to get hash rate channel")
+ }
+ return nil
+}
+
+func (s MinerRPCService) addStatusSubscription(subscription *rpc.Subscription, notifier *rpc.Notifier) error {
+ log.Trace("MinerRPCService.addStatusSubscription")
+ sub := &statusSubscription{subscription, notifier, &s.statusSubscriptions, s.m.GetStatusChannel()}
+ if sub.channel != nil {
+ s.Lock()
+ (*sub.subscriptions)[sub] = true
+ s.Unlock()
+ log.Debug("Starting status subscription monitor")
+ go s.statusSubscriptionMonitor(sub)
+ if len(*sub.subscriptions) == 1 {
+ log.Debug("Starting status subscription worker")
+ go s.statusSubscriptionWorker(sub.channel)
+ }
+ } else {
+ return errors.New("Unable to get status channel")
+ }
+ return nil
+}
+
+func (s MinerRPCService) addSubmissionSubscription(subscription *rpc.Subscription, notifier *rpc.Notifier) error {
+ log.Trace("MinerRPCService.addSubmissionSubscription")
+ sub := &submissionSubscription{subscription, notifier, &s.submissionSubscriptions, s.m.GetSubmissionChannel()}
+ if sub.channel != nil {
+ s.Lock()
+ (*sub.subscriptions)[sub] = true
+ s.Unlock()
+ log.Debug("Starting submission subscription monitor")
+ go s.submissionSubscriptionMonitor(sub)
+ if len(*sub.subscriptions) == 1 {
+ log.Debug("Starting submission subscription worker")
+ go s.submissionSubscriptionWorker(sub.channel)
+ }
+ } else {
+ return errors.New("Unable to get submission channel")
+ }
+ return nil
+}
+
+func (s MinerRPCService) hashRateSubscriptionMonitor(sub *hashRateSubscription) {
+ log.Trace("MinerRPCService.hashRateSubscriptionMonitor")
+ subscription := sub.subscription
+ notifier := sub.notifier
+ for {
+ select {
+ case <-notifier.Closed():
+ log.Debug("Canceling hash rate subscription on notifier.Closed")
+ s.removeHashRateSubscription(sub)
+ return
+ case <-subscription.Err():
+ log.Debug("Canceling hash rate subscription on subscription.Err")
+ s.removeHashRateSubscription(sub)
+ return
+ default:
+ time.Sleep(2 * time.Second)
+ }
+ }
+}
+
+func (s MinerRPCService) hashRateSubscriptionWorker(channel hashRateChannel) {
+ log.Trace("MinerRPCService.hashRateSubscriptionWorker")
+ for {
+ select {
+ case i := <-channel:
+ s.RLock()
+ if len(s.hashRateSubscriptions) == 0 {
+ s.RUnlock()
+ break
+ } else {
+ for sub, _ := range s.hashRateSubscriptions {
+ sub.notifier.Notify(sub.subscription.ID, i)
+ }
+ }
+ s.RUnlock()
+ }
+ }
+}
+
+func (s MinerRPCService) removeHashRateSubscription(sub *hashRateSubscription) {
+ log.Trace("MinerRPCService.removeHashRateSubscription")
+ s.Lock()
+ defer s.Unlock()
+ delete(s.hashRateSubscriptions, sub)
+}
+
+func (s MinerRPCService) removeStatusSubscription(sub *statusSubscription) {
+ log.Trace("MinerRPCService.removeStatusSubscription")
+ s.Lock()
+ defer s.Unlock()
+ delete(s.statusSubscriptions, sub)
+}
+
+func (s MinerRPCService) removeSubmissionSubscription(sub *submissionSubscription) {
+ log.Trace("MinerRPCService.removeSubmissionSubscription")
+ s.Lock()
+ defer s.Unlock()
+ delete(s.submissionSubscriptions, sub)
+}
+
+func (s MinerRPCService) statusSubscriptionMonitor(sub *statusSubscription) {
+ log.Trace("MinerRPCService.statusSubscriptionMonitor")
+ subscription := sub.subscription
+ notifier := sub.notifier
+ for {
+ select {
+ case <-notifier.Closed():
+ log.Debug("Canceling status subscription on notifier.Closed")
+ s.removeStatusSubscription(sub)
+ return
+ case <-subscription.Err():
+ log.Debug("Canceling status subscription on subscription.Err")
+ s.removeStatusSubscription(sub)
+ return
+ default:
+ time.Sleep(2 * time.Second)
+ }
+ }
+}
+
+func (s MinerRPCService) statusSubscriptionWorker(channel statusChannel) {
+ log.Trace("MinerRPCService.statusSubscriptionWorker")
+ for {
+ select {
+ case i := <-channel:
+ s.RLock()
+ if len(s.statusSubscriptions) == 0 {
+ s.RUnlock()
+ break
+ } else {
+ for sub, _ := range s.statusSubscriptions {
+ sub.notifier.Notify(sub.subscription.ID, i)
+ }
+ }
+ s.RUnlock()
+ }
+ }
+}
+
+func (s MinerRPCService) submissionSubscriptionMonitor(sub *submissionSubscription) {
+ log.Trace("MinerRPCService.submissionSubscriptionMonitor")
+ subscription := sub.subscription
+ notifier := sub.notifier
+ for {
+ select {
+ case <-notifier.Closed():
+ log.Debug("Canceling submission subscription on notifier.Closed")
+ s.removeSubmissionSubscription(sub)
+ return
+ case <-subscription.Err():
+ log.Debug("Canceling submission subscription on subscription.Err")
+ s.removeSubmissionSubscription(sub)
+ return
+ default:
+ time.Sleep(2 * time.Second)
+ }
+ }
+}
+
+func (s MinerRPCService) submissionSubscriptionWorker(channel submissionChannel) {
+ log.Trace("MinerRPCService.submissionSubscriptionWorker")
+ for {
+ select {
+ case i := <-channel:
+ s.RLock()
+ if len(s.submissionSubscriptions) == 0 {
+ s.RUnlock()
+ break
+ } else {
+ for sub, _ := range s.submissionSubscriptions {
+ sub.notifier.Notify(sub.subscription.ID, i)
+ }
+ }
+ s.RUnlock()
+ }
+ }
+}
+
+func startRPCServer(mining *Mining) {
+ log.Trace("startRPCServer")
+ newApi := rpc.API{}
+ newApi.Namespace = "mining"
+ newApi.Version = "1"
+ newApi.Service = MinerRPCService{mining, hashRateSubscriptionMap{}, statusSubscriptionMap{}, submissionSubscriptionMap{}, sync.RWMutex{}}
+ newApi.Public = true
+ _, _, err := rpc.StartIPCEndpoint(NamedPipeName, []rpc.API{newApi})
+ if err != nil {
+ // This may happen if the service is already running
+ log.WithError(err).Fatal("Failed to start the RPC Server on the named pipe")
+ }
+}
+
+func getNotifierAndSubscription(ctx context.Context) (*rpc.Notifier, *rpc.Subscription, error) {
+ log.Trace("getNotifierAndSubscription")
+ notifier, supported := rpc.NotifierFromContext(ctx)
+ if !supported {
+ return nil, nil, rpc.ErrNotificationsUnsupported
+ }
+ subscription := notifier.CreateSubscription()
+ return notifier, subscription, nil
+}
diff --git a/prosper-miner/servicecmd.go b/prosper-miner/servicecmd.go
new file mode 100644
index 0000000..6c95f34
--- /dev/null
+++ b/prosper-miner/servicecmd.go
@@ -0,0 +1,59 @@
+package main
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/kardianos/service"
+ log "github.com/sirupsen/logrus"
+ "github.com/spf13/cobra"
+)
+
+var serviceCmd = &cobra.Command{
+ Use: "service",
+ Short: "Manage the miner service",
+ Args: cobra.ExactValidArgs(1),
+ ValidArgs: []string{"debug", "install", "restart", "start", "status", "stop", "uninstall"},
+ Run: func(cmd *cobra.Command, args []string) {
+ action := args[0]
+ if action == "debug" {
+ runMinerService()
+ return
+ }
+ s, _, err := getMinerService()
+ if err != nil {
+ log.Fatal(err)
+ return
+ }
+ switch action {
+ case "install":
+ err = s.Install()
+ case "restart":
+ err = s.Restart()
+ case "start":
+ err = s.Start()
+ case "status":
+ var status service.Status
+ status, err = s.Status()
+ if err == nil {
+ var statusText string
+ var exitCode int
+ if status == service.StatusRunning {
+ statusText = "Running"
+ } else {
+ statusText = "Not running"
+ exitCode = 1
+ }
+ fmt.Println(statusText)
+ os.Exit(exitCode)
+ }
+ case "stop":
+ err = s.Stop()
+ case "uninstall":
+ err = s.Uninstall()
+ }
+ if err != nil {
+ log.Fatal(err)
+ }
+ },
+}
diff --git a/stratum/client.go b/stratum/client.go
index 0833353..dccefd9 100644
--- a/stratum/client.go
+++ b/stratum/client.go
@@ -7,6 +7,7 @@ import (
"encoding/json"
"fmt"
"net"
+ "os"
"reflect"
"strconv"
"strings"
@@ -14,9 +15,13 @@ import (
"time"
"github.com/FactomWyomingEntity/prosper-pool/mining"
+ lxr "github.com/pegnet/LXRHash"
log "github.com/sirupsen/logrus"
+ "golang.org/x/sync/semaphore"
)
+const sharedLXRCount = 1
+
var _ = log.Println
// Clients talk to stratum servers. They are on the miner side of things, so their config's
@@ -40,6 +45,10 @@ type Client struct {
miners []*ControlledMiner
successes chan *mining.Winner
totalSuccesses uint64 // Total submitted shares
+ lxrSemaphore *semaphore.Weighted
+ sharedLXR *lxr.LXRHash
+ notificationChannels *NotificationChannels
+ shutdown chan int
subscriptions []Subscription
requestsMade map[int32]func(Response)
@@ -61,7 +70,20 @@ func (c *ControlledMiner) SendCommand(command *mining.MinerCommand) bool {
}
}
-func NewClient(username, minername, password, invitecode, payoutaddress, version string) (*Client, error) {
+type NotificationChannels struct {
+ HashRateChannel chan float64
+ SubmissionChannel chan int
+}
+
+func NewNotificationChannels() (*NotificationChannels) {
+ nc := &NotificationChannels {
+ HashRateChannel: make(chan float64),
+ SubmissionChannel: make(chan int),
+ }
+ return nc
+}
+
+func NewClient(username, minername, password, invitecode, payoutaddress, version string, notificationChannels *NotificationChannels) (*Client, error) {
c := new(Client)
c.autoreconnect = true
c.version = version
@@ -75,8 +97,13 @@ func NewClient(username, minername, password, invitecode, payoutaddress, version
c.currentTarget = 0xfffe000000000000
c.requestsMade = make(map[int32]func(Response))
+ c.lxrSemaphore = semaphore.NewWeighted(int64(sharedLXRCount))
successChannel := make(chan *mining.Winner, 100)
c.successes = successChannel
+ c.notificationChannels = notificationChannels
+ // Increate the buffer size for the shutdown channel for each goroutine
+ // that will listen to the shutdown channel.
+ c.shutdown = make(chan int, 1)
go c.ListenForSuccess()
//
@@ -85,15 +112,18 @@ func NewClient(username, minername, password, invitecode, payoutaddress, version
return c, nil
}
-func (c *Client) InitMiners(num int) {
+func (c *Client) InitMiners(num int, hashTableDirectory string) {
+ c.initSharedLXRHash(hashTableDirectory)
c.miners = make([]*ControlledMiner, num)
for i := range c.miners {
commandChannel := make(chan *mining.MinerCommand, 15)
c.miners[i] = &ControlledMiner{
CommandChannel: commandChannel,
- Miner: mining.NewPegnetMiner(uint32(i), commandChannel, c.successes),
+ Miner: mining.NewPegnetMiner(uint32(i), commandChannel, c.successes, c.sharedLXR),
}
}
+ // Once the miners are initialized, there can be a hash rate to report.
+ go c.ReportHashRate()
}
func (c *Client) SetFakeHashRate(rate int) {
@@ -313,14 +343,24 @@ func (c *Client) SuggestTarget(preferredTarget string) error {
func (c *Client) Close() error {
c.autoreconnect = false
- if !reflect.ValueOf(c.conn).IsNil() {
- log.Infof("shutting down stratum client")
- return c.conn.Close()
+ // Tell goroutines to shutdown
+ for i, j := 0, cap(c.shutdown); i < j; i++ {
+ c.shutdown <- 1
+ }
+ for i := range c.miners {
+ c.miners[i].Miner.Close()
+ }
+ c.releaseSharedLXRHash()
+ if reflect.ValueOf(c.conn).IsValid() {
+ log.Info("Shutting down Stratum client")
+ if err := c.conn.Close(); err != nil {
+ return err
+ }
}
return nil
}
-func (c *Client) Listen(ctx context.Context) {
+func (c *Client) Listen(ctx context.Context) error {
// Capture a cancel and close the client
go func() {
select {
@@ -340,59 +380,69 @@ func (c *Client) Listen(ctx context.Context) {
readBytes, _, err := r.ReadLine()
if err != nil {
if !c.autoreconnect {
- return // Stop trying to reconnect
+ return err // Stop trying to reconnect
}
_ = c.conn.Close()
log.WithError(err).Errorf("client lost connection to the server, reconnect attempt in 5s")
err := c.BlockTillConnected(originalServerAddress, "5")
if err != nil {
log.WithError(err).Error("miner reconnect failed")
- return
+ return err
}
_ = c.Handshake()
r = bufio.NewReader(c.conn)
} else {
- c.HandleMessage(readBytes)
+ err := c.HandleMessage(readBytes)
+ if err != nil {
+ return err
+ }
}
}
+ return nil
}
-func (c *Client) HandleMessage(data []byte) {
+func (c *Client) HandleMessage(data []byte) error {
var u UnknownRPC
err := json.Unmarshal(data, &u)
if err != nil {
- log.WithError(err).Warnf("client read failed")
+ log.WithError(err).Warn("client.HandleMessage() failed to unmarshal JSON")
+ return err
}
if u.IsRequest() {
req := u.GetRequest()
- c.HandleRequest(req)
+ err := c.HandleRequest(req)
+ if err != nil {
+ return err
+ }
} else {
resp := u.GetResponse()
- c.HandleResponse(resp)
+ err := c.HandleResponse(resp)
+ if err != nil {
+ return err
+ }
}
-
- // TODO: Don't just print everything
- //log.Infof(string(data))
+ return nil
}
-func (c *Client) HandleRequest(req Request) {
+func (c *Client) HandleRequest(req Request) error {
var params RPCParams
if err := req.FitParams(¶ms); err != nil {
log.WithField("method", req.Method).Warnf("bad params %s", req.Method)
- return
+ return err
}
switch req.Method {
case "client.get_version":
if err := c.Encode(GetVersionResponse(req.ID, c.version)); err != nil {
log.WithField("method", req.Method).WithError(err).Error("failed to respond to get_version")
+ return err
}
case "client.reconnect":
if len(params) < 2 {
- log.Errorf("Not enough parameters to reconnect with: %s\n", params)
- return
+ err := fmt.Errorf("Not enough parameters to reconnect with: %s\n", params)
+ return err
}
waittime := "0"
@@ -400,24 +450,27 @@ func (c *Client) HandleRequest(req Request) {
_, err := strconv.ParseInt(params[2], 10, 64)
if err == nil {
waittime = params[2]
+ } else {
+ return err
}
}
if err := c.WaitThenConnect(params[0]+":"+params[1], waittime); err != nil {
log.WithField("method", req.Method).WithError(err).Error("failed to reconnect")
+ return err
}
case "client.show_message":
if len(params) < 1 {
log.Errorln("No message to show")
- return
+ return fmt.Errorf("client.show_message request with no message")
}
- // Print & log message in human-readable way
+ // Print message in human-readable way
fmt.Printf("\n\nMessage from server: %s\n\n\n", params[0])
- //log.Printf("Message from server: %s\n", params[0])
case "mining.notify":
if len(params) < 2 {
- log.Errorf("Not enough parameters from notify: %s\n", params)
- return
+ err := fmt.Errorf("Not enough parameters from notify: %s\n", params)
+ log.Error(err)
+ return err
}
jobID := params[0]
@@ -426,14 +479,14 @@ func (c *Client) HandleRequest(req Request) {
newJobID, err := strconv.ParseInt(jobID, 10, 64)
if err != nil {
log.Error("Not a valid new JobID")
- return
+ return fmt.Errorf("mining.notify has an invalid new JobID")
}
existingJobID, _ := strconv.ParseInt(c.currentJobID, 10, 64)
if newJobID >= existingJobID {
myHexBytes, err := hex.DecodeString(oprHash)
if err != nil {
log.Error(err)
- return
+ return err
}
if newJobID > existingJobID {
c.currentJobID = jobID
@@ -458,13 +511,13 @@ func (c *Client) HandleRequest(req Request) {
case "mining.set_target":
if len(params) < 1 {
log.Errorf("Not enough parameters from set_target: %s\n", params)
- return
+ return fmt.Errorf("Not enough parameters from set_target: %s\n", params)
}
result, err := strconv.ParseUint(strings.Replace(params[0], "0x", "", -1), 16, 64)
if err != nil {
log.Errorln("Target unable to be converted to uint: ", err)
- return
+ return err
}
c.currentTarget = uint64(result)
@@ -477,14 +530,14 @@ func (c *Client) HandleRequest(req Request) {
case "mining.set_nonce":
if len(params) < 1 {
log.Errorf("Not enough parameters from set_nonce: %s\n", params)
- return
+ return fmt.Errorf("mining.set_nonce does not have enough parameters")
}
nonceString := params[0]
nonce, err := strconv.ParseUint(nonceString, 10, 32)
if err != nil {
log.Errorln("Nonce unable to be converted to integer: ", err)
- return
+ return err
}
c.SetNewNonce(uint32(nonce))
@@ -497,6 +550,7 @@ func (c *Client) HandleRequest(req Request) {
default:
log.Warnf("unknown method %s", req.Method)
}
+ return nil
}
func (c *Client) SetNewNonce(nonce uint32) {
@@ -523,15 +577,39 @@ func (c *Client) AggregateStats(job int32, stats chan *mining.SingleMinerStats,
log.WithFields(groupStats.LogFields()).Info("job miner stats")
}
-func (c *Client) HandleResponse(resp Response) {
+func (c *Client) AggregateStatsAndNotify(job int32, stats chan *mining.SingleMinerStats, l int) {
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second * 3)
+ defer cancel() // Must clean up context to avoid a memory leak
+ groupStats := mining.NewGroupMinerStats(job)
+
+ for i := 0; i < l; i ++ {
+ select {
+ case stat := <-stats:
+ groupStats.Miners[stat.ID] = stat
+ case <-ctx.Done():
+ }
+ }
+ if c.notificationChannels != nil {
+ // Notify listeners. Do nothing if no goroutines are
+ // listening.
+ select {
+ case c.notificationChannels.HashRateChannel <- groupStats.TotalHashPower():
+ default:
+ }
+ }
+}
+
+func (c *Client) HandleResponse(resp Response) error {
c.Lock()
if funcToPerform, ok := c.requestsMade[resp.ID]; ok {
funcToPerform(resp)
delete(c.requestsMade, resp.ID)
} else {
log.Errorf("Response received for unrecognized request ID: %d (ignoring)\n", resp.ID)
+ return fmt.Errorf("Response received for unrecognized request ID: %d", resp.ID)
}
c.Unlock()
+ return nil
}
func (c *Client) ListenForSuccess() {
@@ -546,11 +624,58 @@ func (c *Client) ListenForSuccess() {
log.WithError(err).Error("failed to submit to server")
} else {
c.totalSuccesses++
+ if c.notificationChannels != nil {
+ // Notify listeners. Do nothing if no
+ // goroutines are listening.
+ select {
+ case c.notificationChannels.SubmissionChannel <- 1:
+ default:
+ }
+ }
}
}
}
}
+func (c *Client) RemoteAddr() string {
+ return c.conn.RemoteAddr().String()
+}
+
+func (c *Client) ReportHashRate() {
+ ticker := time.NewTicker(time.Second * 10)
+ for {
+ select {
+ case <- c.shutdown:
+ ticker.Stop()
+ return
+ case <- ticker.C:
+ existingJobID, _ := strconv.ParseInt(c.currentJobID, 10, 64)
+ stats := make(chan *mining.SingleMinerStats, len(c.miners))
+ command := mining.BuildCommand().
+ CurrentHashRate(stats).
+ Build()
+ c.SendCommand(command)
+ go c.AggregateStatsAndNotify(int32(existingJobID), stats, len(c.miners))
+ }
+ }
+}
+
func (c *Client) TotalSuccesses() uint64 {
return c.totalSuccesses
}
+
+func (c *Client) initSharedLXRHash(hashTableDirectory string) {
+ if c.lxrSemaphore.TryAcquire(sharedLXRCount) {
+ c.sharedLXR = &lxr.LXRHash{}
+ if size, err := strconv.Atoi(os.Getenv("LXRBITSIZE")); err == nil && size >= 8 && size <=30 {
+ c.sharedLXR.InitFromPath(0xfafaececfafaecec, uint64(size), 256, 5, hashTableDirectory)
+ } else {
+ c.sharedLXR.InitFromPath(lxr.Seed, lxr.MapSizeBits, lxr.HashSize, lxr.Passes, hashTableDirectory)
+ }
+ }
+}
+
+func (c *Client) releaseSharedLXRHash() {
+ c.sharedLXR = nil
+ c.lxrSemaphore.Release(sharedLXRCount)
+}