├── .deepsource.toml
├── .gitignore
├── .idea
├── modules.xml
└── vcs.xml
├── LICENSE
├── README.md
├── algorithm
├── algorithm.go
└── algorithm_test.go
├── api
└── server.go
├── bans
└── banning.go
├── config.bitcoin-qt.json
├── config.btcd.json
├── config.dash.json
├── config.example.json
├── config.litecoin.json
├── config
├── algorithm.go
├── api.go
├── banning.go
├── coin.go
├── daemon.go
├── options.go
├── p2p.go
├── payment.go
├── redis.go
├── stratum.go
├── tls.go
└── vardiff.go
├── daemons
├── getbalance.go
├── getblock.go
├── getblockchaininfo.go
├── getblocktemplate.go
├── getdifficulty.go
├── getinfo.go
├── getmininginfo.go
├── getnetworkinfo.go
├── getwalletinfo.go
├── jsonrpc.go
├── manager.go
├── submitblock.go
└── validateaddress.go
├── go.mod
├── go.sum
├── jobs
├── extraNonce1Generator.go
├── job.go
├── jobManager.go
└── job_test.go
├── main.go
├── merkletree
├── mtree.go
└── mtree_test.go
├── p2p
├── peer.go
└── peer_test.go
├── payments
├── contrib.go
├── payer.go
└── recipient.go
├── pool
├── pool.go
└── stats.go
├── resources
├── logo.png
└── logo.svg
├── storage
└── redis.go
├── stratum
├── client.go
├── server.go
├── shares.go
└── subscriptionCounter.go
├── transactions
├── transactions.go
└── transactions_test.go
├── types
├── block.go
├── errors.go
├── share.go
└── stats.go
├── utils
├── utils.go
└── utils_test.go
└── vardiff
├── ringbuffer.go
├── vardiff.go
└── vardiff_test.go
/.deepsource.toml:
--------------------------------------------------------------------------------
1 | version = 1
2 |
3 | [[analyzers]]
4 | name = "go"
5 | enabled = true
6 |
7 | [analyzers.meta]
8 | import_path = "github.com/mining-pool/not-only-mining-pool"
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | config.json
3 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Command M
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Not-Only-Mining-Pool(NOMP)
2 |
3 | BitcoinCore(bitcoind)-variants' pool written in golang
4 |
5 | ## Difference from NOMP(node-open-mining-portal)
6 |
7 | This pool software is not a portal, but a standalone stratum server with high performance.
8 |
9 | If you want, you can implement the portal page in frontend web.
10 |
11 | ## Why standalone?
12 |
13 | Keep standalone is better to assemble new algorithm/coin to your pool services, without dealing with C lib conflicts or restart the whole site to append new pool, what's more, most pool operators don't need a portal, they just get benefit from few different coins with different algorithms.
14 |
15 | So it's obviously that standalone one with more advantages on deploying and maintaining.
16 |
17 | In other words, "大人,時代變了!".
18 |
19 | ## How to use it?
20 |
21 | ### 0x00 Check
22 |
23 | Make sure your algorithm in support, if not, take an issue.
24 |
25 | ### 0x01 Configuration
26 |
27 | Read `config.example.json` and modify the configurations
28 |
29 | Then rename it to `config.json`
30 |
31 | ### 0x02 Build
32 |
33 | ```bash
34 | go build .
35 |
36 | ```
37 |
38 | ### 0x03 Deploy
39 |
40 | Copy `go-stratum-pool` or `go-stratum-pool.exe` and `config.json` to your VPS server and
41 |
42 | And then
43 |
44 | ```bash
45 | $ ./go-stratum-pool
46 |
47 | ```
48 |
49 | or
50 |
51 | ```cmd
52 | > go-stratum-pool.exe
53 |
54 | ```
55 |
56 | ## TODO
57 |
58 | - API(half done)
59 | - More algorithms
60 | - Web page
61 | - ...
62 |
63 | ## Donation
64 |
65 | **LTC**: LXxqHY4StG79nqRurdNNt1wF2yCf4Mc986
66 |
67 | ## Tested Network
68 | - LTC Testnet
69 |
--------------------------------------------------------------------------------
/algorithm/algorithm.go:
--------------------------------------------------------------------------------
1 | package algorithm
2 |
3 | import (
4 | "math/big"
5 | "strings"
6 |
7 | logging "github.com/ipfs/go-log/v2"
8 | "github.com/mining-pool/not-only-mining-pool/utils"
9 | "github.com/samli88/go-x11-hash"
10 | "golang.org/x/crypto/scrypt"
11 | )
12 |
13 | var log = logging.Logger("algorithm")
14 |
15 | // difficulty = MAX_TARGET / current_target.
16 | var (
17 | MaxTargetTruncated, _ = new(big.Int).SetString("00000000FFFF0000000000000000000000000000000000000000000000000000", 16)
18 | MaxTarget, _ = new(big.Int).SetString("00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16)
19 | )
20 |
21 | func GetHashFunc(hashName string) func([]byte) []byte {
22 | switch strings.ToLower(hashName) {
23 | case "scrypt":
24 | return ScryptHash
25 | case "x11":
26 | return X11Hash
27 | case "sha256d":
28 | return DoubleSha256Hash
29 | default:
30 | log.Panic(hashName, " is not officially supported yet, but you can easily add it with cgo binding by yourself")
31 | return nil
32 | }
33 | }
34 |
35 | // ScryptHash is the algorithm which litecoin uses as its PoW mining algorithm
36 | func ScryptHash(data []byte) []byte {
37 | b, _ := scrypt.Key(data, data, 1024, 1, 1, 32)
38 |
39 | return b
40 | }
41 |
42 | // X11Hash is the algorithm which dash uses as its PoW mining algorithm
43 | func X11Hash(data []byte) []byte {
44 | dst := make([]byte, 32)
45 | x11.New().Hash(data, dst)
46 | return dst
47 | }
48 |
49 | // DoubleSha256Hash is the algorithm which litecoin uses as its PoW mining algorithm
50 | func DoubleSha256Hash(b []byte) []byte {
51 | return utils.Sha256d(b)
52 | }
53 |
--------------------------------------------------------------------------------
/algorithm/algorithm_test.go:
--------------------------------------------------------------------------------
1 | package algorithm
2 |
3 | import (
4 | "encoding/hex"
5 | "testing"
6 |
7 | "github.com/mining-pool/not-only-mining-pool/utils"
8 | )
9 |
10 | func TestHash(t *testing.T) {
11 | t.Log(MaxTargetTruncated)
12 | }
13 |
14 | func TestScryptHash(t *testing.T) {
15 | headerHex := "01000000f615f7ce3b4fc6b8f61e8f89aedb1d0852507650533a9e3b10b9bbcc30639f279fcaa86746e1ef52d3edb3c4ad8259920d509bd073605c9bf1d59983752a6b06b817bb4ea78e011d012d59d4"
16 | headerBytes, err := hex.DecodeString(headerHex)
17 | if err != nil {
18 | t.Log(err)
19 | }
20 | result := hex.EncodeToString(utils.ReverseBytes(GetHashFunc("scrypt")(headerBytes)))
21 | if result != "0000000110c8357966576df46f3b802ca897deb7ad18b12f1c24ecff6386ebd9" {
22 | t.Log(result)
23 | t.Fail()
24 | }
25 | }
26 |
27 | func TestX11Hash(t *testing.T) {
28 | if hex.EncodeToString(X11Hash([]byte("The great experiment continues."))) != "4da3b7c5ff698c6546564ebc72204f31885cd87b75b2b3ca5a93b5d75db85b8c" {
29 | t.Log(hex.EncodeToString(GetHashFunc("x11")([]byte("The great experiment continues."))))
30 | t.Fail()
31 | }
32 |
33 | // Test Dash Tx
34 | raw, _ := hex.DecodeString("0200000001ac7d18f0103f17c44b5b2b1352617735cc3a3a52381a28e923dffa4ac78e1560000000006b483045022100c56b739271efc559d63b04a01c15fddf7a74008b9afbd432c6260c24bde3b0cf02206ce80233e5af953f7e6f4b55427afa86aac6cbf3047c3cf90fcc248c8d3338f9012103e544bf462f31edad02b3d8134f60d20d7180208df68b0d95f8e0cacee880bc93ffffffff013d6c6d02000000001976a91404ed220f5b5bfd1c61becf0d76e21773ed204ac188ac00000000")
35 | hash := hex.EncodeToString(DoubleSha256Hash(raw))
36 |
37 | if hash != hex.EncodeToString(utils.Uint256BytesFromHash("498a7a14586da86d98a26ee00aecb7f8fb61a6160453186c88108e4873beaaff")) {
38 | t.Log(hash)
39 | t.Fail()
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/api/server.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "encoding/json"
5 | "net/http"
6 | "time"
7 |
8 | "github.com/gorilla/mux"
9 | logging "github.com/ipfs/go-log/v2"
10 | "github.com/mining-pool/not-only-mining-pool/config"
11 | "github.com/mining-pool/not-only-mining-pool/storage"
12 | )
13 |
14 | var log = logging.Logger("api")
15 |
16 | // TODO
17 | type Server struct {
18 | *mux.Router
19 |
20 | apiConf *config.APIOptions
21 | storage *storage.DB
22 |
23 | availablePaths []string
24 | config map[string]interface{}
25 | }
26 |
27 | // NewAPIServer creates a Server which follows [standard api](https://github.com/mining-pool/mining-pool-api)
28 | func NewAPIServer(options *config.Options, storage *storage.DB) *Server {
29 | s := &Server{
30 | Router: mux.NewRouter(),
31 |
32 | apiConf: options.API,
33 | storage: storage,
34 |
35 | availablePaths: make([]string, 0),
36 | config: make(map[string]interface{}),
37 | }
38 |
39 | s.ConvertConf(options)
40 |
41 | s.RegisterFunc("/", s.indexFunc)
42 |
43 | s.RegisterFunc("/pool", s.poolFunc)
44 |
45 | s.RegisterFunc("/config", s.configIndexFunc)
46 | s.RegisterFunc("/config/{key}", s.configFunc)
47 |
48 | s.RegisterFunc("/miner/{miner}", s.minerFunc)
49 | s.RegisterFunc("/miner/{miner}/rig/{rig}", s.rigFunc)
50 | s.Use(mux.CORSMethodMiddleware(s.Router))
51 |
52 | http.Handle("/", s)
53 |
54 | return s
55 | }
56 |
57 | func (s *Server) ConvertConf(options *config.Options) {
58 | s.config["ports"] = options.Ports
59 | s.config["algorithm"] = options.Algorithm
60 | s.config["coin"] = options.Coin
61 | s.config["api"] = options.API
62 | }
63 |
64 | func (s *Server) RegisterFunc(path string, fn func(http.ResponseWriter, *http.Request)) {
65 | s.HandleFunc(path, fn)
66 | s.availablePaths = append(s.availablePaths, path)
67 | }
68 |
69 | func (s *Server) Serve() {
70 | addr := s.apiConf.Addr()
71 | log.Warn("API server listening on ", addr)
72 | go func() {
73 | err := http.ListenAndServe(addr, nil)
74 | if err != nil {
75 | panic(err)
76 | }
77 | }()
78 | }
79 |
80 | func (s *Server) indexFunc(writer http.ResponseWriter, _ *http.Request) {
81 | raw, _ := json.Marshal(s.availablePaths)
82 | _, _ = writer.Write(raw)
83 | }
84 |
85 | func (s *Server) configIndexFunc(writer http.ResponseWriter, _ *http.Request) {
86 | keys := make([]string, 0)
87 | for k := range s.config {
88 | keys = append(keys, "/config/"+k)
89 | }
90 |
91 | raw, _ := json.Marshal(keys)
92 | _, _ = writer.Write(raw)
93 | }
94 |
95 | func (s *Server) configFunc(writer http.ResponseWriter, r *http.Request) {
96 | vars := mux.Vars(r)
97 |
98 | raw, _ := json.Marshal(s.config[vars["key"]])
99 | _, _ = writer.Write(raw)
100 | }
101 |
102 | type PoolInfo struct {
103 | CoinName string `json:"coinName"`
104 |
105 | Hashrate1Min float64 `json:"hashrate1min"`
106 | Hashrate30Min float64 `json:"hashrate30min"`
107 | Hashrate1H float64 `json:"hashrate1h"`
108 | Hashrate6H float64 `json:"hashrate6h"`
109 | Hashrate1D float64 `json:"hashrate1d"`
110 |
111 | Miners []string `json:"miners"`
112 | }
113 |
114 | func (s *Server) poolFunc(w http.ResponseWriter, _ *http.Request) {
115 | now := time.Now().Unix() // unit: sec
116 |
117 | hs1M, err := s.storage.GetPoolHashrate(now-60, now)
118 | if err != nil {
119 | log.Error(err)
120 | }
121 | hs30M, err := s.storage.GetPoolHashrate(now-30*60, now)
122 | if err != nil {
123 | log.Error(err)
124 | }
125 | hs1H, err := s.storage.GetPoolHashrate(now-60*60, now)
126 | if err != nil {
127 | log.Error(err)
128 | }
129 | hs6H, err := s.storage.GetPoolHashrate(now-6*60*60, now)
130 | if err != nil {
131 | log.Error(err)
132 | }
133 | hs1D, err := s.storage.GetPoolHashrate(now-24*60*60, now)
134 | if err != nil {
135 | log.Error(err)
136 | }
137 |
138 | miners, err := s.storage.GetMinerIndex()
139 | if err != nil {
140 | log.Error(err)
141 | }
142 |
143 | miner := PoolInfo{
144 | Hashrate1Min: hs1M,
145 | Hashrate30Min: hs30M,
146 | Hashrate1H: hs1H,
147 | Hashrate6H: hs6H,
148 | Hashrate1D: hs1D,
149 |
150 | Miners: miners,
151 | }
152 |
153 | raw, err := json.Marshal(&miner)
154 | if err != nil {
155 | log.Error(err)
156 | }
157 |
158 | _, _ = w.Write(raw)
159 | }
160 |
161 | type MinerInfo struct {
162 | Name string `json:"name"`
163 |
164 | Hashrate1Min float64 `json:"hashrate1min"`
165 | Hashrate30Min float64 `json:"hashrate30min"`
166 | Hashrate1H float64 `json:"hashrate1h"`
167 | Hashrate6H float64 `json:"hashrate6h"`
168 | Hashrate1D float64 `json:"hashrate1d"`
169 |
170 | RoundContrib float64 `json:"roundContrib"`
171 | Rigs []string `json:"rigs"`
172 | }
173 |
174 | func (s *Server) minerFunc(w http.ResponseWriter, r *http.Request) {
175 | vars := mux.Vars(r)
176 | minerName := vars["miner"]
177 |
178 | contrib, err := s.storage.GetMinerCurrentRoundContrib(minerName)
179 | if err != nil {
180 | log.Error(err)
181 | }
182 |
183 | now := time.Now().Unix() // unit: sec
184 |
185 | hs1M, err := s.storage.GetMinerHashrate(minerName, now-60, now)
186 | if err != nil {
187 | log.Error(err)
188 | }
189 | hs30M, err := s.storage.GetMinerHashrate(minerName, now-30*60, now)
190 | if err != nil {
191 | log.Error(err)
192 | }
193 | hs1H, err := s.storage.GetMinerHashrate(minerName, now-60*60, now)
194 | if err != nil {
195 | log.Error(err)
196 | }
197 | hs6H, err := s.storage.GetMinerHashrate(minerName, now-6*60*60, now)
198 | if err != nil {
199 | log.Error(err)
200 | }
201 | hs1D, err := s.storage.GetMinerHashrate(minerName, now-24*60*60, now)
202 | if err != nil {
203 | log.Error(err)
204 | }
205 |
206 | rigs, err := s.storage.GetRigIndex(minerName)
207 | if err != nil {
208 | log.Error(err)
209 | }
210 |
211 | miner := MinerInfo{
212 | Name: minerName,
213 |
214 | Hashrate1Min: hs1M,
215 | Hashrate30Min: hs30M,
216 | Hashrate1H: hs1H,
217 | Hashrate6H: hs6H,
218 | Hashrate1D: hs1D,
219 |
220 | RoundContrib: contrib,
221 | Rigs: rigs,
222 | }
223 |
224 | raw, err := json.Marshal(&miner)
225 | if err != nil {
226 | log.Error(err)
227 | }
228 |
229 | _, _ = w.Write(raw)
230 | }
231 |
232 | type RigInfo struct {
233 | Name string `json:"name"`
234 | MinerName string `json:"minerName"`
235 | Hashrate1Min float64 `json:"hashrate1min"`
236 | Hashrate30Min float64 `json:"hashrate30min"`
237 | Hashrate1H float64 `json:"hashrate1h"`
238 | Hashrate6H float64 `json:"hashrate6h"`
239 | Hashrate1D float64 `json:"hashrate1d"`
240 | }
241 |
242 | func (s *Server) rigFunc(w http.ResponseWriter, r *http.Request) {
243 | vars := mux.Vars(r)
244 | minerName := vars["miner"]
245 | rigName := vars["rig"]
246 |
247 | now := time.Now().Unix() // unit: sec
248 |
249 | hs1M, err := s.storage.GetRigHashrate(minerName, rigName, now-60, now)
250 | if err != nil {
251 | log.Error(err)
252 | }
253 | hs30M, err := s.storage.GetRigHashrate(minerName, rigName, now-30*60, now)
254 | if err != nil {
255 | log.Error(err)
256 | }
257 | hs1H, err := s.storage.GetRigHashrate(minerName, rigName, now-60*60, now)
258 | if err != nil {
259 | log.Error(err)
260 | }
261 | hs6H, err := s.storage.GetRigHashrate(minerName, rigName, now-6*60*60, now)
262 | if err != nil {
263 | log.Error(err)
264 | }
265 | hs1D, err := s.storage.GetRigHashrate(minerName, rigName, now-24*60*60, now)
266 | if err != nil {
267 | log.Error(err)
268 | }
269 |
270 | miner := RigInfo{
271 | Name: rigName,
272 | MinerName: minerName,
273 |
274 | Hashrate1Min: hs1M,
275 | Hashrate30Min: hs30M,
276 | Hashrate1H: hs1H,
277 | Hashrate6H: hs6H,
278 | Hashrate1D: hs1D,
279 | }
280 |
281 | raw, err := json.Marshal(&miner)
282 | if err != nil {
283 | log.Error(err)
284 | }
285 |
286 | _, _ = w.Write(raw)
287 | }
288 |
--------------------------------------------------------------------------------
/bans/banning.go:
--------------------------------------------------------------------------------
1 | package bans
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/mining-pool/not-only-mining-pool/config"
7 | )
8 |
9 | type BanningManager struct {
10 | Options *config.BanningOptions
11 | BannedIPList map[string]*time.Time
12 | }
13 |
14 | func NewBanningManager(options *config.BanningOptions) *BanningManager {
15 | return &BanningManager{
16 | Options: options,
17 | BannedIPList: make(map[string]*time.Time),
18 | }
19 | }
20 |
21 | func (bm *BanningManager) Init() {
22 | go func() {
23 | ticker := time.NewTicker(time.Duration(bm.Options.PurgeInterval) * time.Second)
24 | defer ticker.Stop()
25 |
26 | for {
27 | <-ticker.C
28 | for ip, banTime := range bm.BannedIPList {
29 | if time.Since(*banTime) > time.Duration(bm.Options.Time)*time.Second {
30 | delete(bm.BannedIPList, ip)
31 | }
32 | }
33 | }
34 | }()
35 | }
36 |
37 | func (bm *BanningManager) CheckBan(strRemoteAddr string) (shouldCloseSocket bool) {
38 | if bm.BannedIPList[strRemoteAddr] != nil {
39 | bannedTime := bm.BannedIPList[strRemoteAddr]
40 | bannedTimeAgo := time.Since(*bannedTime)
41 | timeLeft := time.Duration(bm.Options.Time)*time.Second - bannedTimeAgo
42 | if timeLeft > 0 {
43 | return true
44 | // client.Socket.Close()
45 | // kickedBannedIP
46 | } else {
47 | delete(bm.BannedIPList, strRemoteAddr)
48 | // forgaveBannedIP
49 | }
50 | }
51 |
52 | return false
53 | }
54 |
55 | func (bm *BanningManager) AddBannedIP(strRemoteAddr string) {
56 | now := time.Now()
57 | bm.BannedIPList[strRemoteAddr] = &now
58 | }
59 |
--------------------------------------------------------------------------------
/config.bitcoin-qt.json:
--------------------------------------------------------------------------------
1 | {
2 | "coin": {
3 | "name": "Bitcoin",
4 | "symbol": "BTC"
5 | },
6 | "algorithm": {
7 | "name": "sha256d",
8 | "multiplier": 0,
9 | "sha256dBlockHasher": true
10 | },
11 | "poolAddress": {
12 | "address": "76a914892b70294fcea4be8bf41b6c8e69fe79743b08f388ac",
13 | "type": "script"
14 | },
15 | "rewardRecipients": [
16 | {
17 | "address": "76a914892b70294fcea4be8bf41b6c8e69fe79743b08f388ac",
18 | "type": "script",
19 | "percent": 0.01
20 | }
21 | ],
22 | "blockRefreshInterval": 10,
23 | "jobRebroadcastTimeout": 55,
24 | "connectionTimeout": 600,
25 | "emitInvalidBlockHashes": false,
26 | "tcpProxyProtocol": false,
27 | "banning": {
28 | "time": 600,
29 | "invalidPercent": 50,
30 | "checkThreshold": 500,
31 | "purgeInterval": 300
32 | },
33 | "ports": {
34 | "3032": {
35 | "diff": 1,
36 | "varDiff": {
37 | "minDiff": 1,
38 | "maxDiff": 100000,
39 | "targetTime": 15,
40 | "retargetTime": 90,
41 | "variancePercent": 30
42 | }
43 | }
44 | },
45 | "daemons": [
46 | {
47 | "host": "127.0.0.1",
48 | "port": 18332,
49 | "user": "bitcoinrpc",
50 | "password": "testnet"
51 | }
52 | ],
53 | "api": {
54 | "host": "0.0.0.0",
55 | "port": 20000
56 | },
57 | "storage": {
58 | "network": "tcp",
59 | "host": "127.0.0.1",
60 | "port": 6379,
61 | "tls": null
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/config.btcd.json:
--------------------------------------------------------------------------------
1 | {
2 | "disablePayment": true,
3 | "coin": {
4 | "name": "Bitcoin",
5 | "symbol": "BTC"
6 | },
7 | "algorithm": {
8 | "name": "sha256d",
9 | "multiplier": 0,
10 | "sha256dBlockHasher": true
11 | },
12 | "poolAddress": {
13 | "address": "bc1qlr36tpdwctfndcdgzx5ukjkqazl5hclkurw08q",
14 | "type": "p2wsh"
15 | },
16 | "rewardRecipients": [
17 | {
18 | "address": "bc1qlr36tpdwctfndcdgzx5ukjkqazl5hclkurw08q",
19 | "type": "p2wsh",
20 | "percent": 0.01
21 | }
22 | ],
23 | "blockRefreshInterval": 10,
24 | "jobRebroadcastTimeout": 55,
25 | "connectionTimeout": 600,
26 | "emitInvalidBlockHashes": false,
27 | "tcpProxyProtocol": false,
28 | "banning": {
29 | "time": 600,
30 | "invalidPercent": 50,
31 | "checkThreshold": 500,
32 | "purgeInterval": 300
33 | },
34 | "ports": {
35 | "3032": {
36 | "diff": 1,
37 | "varDiff": {
38 | "minDiff": 1,
39 | "maxDiff": 100000,
40 | "targetTime": 15,
41 | "retargetTime": 90,
42 | "variancePercent": 30
43 | }
44 | }
45 | },
46 | "daemons": [
47 | {
48 | "host": "127.0.0.1",
49 | "port": 18334,
50 | "user": "bitcoinrpc",
51 | "password": "testnet",
52 | "tls":{}
53 | }
54 | ],
55 | "api": {
56 | "host": "0.0.0.0",
57 | "port": 20000
58 | },
59 | "storage": {
60 | "network": "tcp",
61 | "host": "127.0.0.1",
62 | "port": 6379,
63 | "tls": null
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/config.dash.json:
--------------------------------------------------------------------------------
1 | {
2 | "coin": {
3 | "name": "Dash",
4 | "symbol": "DASH"
5 | },
6 | "algorithm": {
7 | "name": "x11",
8 | "multiplier": 30,
9 | "sha256dBlockHasher": false
10 | },
11 | "poolAddress": {
12 | "address": "yYtPmC5TGJZrKsJ5HcmEuhabG5UjU5U6kq",
13 | "type": "p2pkh"
14 | },
15 | "rewardRecipients": [
16 | {
17 | "address": "yadZJQPAaQkfKj8Md7zz1N2FqLvRHcF5CL",
18 | "type": "p2pkh",
19 | "percent": 0.01
20 | }
21 | ],
22 | "blockRefreshInterval": 10,
23 | "jobRebroadcastTimeout": 55,
24 | "connectionTimeout": 600,
25 | "emitInvalidBlockHashes": false,
26 | "tcpProxyProtocol": false,
27 | "banning": {
28 | "time": 600,
29 | "invalidPercent": 50,
30 | "checkThreshold": 500,
31 | "purgeInterval": 300
32 | },
33 | "ports": {
34 | "3032": {
35 | "diff": 0.0001,
36 | "varDiff": {
37 | "minDiff": 0.0001,
38 | "maxDiff": 100000,
39 | "targetTime": 15,
40 | "retargetTime": 90,
41 | "variancePercent": 30
42 | }
43 | }
44 | },
45 | "daemons": [
46 | {
47 | "host": "127.0.0.1",
48 | "port": 19998,
49 | "user": "dashrpc",
50 | "password": "testnet"
51 | }
52 | ],
53 | "p2p": null,
54 | "api": {
55 | "host": "0.0.0.0",
56 | "port": 20000
57 | },
58 | "storage": {
59 | "network": "tcp",
60 | "host": "127.0.0.1",
61 | "port": 6379,
62 | "tls": null
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/config.example.json:
--------------------------------------------------------------------------------
1 | {
2 | "coin": {
3 | "name": "Litecoin",
4 | "symbol": "LTC"
5 | },
6 | "algorithm": {
7 | "name": "scrypt",
8 | "multiplier": 16,
9 | "sha256dBlockHasher": true
10 | },
11 | "poolAddress": {
12 | "address": "QPxrDq3sorCk8DWaYX2GeCkxoePhm1asyY",
13 | "type": "p2sh"
14 | },
15 | "rewardRecipients": [
16 | {
17 | "address": "QPxrDq3sorCk8DWaYX2GeCkxoePhm1asyY",
18 | "type": "p2sh",
19 | "percent": 0.01
20 | }
21 | ],
22 | "blockRefreshInterval": 10,
23 | "jobRebroadcastTimeout": 55,
24 | "connectionTimeout": 600,
25 | "emitInvalidBlockHashes": false,
26 | "tcpProxyProtocol": false,
27 | "banning": {
28 | "time": 600,
29 | "invalidPercent": 50,
30 | "checkThreshold": 500,
31 | "purgeInterval": 300
32 | },
33 | "ports": {
34 | "3032": {
35 | "diff": 15,
36 | "varDiff": {
37 | "minDiff": 1,
38 | "maxDiff": 100000,
39 | "targetTime": 15,
40 | "retargetTime": 90,
41 | "variancePercent": 30
42 | },
43 | "tls": null
44 | }
45 | },
46 | "daemons": [
47 | {
48 | "host": "127.0.0.1",
49 | "port": 19332,
50 | "user": "litecoinrpc",
51 | "password": "testnet"
52 | }
53 | ],
54 | "p2p": {
55 | "host": "127.0.0.1",
56 | "port": 19335,
57 | "magic": "fdd2c8f1",
58 | "disableTransactions": true
59 | },
60 | "api": {
61 | "host": "0.0.0.0",
62 | "port": 20000
63 | },
64 | "storage": {
65 | "network": "tcp",
66 | "host": "127.0.0.1",
67 | "port": 6379,
68 | "tls": {
69 | "certFile": "cert.pem",
70 | "keyFile": "server.key"
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/config.litecoin.json:
--------------------------------------------------------------------------------
1 | {
2 | "coin": {
3 | "name": "Litecoin",
4 | "symbol": "LTC"
5 | },
6 | "algorithm": {
7 | "name": "scrypt",
8 | "multiplier": 16,
9 | "sha256dBlockHasher": true
10 | },
11 | "poolAddress": {
12 | "address": "QPxrDq3sorCk8DWaYX2GeCkxoePhm1asyY",
13 | "type": "p2sh"
14 | },
15 | "rewardRecipients": [
16 | {
17 | "address": "QPxrDq3sorCk8DWaYX2GeCkxoePhm1asyY",
18 | "type": "p2sh",
19 | "percent": 0.01
20 | }
21 | ],
22 | "blockRefreshInterval": 10,
23 | "jobRebroadcastTimeout": 55,
24 | "connectionTimeout": 600,
25 | "emitInvalidBlockHashes": false,
26 | "tcpProxyProtocol": false,
27 | "banning": {
28 | "time": 600,
29 | "invalidPercent": 50,
30 | "checkThreshold": 500,
31 | "purgeInterval": 300
32 | },
33 | "ports": {
34 | "3032": {
35 | "diff": 15,
36 | "varDiff": {
37 | "minDiff": 1,
38 | "maxDiff": 100000,
39 | "targetTime": 15,
40 | "retargetTime": 90,
41 | "variancePercent": 30
42 | }
43 | }
44 | },
45 | "daemons": [
46 | {
47 | "host": "127.0.0.1",
48 | "port": 19332,
49 | "user": "litecoinrpc",
50 | "password": "testnet"
51 | }
52 | ],
53 | "p2p": {
54 | "host": "127.0.0.1",
55 | "port": 19335,
56 | "magic": "fdd2c8f1",
57 | "disableTransactions": true
58 | },
59 | "api": {
60 | "host": "0.0.0.0",
61 | "port": 20000
62 | },
63 | "storage": {
64 | "network": "tcp",
65 | "host": "127.0.0.1",
66 | "port": 6379,
67 | "tls": null
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/config/algorithm.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | type AlgorithmOptions struct {
4 | Name string `json:"name"`
5 | Multiplier int `json:"multiplier"`
6 | SHA256dBlockHasher bool `json:"sha256dBlockHasher"`
7 | }
8 |
--------------------------------------------------------------------------------
/config/api.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import "strconv"
4 |
5 | type APIOptions struct {
6 | Host string `json:"host"`
7 | Port int `json:"port"`
8 | }
9 |
10 | func (api *APIOptions) Addr() string {
11 | return api.Host + ":" + strconv.FormatInt(int64(api.Port), 10)
12 | }
13 |
--------------------------------------------------------------------------------
/config/banning.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | type BanningOptions struct {
4 | Time int `json:"time"`
5 | InvalidPercent float64 `json:"invalidPercent"`
6 | CheckThreshold uint64 `json:"checkThreshold"`
7 | PurgeInterval int `json:"purgeInterval"` // unit seconds
8 | }
9 |
--------------------------------------------------------------------------------
/config/coin.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | type CoinOptions struct {
4 | Name string `json:"name"`
5 | Symbol string `json:"symbol"`
6 | TxMessages bool `json:"txMessages"`
7 |
8 | // auto-filled from rpc
9 | Reward string `json:"reward"`
10 | NoSubmitBlock bool `json:"noSubmitBlock"`
11 | Testnet bool `json:"testnet"`
12 | }
13 |
--------------------------------------------------------------------------------
/config/daemon.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "strconv"
5 | )
6 |
7 | type DaemonOptions struct {
8 | Host string `json:"host"`
9 | Port int `json:"port"`
10 | User string `json:"user"`
11 | Password string `json:"password"`
12 | TLS *TLSClientOptions `json:"tls"`
13 | }
14 |
15 | func (d *DaemonOptions) String() string {
16 | return d.User + ":" + d.Password + "@" + d.Host + strconv.FormatInt(int64(d.Port), 10)
17 | }
18 |
19 | func (d *DaemonOptions) URL() string {
20 | if d.TLS != nil {
21 | return "https://" + d.Host + ":" + strconv.FormatInt(int64(d.Port), 10)
22 | }
23 |
24 | return "http://" + d.Host + ":" + strconv.FormatInt(int64(d.Port), 10)
25 | }
26 |
--------------------------------------------------------------------------------
/config/options.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import logging "github.com/ipfs/go-log/v2"
4 |
5 | var log = logging.Logger("config")
6 |
7 | type Options struct {
8 | DisablePayment bool `json:"disablePayment"`
9 | Coin *CoinOptions `json:"coin"`
10 |
11 | PoolAddress *Recipient `json:"poolAddress"`
12 | RewardRecipients []*Recipient `json:"rewardRecipients"`
13 |
14 | BlockRefreshInterval int `json:"blockRefreshInterval"`
15 | JobRebroadcastTimeout int `json:"jobRebroadcastTimeout"`
16 | ConnectionTimeout int `json:"connectionTimeout"`
17 | EmitInvalidBlockHashes bool `json:"emitInvalidBlockHashes"`
18 | TCPProxyProtocol bool `json:"tcpProxyProtocol"` // http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
19 |
20 | API *APIOptions `json:"api"`
21 | Banning *BanningOptions `json:"banning"`
22 | Ports map[int]*PortOptions `json:"ports"`
23 | Daemons []*DaemonOptions `json:"daemons"`
24 | P2P *P2POptions `json:"p2p"`
25 | Storage *RedisOptions `json:"storage"`
26 | Algorithm *AlgorithmOptions `json:"algorithm"`
27 | }
28 |
29 | func (o *Options) TotalFeePercent() float64 {
30 | var totalFeePercent float64
31 | for i := range o.RewardRecipients {
32 | totalFeePercent = totalFeePercent + o.RewardRecipients[i].Percent
33 | }
34 |
35 | return totalFeePercent
36 | }
37 |
--------------------------------------------------------------------------------
/config/p2p.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import "strconv"
4 |
5 | type P2POptions struct {
6 | Host string `json:"host"`
7 | Port int `json:"port"`
8 | Magic string `json:"magic"`
9 | DisableTransactions bool `json:"disableTransactions"`
10 | }
11 |
12 | func (p2p *P2POptions) Addr() string {
13 | return p2p.Host + ":" + strconv.FormatInt(int64(p2p.Port), 10)
14 | }
15 |
--------------------------------------------------------------------------------
/config/payment.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/mining-pool/not-only-mining-pool/utils"
7 | )
8 |
9 | type Recipient struct {
10 | Address string `json:"address"`
11 | Type string `json:"type"`
12 | Percent float64 `json:"percent"`
13 |
14 | script []byte
15 | }
16 |
17 | func (r *Recipient) GetScript() []byte {
18 | if r.script == nil {
19 | switch strings.ToLower(r.Type) {
20 | case "p2sh":
21 | r.script = utils.P2SHAddressToScript(r.Address)
22 | case "p2pkh":
23 | r.script = utils.P2PKHAddressToScript(r.Address)
24 | case "p2wsh":
25 | r.script = utils.P2WSHAddressToScript(r.Address)
26 | case "pk", "publickey":
27 | r.script = utils.PublicKeyToScript(r.Address)
28 | case "script":
29 | r.script = utils.ScriptPubKeyToScript(r.Address)
30 | case "":
31 | log.Error(r.Address, " has no type!")
32 | default:
33 | log.Error(r.Address, " uses an unsupported type: ", r.Type)
34 |
35 | }
36 | }
37 |
38 | return r.script
39 | }
40 |
--------------------------------------------------------------------------------
/config/redis.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "crypto/tls"
5 | "strconv"
6 |
7 | "github.com/go-redis/redis/v8"
8 | )
9 |
10 | type RedisOptions struct {
11 | // The network type, either tcp or unix.
12 | // Default is tcp.
13 | Network string `json:"network"`
14 |
15 | Host string `json:"host"`
16 | Port int `json:"port"`
17 |
18 | Password string `json:"password"`
19 | DB int `json:"db"`
20 |
21 | TLS *TLSClientOptions `json:"tls"`
22 | }
23 |
24 | func (ro *RedisOptions) Addr() string {
25 | return ro.Host + ":" + strconv.Itoa(ro.Port)
26 | }
27 |
28 | func (ro *RedisOptions) ToRedisOptions() *redis.Options {
29 | var tlsConfig *tls.Config
30 |
31 | if ro.TLS != nil {
32 | tlsConfig = ro.TLS.ToTLSConfig()
33 | }
34 |
35 | return &redis.Options{
36 | Network: ro.Network,
37 | Addr: ro.Addr(),
38 | Password: ro.Password,
39 | DB: ro.DB,
40 | TLSConfig: tlsConfig,
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/config/stratum.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | type PortOptions struct {
4 | Diff float64 `json:"diff"`
5 | VarDiff *VarDiffOptions `json:"varDiff"`
6 | TLS *TLSServerOptions `json:"tls"`
7 | }
8 |
--------------------------------------------------------------------------------
/config/tls.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "crypto/tls"
5 | )
6 |
7 | type TLSClientOptions struct {
8 | CertFile string `json:"certFile"`
9 | KeyFile string `json:"keyFile"`
10 | }
11 |
12 | func (to *TLSClientOptions) ToTLSConfig() *tls.Config {
13 | certs := make([]tls.Certificate, 0)
14 | if len(to.CertFile) > 0 && len(to.KeyFile) > 0 {
15 | cert, err := tls.LoadX509KeyPair(to.CertFile, to.KeyFile)
16 | if err != nil {
17 | log.Panic(err)
18 | }
19 | certs = append(certs, cert)
20 | }
21 |
22 | return &tls.Config{
23 | Certificates: certs,
24 | InsecureSkipVerify: true,
25 | }
26 | }
27 |
28 | type TLSServerOptions struct {
29 | CertFile string `json:"certFile"`
30 | KeyFile string `json:"keyFile"`
31 | }
32 |
33 | func (to *TLSServerOptions) ToTLSConfig() *tls.Config {
34 | certs := make([]tls.Certificate, 0)
35 | if len(to.CertFile) > 0 && len(to.KeyFile) > 0 {
36 | cert, err := tls.LoadX509KeyPair(to.CertFile, to.KeyFile)
37 | if err != nil {
38 | log.Fatal(err)
39 | }
40 | certs = append(certs, cert)
41 | }
42 |
43 | return &tls.Config{
44 | Certificates: certs,
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/config/vardiff.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | type VarDiffOptions struct {
4 | MinDiff float64 `json:"minDiff"`
5 | MaxDiff float64 `json:"maxDiff"`
6 | TargetTime int64 `json:"targetTime"`
7 | RetargetTime int64 `json:"retargetTime"`
8 | VariancePercent float64 `json:"variancePercent"`
9 | X2Mode bool `json:"x2mode"`
10 | }
11 |
--------------------------------------------------------------------------------
/daemons/getbalance.go:
--------------------------------------------------------------------------------
1 | package daemons
2 |
3 | type GetBalance float64
4 |
--------------------------------------------------------------------------------
/daemons/getblock.go:
--------------------------------------------------------------------------------
1 | package daemons
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | )
7 |
8 | type GetBlock struct {
9 | Hash string `json:"hash"`
10 | Confirmations int `json:"confirmations"`
11 | Strippedsize int `json:"strippedsize"`
12 | Size int `json:"size"`
13 | Weight int `json:"weight"`
14 | Height int `json:"height"`
15 | Version int `json:"version"`
16 | VersionHex string `json:"versionHex"`
17 | Merkleroot string `json:"merkleroot"`
18 | Tx []string `json:"tx"`
19 | Time int `json:"time"`
20 | Mediantime int `json:"mediantime"`
21 | Nonce int `json:"nonce"`
22 | Bits string `json:"bits"`
23 | Difficulty float64 `json:"difficulty"`
24 | Chainwork string `json:"chainwork"`
25 | NTx int `json:"nTx"`
26 | Previousblockhash string `json:"previousblockhash"`
27 | }
28 |
29 | func BytesToGetBlock(b []byte) *GetBlock {
30 | var getBlock GetBlock
31 | err := json.Unmarshal(b, &getBlock)
32 | if err != nil {
33 | log.Fatal(fmt.Sprint("getblock call failed with error ", err))
34 | }
35 |
36 | return &getBlock
37 | }
38 |
--------------------------------------------------------------------------------
/daemons/getblockchaininfo.go:
--------------------------------------------------------------------------------
1 | package daemons
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | )
7 |
8 | type GetBlockchainInfo struct {
9 | Chain string `json:"chain"`
10 | Blocks int `json:"blocks"`
11 | Headers int `json:"headers"`
12 | Bestblockhash string `json:"bestblockhash"`
13 | Difficulty float64 `json:"difficulty"`
14 | Mediantime int `json:"mediantime"`
15 | Verificationprogress float64 `json:"verificationprogress"`
16 | Chainwork string `json:"chainwork"`
17 | Pruned bool `json:"pruned"`
18 | Softforks interface{} // having difference between LTC & BTC, so use interface to suppress the error
19 | Bip9Softforks struct {
20 | Csv struct {
21 | Status string `json:"status"`
22 | StartTime int `json:"startTime"`
23 | Timeout int `json:"timeout"`
24 | Since int `json:"since"`
25 | } `json:"csv"`
26 | Dip0001 struct {
27 | Status string `json:"status"`
28 | StartTime int `json:"startTime"`
29 | Timeout int `json:"timeout"`
30 | Since int `json:"since"`
31 | } `json:"dip0001"`
32 | Dip0003 struct {
33 | Status string `json:"status"`
34 | StartTime int `json:"startTime"`
35 | Timeout int `json:"timeout"`
36 | Since int `json:"since"`
37 | } `json:"dip0003"`
38 | Dip0008 struct {
39 | Status string `json:"status"`
40 | StartTime int `json:"startTime"`
41 | Timeout int `json:"timeout"`
42 | Since int `json:"since"`
43 | } `json:"dip0008"`
44 | Bip147 struct {
45 | Status string `json:"status"`
46 | StartTime int `json:"startTime"`
47 | Timeout int `json:"timeout"`
48 | Since int `json:"since"`
49 | } `json:"bip147"`
50 | } `json:"bip9_softforks"`
51 | }
52 |
53 | func BytesToGetBlockchainInfo(b []byte) *GetBlockchainInfo {
54 | var getBlockchainInfo GetBlockchainInfo
55 | err := json.Unmarshal(b, &getBlockchainInfo)
56 | if err != nil {
57 | log.Panic(fmt.Sprint("getblockchaininfo call failed with error: ", err))
58 | }
59 |
60 | return &getBlockchainInfo
61 | }
62 |
--------------------------------------------------------------------------------
/daemons/getblocktemplate.go:
--------------------------------------------------------------------------------
1 | package daemons
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | )
8 |
9 | type MasternodeParams struct {
10 | Payee string `json:"payee"`
11 | Script string `json:"script"`
12 | Amount uint64 `json:"amount"`
13 | }
14 |
15 | type SuperblockParams struct {
16 | Payee string `json:"payee"`
17 | Script string `json:"script"`
18 | Amount uint64 `json:"amount"`
19 | }
20 |
21 | type TxParams struct {
22 | Data string `json:"data"`
23 | Hash string `json:"hash"`
24 | Depends []interface{} `json:"depends"`
25 | Fee uint64 `json:"fee"`
26 | Sigops int `json:"sigops"`
27 | TxId string `json:"txid"`
28 | }
29 |
30 | type GetBlockTemplate struct {
31 | // Base fields from BIP 0022. CoinbaseAux is optional. One of
32 | // CoinbaseTxn or CoinbaseValue must be specified, but not both.
33 | Version int32 `json:"version"`
34 | Bits string `json:"bits"`
35 | CurTime uint32 `json:"curtime"`
36 | Height int64 `json:"height"`
37 | // Rules []string `json:"rules"`
38 | PreviousBlockHash string `json:"previousblockhash"`
39 | // PreviousBits string `json:"previousbits"`
40 | // SigOpLimit int64 `json:"sigoplimit,omitempty"`
41 | // SizeLimit int64 `json:"sizelimit,omitempty"`
42 | // WeightLimit int64 `json:"weightlimit,omitempty"`
43 | // WorkID string `json:"workid,omitempty"`
44 | Transactions []*TxParams `json:"transactions"`
45 | // CoinbaseTxn *TxParams `json:"coinbasetxn,omitempty"` // Bitcoin does not produce the coinbasetxn for you, you will have to build it manually.
46 | CoinbaseAux struct {
47 | Flags string `json:"flags"`
48 | } `json:"coinbaseaux"`
49 | CoinbaseValue uint64 `json:"coinbasevalue"`
50 |
51 | // Block proposal from BIP 0023.
52 | // Capabilities []string `json:"capabilities,omitempty"`
53 | // RejectReason string `json:"reject-reason,omitempty"`
54 |
55 | //Vbavailable struct {
56 | //} `json:"vbavailable"`
57 | //Vbrequired int `json:"vbrequired"`
58 |
59 | // Witness commitment defined in BIP 0141.
60 | DefaultWitnessCommitment string `json:"default_witness_commitment,omitempty"`
61 |
62 | // Optional long polling from BIP 0022.
63 | // LongPollID string `json:"longpollid,omitempty"`
64 | // LongPollURI string `json:"longpolluri,omitempty"`
65 | // SubmitOld *bool `json:"submitold,omitempty"`
66 |
67 | // Basic pool extension from BIP 0023.
68 | Target string `json:"target,omitempty"`
69 | // Expires int64 `json:"expires,omitempty"`
70 |
71 | // Mutations from BIP 0023
72 | // MaxTime int64 `json:"maxtime,omitempty"`
73 | // MinTime int64 `json:"mintime,omitempty"`
74 | // Mutable []string `json:"mutable,omitempty"`
75 | // NonceRange string `json:"noncerange,omitempty"`
76 |
77 | // Dash
78 | Masternode []MasternodeParams `json:"masternode"`
79 | // MasternodePaymentsStarted bool `json:"masternode_payments_started"`
80 | // MasternodePaymentsEnforced bool `json:"masternode_payments_enforced"`
81 |
82 | Superblock []SuperblockParams `json:"superblock"`
83 | // SuperblocksStarted bool `json:"superblocks_started"`
84 | // SuperblocksEnabled bool `json:"superblocks_enabled"`
85 | CoinbasePayload string `json:"coinbase_payload"`
86 |
87 | // unknown
88 | Votes []string
89 | MasternodePayments interface{}
90 | Payee interface{}
91 | PayeeAmount interface{}
92 | }
93 |
94 | // then JobManager.ProcessTemplate(rpcData)
95 | func (dm *DaemonManager) GetBlockTemplate() (getBlockTemplate *GetBlockTemplate, err error) {
96 | instance, result, _ := dm.Cmd("getblocktemplate",
97 | []interface{}{map[string]interface{}{"capabilities": []string{"coinbasetxn", "workid", "coinbase/append"}, "rules": []string{"segwit"}}},
98 | )
99 |
100 | if result.Error != nil {
101 | return nil, errors.New(fmt.Sprint("getblocktemplate call failed for daemon instance ", instance, " with error ", result.Error))
102 | }
103 |
104 | getBlockTemplate = BytesToGetBlockTemplate(result.Result)
105 | if getBlockTemplate == nil {
106 | return nil, errors.New(fmt.Sprint("getblocktemplate call failed for daemon instance ", instance, " with error ", getBlockTemplate))
107 | }
108 |
109 | return getBlockTemplate, nil
110 | }
111 |
112 | func BytesToGetBlockTemplate(b []byte) *GetBlockTemplate {
113 | var getBlockTemplate GetBlockTemplate
114 | err := json.Unmarshal(b, &getBlockTemplate)
115 | if err != nil {
116 | log.Fatal(fmt.Sprint("getblocktemplate call failed with error ", err))
117 | }
118 |
119 | return &getBlockTemplate
120 | }
121 |
--------------------------------------------------------------------------------
/daemons/getdifficulty.go:
--------------------------------------------------------------------------------
1 | package daemons
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | )
7 |
8 | // type GetDifficulty interface {}
9 |
10 | func BytesToGetDifficulty(b []byte) interface{} {
11 | var getDifficulty interface{}
12 | err := json.Unmarshal(b, &getDifficulty)
13 | if err != nil {
14 | log.Fatal(fmt.Sprint("getDifficulty call failed with error ", err))
15 | }
16 |
17 | return getDifficulty
18 | }
19 |
--------------------------------------------------------------------------------
/daemons/getinfo.go:
--------------------------------------------------------------------------------
1 | package daemons
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | )
7 |
8 | type GetInfo struct {
9 | Version int `json:"version"`
10 | Protocolversion int `json:"protocolversion"`
11 | Walletversion int `json:"walletversion"`
12 | Balance float64 `json:"balance"`
13 | PrivatesendBalance float64 `json:"privatesend_balance"`
14 | Blocks int `json:"blocks"`
15 | Timeoffset int `json:"timeoffset"`
16 | Connections int `json:"connections"`
17 | Proxy string `json:"proxy"`
18 | Difficulty float64 `json:"difficulty"`
19 | Testnet bool `json:"testnet"`
20 | Keypoololdest int `json:"keypoololdest"`
21 | Keypoolsize int `json:"keypoolsize"`
22 | Paytxfee float64 `json:"paytxfee"`
23 | Relayfee float64 `json:"relayfee"`
24 | Errors string `json:"errors"`
25 | }
26 |
27 | func BytesToGetInfo(b []byte) *GetInfo {
28 | var getInfo GetInfo
29 | err := json.Unmarshal(b, &getInfo)
30 | if err != nil {
31 | log.Fatal(fmt.Sprint("getDifficulty call failed with error ", err))
32 | }
33 |
34 | return &getInfo
35 | }
36 |
--------------------------------------------------------------------------------
/daemons/getmininginfo.go:
--------------------------------------------------------------------------------
1 | package daemons
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | )
7 |
8 | type GetMiningInfo struct {
9 | Blocks int `json:"blocks"`
10 | Currentblocksize int `json:"currentblocksize"`
11 | Currentblocktx int `json:"currentblocktx"`
12 | Difficulty float64 `json:"difficulty"`
13 | Errors string `json:"errors"`
14 | Networkhashps float64 `json:"networkhashps"`
15 | Pooledtx int `json:"pooledtx"`
16 | Chain string `json:"chain"`
17 | }
18 |
19 | func BytesToGetMiningInfo(b []byte) *GetMiningInfo {
20 | var getMiningInfo GetMiningInfo
21 | err := json.Unmarshal(b, &getMiningInfo)
22 | if err != nil {
23 | log.Fatal(fmt.Sprint("getDifficulty call failed with error ", err))
24 | }
25 |
26 | return &getMiningInfo
27 | }
28 |
--------------------------------------------------------------------------------
/daemons/getnetworkinfo.go:
--------------------------------------------------------------------------------
1 | package daemons
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | )
7 |
8 | type GetNetworkInfo struct {
9 | Version int `json:"version"`
10 | Subversion string `json:"subversion"`
11 | Protocolversion int `json:"protocolversion"`
12 | Localservices string `json:"localservices"`
13 | Localrelay bool `json:"localrelay"`
14 | Timeoffset int `json:"timeoffset"`
15 | Networkactive bool `json:"networkactive"`
16 | Connections int `json:"connections"`
17 | Networks []struct {
18 | Name string `json:"name"`
19 | Limited bool `json:"limited"`
20 | Reachable bool `json:"reachable"`
21 | Proxy string `json:"proxy"`
22 | ProxyRandomizeCredentials bool `json:"proxy_randomize_credentials"`
23 | } `json:"networks"`
24 | Relayfee float64 `json:"relayfee"`
25 | Incrementalfee float64 `json:"incrementalfee"`
26 | Localaddresses []interface{} `json:"localaddresses"`
27 | Warnings string `json:"warnings"`
28 | }
29 |
30 | func BytesToGetNetworkInfo(b []byte) *GetNetworkInfo {
31 | var getNetworkInfo GetNetworkInfo
32 | err := json.Unmarshal(b, &getNetworkInfo)
33 | if err != nil {
34 | log.Fatal(fmt.Sprint("getDifficulty call failed with error ", err))
35 | }
36 |
37 | return &getNetworkInfo
38 | }
39 |
--------------------------------------------------------------------------------
/daemons/getwalletinfo.go:
--------------------------------------------------------------------------------
1 | package daemons
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | )
7 |
8 | type GetWalletInfo struct {
9 | Walletversion int `json:"walletversion"`
10 | Balance float64 `json:"balance"`
11 | PrivatesendBalance float64 `json:"privatesend_balance"`
12 | UnconfirmedBalance float64 `json:"unconfirmed_balance"`
13 | ImmatureBalance float64 `json:"immature_balance"`
14 | Txcount int `json:"txcount"`
15 | Keypoololdest int `json:"keypoololdest"`
16 | Keypoolsize int `json:"keypoolsize"`
17 | KeysLeft int `json:"keys_left"`
18 | Paytxfee float64 `json:"paytxfee"`
19 | }
20 |
21 | func BytesToGetWalletInfo(b []byte) *GetWalletInfo {
22 | var getWalletInfo GetWalletInfo
23 | err := json.Unmarshal(b, &getWalletInfo)
24 | if err != nil {
25 | log.Fatal(fmt.Sprint("getDifficulty call failed with error ", err))
26 | }
27 |
28 | return &getWalletInfo
29 | }
30 |
--------------------------------------------------------------------------------
/daemons/jsonrpc.go:
--------------------------------------------------------------------------------
1 | package daemons
2 |
3 | import "encoding/json"
4 |
5 | type JsonRpc interface {
6 | GetJsonRpcId() int64
7 | Json() []byte
8 | }
9 |
10 | type JsonRpcResponse struct {
11 | Id interface{} `json:"id"` // be int64 or null
12 | Result json.RawMessage `json:"result,omitempty"`
13 | Error *JsonRpcError `json:"error,omitempty"`
14 | }
15 |
16 | func (j *JsonRpcResponse) GetJsonRpcId() int64 {
17 | if j.Id == nil {
18 | return 0
19 | }
20 |
21 | return j.Id.(int64)
22 | }
23 |
24 | func (j *JsonRpcResponse) Json() []byte {
25 | raw, _ := json.Marshal(j)
26 | return raw
27 | }
28 |
29 | type JsonRpcRequest struct {
30 | Id interface{} `json:"id"`
31 | Method string `json:"method"`
32 | Params []json.RawMessage `json:"params"`
33 | }
34 |
35 | func (j *JsonRpcRequest) GetJsonRpcId() int64 {
36 | if j.Id == nil {
37 | return 0
38 | }
39 |
40 | return j.Id.(int64)
41 | }
42 |
43 | func (j *JsonRpcRequest) Json() []byte {
44 | raw, _ := json.Marshal(j)
45 | return raw
46 | }
47 |
48 | type JsonRpcError struct {
49 | Code int `json:"code"`
50 | Message string `json:"message"`
51 | }
52 |
53 | //type Method string
54 | //
55 | //const (
56 | // MethodSubmitBlock Method = "getsubmitblock"
57 | // MethodGetBlockTemplate Method = "getblocktemplate"
58 | // MethodGetBlock Method = "getblock"
59 | // MethodGetBalance Method = "getbalance"
60 | // MethodValidateAddress Method = "validateaddress"
61 | // )
62 |
--------------------------------------------------------------------------------
/daemons/manager.go:
--------------------------------------------------------------------------------
1 | package daemons
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "errors"
7 | "io/ioutil"
8 | "net/http"
9 | "strconv"
10 | "sync"
11 |
12 | logging "github.com/ipfs/go-log/v2"
13 | "github.com/mining-pool/not-only-mining-pool/config"
14 | "github.com/mining-pool/not-only-mining-pool/utils"
15 | )
16 |
17 | var log = logging.Logger("daemons")
18 |
19 | type DaemonManager struct {
20 | Daemons []*config.DaemonOptions
21 | clients map[string]*http.Client
22 | Coin *config.CoinOptions
23 | }
24 |
25 | func NewDaemonManager(daemons []*config.DaemonOptions, coin *config.CoinOptions) *DaemonManager {
26 | if daemons == nil || coin == nil {
27 | log.Fatal("new daemon with empty options!")
28 | }
29 |
30 | clients := make(map[string]*http.Client)
31 | for _, daemon := range daemons {
32 | transport := &http.Transport{}
33 | if daemon.TLS != nil {
34 | transport.TLSClientConfig = daemon.TLS.ToTLSConfig()
35 | }
36 |
37 | client := &http.Client{Transport: transport}
38 | clients[daemon.String()] = client
39 | }
40 |
41 | return &DaemonManager{
42 | Daemons: daemons,
43 | Coin: coin,
44 | clients: clients,
45 | }
46 | }
47 |
48 | func (dm *DaemonManager) Check() {
49 | if !dm.IsAllOnline() {
50 | log.Fatal("daemons are not all online!")
51 | }
52 | }
53 |
54 | func (dm *DaemonManager) IsAllOnline() bool {
55 | responses, _ := dm.CmdAll("getpeerinfo", []interface{}{})
56 | for _, res := range responses {
57 | if res.StatusCode/100 != 2 {
58 | return false
59 | }
60 |
61 | var jsonRes JsonRpcResponse
62 | err := json.NewDecoder(res.Body).Decode(&jsonRes)
63 | if err != nil {
64 | log.Error(err)
65 | return false
66 | }
67 |
68 | if jsonRes.Error != nil {
69 | log.Error(jsonRes.Error)
70 | return false
71 | }
72 |
73 | }
74 |
75 | return true
76 | }
77 |
78 | func (dm *DaemonManager) DoHttpRequest(daemon *config.DaemonOptions, reqRawData []byte) (*http.Response, error) {
79 | client := dm.clients[daemon.String()]
80 |
81 | req, err := http.NewRequest("POST", daemon.URL(), bytes.NewReader(reqRawData))
82 | if err != nil {
83 | log.Panic(err)
84 | }
85 | if daemon.User != "" {
86 | req.SetBasicAuth(daemon.User, daemon.Password)
87 | }
88 |
89 | return client.Do(req)
90 | }
91 |
92 | func (dm *DaemonManager) BatchCmd(commands []interface{}) (*config.DaemonOptions, []*JsonRpcResponse, error) {
93 | requestJson := make([]map[string]interface{}, len(commands))
94 | for i := range commands {
95 | requestJson[i] = map[string]interface{}{
96 | "id": utils.RandPositiveInt64(),
97 | "method": commands[i].([]interface{})[0],
98 | "params": commands[i].([]interface{})[1],
99 | }
100 | }
101 |
102 | for i := range dm.Daemons {
103 | raw, _ := json.Marshal(requestJson)
104 | res, err := dm.DoHttpRequest(dm.Daemons[i], raw)
105 | if err != nil {
106 | return dm.Daemons[i], nil, err
107 | }
108 | var rpcResponses []*JsonRpcResponse
109 | err = json.NewDecoder(res.Body).Decode(&rpcResponses)
110 | if err != nil {
111 | return dm.Daemons[i], nil, err
112 | }
113 |
114 | return dm.Daemons[i], rpcResponses, err
115 | }
116 |
117 | return nil, nil, nil
118 | }
119 |
120 | // CmdAll sends the rpc call to all daemon, and never break because of any error.
121 | // So the elem in responses may be nil
122 | func (dm *DaemonManager) CmdAll(method string, params []interface{}) (responses []*http.Response, results []*JsonRpcResponse) {
123 | responses = make([]*http.Response, len(dm.Daemons))
124 | results = make([]*JsonRpcResponse, len(dm.Daemons))
125 |
126 | msg := map[string]interface{}{
127 | "id": utils.RandPositiveInt64(),
128 | "method": method,
129 | "params": params,
130 | }
131 |
132 | reqRawData, err := json.Marshal(msg)
133 | if err != nil {
134 | log.Errorf("failed marshaling %v: %s", msg, err)
135 | return // all elem are nil
136 | }
137 |
138 | log.Debug(string(reqRawData))
139 |
140 | wg := sync.WaitGroup{}
141 | for i := range dm.Daemons {
142 | wg.Add(1)
143 | go func(i int) {
144 | res, err := dm.DoHttpRequest(dm.Daemons[i], reqRawData)
145 | if err != nil {
146 | log.Errorf("failed on daemon %s: %s", dm.Daemons[i].String(), err)
147 | return
148 | }
149 |
150 | //if err := dm.CheckStatusCode(res.StatusCode); err != nil {
151 | // log.Println(err)
152 | //}
153 |
154 | responses[i] = res
155 |
156 | var result JsonRpcResponse
157 | raw, err := ioutil.ReadAll(res.Body)
158 | if err != nil {
159 | log.Error(err)
160 | }
161 | res.Body = ioutil.NopCloser(bytes.NewBuffer(raw))
162 |
163 | err = json.Unmarshal(raw, &result)
164 | if err != nil {
165 | log.Panicf("failed to unmarshal response body: %s", raw)
166 | }
167 |
168 | results[i] = &result
169 |
170 | wg.Done()
171 | }(i)
172 | }
173 |
174 | wg.Wait()
175 |
176 | return responses, results
177 | }
178 |
179 | func (dm *DaemonManager) CheckStatusCode(statusCode int) error {
180 | switch statusCode / 100 {
181 | case 2:
182 | return nil
183 | case 4:
184 | switch statusCode % 100 {
185 | case 0:
186 | return errors.New("daemon cannot understand the request")
187 | case 1:
188 | return errors.New("daemon requires authorization (have to login before request)")
189 | case 3:
190 | return errors.New("daemon rejected the request")
191 |
192 | case 4:
193 | return errors.New("daemon cannot find the resource requested")
194 | case 13:
195 | return errors.New("daemon cannot deal this large request")
196 |
197 | }
198 |
199 | case 5:
200 | return errors.New("daemon internal error")
201 | }
202 |
203 | return errors.New("unknown status code:" + strconv.Itoa(statusCode))
204 | }
205 |
206 | // Cmd will call daemons one by one and return the first answer
207 | // one by one not all is to try fetching from the same one not random one
208 | func (dm *DaemonManager) Cmd(method string, params []interface{}) (*config.DaemonOptions, *JsonRpcResponse, *http.Response) {
209 | reqRawData, err := json.Marshal(map[string]interface{}{
210 | "id": utils.RandPositiveInt64(),
211 | "method": method,
212 | "params": params,
213 | })
214 | if err != nil {
215 | log.Error(err)
216 | }
217 |
218 | var result JsonRpcResponse
219 | var res *http.Response
220 | for i := range dm.Daemons {
221 | var err error
222 | res, err = dm.DoHttpRequest(dm.Daemons[i], reqRawData)
223 | if err != nil {
224 | log.Error(err)
225 | }
226 |
227 | //if err := dm.CheckStatusCode(res.StatusCode); err != nil {
228 | // log.Println(err)
229 | //}
230 |
231 | raw, err := ioutil.ReadAll(res.Body)
232 | if err != nil {
233 | log.Error(err)
234 | }
235 | res.Body = ioutil.NopCloser(bytes.NewBuffer(raw))
236 |
237 | err = json.Unmarshal(raw, &result)
238 | if err != nil {
239 | log.Error(err)
240 | }
241 |
242 | return dm.Daemons[i], &result, res
243 | }
244 |
245 | log.Error("failed getting GBT from all daemons!")
246 | return nil, nil, nil
247 | }
248 |
--------------------------------------------------------------------------------
/daemons/submitblock.go:
--------------------------------------------------------------------------------
1 | package daemons
2 |
3 | import (
4 | "encoding/json"
5 |
6 | "github.com/mining-pool/not-only-mining-pool/utils"
7 | )
8 |
9 | // submitblock has no result
10 | func (dm *DaemonManager) SubmitBlock(blockHex string) {
11 | var results []*JsonRpcResponse
12 | if dm.Coin.NoSubmitBlock {
13 | _, results = dm.CmdAll("getblocktemplate", []interface{}{map[string]interface{}{"mode": "submit", "data": blockHex}})
14 | } else {
15 | _, results = dm.CmdAll("submitblock", []interface{}{blockHex})
16 | }
17 |
18 | for i := range results {
19 | if results[i] == nil {
20 | log.Errorf("failed submitting to daemon %s, see log above for details", dm.Daemons[i].String())
21 | continue
22 | }
23 |
24 | if results[i].Error != nil {
25 | log.Error("rpc error with daemon when submitting block: " + string(utils.Jsonify(results[i].Error)))
26 | } else {
27 | var result string
28 | err := json.Unmarshal(results[i].Result, &result)
29 | if err == nil && result == "rejected" {
30 | log.Error("Daemon instance rejected a supposedly valid block")
31 | }
32 |
33 | if err == nil && result == "invalid" {
34 | log.Error("Daemon instance rejected an invalid block")
35 | }
36 |
37 | if err == nil && result == "inconclusive" {
38 | log.Warn("Daemon instance warns an inconclusive block")
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/daemons/validateaddress.go:
--------------------------------------------------------------------------------
1 | package daemons
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | )
7 |
8 | type ValidateAddress struct {
9 | Isvalid bool `json:"isvalid"`
10 | Address string `json:"address"`
11 | ScriptPubKey string `json:"scriptPubKey"`
12 | Ismine bool `json:"ismine"`
13 | Iswatchonly bool `json:"iswatchonly"`
14 | Isscript bool `json:"isscript"`
15 | Iswitness bool `json:"iswitness"`
16 | Script string `json:"script"`
17 | Hex string `json:"hex"`
18 | Pubkey string `json:"pubkey"`
19 | Embedded struct {
20 | Isscript bool `json:"isscript"`
21 | Iswitness bool `json:"iswitness"`
22 | WitnessVersion int `json:"witness_version"`
23 | WitnessProgram string `json:"witness_program"`
24 | Pubkey string `json:"pubkey"`
25 | Address string `json:"address"`
26 | ScriptPubKey string `json:"scriptPubKey"`
27 | } `json:"embedded"`
28 | Addresses []string `json:"addresses"`
29 | Label string `json:"label"`
30 | Timestamp int `json:"timestamp"`
31 | Hdkeypath string `json:"hdkeypath"`
32 | Hdseedid string `json:"hdseedid"`
33 | Hdmasterkeyid string `json:"hdmasterkeyid"`
34 | Labels []struct {
35 | Name string `json:"name"`
36 | Purpose string `json:"purpose"`
37 | } `json:"labels"`
38 | }
39 |
40 | func BytesToValidateAddress(b []byte) *ValidateAddress {
41 | var validateAddress ValidateAddress
42 | err := json.Unmarshal(b, &validateAddress)
43 | if err != nil {
44 | log.Fatal(fmt.Sprint("validateAddress call failed with error ", err))
45 | }
46 |
47 | return &validateAddress
48 | }
49 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/mining-pool/not-only-mining-pool
2 |
3 | go 1.13
4 |
5 | require (
6 | github.com/c0mm4nd/go-bech32 v0.0.0-20201015031713-6bb434e0ac5d
7 | github.com/go-redis/redis/v8 v8.3.1
8 | github.com/gorilla/mux v1.8.0
9 | github.com/ipfs/go-log/v2 v2.1.1
10 | github.com/mr-tron/base58 v1.2.0
11 | github.com/samli88/go-x11-hash v0.0.0-20180604013825-c37c1282f506
12 | golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee
13 | )
14 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
3 | github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
4 | github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw=
5 | github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
6 | github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
7 | github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
8 | github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts=
9 | github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts=
10 | github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
11 | github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
12 | github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
13 | github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
14 | github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
15 | github.com/c0mm4nd/go-bech32 v0.0.0-20201015031713-6bb434e0ac5d h1:nQf3TD5A+VPDemt6zG0MQWMhlCx9aaNooC4+JONwlXs=
16 | github.com/c0mm4nd/go-bech32 v0.0.0-20201015031713-6bb434e0ac5d/go.mod h1:D08DIm3/NJ0pLz+OWLYfGtVqVLTns8Y5sA3vHhcMkww=
17 | github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
18 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
19 | github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495 h1:6IyqGr3fnd0tM3YxipK27TUskaOVUjU2nG45yzwcQKY=
20 | github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
21 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
22 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
23 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
24 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
25 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
26 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
27 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
28 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
29 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
30 | github.com/go-redis/redis/v8 v8.3.1 h1:jEPCgHQopfNaABun3NVN9pv2K7RjstY/7UJD6UEKFEY=
31 | github.com/go-redis/redis/v8 v8.3.1/go.mod h1:a2xkpBM7NJUN5V5kiF46X5Ltx4WeXJ9757X/ScKUBdE=
32 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
33 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
34 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
35 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
36 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
37 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
38 | github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
39 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
40 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
41 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
42 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
43 | github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
44 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
45 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
46 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
47 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
48 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
49 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
50 | github.com/ipfs/go-log/v2 v2.1.1 h1:G4TtqN+V9y9HY9TA6BwbCVyyBZ2B9MbCjR2MtGx8FR0=
51 | github.com/ipfs/go-log/v2 v2.1.1/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM=
52 | github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
53 | github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
54 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
55 | github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
56 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
57 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
58 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
59 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
60 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
61 | github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
62 | github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
63 | github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
64 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
65 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
66 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
67 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
68 | github.com/onsi/ginkgo v1.14.1 h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4=
69 | github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
70 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
71 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
72 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
73 | github.com/onsi/gomega v1.10.2 h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs=
74 | github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
75 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
76 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
77 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
78 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
79 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
80 | github.com/samli88/go-x11-hash v0.0.0-20180604013825-c37c1282f506 h1:BBdYETqJwXeC8LpzMyuDajo4qLU7l87dhB59IUqV0+c=
81 | github.com/samli88/go-x11-hash v0.0.0-20180604013825-c37c1282f506/go.mod h1:AN54awRQDkyEDfZ7XlAz1GaXRdenWUoJijL17HHIsh4=
82 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
83 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
84 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
85 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
86 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
87 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
88 | go.opentelemetry.io/otel v0.13.0 h1:2isEnyzjjJZq6r2EKMsFj4TxiQiexsM04AVhwbR/oBA=
89 | go.opentelemetry.io/otel v0.13.0/go.mod h1:dlSNewoRYikTkotEnxdmuBHgzT+k/idJSfDv/FxEnOY=
90 | go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
91 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
92 | go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
93 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
94 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
95 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
96 | go.uber.org/zap v1.14.1 h1:nYDKopTbvAPq/NrUVZwT15y2lpROBiLLyoRTbXOYWOo=
97 | go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
98 | golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
99 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
100 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
101 | golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
102 | golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee h1:4yd7jl+vXjalO5ztz6Vc1VADv+S/80LGJmyl1ROJ2AI=
103 | golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
104 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
105 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
106 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
107 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
108 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
109 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
110 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
111 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0=
112 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
113 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
114 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
115 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
116 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
117 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
118 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
119 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
120 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
121 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
122 | golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
123 | golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
124 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
125 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
126 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
127 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
128 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
129 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
130 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
131 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
132 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
133 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
134 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
135 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
136 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
137 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
138 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
139 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
140 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
141 | google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
142 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
143 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
144 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
145 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
146 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
147 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
148 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
149 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
150 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
151 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
152 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
153 | gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
154 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
155 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
156 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
157 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
158 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
159 | honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
160 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
161 |
--------------------------------------------------------------------------------
/jobs/extraNonce1Generator.go:
--------------------------------------------------------------------------------
1 | package jobs
2 |
3 | import "crypto/rand"
4 |
5 | type ExtraNonce1Generator struct {
6 | Size int
7 | }
8 |
9 | func NewExtraNonce1Generator() *ExtraNonce1Generator {
10 | return &ExtraNonce1Generator{
11 | Size: 4,
12 | }
13 | }
14 |
15 | func (eng *ExtraNonce1Generator) GetExtraNonce1() []byte {
16 | extraNonce := make([]byte, eng.Size)
17 | _, _ = rand.Read(extraNonce)
18 |
19 | return extraNonce
20 | }
21 |
--------------------------------------------------------------------------------
/jobs/job.go:
--------------------------------------------------------------------------------
1 | package jobs
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "encoding/hex"
7 | "math/big"
8 | "time"
9 |
10 | "github.com/mining-pool/not-only-mining-pool/algorithm"
11 | "github.com/mining-pool/not-only-mining-pool/config"
12 | "github.com/mining-pool/not-only-mining-pool/daemons"
13 | "github.com/mining-pool/not-only-mining-pool/merkletree"
14 | "github.com/mining-pool/not-only-mining-pool/transactions"
15 | "github.com/mining-pool/not-only-mining-pool/utils"
16 | )
17 |
18 | type Job struct {
19 | GetBlockTemplate *daemons.GetBlockTemplate
20 | Submits []string
21 | GenerationTransaction [][]byte
22 | JobId string
23 | PrevHashReversed string
24 | MerkleBranch []string
25 | Target *big.Int
26 | Difficulty *big.Float
27 | TransactionData []byte
28 | Reward string
29 | MerkleTree *merkletree.MerkleTree
30 | }
31 |
32 | func NewJob(jobId string, rpcData *daemons.GetBlockTemplate, poolAddressScript, extraNoncePlaceholder []byte, reward string, txMessages bool, recipients []*config.Recipient) *Job {
33 | var bigTarget *big.Int
34 |
35 | if rpcData.Target != "" {
36 | bigTarget, _ = new(big.Int).SetString(rpcData.Target, 16)
37 | } else {
38 | utils.BigIntFromBitsHex(rpcData.Bits)
39 | }
40 |
41 | bigDiff := new(big.Float).Quo(
42 | new(big.Float).SetInt(algorithm.MaxTargetTruncated),
43 | new(big.Float).SetInt(bigTarget),
44 | )
45 |
46 | bPreviousBlockHash, err := hex.DecodeString(rpcData.PreviousBlockHash)
47 | if err != nil {
48 | log.Error(err)
49 | }
50 | prevHashReversed := hex.EncodeToString(utils.ReverseByteOrder(bPreviousBlockHash))
51 |
52 | transactionData := make([][]byte, len(rpcData.Transactions))
53 | for i := 0; i < len(rpcData.Transactions); i++ {
54 | transactionData[i], err = hex.DecodeString(rpcData.Transactions[i].Data)
55 | if err != nil {
56 | log.Error(err)
57 | }
58 | }
59 |
60 | txsBytes := GetTransactionBytes(rpcData.Transactions)
61 | merkleTree := merkletree.NewMerkleTree(txsBytes)
62 | merkleBranch := merkletree.GetMerkleHashes(merkleTree.Steps)
63 | generationTransaction := transactions.CreateGeneration(
64 | rpcData,
65 | poolAddressScript,
66 | extraNoncePlaceholder,
67 | reward,
68 | txMessages,
69 | recipients,
70 | )
71 |
72 | txData := make([][]byte, len(rpcData.Transactions))
73 | for i := 0; i < len(rpcData.Transactions); i++ {
74 | data, err := hex.DecodeString(rpcData.Transactions[i].Data)
75 | if err != nil {
76 | log.Panic("failed to decode tx: ", rpcData.Transactions[i])
77 | }
78 |
79 | txData[i] = data
80 | }
81 |
82 | log.Info("New Job, diff: ", bigDiff)
83 |
84 | return &Job{
85 | GetBlockTemplate: rpcData,
86 | Submits: nil,
87 | GenerationTransaction: generationTransaction,
88 | JobId: jobId,
89 | PrevHashReversed: prevHashReversed,
90 | MerkleBranch: merkleBranch,
91 | Target: bigTarget,
92 | Difficulty: bigDiff,
93 | TransactionData: bytes.Join(txData, nil),
94 | Reward: "",
95 | MerkleTree: merkleTree,
96 | }
97 | }
98 |
99 | func (j *Job) SerializeCoinbase(extraNonce1, extraNonce2 []byte) []byte {
100 | if j.GenerationTransaction[0] == nil || j.GenerationTransaction[1] == nil {
101 | log.Warn("empty generation transaction", j.GenerationTransaction)
102 | }
103 |
104 | return bytes.Join([][]byte{
105 | j.GenerationTransaction[0],
106 | extraNonce1,
107 | extraNonce2,
108 | j.GenerationTransaction[1],
109 | }, nil)
110 | }
111 |
112 | func (j *Job) SerializeBlock(header, coinbase []byte) []byte {
113 | // POS coins require a zero byte appended to block which the daemon replaces with the signature
114 | var suffix []byte
115 | if j.Reward == "POS" {
116 | suffix = []byte{0}
117 | } else {
118 | suffix = []byte{}
119 | }
120 |
121 | if j.TransactionData == nil {
122 | log.Warn("transaction data is empty")
123 | }
124 |
125 | voteData := j.GetVoteData()
126 | if voteData == nil {
127 | log.Warn("no vote data")
128 | }
129 |
130 | return bytes.Join([][]byte{
131 | header,
132 |
133 | utils.VarIntBytes(uint64(len(j.GetBlockTemplate.Transactions) + 1)), // coinbase(generation) + txs
134 | coinbase,
135 | j.TransactionData,
136 |
137 | j.GetVoteData(),
138 |
139 | suffix,
140 | }, nil)
141 | }
142 |
143 | //https://en.bitcoin.it/wiki/Protocol_specification#Block_Headers
144 | func (j *Job) SerializeHeader(merkleRoot, nTime, nonce []byte) []byte {
145 | header := make([]byte, 80)
146 |
147 | bits, _ := hex.DecodeString(j.GetBlockTemplate.Bits)
148 | prevHash, _ := hex.DecodeString(j.GetBlockTemplate.PreviousBlockHash)
149 |
150 | pos := 0
151 | copy(header[pos:], nonce) // 4
152 | pos += len(nonce)
153 | copy(header[pos:], bits) // 4
154 | pos += len(bits)
155 | copy(header[pos:], nTime) // 4
156 | pos += len(nTime)
157 | copy(header[pos:], merkleRoot) // 32
158 | pos += len(merkleRoot)
159 | copy(header[pos:], prevHash) // 32
160 | pos += len(prevHash)
161 | binary.BigEndian.PutUint32(header[pos:], uint32(j.GetBlockTemplate.Version)) // 4
162 | pos += 4
163 |
164 | return utils.ReverseBytes(header)
165 | }
166 |
167 | // record the submit times and contents => check duplicate
168 | func (j *Job) RegisterSubmit(extraNonce1, extraNonce2, nTime, nonce string) bool {
169 | submission := extraNonce1 + extraNonce2 + nTime + nonce
170 |
171 | if utils.StringsIndexOf(j.Submits, submission) == -1 {
172 | j.Submits = append(j.Submits, submission)
173 | return true
174 | }
175 |
176 | return false
177 | }
178 |
179 | func (j *Job) GetJobParams(forceUpdate bool) []interface{} {
180 | return []interface{}{
181 | j.JobId,
182 | j.PrevHashReversed,
183 | hex.EncodeToString(j.GenerationTransaction[0]),
184 | hex.EncodeToString(j.GenerationTransaction[1]),
185 | j.MerkleBranch,
186 | hex.EncodeToString(utils.PackInt32BE(j.GetBlockTemplate.Version)),
187 | j.GetBlockTemplate.Bits,
188 | hex.EncodeToString(utils.PackUint32BE(uint32(time.Now().Unix()))), // Updated: implement time rolling
189 | forceUpdate,
190 | }
191 | }
192 |
193 | func GetTransactionBytes(txs []*daemons.TxParams) [][]byte {
194 | txHashes := make([][]byte, len(txs))
195 | for i := 0; i < len(txs); i++ {
196 | if txs[i].TxId != "" {
197 | txHashes[i] = utils.Uint256BytesFromHash(txs[i].TxId)
198 | continue
199 | }
200 |
201 | if txs[i].Hash != "" {
202 | txHashes[i] = utils.Uint256BytesFromHash(txs[i].Hash)
203 | continue
204 | }
205 |
206 | log.Panic("no hash or txid in transactions params")
207 | }
208 |
209 | return append([][]byte{nil}, txHashes...)
210 | }
211 |
212 | func (j *Job) GetVoteData() []byte {
213 | if j.GetBlockTemplate.MasternodePayments == nil {
214 | return nil
215 | }
216 |
217 | hexVotes := make([][]byte, len(j.GetBlockTemplate.Votes))
218 | for i := 0; i < len(j.GetBlockTemplate.Votes); i++ {
219 | hexVotes[i], _ = hex.DecodeString(j.GetBlockTemplate.Votes[i])
220 | }
221 |
222 | return bytes.Join([][]byte{
223 | utils.VarIntBytes(uint64(len(j.GetBlockTemplate.Votes))),
224 | }, nil)
225 | }
226 |
--------------------------------------------------------------------------------
/jobs/jobManager.go:
--------------------------------------------------------------------------------
1 | package jobs
2 |
3 | import (
4 | "encoding/hex"
5 | "math/big"
6 | "net"
7 | "strconv"
8 | "strings"
9 | "time"
10 |
11 | logging "github.com/ipfs/go-log/v2"
12 | "github.com/mining-pool/not-only-mining-pool/algorithm"
13 | "github.com/mining-pool/not-only-mining-pool/config"
14 | "github.com/mining-pool/not-only-mining-pool/daemons"
15 | "github.com/mining-pool/not-only-mining-pool/storage"
16 | "github.com/mining-pool/not-only-mining-pool/types"
17 | "github.com/mining-pool/not-only-mining-pool/utils"
18 | )
19 |
20 | var log = logging.Logger("jobMgr")
21 |
22 | type JobCounter struct {
23 | Counter *big.Int
24 | }
25 |
26 | type JobManager struct {
27 | PoolAddress *config.Recipient
28 |
29 | Storage *storage.DB
30 | Options *config.Options
31 | JobCounter *JobCounter
32 | ExtraNonce1Generator *ExtraNonce1Generator
33 | ExtraNoncePlaceholder []byte
34 | ExtraNonce2Size int
35 |
36 | CurrentJob *Job
37 | ValidJobs map[string]*Job
38 |
39 | CoinbaseHasher func([]byte) []byte
40 |
41 | DaemonManager *daemons.DaemonManager
42 |
43 | NewBlockEvent chan *Job
44 | }
45 |
46 | func NewJobManager(options *config.Options, dm *daemons.DaemonManager, storage *storage.DB) *JobManager {
47 | placeholder, _ := hex.DecodeString("f000000ff111111f")
48 | extraNonce1Generator := NewExtraNonce1Generator()
49 |
50 | return &JobManager{
51 | PoolAddress: options.PoolAddress,
52 |
53 | Options: options,
54 | ExtraNonce1Generator: extraNonce1Generator,
55 | ExtraNoncePlaceholder: placeholder,
56 | ExtraNonce2Size: len(placeholder) - extraNonce1Generator.Size,
57 | CurrentJob: nil,
58 | ValidJobs: make(map[string]*Job),
59 | CoinbaseHasher: utils.Sha256d,
60 | Storage: storage,
61 | DaemonManager: dm,
62 | }
63 | }
64 |
65 | func (jm *JobManager) Init(gbt *daemons.GetBlockTemplate) {
66 | jm.ProcessTemplate(gbt)
67 | }
68 |
69 | func (jm *JobManager) ProcessShare(share *types.Share) {
70 | // isValidBlock
71 | var isAccepted bool
72 | var tx string
73 | if share.BlockHex != "" {
74 | log.Info("submitting new Block: ", share.BlockHex)
75 | jm.DaemonManager.SubmitBlock(share.BlockHex)
76 |
77 | isAccepted, tx = jm.CheckBlockAccepted(share.BlockHash)
78 | share.TxHash = tx
79 | if isAccepted {
80 | log.Info("Block ", share.BlockHash, " Accepted! generation tx: ", share.TxHash, ". Wait for pendding!")
81 | }
82 |
83 | gbt, err := jm.DaemonManager.GetBlockTemplate()
84 | if err != nil {
85 | log.Panic("failed fetching GBT: ", err)
86 | }
87 | jm.ProcessTemplate(gbt)
88 | }
89 |
90 | // notValidBlock but isValidShare
91 | go jm.Storage.PutShare(share, isAccepted)
92 |
93 | }
94 |
95 | func (jm *JobManager) CheckBlockAccepted(blockHash string) (isAccepted bool, tx string) {
96 | _, results := jm.DaemonManager.CmdAll("getblock", []interface{}{blockHash})
97 |
98 | isAccepted = true
99 | for i := range results {
100 | isAccepted = isAccepted && results[i] != nil && results[i].Error == nil
101 | }
102 |
103 | for i := range results {
104 | if results[i] == nil {
105 | continue
106 | }
107 |
108 | gb := daemons.BytesToGetBlock(results[i].Result)
109 | if gb.Tx != nil {
110 | return isAccepted, gb.Tx[0]
111 | }
112 | }
113 |
114 | return isAccepted, ""
115 | }
116 |
117 | // UpdateCurrentJob updates the job when mining the same height but tx changes
118 | func (jm *JobManager) UpdateCurrentJob(rpcData *daemons.GetBlockTemplate) {
119 | tmpBlockTemplate := NewJob(
120 | jm.CurrentJob.JobId,
121 | rpcData,
122 | jm.PoolAddress.GetScript(),
123 | jm.ExtraNoncePlaceholder,
124 | jm.Options.Coin.Reward,
125 | jm.Options.Coin.TxMessages,
126 | jm.Options.RewardRecipients,
127 | )
128 |
129 | jm.CurrentJob = tmpBlockTemplate
130 | jm.ValidJobs[tmpBlockTemplate.JobId] = tmpBlockTemplate
131 |
132 | log.Debug("Job updated")
133 | }
134 |
135 | // CreateNewJob creates a new job when mining new height
136 | func (jm *JobManager) CreateNewJob(rpcData *daemons.GetBlockTemplate) {
137 | // creates a new job when mining new height
138 |
139 | tmpBlockTemplate := NewJob(
140 | utils.RandHexUint64(),
141 | rpcData,
142 | jm.PoolAddress.GetScript(),
143 | jm.ExtraNoncePlaceholder,
144 | jm.Options.Coin.Reward,
145 | jm.Options.Coin.TxMessages,
146 | jm.Options.RewardRecipients,
147 | )
148 |
149 | jm.CurrentJob = tmpBlockTemplate
150 | jm.ValidJobs[tmpBlockTemplate.JobId] = tmpBlockTemplate
151 |
152 | log.Info("New Job (Block) from block template")
153 | }
154 |
155 | // ProcessTemplate handles the template
156 | func (jm *JobManager) ProcessTemplate(rpcData *daemons.GetBlockTemplate) {
157 | if jm.CurrentJob != nil && rpcData.Height < jm.CurrentJob.GetBlockTemplate.Height {
158 | return
159 | }
160 |
161 | if jm.CurrentJob != nil && rpcData.Height == jm.CurrentJob.GetBlockTemplate.Height {
162 | jm.UpdateCurrentJob(rpcData)
163 | return
164 | }
165 |
166 | jm.CreateNewJob(rpcData)
167 | }
168 |
169 | func (jm *JobManager) ProcessSubmit(jobId string, prevDiff, diff *big.Float, extraNonce1 []byte, hexExtraNonce2, hexNTime, hexNonce string, ipAddr net.Addr, workerName string) (share *types.Share) {
170 | submitTime := time.Now()
171 |
172 | var miner, rig string
173 | names := strings.Split(workerName, ".")
174 | if len(names) < 2 {
175 | miner = names[0]
176 | rig = "unknown"
177 | } else {
178 | miner = names[0]
179 | rig = names[1]
180 | }
181 |
182 | job, exists := jm.ValidJobs[jobId]
183 | if !exists || job == nil || job.JobId != jobId {
184 | return &types.Share{
185 | JobId: jobId,
186 | RemoteAddr: ipAddr,
187 | Miner: miner,
188 | Rig: rig,
189 |
190 | ErrorCode: types.ErrJobNotFound,
191 | }
192 | }
193 |
194 | extraNonce2, err := hex.DecodeString(hexExtraNonce2)
195 | if err != nil {
196 | log.Error(err)
197 | }
198 |
199 | if len(extraNonce2) != jm.ExtraNonce2Size {
200 | return &types.Share{
201 | JobId: jobId,
202 | RemoteAddr: ipAddr,
203 | Miner: miner,
204 | Rig: rig,
205 |
206 | ErrorCode: types.ErrIncorrectExtraNonce2Size,
207 | }
208 | }
209 |
210 | if len(hexNTime) != 8 {
211 | return &types.Share{
212 | JobId: jobId,
213 | RemoteAddr: ipAddr,
214 | Miner: miner,
215 | Rig: rig,
216 |
217 | ErrorCode: types.ErrIncorrectNTimeSize,
218 | }
219 | }
220 |
221 | // allowed nTime range [GBT's CurTime, submitTime+7s]
222 | nTimeInt, err := strconv.ParseInt(hexNTime, 16, 64)
223 | if err != nil {
224 | log.Error(err)
225 | }
226 | if uint32(nTimeInt) < job.GetBlockTemplate.CurTime || nTimeInt > submitTime.Unix()+7 {
227 | log.Error("nTime incorrect: expect from ", job.GetBlockTemplate.CurTime, " to ", submitTime.Unix()+7, ", got ", uint32(nTimeInt))
228 | return &types.Share{
229 | JobId: jobId,
230 | RemoteAddr: ipAddr,
231 | Miner: miner,
232 | Rig: rig,
233 |
234 | ErrorCode: types.ErrNTimeOutOfRange,
235 | }
236 | }
237 |
238 | if len(hexNonce) != 8 {
239 | return &types.Share{
240 | JobId: jobId,
241 | RemoteAddr: ipAddr,
242 | Miner: miner,
243 | Rig: rig,
244 |
245 | ErrorCode: types.ErrIncorrectNonceSize,
246 | }
247 | }
248 |
249 | if !job.RegisterSubmit(hex.EncodeToString(extraNonce1), hexExtraNonce2, hexNTime, hexNonce) {
250 | return &types.Share{
251 | JobId: jobId,
252 | RemoteAddr: ipAddr,
253 | Miner: miner,
254 | Rig: rig,
255 |
256 | ErrorCode: types.ErrDuplicateShare,
257 | }
258 | }
259 |
260 | coinbaseBytes := job.SerializeCoinbase(extraNonce1, extraNonce2)
261 | coinbaseHash := jm.CoinbaseHasher(coinbaseBytes)
262 | merkleRoot := utils.ReverseBytes(job.MerkleTree.WithFirst(coinbaseHash))
263 |
264 | nonce, err := hex.DecodeString(hexNonce) // in big-endian
265 | if err != nil {
266 | log.Error(err)
267 | }
268 |
269 | nTimeBytes, err := hex.DecodeString(hexNTime) // in big-endian
270 | if err != nil {
271 | log.Error(err)
272 | }
273 |
274 | headerBytes := job.SerializeHeader(merkleRoot, nTimeBytes, nonce) // in LE
275 | headerHash := algorithm.GetHashFunc(jm.Options.Algorithm.Name)(headerBytes)
276 | headerHashBigInt := new(big.Int).SetBytes(utils.ReverseBytes(headerHash))
277 |
278 | bigShareDiff := new(big.Float).Quo(
279 | new(big.Float).SetInt(new(big.Int).Mul(algorithm.MaxTargetTruncated, big.NewInt(1< 0 {
286 | blockHex := hex.EncodeToString(job.SerializeBlock(headerBytes, coinbaseBytes))
287 | var blockHash string
288 | if jm.Options.Algorithm.SHA256dBlockHasher {
289 | // LTC
290 | blockHash = hex.EncodeToString(utils.ReverseBytes(utils.Sha256d(headerBytes)))
291 | } else {
292 | // DASH
293 | blockHash = hex.EncodeToString(utils.ReverseBytes(algorithm.GetHashFunc(jm.Options.Algorithm.Name)(headerBytes)))
294 | }
295 |
296 | log.Warn("Found Block: ", blockHash)
297 | return &types.Share{
298 | JobId: jobId,
299 | RemoteAddr: ipAddr,
300 | Miner: miner,
301 | Rig: rig,
302 |
303 | BlockHeight: job.GetBlockTemplate.Height,
304 | BlockReward: job.GetBlockTemplate.CoinbaseValue,
305 | Diff: shareDiff,
306 | BlockHash: blockHash,
307 | BlockHex: blockHex,
308 | }
309 | }
310 |
311 | // Check if share didn't reached the miner's difficulty)
312 | if new(big.Float).Quo(bigShareDiff, diff).Cmp(big.NewFloat(0.99)) < 0 {
313 | // Check if share matched a previous difficulty from before a vardiff retarget
314 | if prevDiff != nil && bigShareDiff.Cmp(prevDiff) >= 0 {
315 | return &types.Share{
316 | JobId: jobId,
317 | RemoteAddr: ipAddr,
318 | Miner: miner,
319 | Rig: rig,
320 |
321 | BlockHeight: job.GetBlockTemplate.Height,
322 | BlockReward: job.GetBlockTemplate.CoinbaseValue,
323 | Diff: shareDiff,
324 | }
325 | } else {
326 | return &types.Share{
327 | JobId: jobId,
328 | RemoteAddr: ipAddr,
329 | Miner: workerName,
330 |
331 | ErrorCode: types.ErrLowDiffShare,
332 | }
333 | }
334 | }
335 |
336 | // share reaches the miner's difficulty but doesnt not reach the block's
337 | return &types.Share{
338 | JobId: jobId,
339 | RemoteAddr: ipAddr,
340 | Miner: miner,
341 | Rig: rig,
342 |
343 | Diff: shareDiff,
344 | }
345 | }
346 |
347 | //func GetPoolAddressScript(reward string, validateAddress *daemons.ValidateAddress) []byte {
348 | // switch reward {
349 | // case "POS":
350 | // return utils.PublicKeyToScript(validateAddress.Pubkey)
351 | // case "POW":
352 | // if validateAddress.Isscript {
353 | // return utils.P2SHAddressToScript(validateAddress.Address)
354 | // }
355 | // return utils.P2PKHAddressToScript(validateAddress.Address)
356 | // default:
357 | // // as POW
358 | // log.Fatal("unknown reward type: " + reward)
359 | // return nil
360 | // }
361 | //}
362 |
--------------------------------------------------------------------------------
/jobs/job_test.go:
--------------------------------------------------------------------------------
1 | package jobs
2 |
3 | import (
4 | "encoding/hex"
5 | "encoding/json"
6 | "fmt"
7 | "math/big"
8 | "testing"
9 |
10 | "github.com/mining-pool/not-only-mining-pool/algorithm"
11 | "github.com/mining-pool/not-only-mining-pool/daemons"
12 | "github.com/mining-pool/not-only-mining-pool/merkletree"
13 | "github.com/mining-pool/not-only-mining-pool/utils"
14 | )
15 |
16 | func TestNewBlockTemplate(t *testing.T) {
17 | // 1e06109b bits
18 | // 000006109b000000000000000000000000000000000000000000000000000000 target
19 | // 0.0006395185894153062 diff
20 |
21 | target, _ := hex.DecodeString("000006109b000000000000000000000000000000000000000000000000000000")
22 | bigTarget := new(big.Float).SetInt(new(big.Int).SetBytes(target))
23 | diff := big.NewFloat(0.0006395185894153062)
24 | fmt.Println(0.0006395185894153062)
25 | maxTarget := new(big.Float).SetInt(algorithm.MaxTarget)
26 | fmt.Println(algorithm.MaxTarget)
27 | fmt.Println(maxTarget)
28 | fmt.Println(new(big.Float).SetInt(new(big.Int).SetBytes(target)))
29 | fmt.Println(new(big.Float).Mul(bigTarget, diff))
30 | fmt.Println(utils.BigIntFromBitsHex("1e06109b"))
31 |
32 | fmt.Println(algorithm.MaxTarget)
33 | }
34 |
35 | func TestGetTransactionBytes(t *testing.T) {
36 | //txs := []*daemons.TxParams{
37 | // &daemons.TxParams{
38 | // Data: "",
39 | // Hash: "",
40 | // Depends: nil,
41 | // Fee: 0,
42 | // Sigops: 0,
43 | // TxId: "",
44 | // },
45 | //}
46 |
47 | rawTxs := `
48 | [
49 | {
50 | "data": "01000000012f8975c900f56662f35c317a0669fecc5fe0e1fb8ee53f4de72f1cb68c07e606010000008a473044022061a9ac17f269f3c69e18b5d67dfa6bf8b6a5a60eb7f9b0c992ffaeb66b5b88fb02202bb6fd7eb539302d97f4b8604bc822c91747e1cb365fecc91b37526b6b8c2c25014104fe67366f857106ee7b4cc48abb4dabd46302e12fe4140f4c933b92bd3ce75b1f4ae45055312f9a6c5ddc1f8d94d4f6d11e2a13372bcd6bfd651e48997b0f767effffffff02e8030000000000001976a914dffec839eba107e556d6c4f25f90765b3d10583288acbb60da04000000001976a914bdd83cf3ab8b7a57ff9b841752c1ae764f2a02ee88ac00000000",
51 | "txid": "f9b8b0bdd0dc38b2a707faf89acf064f543c3a88d39f54fb126cbd084ffb5ed9",
52 | "hash": "f9b8b0bdd0dc38b2a707faf89acf064f543c3a88d39f54fb126cbd084ffb5ed9",
53 | "depends": [
54 | ],
55 | "fee": 6450,
56 | "sigops": 8,
57 | "weight": 1028
58 | },
59 | {
60 | "data": "0200000001979a795a82096fc375487778939d9193bb284c58525e5df9c3a404c81c9220ef01000000d9004730440220086f0b09ded442c84e602520f5a8b38b41a1bc860fb595bd47834c20fa8db39402200a40cb86c15198302cabfd5c620c24fa6ac9ac5d946394e37dd3f9960b65a0e701473044022049bc0be153a4535196f73455bf82667956f2089019db4eeb57cb35649d8f69b202206ddd411917cb3e54f7b9a89c0693c971a6499eb6e421dce1d5fa9358300525d301475221025ad7eedea4c87b98463b8c7316c139f94c0e75fe4c849f42dab112479e1a1bb7210257591ace4d6a9fc94b8114cffd84df9bd0349c974a792580f7f5afb74f5ba94952ae0000000002102700000000000017a914b75a640760f2caae367c0e0cd6bfb85e8d80755987e17608000000000017a914ef20c4471b54fc47c93d587a318d351e93fbc13b8700000000",
61 | "txid": "620c724890f76b802714d786d5d3fe13a89106d81e93b74c4eafd6dc04179f37",
62 | "hash": "620c724890f76b802714d786d5d3fe13a89106d81e93b74c4eafd6dc04179f37",
63 | "depends": [
64 | ],
65 | "fee": 3493,
66 | "sigops": 8,
67 | "weight": 1328
68 | }
69 | ]
70 | `
71 | var txs []*daemons.TxParams
72 | err := json.Unmarshal([]byte(rawTxs), &txs)
73 | if err != nil {
74 | t.Fatal(err)
75 | }
76 |
77 | b := GetTransactionBytes(txs)
78 | for i := 0; i < len(b); i++ {
79 | t.Log(hex.EncodeToString(b[i]))
80 | }
81 |
82 | merkleTree := merkletree.NewMerkleTree(GetTransactionBytes(txs))
83 | t.Log(merkleTree.Steps)
84 | merkleBranch := merkletree.GetMerkleHashes(merkleTree.Steps)
85 | t.Log(merkleBranch)
86 | }
87 |
88 | func TestJob_SerializeHeader(t *testing.T) {
89 | rawTxs := `
90 | [
91 | {
92 | "data": "01000000000101899522d55c2fdf575cfb549f15f6c7c34e43e98b17300ae34edc63f8272311410100000000ffffffff020000000000000000136a0c0701007ac01b000140000000530345d471abcc1e0b000000001600148d5ce2c127c03ad8d1f9fc9bbe3fad1023bb2aa202473044022029ba3fbd618616a48ff211450008d0ced9654ffdd3f46640e51535322cef122c022017db76be63a080580144cebdefceee4032d4c481f2961a8dc67f23f2d0ccfc70012103df64a645452a26c559adeff4825e6ab5a4935a0627b4b13b5f5d716e818906a700000000",
93 | "hash": "d12deb5951b796a3c478ae446598f5591b3ee571188980eba4c8bb3276260c0d",
94 | "depends": [],
95 | "fee": 3450,
96 | "sigops": 1,
97 | "txid": "78c1505e1312bdc723e4a86aad3e2e798cd6761d0a589b87de248b33f2c5773f"
98 | },
99 | {
100 | "data": "0100000000010112d8e244e6daa7b4d04d1724318b335d63291e60a80bda9760f76b79462c37470000000000ffffffff020000000000000000146a0d07080003a67427265646564640530345d4716db4eb0b000000001600142aeb9224a6491f75aa3a8fdaa60b17cae735b8ce0247304402207ed0d9b0e044f5cc3d68129931c8cb8e8ec1f941d4f820d1595b75b384e753bf022024fade5ed065e8e6916c2fe9e8a5e585c6ce9d9b7a831406250f9ac11a7a1b660121028d20c4336c742ef18bd958b2db1289e93f7f158d1d0e462b958fe2f3e2018ac100000000",
101 | "hash": "ef7f84c048d8cb4f55bc824264199a0e752aba47d54c897e46f2a240ffa58386",
102 | "depends": [],
103 | "fee": 3475,
104 | "sigops": 1,
105 | "txid": "03bef03f26f28fe5bd2b5ef89d3dc1fe498da2b215b1570e924810079dc7fd42"
106 | },
107 | {
108 | "data": "010000000001015af7409fdc3f99f8b5ec2638a16f4f249d0dc2dd13b42fe5ced9b4af8bb6620a0100000000ffffffff020000000000000000136a0c0701000c4001000080000000530345d47139b3810b00000000160014de9d9a2c541fce2c54d7bf7e88bb038071a329ad0247304402201a668bf396d01b29055fa7e1d996643a6df5da74a9b0000674f44bfea8c7bdbd022006615dfd0ac4dcd3036948e672107f8241f257e4ba4b6ca2f7bf5e2227eea442012102d788d048abf4325a4ac018f5b369aa8ea9b252cb9239dd6abc5270673041a7b100000000",
109 | "hash": "ec2857e3e6874ceed835e7b6949d98e383a929c9bff5951ab3c8a9d0067a0a4c",
110 | "depends": [],
111 | "fee": 3450,
112 | "sigops": 1,
113 | "txid": "18133eb939f15814826de97ace1cff26de7d0378666989c59c00beb0c9cb4151"
114 | },
115 | {
116 | "data": "0100000000010134d0cbd4f023d9aaa5aab78b81fd1ed17ea2ac512e9b462936a4eaaa3ae5f0de0100000000ffffffff030000000000000000226a1b0701027ac001000083001000003000b0003000f0000c001c000000530345d471a0252600000000001976a914b66c71c784d8804ec8591011f3d78f3e1e23c2f288ac61f49e0b00000000160014bb471c71724b39beadf55fc1b334b75f45aa8b4c0247304402200dd0adaa9949c4e0fd269d1472b556145eec542f914ede27fffe87586c2eb9c90220542797fac36d7e1b041837d85e688396f0e7b9ae2283fe1c60f9486e0a8e693801210317993020134466e9db343f04f56f3c53c65e7984565ddaf1e0ebe4cde521861000000000",
117 | "hash": "a0127b166709395c3c0fdf919c4563d9f2d8fdb7a81367c63932cc8088b4760a",
118 | "depends": [],
119 | "fee": 4675,
120 | "sigops": 5,
121 | "txid": "3b94cc39ddb80b052f7135b09c791c34fdcc726ade24ed9d0d7369167ef1a66d"
122 | },
123 | {
124 | "data": "010000000001013f77c5f2338b24de879b580a1d76d68c792e3ead6aa8e423c7bd12135e50c1780100000000ffffffff020000000000000000136a0c0701007ac0010000c0000000530345d47131bf1e0b000000001600148d5ce2c127c03ad8d1f9fc9bbe3fad1023bb2aa2024730440220359e9d5d96dadf5bddf276ab5f0d5d5ceed433b57039dc5d1f572568b465a7ec022011c1379815a50a58e73cfd69ec590540f31342b1cbac1f533e0900bd670df1d7012103df64a645452a26c559adeff4825e6ab5a4935a0627b4b13b5f5d716e818906a700000000",
125 | "hash": "864359eeabe597f57dbfb65975fde5b6ecbd191baddc2f29d5d738728583c840",
126 | "depends": [
127 | 1
128 | ],
129 | "fee": 3450,
130 | "sigops": 1,
131 | "txid": "d4bda2d38acd3815a45636f4d06db1acc0ec9b713226b131989f057b9bd8a988"
132 | },
133 | {
134 | "data": "010000000001015141cbc9b0be009cc589696678037dde26ff1cce7ae96d821458f139b93e13180100000000ffffffff020000000000000000136a0c0701000c4001000040000000530345d471bfa5810b00000000160014de9d9a2c541fce2c54d7bf7e88bb038071a329ad0247304402201569f50e32a337e06214398756d4517612e757e82f185d8c28f891b07a9fa3480220770e8e2ee2c25022eb6289e9504fca132040e207369a45c30c5e2a3fa4d7e137012102d788d048abf4325a4ac018f5b369aa8ea9b252cb9239dd6abc5270673041a7b100000000",
135 | "hash": "799c4a377661d3d2cdd2f5ab225413fa54e0cc58a3028fe7cab7bc8ce4626abd",
136 | "depends": [
137 | 3
138 | ],
139 | "fee": 3450,
140 | "sigops": 1,
141 | "txid": "2c5f761385fe6c79901b39a8ef09af38c544214805ac3f6eebb07dcffc122abb"
142 | },
143 | {
144 | "data": "0100000000010188a9d89b7b059f9831b12632719becc0acb16dd0f43656a41538cd8ad3a2bdd40100000000ffffffff020000000000000000136a0c0701007ac001800080000000530345d471b7b11e0b000000001600148d5ce2c127c03ad8d1f9fc9bbe3fad1023bb2aa2024730440220322d0a9466f5b1af4ecf9c549748b0377825433589c55f16fcde8c34d825e804022009f60c98c18425495d7acd081dc2b81649d5ca4821f17d52ffe16e0f5db76b26012103df64a645452a26c559adeff4825e6ab5a4935a0627b4b13b5f5d716e818906a700000000",
145 | "hash": "b53eb9b0af014e1903d4689d1a7d6cc6d07eeb3ef027956dfb862fbd9ba4020f",
146 | "depends": [
147 | 5
148 | ],
149 | "fee": 3450,
150 | "sigops": 1,
151 | "txid": "85fbc84d63b76346fbac28c005cba5bbe5153e6851d0432963d3651f4559e5dd"
152 | }
153 | ]
154 | `
155 | var txs []*daemons.TxParams
156 | err := json.Unmarshal([]byte(rawTxs), &txs)
157 | if err != nil {
158 | t.Fatal(err)
159 | }
160 |
161 | txsBytes := GetTransactionBytes(txs)
162 | //for i:=0; i 1 {
31 | steps = append(steps, L[1])
32 |
33 | if Ll%2 != 0 {
34 | L = append(L, L[len(L)-1])
35 | }
36 |
37 | r := utils.Range(StartL, Ll, 2)
38 | Ld := make([][]byte, len(r))
39 |
40 | for i := 0; i < len(r); i++ {
41 | Ld[i] = MerkleJoin(L[r[i]], L[r[i]+1])
42 | }
43 | L = append(PreL, Ld...)
44 | Ll = len(L)
45 | }
46 |
47 | return steps
48 | }
49 |
50 | func MerkleJoin(h1, h2 []byte) []byte {
51 | return utils.Sha256d(bytes.Join([][]byte{h1, h2}, nil))
52 | }
53 |
54 | func (mt *MerkleTree) WithFirst(f []byte) []byte {
55 | for i := 0; i < len(mt.Steps); i++ {
56 | f = utils.Sha256d(bytes.Join([][]byte{f, mt.Steps[i]}, nil))
57 | }
58 | return f
59 | }
60 |
61 | func GetMerkleHashes(steps [][]byte) []string {
62 | hashes := make([]string, len(steps))
63 | for i := 0; i < len(steps); i++ {
64 | // hash := make([]byte, 32)
65 | // copy(hash, steps[i])
66 | hashes[i] = hex.EncodeToString(steps[i])
67 | }
68 |
69 | return hashes
70 | }
71 |
--------------------------------------------------------------------------------
/merkletree/mtree_test.go:
--------------------------------------------------------------------------------
1 | package merkletree
2 |
3 | import (
4 | "encoding/hex"
5 | "testing"
6 | )
7 |
8 | func TestNewMerkleTree(t *testing.T) {
9 | mt1 := NewMerkleTree([][]byte{[]byte("hello"), []byte("world")})
10 |
11 | if hex.EncodeToString(mt1.WithFirst([]byte("first"))) != "11f206ce3848f46083c5f30d01b95a8dd75194ef5781b24202d34720b2b4c12f" {
12 | t.Fail()
13 | }
14 |
15 | if GetMerkleHashes(mt1.Steps)[0] != "776f726c64" {
16 | t.Log(GetMerkleHashes(mt1.Steps)[0])
17 | t.Fail()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/p2p/peer.go:
--------------------------------------------------------------------------------
1 | package p2p
2 |
3 | import (
4 | "bytes"
5 | "crypto/rand"
6 | "encoding/binary"
7 | "encoding/hex"
8 | "io"
9 | "net"
10 | "time"
11 |
12 | logging "github.com/ipfs/go-log/v2"
13 | "github.com/mining-pool/not-only-mining-pool/config"
14 | "github.com/mining-pool/not-only-mining-pool/utils"
15 | )
16 |
17 | var log = logging.Logger("p2p")
18 |
19 | type Peer struct {
20 | Magic []byte
21 |
22 | VerAck bool
23 | ValidConnectionConfig bool
24 |
25 | NetworkServices []byte
26 | EmptyNetAddress []byte
27 | UserAgent []byte
28 | BlockStartHeight []byte
29 | RelayTransactions []byte
30 |
31 | InvCodes map[string]uint32
32 | Commands map[string][]byte
33 | Options *config.P2POptions
34 | Conn net.Conn
35 | ProtocolVersion int
36 |
37 | BlockNotifyCh chan string
38 | }
39 |
40 | func NewPeer(protocolVersion int, options *config.P2POptions) *Peer {
41 | magic, err := hex.DecodeString(options.Magic)
42 | if err != nil {
43 | log.Fatal("magic hex string is incorrect")
44 | }
45 |
46 | networkServices, _ := hex.DecodeString("0100000000000000") // NODE_NETWORK services (value 1 packed as uint64)
47 | emptyNetAddress, _ := hex.DecodeString("010000000000000000000000000000000000ffff000000000000")
48 | userAgent := utils.VarStringBytes("/node-stratum/")
49 | blockStartHeight, _ := hex.DecodeString("00000000") // block start_height, can be empty
50 |
51 | //If protocol version is new enough, add do not relay transactions flag byte, outlined in BIP37
52 | //https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki#extensions-to-existing-messages
53 | var relayTransactions []byte
54 | if options.DisableTransactions {
55 | relayTransactions = []byte{0}
56 | } else {
57 | relayTransactions = []byte{}
58 | }
59 |
60 | return &Peer{
61 | Magic: magic,
62 | VerAck: false,
63 | ValidConnectionConfig: true,
64 |
65 | Options: options,
66 | ProtocolVersion: protocolVersion,
67 |
68 | NetworkServices: networkServices,
69 | EmptyNetAddress: emptyNetAddress,
70 | UserAgent: userAgent,
71 | BlockStartHeight: blockStartHeight,
72 | RelayTransactions: relayTransactions,
73 |
74 | InvCodes: map[string]uint32{
75 | "error": 0,
76 | "tx": 1,
77 | "block": 2,
78 | },
79 |
80 | Commands: map[string][]byte{
81 | "version": utils.CommandStringBytes("version"),
82 | "inv": utils.CommandStringBytes("inv"),
83 | "verack": utils.CommandStringBytes("verack"),
84 | "addr": utils.CommandStringBytes("addr"),
85 | "getblocks": utils.CommandStringBytes("getblocks"),
86 | },
87 |
88 | BlockNotifyCh: make(chan string),
89 | }
90 | }
91 |
92 | func (p *Peer) Init() {
93 | p.Connect()
94 | }
95 |
96 | func (p *Peer) Connect() {
97 | var err error
98 | p.Conn, err = net.Dial("tcp", p.Options.Addr())
99 | if err != nil {
100 | log.Fatal("failed to connect to coin's p2p port: ", err)
101 | }
102 |
103 | p.SetupMessageParser()
104 | }
105 |
106 | func (p *Peer) SetupMessageParser() {
107 | go func() {
108 | p.SendVersion()
109 |
110 | header := make([]byte, 24)
111 | for {
112 | n, err := p.Conn.Read(header)
113 | if err == io.EOF {
114 | continue
115 | }
116 |
117 | if err != nil || n < 24 {
118 | log.Error(err)
119 | continue
120 | }
121 |
122 | if !bytes.Equal(header[0:4], p.Magic) {
123 | continue
124 | }
125 |
126 | payload := make([]byte, binary.LittleEndian.Uint32(header[16:20]))
127 | _, err = p.Conn.Read(payload)
128 | if err != nil {
129 | log.Error(err)
130 | continue
131 | }
132 |
133 | if bytes.Equal(utils.Sha256d(payload)[0:4], header[20:24]) {
134 | go p.HandleMessage(header[4:16], payload)
135 | }
136 | }
137 | }()
138 | }
139 |
140 | func (p *Peer) HandleMessage(command, payload []byte) {
141 | log.Info("handling: ", command, payload)
142 | switch string(command) {
143 | case string(p.Commands["inv"]):
144 | p.HandleInv(payload)
145 | case string(p.Commands["verack"]):
146 | if !p.VerAck {
147 | p.VerAck = true
148 | // connected
149 | }
150 | case string(p.Commands["version"]):
151 | p.SendMessage(p.Commands["verack"], make([]byte, 0))
152 | default:
153 | break
154 | }
155 | }
156 |
157 | // Parsing inv message https://en.bitcoin.it/wiki/Protocol_specification#inv
158 | func (p *Peer) HandleInv(payload []byte) {
159 | // sloppy varint decoding
160 | var count int
161 | var buf []byte
162 | if payload[0] < 0xFD {
163 | count = int(payload[0])
164 | buf = payload[1:]
165 | } else {
166 | count = int(binary.LittleEndian.Uint16(payload[0:2]))
167 | buf = payload[2:]
168 | }
169 |
170 | for count--; count != 0; count-- {
171 | switch binary.LittleEndian.Uint32(buf) {
172 | case p.InvCodes["error"]:
173 | case p.InvCodes["tx"]:
174 | // tx := hex.EncodeToString(buf[4:36])
175 | case p.InvCodes["block"]:
176 | block := hex.EncodeToString(buf[4:36])
177 | log.Warn("block found: ", block)
178 | // block found
179 | p.ProcessBlockNotify(block)
180 | }
181 | buf = buf[36:]
182 | }
183 | }
184 |
185 | func (p *Peer) SendMessage(command, payload []byte) {
186 | log.Info("sending: ", command, payload)
187 | if p.Conn == nil {
188 | p.Connect()
189 | }
190 |
191 | message := bytes.Join([][]byte{
192 | p.Magic,
193 | command,
194 | utils.PackUint32LE(uint32(len(payload))),
195 | utils.Sha256d(payload)[0:4],
196 | payload,
197 | }, nil)
198 |
199 | _, err := p.Conn.Write(message)
200 | if err != nil {
201 | log.Error(err)
202 | }
203 |
204 | log.Info(string(message))
205 | }
206 |
207 | func (p *Peer) SendVersion() {
208 | nonce := make([]byte, 8)
209 | rand.Read(nonce)
210 | payload := bytes.Join([][]byte{
211 | utils.PackUint32LE(uint32(p.ProtocolVersion)),
212 | p.NetworkServices,
213 | utils.PackUint64LE(uint64(time.Now().Unix())),
214 | p.EmptyNetAddress, // addr_recv, can be empty
215 |
216 | p.EmptyNetAddress, // addr_from, can be empty
217 | nonce, // nonce, random unique ID
218 | p.UserAgent,
219 | p.BlockStartHeight,
220 |
221 | p.RelayTransactions,
222 | }, nil)
223 |
224 | p.SendMessage(p.Commands["version"], payload)
225 | }
226 |
227 | func (p *Peer) ProcessBlockNotify(blockHash string) {
228 | log.Info("Block notification via p2p")
229 | // if p.JobManager.CurrentJob != nil && blockHash != p.JobManager.CurrentJob.GetBlockTemplate.PreviousBlockHash {
230 | p.BlockNotifyCh <- blockHash
231 | //}
232 | }
233 |
--------------------------------------------------------------------------------
/p2p/peer_test.go:
--------------------------------------------------------------------------------
1 | package p2p
2 |
3 | import (
4 | "encoding/json"
5 | "testing"
6 | "time"
7 |
8 | "github.com/mining-pool/not-only-mining-pool/config"
9 | )
10 |
11 | func TestNewPeer(t *testing.T) {
12 | var options config.P2POptions
13 | json.Unmarshal([]byte(`
14 | {
15 | "host": "0.0.0.0",
16 | "port": 19335,
17 | "magic": "fdd2c8f1",
18 | "disableTransactions": true
19 | }
20 | `), &options)
21 | peer := NewPeer(70015, &options)
22 | peer.Init()
23 |
24 | c := time.After(time.Minute)
25 | for {
26 | select {
27 | case <-c:
28 | return
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/payments/contrib.go:
--------------------------------------------------------------------------------
1 | package payments
2 |
--------------------------------------------------------------------------------
/payments/payer.go:
--------------------------------------------------------------------------------
1 | package payments
2 |
3 | import (
4 | logging "github.com/ipfs/go-log/v2"
5 | "github.com/mining-pool/not-only-mining-pool/daemons"
6 | )
7 |
8 | var log = logging.Logger("payments")
9 |
10 | type PayMode int
11 |
12 | const (
13 | PayOnManual PayMode = iota
14 | PayPPLNS // calc rewards on block found
15 | PayPPS // calc rewards every day
16 | )
17 |
18 | type PaymentManager struct {
19 | pay chan struct{}
20 | dm *daemons.DaemonManager
21 | }
22 |
23 | func NewPaymentManager(mode PayMode, dm *daemons.DaemonManager) *PaymentManager {
24 | return &PaymentManager{
25 | pay: make(chan struct{}),
26 | dm: dm,
27 | }
28 | }
29 |
30 | func (pm *PaymentManager) Serve() {
31 | func() {
32 | for {
33 | <-pm.pay
34 | pm.doPay()
35 | }
36 | }()
37 | }
38 |
39 | func (pm *PaymentManager) doPay() {
40 | // PPLNS solution:
41 | //
42 | // for range // not wg => keep balance safe
43 | // validateaddress
44 | // getbalance
45 | // ['hgetall', coin + ':balances'],
46 | // ['smembers', coin + ':blocksPending']
47 | //
48 | // {
49 | // blockHash: details[0],
50 | // txHash: details[1],
51 | // height: details[2],
52 | // };
53 | //
54 | // gettransaction generatiion tx & getaccount pooladdress -> chech tx detail -> kick/orphan/confirm
55 | //
56 | // ['hgetall', coin + ':shares:round' + r.height]
57 | // var reward = parseInt(round.reward * magnitude);
58 | //
59 | // sendmany [addressAccount, addressAmounts]
60 | //
61 | // ['hincrbyfloat', coin + ':balances', w, satoshisToCoins(worker.balanceChange)]
62 | // ['hincrbyfloat', coin + ':payouts', w, worker.sent]
63 | //
64 | // smove => move block from pending to kick/orphan/confirm
65 | }
66 |
--------------------------------------------------------------------------------
/payments/recipient.go:
--------------------------------------------------------------------------------
1 | package payments
2 |
--------------------------------------------------------------------------------
/pool/pool.go:
--------------------------------------------------------------------------------
1 | package pool
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | "fmt"
7 | "reflect"
8 | "strconv"
9 | "strings"
10 | "time"
11 |
12 | logging "github.com/ipfs/go-log/v2"
13 |
14 | "github.com/mining-pool/not-only-mining-pool/api"
15 | "github.com/mining-pool/not-only-mining-pool/bans"
16 | "github.com/mining-pool/not-only-mining-pool/config"
17 | "github.com/mining-pool/not-only-mining-pool/daemons"
18 | "github.com/mining-pool/not-only-mining-pool/jobs"
19 | "github.com/mining-pool/not-only-mining-pool/p2p"
20 | "github.com/mining-pool/not-only-mining-pool/storage"
21 | "github.com/mining-pool/not-only-mining-pool/stratum"
22 | "github.com/mining-pool/not-only-mining-pool/utils"
23 | )
24 |
25 | var log = logging.Logger("pool")
26 |
27 | type Pool struct {
28 | DaemonManager *daemons.DaemonManager
29 | JobManager *jobs.JobManager
30 | P2PManager *p2p.Peer
31 |
32 | StratumServer *stratum.Server
33 |
34 | Options *config.Options
35 | Magnitude uint64
36 | CoinPrecision int
37 | HasGetInfo bool
38 | Stats *Stats
39 | BlockPollingIntervalTicker *time.Ticker
40 | Recipients []*config.Recipient
41 | ProtocolVersion int
42 | APIServer *api.Server
43 | }
44 |
45 | func NewPool(options *config.Options) *Pool {
46 | dm := daemons.NewDaemonManager(options.Daemons, options.Coin)
47 | dm.Check()
48 |
49 | if options.PoolAddress.GetScript() == nil {
50 | log.Panicf("failed to get poolAddress' script, check the address and type")
51 | }
52 |
53 | for _, addr := range options.RewardRecipients {
54 | if addr.GetScript() == nil {
55 | log.Panicf("failed to get addr %s' script, check the address and type", addr.Address)
56 | }
57 | }
58 |
59 | var magnitude int64 = 100000000 //sat
60 | if !options.DisablePayment {
61 | _, getBalance, _ := dm.Cmd("getbalance", []interface{}{})
62 |
63 | if getBalance.Error != nil {
64 | log.Fatal(errors.New(fmt.Sprint(getBalance.Error)))
65 | }
66 |
67 | split0 := bytes.Split(utils.Jsonify(getBalance), []byte(`result":`))
68 | split2 := bytes.Split(split0[1], []byte(","))
69 | split3 := bytes.Split(split2[0], []byte("."))
70 | d := split3[1]
71 |
72 | var err error
73 | magnitude, err = strconv.ParseInt("10"+strconv.Itoa(len(d))+"0", 10, 64)
74 | if err != nil {
75 | log.Fatal("ErrorCode detecting number of satoshis in a coin, cannot do payments processing. Tried parsing: ", string(utils.Jsonify(getBalance)))
76 | }
77 | }
78 |
79 | db := storage.NewStorage(options.Coin.Name, options.Storage)
80 |
81 | jm := jobs.NewJobManager(options, dm, db)
82 | bm := bans.NewBanningManager(options.Banning)
83 | s := api.NewAPIServer(options, db)
84 |
85 | return &Pool{
86 | Options: options,
87 | DaemonManager: dm,
88 | JobManager: jm,
89 | APIServer: s,
90 |
91 | StratumServer: stratum.NewStratumServer(options, jm, bm),
92 | Magnitude: uint64(magnitude),
93 | CoinPrecision: len(strconv.FormatUint(uint64(magnitude), 10)) - 1,
94 | Stats: NewStats(),
95 | }
96 | }
97 |
98 | //
99 | func (p *Pool) Init() {
100 | p.CheckAllReady()
101 | p.DetectCoinData()
102 |
103 | initGBT, err := p.DaemonManager.GetBlockTemplate()
104 | if err != nil {
105 | log.Fatal(err)
106 | }
107 |
108 | p.SetupP2PBlockNotify()
109 | p.SetupBlockPolling()
110 |
111 | p.JobManager.Init(initGBT)
112 |
113 | p.StartStratumServer()
114 | p.APIServer.Serve()
115 |
116 | p.OutputPoolInfo()
117 | }
118 |
119 | func (p *Pool) SetupP2PBlockNotify() {
120 | if p.Options.P2P == nil {
121 | return
122 | }
123 |
124 | p.P2PManager = p2p.NewPeer(p.ProtocolVersion, p.Options.P2P)
125 | p.Init()
126 |
127 | go func() {
128 | for {
129 | blockHash, ok := <-p.P2PManager.BlockNotifyCh
130 | if !ok {
131 | log.Warn("Block notify is stopped!")
132 | return
133 | }
134 |
135 | if p.JobManager.CurrentJob != nil && blockHash != p.JobManager.CurrentJob.GetBlockTemplate.PreviousBlockHash {
136 | gbt, err := p.DaemonManager.GetBlockTemplate()
137 | if err != nil {
138 | log.Error("p2p block notify failed getting block template: ", err)
139 | }
140 | p.JobManager.ProcessTemplate(gbt)
141 | }
142 | }
143 | }()
144 | }
145 |
146 | func (p *Pool) AttachMiners(miners []*stratum.Client) {
147 | for i := range miners {
148 | p.StratumServer.ManuallyAddStratumClient(miners[i])
149 | }
150 |
151 | p.StratumServer.BroadcastCurrentMiningJob(p.JobManager.CurrentJob.GetJobParams(true))
152 | }
153 |
154 | func (p *Pool) StartStratumServer() {
155 | portStarted := p.StratumServer.Init()
156 | p.Stats.StratumPorts = portStarted
157 | }
158 |
159 | // enrich the config options from rpc
160 | func (p *Pool) DetectCoinData() {
161 | var diff float64
162 |
163 | // getdifficulty
164 | _, rpcResponse, _ := p.DaemonManager.Cmd("getdifficulty", []interface{}{})
165 | if rpcResponse.Error != nil || rpcResponse == nil {
166 | log.Error("Could not start pool, error with init batch RPC call: " + string(utils.Jsonify(rpcResponse)))
167 | return
168 | }
169 | getDifficulty := daemons.BytesToGetDifficulty(rpcResponse.Result)
170 | switch reflect.ValueOf(getDifficulty).Kind() {
171 | case reflect.Float64:
172 | diff = getDifficulty.(float64)
173 | p.Options.Coin.Reward = "POW"
174 | case reflect.Array:
175 | diff = getDifficulty.(map[string]interface{})["proof-of-work"].(float64)
176 | if p.Options.Coin.Reward == "" {
177 | if bytes.Contains(rpcResponse.Result, []byte("proof-of-stake")) {
178 | p.Options.Coin.Reward = "POS"
179 | } else {
180 | p.Options.Coin.Reward = "POW"
181 | }
182 | }
183 | default:
184 | log.Error(reflect.ValueOf(getDifficulty).Kind())
185 | }
186 |
187 | // getmininginfo
188 | _, rpcResponse, _ = p.DaemonManager.Cmd("getmininginfo", []interface{}{})
189 | if rpcResponse.Error != nil || rpcResponse == nil {
190 | log.Error("Could not start pool, error with init batch RPC call: " + string(utils.Jsonify(rpcResponse)))
191 | return
192 | }
193 | getMiningInfo := daemons.BytesToGetMiningInfo(rpcResponse.Result)
194 | p.Stats.NetworkHashrate = getMiningInfo.Networkhashps
195 |
196 | _, rpcResponse, _ = p.DaemonManager.Cmd("submitblock", []interface{}{})
197 | if rpcResponse == nil || rpcResponse.Error == nil {
198 | log.Error("Could not start pool, error with init batch RPC call: " + utils.JsonifyIndentString(rpcResponse))
199 | return
200 | }
201 |
202 | if rpcResponse.Error.Message == "Method not found" {
203 | p.Options.Coin.NoSubmitBlock = true
204 | } else if rpcResponse.Error.Code == -1 {
205 | p.Options.Coin.NoSubmitBlock = false
206 | } else {
207 | log.Fatal("Could not detect block submission RPC method, " + utils.JsonifyIndentString(rpcResponse))
208 | }
209 |
210 | _, rpcResponse, _ = p.DaemonManager.Cmd("getwalletinfo", []interface{}{})
211 | if rpcResponse.Error != nil || rpcResponse == nil {
212 | log.Error("Could not start pool, error with init batch RPC call: " + string(utils.Jsonify(rpcResponse)))
213 | return
214 | }
215 |
216 | _, rpcResponse, _ = p.DaemonManager.Cmd("getinfo", []interface{}{})
217 | if rpcResponse.Error == nil && rpcResponse != nil {
218 | getInfo := daemons.BytesToGetInfo(rpcResponse.Result)
219 |
220 | p.Options.Coin.Testnet = getInfo.Testnet
221 | p.ProtocolVersion = getInfo.Protocolversion
222 | // diff = getInfo.Difficulty
223 |
224 | p.Stats.Connections = getInfo.Connections
225 | } else {
226 | _, rpcResponse, _ := p.DaemonManager.Cmd("getnetworkinfo", []interface{}{})
227 | if rpcResponse.Error != nil || rpcResponse == nil {
228 | log.Error("Could not start pool, error with init batch RPC call: " + string(utils.Jsonify(rpcResponse)))
229 | return
230 | }
231 | getNetworkInfo := daemons.BytesToGetNetworkInfo(rpcResponse.Result)
232 |
233 | _, rpcResponse, _ = p.DaemonManager.Cmd("getblockchaininfo", []interface{}{})
234 | if rpcResponse.Error != nil || rpcResponse == nil {
235 | log.Error("Could not start pool, error with init batch RPC call: " + string(utils.Jsonify(rpcResponse)))
236 | return
237 | }
238 | getBlockchainInfo := daemons.BytesToGetBlockchainInfo(rpcResponse.Result)
239 | p.Options.Coin.Testnet = strings.Contains(getBlockchainInfo.Chain, "test")
240 | p.ProtocolVersion = getNetworkInfo.Protocolversion
241 | // diff = getBlockchainInfo.Difficulty
242 |
243 | p.Stats.Connections = getNetworkInfo.Connections
244 | }
245 |
246 | mul := 1 << p.Options.Algorithm.Multiplier
247 | p.Stats.Difficulty = diff * float64(mul)
248 | }
249 |
250 | func (p *Pool) OutputPoolInfo() {
251 | startMessage := "Stratum Pool Server Started for " + p.Options.Coin.Name + " [" + strings.ToUpper(p.Options.Coin.Symbol) + "] "
252 |
253 | var network string
254 | if p.Options.Coin.Testnet {
255 | network = "Testnet"
256 | } else {
257 | network = "Mainnet"
258 | }
259 |
260 | diff, _ := p.JobManager.CurrentJob.Difficulty.Float64()
261 | mul := 1 << p.Options.Algorithm.Multiplier
262 |
263 | infoLines := []string{
264 | startMessage,
265 | "Network Connected:\t" + network,
266 | "Detected Reward Type:\t" + p.Options.Coin.Reward,
267 | "Current Block Height:\t" + strconv.FormatInt(p.JobManager.CurrentJob.GetBlockTemplate.Height, 10),
268 | "Current Connect Peers:\t" + strconv.Itoa(p.Stats.Connections),
269 | "Current Block Diff:\t" + strconv.FormatFloat(diff*float64(mul), 'f', 7, 64),
270 | "Network Difficulty:\t" + strconv.FormatFloat(p.Stats.Difficulty, 'f', 7, 64),
271 | "Network Hash Rate:\t" + utils.GetReadableHashRateString(p.Stats.NetworkHashrate),
272 | "Stratum Port(s):\t" + string(utils.Jsonify(p.Stats.StratumPorts)),
273 | "Total Pool Fee Percent:\t" + strconv.FormatFloat(p.Options.TotalFeePercent(), 'f', 7, 64) + "%",
274 | }
275 |
276 | fmt.Println(strings.Join(infoLines, "\n\t"))
277 | }
278 |
279 | func (p *Pool) CheckAllReady() {
280 | _, results := p.DaemonManager.CmdAll("getblocktemplate", []interface{}{map[string]interface{}{"capabilities": []string{"coinbasetxn", "workid", "coinbase/append"}, "rules": []string{"segwit"}}})
281 | for i := range results {
282 | if results[i] == nil {
283 | log.Fatalf("daemon %s is not available", p.DaemonManager.Daemons[i])
284 | }
285 |
286 | if results[i].Error != nil {
287 | log.Fatalf("daemon %s is not ready for mining: %s", p.DaemonManager.Daemons[i], results[i].Error.Message)
288 | }
289 | }
290 | }
291 |
292 | func (p *Pool) SetupBlockPolling() {
293 | if p.Options.BlockRefreshInterval <= 0 {
294 | log.Warn("Block template polling has been disabled")
295 | return
296 | }
297 |
298 | pollingInterval := time.Duration(p.Options.BlockRefreshInterval) * time.Second
299 | p.BlockPollingIntervalTicker = time.NewTicker(pollingInterval)
300 |
301 | go func() {
302 | for {
303 | _, ok := <-p.BlockPollingIntervalTicker.C
304 | if !ok {
305 | log.Warn("Block polling is stopped!")
306 | p.BlockPollingIntervalTicker.Stop()
307 | return
308 | }
309 |
310 | gbt, err := p.DaemonManager.GetBlockTemplate()
311 | if err != nil {
312 | log.Error("Block notify error getting block template for ", p.Options.Coin.Name, err)
313 | }
314 |
315 | if gbt != nil {
316 | p.JobManager.ProcessTemplate(gbt)
317 | }
318 | }
319 | }()
320 | }
321 |
--------------------------------------------------------------------------------
/pool/stats.go:
--------------------------------------------------------------------------------
1 | package pool
2 |
3 | type Stats struct {
4 | Connections int
5 | Difficulty float64
6 | NetworkHashrate float64
7 | StratumPorts []int
8 | }
9 |
10 | func NewStats() *Stats {
11 | return &Stats{
12 | Connections: 0,
13 | Difficulty: 0.0,
14 | NetworkHashrate: 0,
15 | StratumPorts: []int{},
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/resources/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mining-pool/not-only-mining-pool/2a6ccb11a1ed6cbc7be849c7611c178ff2163da9/resources/logo.png
--------------------------------------------------------------------------------
/resources/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
83 |
--------------------------------------------------------------------------------
/storage/redis.go:
--------------------------------------------------------------------------------
1 | package storage
2 |
3 | import (
4 | "context"
5 | "strconv"
6 | "strings"
7 | "time"
8 |
9 | "github.com/go-redis/redis/v8"
10 | logging "github.com/ipfs/go-log/v2"
11 | "github.com/mining-pool/not-only-mining-pool/config"
12 | "github.com/mining-pool/not-only-mining-pool/types"
13 | )
14 |
15 | var log = logging.Logger("storage")
16 |
17 | type DB struct {
18 | *redis.Client
19 | coin string
20 | }
21 |
22 | func NewStorage(coinName string, options *config.RedisOptions) *DB {
23 | client := redis.NewClient(options.ToRedisOptions())
24 | if client == nil {
25 | log.Panic("failed to connect to the redis server. If you dont wanna db storage please delete redis config in config file")
26 | return nil
27 | }
28 |
29 | result, err := client.Ping(context.Background()).Result()
30 | if err != nil || strings.ToLower(result) != "pong" {
31 | log.Panicf("failed to connect to the redis server: %s %s", result, err)
32 | }
33 |
34 | return &DB{
35 | Client: client,
36 | coin: coinName,
37 | }
38 | }
39 |
40 | func (s *DB) PutShare(share *types.Share, accepted bool) {
41 | now := time.Now().Unix()
42 | strNow := strconv.FormatInt(now, 10)
43 |
44 | ppl := s.Pipeline()
45 | ctx := context.Background()
46 |
47 | strDiff := strconv.FormatFloat(share.Diff, 'f', 5, 64)
48 | ppl.SAdd(ctx, s.coin+":pool:miners", share.Miner) // miner index
49 | ppl.SAdd(ctx, s.coin+":miner:"+share.Miner+":rigs", share.Rig) // rig index
50 |
51 | if share.ErrorCode == 0 {
52 | log.Warn("recording valid share")
53 | ppl.HIncrByFloat(ctx, s.coin+":pool:contrib", share.Miner, share.Diff)
54 | ppl.HIncrBy(ctx, s.coin+":miners:validShares", share.Miner, 1)
55 |
56 | ppl.HIncrBy(ctx, s.coin+":pool", "validShares", 1)
57 |
58 | // cost storage for speed, dont use for range to replace this
59 | ppl.ZAdd(ctx, s.coin+":pool:shares", &redis.Z{
60 | Score: float64(now),
61 | Member: strDiff,
62 | })
63 |
64 | ppl.ZAdd(ctx, s.coin+":miner:"+share.Miner+":hashes", &redis.Z{
65 | Score: float64(now),
66 | Member: strDiff,
67 | })
68 |
69 | ppl.ZAdd(ctx, s.coin+":miner:"+share.Miner+":rig:"+share.Rig+":hashes", &redis.Z{
70 | Score: float64(now),
71 | Member: strDiff,
72 | })
73 |
74 | } else {
75 | log.Warn("recording invalid share")
76 | ppl.HIncrBy(ctx, s.coin+":miners:invalidShares", share.Miner, 1)
77 |
78 | ppl.HIncrBy(ctx, s.coin+":pool", "invalidShares", 1)
79 | }
80 |
81 | // when mined one => seal roundCount,
82 | // BlockHex is not accuracy, maybe out of date
83 | if len(share.BlockHex) > 0 {
84 | // share is valid but block from share can be also invalid
85 | if accepted {
86 | log.Warn("recording valid block")
87 | ppl.Rename(ctx, s.coin+":pool:contrib", s.coin+":pool:contrib:"+strconv.FormatInt(share.BlockHeight, 10))
88 | ppl.SAdd(ctx, s.coin+":blocks:pending", share.BlockHash)
89 | ppl.HSetNX(ctx, s.coin+":blocks", share.BlockHash, strings.Join([]string{
90 | share.TxHash,
91 | strconv.FormatInt(share.BlockHeight, 10),
92 | share.Miner,
93 | strNow,
94 | }, ":"))
95 |
96 | ppl.HIncrBy(ctx, s.coin+":pool", "validBlocks", 1)
97 | } else {
98 | log.Warn("recording invalid block")
99 | ppl.HIncrBy(ctx, s.coin+":pool", "invalidBlocks", 1)
100 | }
101 | }
102 |
103 | _, err := ppl.Exec(ctx)
104 | if err != nil {
105 | log.Error(err)
106 | }
107 | }
108 |
109 | func (s *DB) GetMinerIndex() ([]string, error) {
110 | return s.SMembers(context.Background(), s.coin+":pool:miners").Result()
111 | }
112 |
113 | func (s *DB) GetRigIndex(minerName string) ([]string, error) {
114 | return s.SMembers(context.Background(), s.coin+":miner:"+minerName+":rigs").Result()
115 | }
116 |
117 | // GetCurrentRoundCount will return a total diff of shares the miner submitted
118 | func (s *DB) GetMinerCurrentRoundContrib(minerName string) (float64, error) {
119 | return s.HGet(context.Background(), s.coin+":shares:contrib", minerName).Float64()
120 | }
121 |
122 | // GetMinerTotalShares will return the number of all valid shares
123 | func (s *DB) GetPoolTotalValidShares() (uint64, error) {
124 | return s.HGet(context.Background(), s.coin+":pool", "validShares").Uint64()
125 | }
126 |
127 | // GetMinerTotalShares will return the number of all valid blocks
128 | func (s *DB) GetPoolTotalValidBlocks() (uint64, error) {
129 | return s.HGet(context.Background(), s.coin+":pool", "validBlocks").Uint64()
130 | }
131 |
132 | // GetMinerTotalShares will return the number of all invalid shares
133 | func (s *DB) GetPoolTotalInvalidShares() (uint64, error) {
134 | return s.HGet(context.Background(), s.coin+":pool", "validShares").Uint64()
135 | }
136 |
137 | // GetMinerTotalShares will return the number of all invalid blocks
138 | func (s *DB) GetPoolTotalInvalidBlocks() (uint64, error) {
139 | return s.HGet(context.Background(), s.coin+":pool", "invalidBlocks").Uint64()
140 | }
141 |
142 | // GetMinerTotalShares will return the number of all invalid blocks
143 | func (s *DB) GetRigHashrate(minerName, rigName string, from, to int64) (hashrate float64, err error) {
144 | slice, err := s.ZRange(context.Background(), s.coin+":miner:"+minerName+":rig:"+rigName+":hashes", from, to).Result()
145 | if err != nil {
146 | return 0.0, err
147 | }
148 |
149 | var totalDiff float64
150 | for i := range slice {
151 | diff, err := strconv.ParseFloat(slice[i], 64)
152 | if err != nil {
153 | return 0.0, err
154 | }
155 |
156 | totalDiff += diff
157 | }
158 |
159 | return totalDiff / float64(to-from), nil
160 | }
161 |
162 | // GetMinerTotalShares will return the number of all invalid blocks
163 | func (s *DB) GetMinerHashrate(minerName string, from, to int64) (hashrate float64, err error) {
164 | slice, err := s.ZRange(context.Background(), s.coin+":miner:"+minerName+":shares", from, to).Result()
165 | if err != nil {
166 | return 0.0, err
167 | }
168 |
169 | var totalDiff float64
170 | for i := range slice {
171 | diff, err := strconv.ParseFloat(slice[i], 64)
172 | if err != nil {
173 | return 0.0, err
174 | }
175 |
176 | totalDiff += diff
177 | }
178 |
179 | return totalDiff / float64(to-from), nil
180 | }
181 |
182 | // GetMinerTotalShares will return the number of all invalid blocks
183 | func (s *DB) GetPoolHashrate(from, to int64) (float64, error) {
184 | slice, err := s.ZRange(context.Background(), s.coin+":pool:shares", from, to).Result()
185 | if err != nil {
186 | return 0.0, err
187 | }
188 |
189 | var totalDiff float64
190 | for i := range slice {
191 | diff, err := strconv.ParseFloat(slice[i], 64)
192 | if err != nil {
193 | return 0.0, err
194 | }
195 |
196 | totalDiff += diff
197 | }
198 |
199 | return totalDiff / float64(to-from), nil
200 | }
201 |
202 | // GetCurrentRoundCount will return a total diff of shares the miner submitted
203 | func (s *DB) GetMinerRigs(minerName string) (float64, error) {
204 | return s.HGet(context.Background(), s.coin+":shares:contrib", minerName).Float64()
205 | }
206 |
207 | // ConfirmBlock alt one pending block to confirmed
208 | func (s *DB) ConfirmBlock(blockHash string) (ok bool, err error) {
209 | return s.SMove(context.Background(), s.coin+":blocks:pending", s.coin+":blocks:confirmed", blockHash).Result()
210 | }
211 |
212 | // KickBlock alt one pending block to kicked
213 | func (s *DB) KickBlock(blockHash string) (ok bool, err error) {
214 | return s.SMove(context.Background(), s.coin+":blocks:pending", s.coin+":blocks:kicked", blockHash).Result()
215 | }
216 |
--------------------------------------------------------------------------------
/stratum/client.go:
--------------------------------------------------------------------------------
1 | package stratum
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "encoding/binary"
7 | "encoding/hex"
8 | "encoding/json"
9 | "io"
10 | "math/big"
11 | "net"
12 | "strconv"
13 | "time"
14 |
15 | "github.com/mining-pool/not-only-mining-pool/types"
16 |
17 | "github.com/mining-pool/not-only-mining-pool/bans"
18 | "github.com/mining-pool/not-only-mining-pool/config"
19 | "github.com/mining-pool/not-only-mining-pool/daemons"
20 | "github.com/mining-pool/not-only-mining-pool/jobs"
21 | "github.com/mining-pool/not-only-mining-pool/utils"
22 | "github.com/mining-pool/not-only-mining-pool/vardiff"
23 | )
24 |
25 | type Client struct {
26 | SubscriptionId []byte
27 | Options *config.Options
28 | RemoteAddress net.Addr
29 |
30 | Socket net.Conn
31 | SocketBufIO *bufio.ReadWriter
32 |
33 | LastActivity time.Time
34 | Shares *Shares
35 |
36 | IsAuthorized bool
37 | SubscriptionBeforeAuth bool
38 |
39 | ExtraNonce1 []byte
40 |
41 | VarDiff *vardiff.VarDiff
42 |
43 | WorkerName string
44 | WorkerPass string
45 |
46 | PendingDifficulty *big.Float
47 | CurrentDifficulty *big.Float
48 | PreviousDifficulty *big.Float
49 |
50 | BanningManager *bans.BanningManager
51 | JobManager *jobs.JobManager
52 | SocketClosedEvent chan struct{}
53 | }
54 |
55 | func NewStratumClient(subscriptionId []byte, socket net.Conn, options *config.Options, jm *jobs.JobManager, bm *bans.BanningManager) *Client {
56 | var varDiff *vardiff.VarDiff
57 | if options.Ports[socket.LocalAddr().(*net.TCPAddr).Port] != nil && options.Ports[socket.LocalAddr().(*net.TCPAddr).Port].VarDiff != nil {
58 | varDiff = vardiff.NewVarDiff(options.Ports[socket.LocalAddr().(*net.TCPAddr).Port].VarDiff)
59 | }
60 |
61 | return &Client{
62 | SubscriptionId: subscriptionId,
63 | PendingDifficulty: big.NewFloat(0),
64 | Options: options,
65 | RemoteAddress: socket.RemoteAddr(),
66 | Socket: socket,
67 | SocketBufIO: bufio.NewReadWriter(bufio.NewReader(socket), bufio.NewWriter(socket)),
68 | LastActivity: time.Now(),
69 | Shares: &Shares{
70 | Valid: 0,
71 | Invalid: 0,
72 | },
73 | IsAuthorized: false,
74 | SubscriptionBeforeAuth: false,
75 | ExtraNonce1: jm.ExtraNonce1Generator.GetExtraNonce1(),
76 |
77 | VarDiff: varDiff,
78 | JobManager: jm,
79 | BanningManager: bm,
80 | }
81 | }
82 |
83 | func (sc *Client) ShouldBan(shareValid bool) bool {
84 | if shareValid {
85 | sc.Shares.Valid++
86 | } else {
87 | sc.Shares.Invalid++
88 | if sc.Shares.TotalShares() >= sc.Options.Banning.CheckThreshold {
89 | if sc.Shares.BadPercent() < sc.Options.Banning.InvalidPercent {
90 | sc.Shares.Reset()
91 | } else {
92 | // sc.TriggerBanEvent <- strconv.FormatUint(sc.Shares.Invalid, 10) + " out of the last " + strconv.FormatUint(sc.Shares.TotalShares(), 10) + " shares were invalid"
93 | log.Info(strconv.FormatUint(sc.Shares.Invalid, 10) + " out of the last " + strconv.FormatUint(sc.Shares.TotalShares(), 10) + " shares were invalid")
94 | sc.BanningManager.AddBannedIP(sc.RemoteAddress.String())
95 | log.Warn("closed socket", sc.WorkerName, " due to shares bad percent reached the banning invalid percent threshold")
96 | sc.SocketClosedEvent <- struct{}{}
97 | _ = sc.Socket.Close()
98 | return true
99 | }
100 | }
101 | }
102 |
103 | return false
104 | }
105 |
106 | func (sc *Client) Init() {
107 | sc.SetupSocket()
108 | }
109 |
110 | func (sc *Client) HandleMessage(message *daemons.JsonRpcRequest) {
111 | switch message.Method {
112 | case "mining.subscribe":
113 | sc.HandleSubscribe(message)
114 | case "mining.authorize":
115 | sc.HandleAuthorize(message, true)
116 | case "mining.submit":
117 | sc.LastActivity = time.Now()
118 | sc.HandleSubmit(message)
119 | case "mining.get_transactions":
120 | sc.SendJsonRPC(&daemons.JsonRpcResponse{
121 | Id: 0,
122 | Result: nil,
123 | Error: nil, // TODO: Support this
124 | })
125 | default:
126 | log.Warn("unknown stratum method: ", string(utils.Jsonify(message)))
127 | }
128 | }
129 |
130 | func (sc *Client) HandleSubscribe(message *daemons.JsonRpcRequest) {
131 | log.Info("handling subscribe")
132 | if !sc.IsAuthorized {
133 | sc.SubscriptionBeforeAuth = true
134 | }
135 |
136 | extraNonce2Size := sc.JobManager.ExtraNonce2Size
137 |
138 | // TODO
139 | //var err error
140 | //if err != nil {
141 | // sc.SendJson(&daemons.JsonRpcResponse{
142 | // Id: message.Id,
143 | // Result: nil,
144 | // ErrorCode: &daemons.JsonRpcError{
145 | // Code: 20,
146 | // Message: err.ErrorCode(),
147 | // },
148 | // })
149 | //
150 | // return
151 | //}
152 |
153 | sc.SendJsonRPC(&daemons.JsonRpcResponse{
154 | Id: message.Id,
155 | Result: utils.Jsonify([]interface{}{
156 | [][]string{
157 | {"mining.set_difficulty", strconv.FormatUint(binary.LittleEndian.Uint64(sc.SubscriptionId), 10)},
158 | {"mining.notify", strconv.FormatUint(binary.LittleEndian.Uint64(sc.SubscriptionId), 10)},
159 | },
160 | hex.EncodeToString(sc.ExtraNonce1),
161 | extraNonce2Size,
162 | }),
163 | Error: nil,
164 | })
165 | }
166 |
167 | func (sc *Client) HandleAuthorize(message *daemons.JsonRpcRequest, replyToSocket bool) {
168 | log.Info("handling authorize")
169 |
170 | sc.WorkerName = string(message.Params[0])
171 | sc.WorkerPass = string(message.Params[1])
172 |
173 | authorized, disconnect, err := sc.AuthorizeFn(sc.RemoteAddress, sc.Socket.LocalAddr().(*net.TCPAddr).Port, sc.WorkerName, sc.WorkerPass)
174 | sc.IsAuthorized = err == nil && authorized
175 |
176 | if replyToSocket {
177 | if sc.IsAuthorized {
178 | sc.SendJsonRPC(&daemons.JsonRpcResponse{
179 | Id: message.Id,
180 | Result: utils.Jsonify(sc.IsAuthorized),
181 | Error: nil,
182 | })
183 | } else {
184 | sc.SendJsonRPC(&daemons.JsonRpcResponse{
185 | Id: message.Id,
186 | Result: utils.Jsonify(sc.IsAuthorized),
187 | Error: &daemons.JsonRpcError{
188 | Code: 20,
189 | Message: string(utils.Jsonify(err)),
190 | },
191 | })
192 | }
193 | }
194 |
195 | if disconnect {
196 | log.Warn("closed socket", sc.WorkerName, "due to failed to authorize the miner")
197 | _ = sc.Socket.Close()
198 | sc.SocketClosedEvent <- struct{}{}
199 | }
200 |
201 | // the init Diff for miners
202 | log.Info("sending init difficulty: ", sc.Options.Ports[sc.Socket.LocalAddr().(*net.TCPAddr).Port].Diff)
203 | sc.SendDifficulty(big.NewFloat(sc.Options.Ports[sc.Socket.LocalAddr().(*net.TCPAddr).Port].Diff))
204 | sc.SendMiningJob(sc.JobManager.CurrentJob.GetJobParams(true))
205 | }
206 |
207 | // TODO: Can be DIY
208 | func (sc *Client) AuthorizeFn(ip net.Addr, port int, workerName string, password string) (authorized bool, disconnect bool, err error) {
209 | log.Info("Authorize " + workerName + ": " + password + "@" + ip.String())
210 | return true, false, nil
211 | }
212 |
213 | func (sc *Client) HandleSubmit(message *daemons.JsonRpcRequest) {
214 | /* Avoid hash flood */
215 | if !sc.IsAuthorized {
216 | sc.SendJsonRPC(&daemons.JsonRpcResponse{
217 | Id: message.Id,
218 | Result: nil,
219 | Error: &daemons.JsonRpcError{
220 | Code: 24,
221 | Message: "unauthorized worker",
222 | },
223 | })
224 | sc.ShouldBan(false)
225 | return
226 | }
227 |
228 | if sc.ExtraNonce1 == nil {
229 | sc.SendJsonRPC(&daemons.JsonRpcResponse{
230 | Id: message.Id,
231 | Result: nil,
232 | Error: &daemons.JsonRpcError{
233 | Code: 25,
234 | Message: "not subscribed",
235 | },
236 | })
237 | sc.ShouldBan(false)
238 | return
239 | }
240 |
241 | share := sc.JobManager.ProcessSubmit(
242 | utils.RawJsonToString(message.Params[1]),
243 | sc.PreviousDifficulty,
244 | sc.CurrentDifficulty,
245 | sc.ExtraNonce1,
246 | utils.RawJsonToString(message.Params[2]),
247 | utils.RawJsonToString(message.Params[3]),
248 | utils.RawJsonToString(message.Params[4]),
249 | sc.RemoteAddress,
250 | utils.RawJsonToString(message.Params[0]),
251 | )
252 |
253 | sc.JobManager.ProcessShare(share)
254 |
255 | if share.ErrorCode == types.ErrLowDiffShare {
256 | // warn the miner with current diff
257 | log.Error("Error on handling submit: sending new diff ", string(utils.Jsonify([]json.RawMessage{utils.Jsonify(sc.CurrentDifficulty)})), " to miner")
258 | f, _ := sc.CurrentDifficulty.Float64()
259 | sc.SendJsonRPC(&daemons.JsonRpcRequest{
260 | Id: nil,
261 | Method: "mining.set_difficulty",
262 | Params: []json.RawMessage{utils.Jsonify(f)},
263 | })
264 | }
265 |
266 | if share.ErrorCode == types.ErrNTimeOutOfRange {
267 | sc.SendMiningJob(sc.JobManager.CurrentJob.GetJobParams(true))
268 | }
269 |
270 | // vardiff
271 | if sc.VarDiff != nil {
272 | diff, _ := sc.CurrentDifficulty.Float64()
273 | if nextDiff := sc.VarDiff.CalcNextDiff(diff); nextDiff != diff && nextDiff != 0 {
274 | sc.EnqueueNextDifficulty(nextDiff)
275 | }
276 | }
277 |
278 | if sc.PendingDifficulty != nil && sc.PendingDifficulty.Cmp(big.NewFloat(0)) != 0 {
279 | diff := sc.PendingDifficulty
280 | log.Info("sending new difficulty: ", diff)
281 | ok := sc.SendDifficulty(diff)
282 | sc.PendingDifficulty = nil
283 | if ok {
284 | // difficultyChanged
285 | // -> difficultyUpdate client.workerName, diff
286 | displayDiff, _ := diff.Float64()
287 | log.Info("Difficulty update to diff:", displayDiff, "&workerName:", sc.WorkerName)
288 | }
289 | }
290 |
291 | if sc.ShouldBan(share.ErrorCode == 0) {
292 | return
293 | }
294 |
295 | var errParams *daemons.JsonRpcError
296 | if share.ErrorCode != 0 {
297 | errParams = &daemons.JsonRpcError{
298 | Code: int(share.ErrorCode),
299 | Message: share.ErrorCode.String(),
300 | }
301 |
302 | log.Error(sc.WorkerName, "'s share is invalid: ", errParams.Message)
303 | sc.SendJsonRPC(&daemons.JsonRpcResponse{
304 | Id: message.Id,
305 | Result: utils.Jsonify(false),
306 | Error: errParams,
307 | })
308 | }
309 |
310 | log.Info(sc.WorkerName, " submitted a valid share")
311 | sc.SendJsonRPC(&daemons.JsonRpcResponse{
312 | Id: message.Id,
313 | Result: utils.Jsonify(true),
314 | })
315 | }
316 |
317 | func (sc *Client) SendJsonRPC(jsonRPCs daemons.JsonRpc) {
318 | raw := jsonRPCs.Json()
319 |
320 | message := make([]byte, 0, len(raw)+1)
321 | message = append(raw, '\n')
322 | _, err := sc.SocketBufIO.Write(message)
323 | if err != nil {
324 | log.Error("failed inputting", string(raw), err)
325 | }
326 |
327 | err = sc.SocketBufIO.Flush()
328 | if err != nil {
329 | log.Error("failed sending data", err)
330 | }
331 |
332 | log.Debug("sent raw bytes: ", string(raw))
333 | }
334 |
335 | func (sc *Client) SendSubscriptionFirstResponse() {
336 | }
337 |
338 | func (sc *Client) SetupSocket() {
339 | sc.BanningManager.CheckBan(sc.RemoteAddress.String())
340 | once := true
341 |
342 | go func() {
343 | for {
344 | select {
345 | case <-sc.SocketClosedEvent:
346 | return
347 | default:
348 | raw, err := sc.SocketBufIO.ReadBytes('\n')
349 | if err != nil {
350 | if err == io.EOF {
351 | sc.SocketClosedEvent <- struct{}{}
352 | return
353 | }
354 | e, ok := err.(net.Error)
355 |
356 | if !ok {
357 | log.Error("failed to ready bytes from socket due to non-network error:", err)
358 | return
359 | }
360 |
361 | if ok && e.Timeout() {
362 | log.Error("socket is timeout:", err)
363 | return
364 | }
365 |
366 | if ok && e.Temporary() {
367 | log.Error("failed to ready bytes from socket due to temporary error:", err)
368 | continue
369 | }
370 |
371 | log.Error("failed to ready bytes from socket:", err)
372 | return
373 | }
374 |
375 | if len(raw) > 10240 {
376 | // socketFlooded
377 | log.Warn("Flooding message from", sc.GetLabel(), ":", string(raw))
378 | _ = sc.Socket.Close()
379 | sc.SocketClosedEvent <- struct{}{}
380 | return
381 | }
382 |
383 | if len(raw) == 0 {
384 | continue
385 | }
386 |
387 | var message daemons.JsonRpcRequest
388 | err = json.Unmarshal(raw, &message)
389 | if err != nil {
390 | if !sc.Options.TCPProxyProtocol {
391 | log.Error("Malformed message from", sc.GetLabel(), ":", string(raw))
392 | _ = sc.Socket.Close()
393 | sc.SocketClosedEvent <- struct{}{}
394 | }
395 |
396 | return
397 | }
398 |
399 | if once && sc.Options.TCPProxyProtocol {
400 | once = false
401 | if bytes.HasPrefix(raw, []byte("PROXY")) {
402 | sc.RemoteAddress, err = net.ResolveTCPAddr("tcp", string(bytes.Split(raw, []byte(" "))[2]))
403 | if err != nil {
404 | log.Error("failed to resolve tcp addr behind proxy:", err)
405 | }
406 | } else {
407 | log.Error("Client IP detection failed, tcpProxyProtocol is enabled yet did not receive proxy protocol message, instead got data:", raw)
408 | }
409 | }
410 |
411 | sc.BanningManager.CheckBan(sc.RemoteAddress.String())
412 |
413 | if &message != nil {
414 | log.Debug("handling message: ", string(message.Json()))
415 | sc.HandleMessage(&message)
416 | }
417 | }
418 | }
419 | }()
420 | }
421 |
422 | func (sc *Client) GetLabel() string {
423 | if sc.WorkerName != "" {
424 | return sc.WorkerName + " [" + sc.RemoteAddress.String() + "]"
425 | } else {
426 | return "(unauthorized)" + " [" + sc.RemoteAddress.String() + "]"
427 | }
428 | }
429 |
430 | func (sc *Client) EnqueueNextDifficulty(nextDiff float64) bool {
431 | log.Info("Enqueue next difficulty:", nextDiff)
432 | sc.PendingDifficulty = big.NewFloat(nextDiff)
433 | return true
434 | }
435 |
436 | func (sc *Client) SendDifficulty(diff *big.Float) bool {
437 | if diff == nil {
438 | log.Fatal("trying to send empty diff!")
439 | }
440 | if sc.CurrentDifficulty != nil && diff.Cmp(sc.CurrentDifficulty) == 0 {
441 | return false
442 | }
443 |
444 | sc.PreviousDifficulty = sc.CurrentDifficulty
445 | sc.CurrentDifficulty = diff
446 |
447 | f, _ := diff.Float64()
448 | sc.SendJsonRPC(&daemons.JsonRpcRequest{
449 | Id: 0,
450 | Method: "mining.set_difficulty",
451 | Params: []json.RawMessage{utils.Jsonify(f)},
452 | })
453 |
454 | return true
455 | }
456 |
457 | func (sc *Client) SendMiningJob(jobParams []interface{}) {
458 | log.Info("sending job: ", string(utils.Jsonify(jobParams)))
459 |
460 | lastActivityAgo := time.Since(sc.LastActivity)
461 | if lastActivityAgo > time.Duration(sc.Options.ConnectionTimeout)*time.Second {
462 | log.Info("closed socket", sc.WorkerName, "due to activity timeout")
463 | _ = sc.Socket.Close()
464 | sc.SocketClosedEvent <- struct{}{}
465 | return
466 | }
467 |
468 | if sc.PendingDifficulty != nil && sc.PendingDifficulty.Cmp(big.NewFloat(0)) != 0 {
469 | diff := sc.PendingDifficulty
470 | ok := sc.SendDifficulty(diff)
471 | sc.PendingDifficulty = nil
472 | if ok {
473 | // difficultyChanged
474 | // -> difficultyUpdate client.workerName, diff
475 | displayDiff, _ := diff.Float64()
476 | log.Info("Difficulty update to diff:", displayDiff, "&workerName:", sc.WorkerName)
477 | }
478 | }
479 |
480 | params := make([]json.RawMessage, len(jobParams))
481 | for i := range jobParams {
482 | params[i] = utils.Jsonify(jobParams[i])
483 | }
484 |
485 | sc.SendJsonRPC(&daemons.JsonRpcRequest{
486 | Id: nil,
487 | Method: "mining.notify",
488 | Params: params,
489 | })
490 | }
491 |
492 | func (sc *Client) ManuallyAuthClient(username, password string) {
493 | sc.HandleAuthorize(&daemons.JsonRpcRequest{
494 | Id: 1,
495 | Method: "",
496 | Params: []json.RawMessage{utils.Jsonify(username), utils.Jsonify(password)},
497 | }, false)
498 | }
499 |
500 | func (sc *Client) ManuallySetValues(otherClient *Client) {
501 | sc.ExtraNonce1 = otherClient.ExtraNonce1
502 | sc.PreviousDifficulty = otherClient.PreviousDifficulty
503 | sc.CurrentDifficulty = otherClient.CurrentDifficulty
504 | }
505 |
--------------------------------------------------------------------------------
/stratum/server.go:
--------------------------------------------------------------------------------
1 | package stratum
2 |
3 | import (
4 | "bytes"
5 | "crypto/tls"
6 | "encoding/binary"
7 | "net"
8 | "strconv"
9 | "time"
10 |
11 | logging "github.com/ipfs/go-log/v2"
12 |
13 | "github.com/mining-pool/not-only-mining-pool/bans"
14 | "github.com/mining-pool/not-only-mining-pool/config"
15 | "github.com/mining-pool/not-only-mining-pool/daemons"
16 | "github.com/mining-pool/not-only-mining-pool/jobs"
17 | "github.com/mining-pool/not-only-mining-pool/vardiff"
18 | )
19 |
20 | var log = logging.Logger("stratum")
21 |
22 | type Server struct {
23 | Options *config.Options
24 | Listener net.Listener
25 |
26 | DaemonManager *daemons.DaemonManager
27 | VarDiff *vardiff.VarDiff
28 | JobManager *jobs.JobManager
29 | StratumClients map[uint64]*Client
30 | SubscriptionCounter *SubscriptionCounter
31 | BanningManager *bans.BanningManager
32 |
33 | rebroadcastTicker *time.Ticker
34 | }
35 |
36 | func NewStratumServer(options *config.Options, jm *jobs.JobManager, bm *bans.BanningManager) *Server {
37 | return &Server{
38 | Options: options,
39 | BanningManager: bm,
40 | SubscriptionCounter: NewSubscriptionCounter(),
41 |
42 | JobManager: jm,
43 | StratumClients: make(map[uint64]*Client),
44 | }
45 | }
46 |
47 | func (ss *Server) Init() (portStarted []int) {
48 | if ss.Options.Banning != nil {
49 | ss.BanningManager.Init()
50 | }
51 |
52 | for port, options := range ss.Options.Ports {
53 | var err error
54 | if options.TLS != nil {
55 | ss.Listener, err = tls.Listen("tcp", ":"+strconv.Itoa(port), options.TLS.ToTLSConfig())
56 | } else {
57 | ss.Listener, err = net.Listen("tcp", ":"+strconv.Itoa(port))
58 | }
59 |
60 | if err != nil {
61 | log.Error(err)
62 | continue
63 | }
64 |
65 | portStarted = append(portStarted, port)
66 | //if len(portStarted) == len(ss.Options.Ports) {
67 | // // emit started
68 | //}
69 | }
70 |
71 | if len(portStarted) == 0 {
72 | log.Panic("No port listened")
73 | }
74 |
75 | go func() {
76 | var id string
77 | var txs []byte
78 | ss.rebroadcastTicker = time.NewTicker(time.Duration(ss.Options.JobRebroadcastTimeout) * time.Second)
79 | defer log.Warn("broadcaster stopped")
80 | defer ss.rebroadcastTicker.Stop()
81 | for {
82 | <-ss.rebroadcastTicker.C
83 | go ss.BroadcastCurrentMiningJob(ss.JobManager.CurrentJob.GetJobParams(
84 | id != ss.JobManager.CurrentJob.JobId || !bytes.Equal(txs, ss.JobManager.CurrentJob.TransactionData),
85 | ))
86 |
87 | id = ss.JobManager.CurrentJob.JobId
88 | txs = ss.JobManager.CurrentJob.TransactionData
89 | }
90 | }()
91 |
92 | go func() {
93 | for {
94 | conn, err := ss.Listener.Accept()
95 | if err != nil {
96 | log.Error(err)
97 | continue
98 | }
99 |
100 | if conn != nil {
101 | log.Info("new conn from ", conn.RemoteAddr().String())
102 | go ss.HandleNewClient(conn)
103 | }
104 | }
105 | }()
106 |
107 | return portStarted
108 | }
109 |
110 | // HandleNewClient converts the conn to an underlying client instance and finally return its unique subscriptionID
111 | func (ss *Server) HandleNewClient(socket net.Conn) []byte {
112 | subscriptionID := ss.SubscriptionCounter.Next()
113 | client := NewStratumClient(subscriptionID, socket, ss.Options, ss.JobManager, ss.BanningManager)
114 | ss.StratumClients[binary.LittleEndian.Uint64(subscriptionID)] = client
115 | // client.connected
116 |
117 | go func() {
118 | for {
119 | <-client.SocketClosedEvent
120 | log.Warn("a client socket closed")
121 | ss.RemoveStratumClientBySubscriptionId(subscriptionID)
122 | // client.disconnected
123 | }
124 | }()
125 |
126 | client.Init()
127 |
128 | return subscriptionID
129 | }
130 |
131 | func (ss *Server) BroadcastCurrentMiningJob(jobParams []interface{}) {
132 | log.Info("broadcasting job params")
133 | for clientId := range ss.StratumClients {
134 | ss.StratumClients[clientId].SendMiningJob(jobParams)
135 | }
136 | }
137 |
138 | func (ss *Server) RemoveStratumClientBySubscriptionId(subscriptionId []byte) {
139 | delete(ss.StratumClients, binary.LittleEndian.Uint64(subscriptionId))
140 | }
141 |
142 | func (ss *Server) ManuallyAddStratumClient(client *Client) {
143 | subscriptionId := ss.HandleNewClient(client.Socket)
144 | if subscriptionId != nil {
145 | ss.StratumClients[binary.LittleEndian.Uint64(subscriptionId)].ManuallyAuthClient(client.WorkerName, client.WorkerPass)
146 | ss.StratumClients[binary.LittleEndian.Uint64(subscriptionId)].ManuallySetValues(client)
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/stratum/shares.go:
--------------------------------------------------------------------------------
1 | package stratum
2 |
3 | import "sync/atomic"
4 |
5 | type Shares struct {
6 | Valid uint64
7 | Invalid uint64
8 | }
9 |
10 | func (s *Shares) TotalShares() uint64 {
11 | return s.Valid + s.Invalid
12 | }
13 |
14 | func (s *Shares) BadPercent() float64 {
15 | return float64(s.Invalid*100) / float64(s.TotalShares())
16 | }
17 |
18 | func (s *Shares) Reset() {
19 | atomic.StoreUint64(&s.Invalid, 0)
20 | atomic.StoreUint64(&s.Valid, 0)
21 | }
22 |
--------------------------------------------------------------------------------
/stratum/subscriptionCounter.go:
--------------------------------------------------------------------------------
1 | package stratum
2 |
3 | import (
4 | "encoding/binary"
5 | "math"
6 | )
7 |
8 | // support 18446744073709551615 max conn
9 | type SubscriptionCounter struct {
10 | Count uint64
11 | Padding []byte
12 | }
13 |
14 | func NewSubscriptionCounter() *SubscriptionCounter {
15 | return &SubscriptionCounter{
16 | Count: 0,
17 | Padding: nil,
18 | }
19 | }
20 |
21 | func (sc *SubscriptionCounter) Next() []byte {
22 | sc.Count++
23 | if sc.Count == math.MaxUint64 {
24 | sc.Count = 0
25 | }
26 |
27 | b := make([]byte, 8)
28 | binary.LittleEndian.PutUint64(b, sc.Count)
29 | return append(sc.Padding, b...)
30 | }
31 |
--------------------------------------------------------------------------------
/transactions/transactions.go:
--------------------------------------------------------------------------------
1 | package transactions
2 |
3 | import (
4 | "bytes"
5 | "encoding/hex"
6 | "math"
7 | "time"
8 |
9 | logging "github.com/ipfs/go-log/v2"
10 | "github.com/mining-pool/not-only-mining-pool/config"
11 | "github.com/mining-pool/not-only-mining-pool/daemons"
12 | "github.com/mining-pool/not-only-mining-pool/utils"
13 | )
14 |
15 | var log = logging.Logger("tx")
16 |
17 | //type struct {
18 | // CoinbaseValue uint64 // unit: Satoshis
19 | // MasterNode *MasternodeParams
20 | // Superblock []SuperBlockTemplate
21 | // DefaultWitnessCommitment string
22 | //}
23 |
24 | func GenerateOutputTransactions(poolRecipient []byte, recipients []*config.Recipient, rpcData *daemons.GetBlockTemplate) []byte {
25 | reward := rpcData.CoinbaseValue
26 | rewardToPool := reward
27 | txOutputBuffers := make([][]byte, 0)
28 |
29 | if rpcData.Masternode != nil && len(rpcData.Masternode) > 0 {
30 | log.Info("handling dash's masternode")
31 | for i := range rpcData.Masternode {
32 | payeeReward := rpcData.Masternode[i].Amount
33 | reward -= payeeReward
34 | rewardToPool -= payeeReward
35 |
36 | var payeeScript []byte
37 | if len(rpcData.Masternode[i].Script) > 0 {
38 | payeeScript, _ = hex.DecodeString(rpcData.Masternode[i].Script)
39 | } else {
40 | payeeScript = utils.P2PKHAddressToScript(rpcData.Masternode[i].Payee)
41 | }
42 | txOutputBuffers = append(txOutputBuffers, bytes.Join([][]byte{
43 | utils.PackUint64BE(payeeReward),
44 | utils.VarIntBytes(uint64(len(payeeScript))),
45 | }, nil))
46 | }
47 | }
48 |
49 | if rpcData.Superblock != nil && len(rpcData.Superblock) > 0 {
50 | log.Info("handling dash's superblock")
51 | for i := range rpcData.Superblock {
52 | payeeReward := rpcData.Superblock[i].Amount
53 | reward -= payeeReward
54 | rewardToPool -= payeeReward
55 |
56 | var payeeScript []byte
57 | if len(rpcData.Superblock[i].Script) > 0 {
58 | payeeScript, _ = hex.DecodeString(rpcData.Superblock[i].Script)
59 | } else {
60 | payeeScript = utils.P2PKHAddressToScript(rpcData.Superblock[i].Payee)
61 | }
62 |
63 | txOutputBuffers = append(txOutputBuffers, bytes.Join([][]byte{
64 | utils.PackUint64LE(payeeReward),
65 | utils.VarIntBytes(uint64(len(payeeScript))),
66 | payeeScript,
67 | }, nil))
68 | }
69 | }
70 |
71 | if rpcData.Payee != nil {
72 | var payeeReward uint64
73 | if rpcData.PayeeAmount != nil {
74 | payeeReward = rpcData.PayeeAmount.(uint64)
75 | } else {
76 | payeeReward = uint64(math.Ceil(float64(reward) / 5))
77 | }
78 |
79 | reward -= payeeReward
80 | rewardToPool -= payeeReward
81 |
82 | payeeScript := utils.P2PKHAddressToScript(rpcData.Payee.(string))
83 | txOutputBuffers = append(txOutputBuffers, bytes.Join([][]byte{
84 | utils.PackUint64LE(payeeReward),
85 | utils.VarIntBytes(uint64(len(payeeScript))),
86 | payeeScript,
87 | }, nil))
88 | }
89 |
90 | for i := range recipients {
91 | script := recipients[i].GetScript()
92 |
93 | recipientReward := uint64(math.Floor(recipients[i].Percent * float64(reward)))
94 | rewardToPool -= recipientReward
95 |
96 | txOutputBuffers = append(txOutputBuffers, bytes.Join([][]byte{
97 | utils.PackUint64LE(recipientReward),
98 | utils.VarIntBytes(uint64(len(script))),
99 | script,
100 | }, nil))
101 | }
102 |
103 | txOutputBuffers = append([][]byte{bytes.Join([][]byte{
104 | utils.PackUint64LE(rewardToPool),
105 | utils.VarIntBytes(uint64(len(poolRecipient))),
106 | poolRecipient,
107 | }, nil)}, txOutputBuffers...)
108 |
109 | if rpcData.DefaultWitnessCommitment != "" {
110 | witnessCommitment, err := hex.DecodeString(rpcData.DefaultWitnessCommitment)
111 | if err != nil {
112 | log.Error(err)
113 | }
114 |
115 | txOutputBuffers = append([][]byte{bytes.Join([][]byte{
116 | utils.PackUint64LE(0),
117 | utils.VarIntBytes(uint64(len(witnessCommitment))),
118 | witnessCommitment,
119 | }, nil)}, txOutputBuffers...)
120 | }
121 |
122 | return bytes.Join([][]byte{
123 | utils.VarIntBytes(uint64(len(txOutputBuffers))),
124 | bytes.Join(txOutputBuffers, nil),
125 | }, nil)
126 | }
127 |
128 | func CreateGeneration(rpcData *daemons.GetBlockTemplate, publicKey, extraNoncePlaceholder []byte, reward string, txMessages bool, recipients []*config.Recipient) [][]byte {
129 | var txVersion int
130 | var txComment []byte
131 | txType := 0
132 | var txExtraPayload []byte
133 | if txMessages {
134 | txVersion = 2
135 | txComment = utils.SerializeString("by Command")
136 | } else {
137 | txVersion = 1
138 | txComment = make([]byte, 0)
139 | }
140 | txLockTime := 0
141 |
142 | if rpcData.CoinbasePayload != "" && len(rpcData.CoinbasePayload) > 0 {
143 | txVersion = 3
144 | txType = 5
145 | txExtraPayload, _ = hex.DecodeString(rpcData.CoinbasePayload)
146 | }
147 |
148 | txVersion = txVersion + (txType << 16)
149 |
150 | txInPrevOutHash := ""
151 | txInPrevOutIndex := 1<<32 - 1
152 | txInSequence := 0
153 |
154 | txTimestamp := make([]byte, 0)
155 | if reward == "POS" {
156 | txTimestamp = utils.PackUint32LE(rpcData.CurTime)
157 | }
158 |
159 | bCoinbaseAuxFlags, err := hex.DecodeString(rpcData.CoinbaseAux.Flags)
160 | if err != nil {
161 | log.Error(err)
162 | }
163 | scriptSigPart1 := bytes.Join([][]byte{
164 | utils.SerializeNumber(uint64(rpcData.Height)),
165 | bCoinbaseAuxFlags,
166 | utils.SerializeNumber(uint64(time.Now().Unix())),
167 | {byte(len(extraNoncePlaceholder))},
168 | }, nil)
169 |
170 | scriptSigPart2 := utils.SerializeString("/by Command/")
171 |
172 | p1 := bytes.Join([][]byte{
173 | utils.PackUint32LE(uint32(txVersion)),
174 | txTimestamp,
175 |
176 | // transaction input
177 | utils.VarIntBytes(1), // only one txIn
178 | utils.Uint256BytesFromHash(txInPrevOutHash),
179 | utils.PackUint32LE(uint32(txInPrevOutIndex)),
180 | utils.VarIntBytes(uint64(len(scriptSigPart1) + len(extraNoncePlaceholder) + len(scriptSigPart2))),
181 | scriptSigPart1,
182 | }, nil)
183 |
184 | outputTransactions := GenerateOutputTransactions(publicKey, recipients, rpcData)
185 |
186 | p2 := bytes.Join([][]byte{
187 | scriptSigPart2,
188 | utils.PackUint32LE(uint32(txInSequence)),
189 | // end transaction input
190 |
191 | // transaction output
192 | outputTransactions,
193 | // end transaction ouput
194 |
195 | utils.PackUint32LE(uint32(txLockTime)),
196 | txComment,
197 | }, nil)
198 |
199 | if len(txExtraPayload) > 0 {
200 | p2 = bytes.Join([][]byte{
201 | p2,
202 | utils.VarIntBytes(uint64(len(txExtraPayload))),
203 | txExtraPayload,
204 | }, nil)
205 | }
206 |
207 | return [][]byte{p1, p2}
208 | }
209 |
--------------------------------------------------------------------------------
/transactions/transactions_test.go:
--------------------------------------------------------------------------------
1 | package transactions
2 |
3 | import (
4 | "bytes"
5 | "encoding/hex"
6 | "encoding/json"
7 | "testing"
8 |
9 | "github.com/mining-pool/not-only-mining-pool/config"
10 | "github.com/mining-pool/not-only-mining-pool/daemons"
11 | "github.com/mining-pool/not-only-mining-pool/utils"
12 | )
13 |
14 | func TestCreateGeneration(t *testing.T) {
15 | data := `
16 | {
17 | "capabilities": [
18 | "proposal"
19 | ],
20 | "version": 536870912,
21 | "rules": [
22 | "csv",
23 | "segwit"
24 | ],
25 | "vbavailable": {
26 | },
27 | "vbrequired": 0,
28 | "previousblockhash": "b83b698bed0897ac94819041aec857d1a26a567bf7bf046d60849d5ccf24155e",
29 | "transactions": [
30 | {
31 | "data": "01000000012f8975c900f56662f35c317a0669fecc5fe0e1fb8ee53f4de72f1cb68c07e606010000008a473044022061a9ac17f269f3c69e18b5d67dfa6bf8b6a5a60eb7f9b0c992ffaeb66b5b88fb02202bb6fd7eb539302d97f4b8604bc822c91747e1cb365fecc91b37526b6b8c2c25014104fe67366f857106ee7b4cc48abb4dabd46302e12fe4140f4c933b92bd3ce75b1f4ae45055312f9a6c5ddc1f8d94d4f6d11e2a13372bcd6bfd651e48997b0f767effffffff02e8030000000000001976a914dffec839eba107e556d6c4f25f90765b3d10583288acbb60da04000000001976a914bdd83cf3ab8b7a57ff9b841752c1ae764f2a02ee88ac00000000",
32 | "txid": "f9b8b0bdd0dc38b2a707faf89acf064f543c3a88d39f54fb126cbd084ffb5ed9",
33 | "hash": "f9b8b0bdd0dc38b2a707faf89acf064f543c3a88d39f54fb126cbd084ffb5ed9",
34 | "depends": [
35 | ],
36 | "fee": 6450,
37 | "sigops": 8,
38 | "weight": 1028
39 | },
40 | {
41 | "data": "0200000001979a795a82096fc375487778939d9193bb284c58525e5df9c3a404c81c9220ef01000000d9004730440220086f0b09ded442c84e602520f5a8b38b41a1bc860fb595bd47834c20fa8db39402200a40cb86c15198302cabfd5c620c24fa6ac9ac5d946394e37dd3f9960b65a0e701473044022049bc0be153a4535196f73455bf82667956f2089019db4eeb57cb35649d8f69b202206ddd411917cb3e54f7b9a89c0693c971a6499eb6e421dce1d5fa9358300525d301475221025ad7eedea4c87b98463b8c7316c139f94c0e75fe4c849f42dab112479e1a1bb7210257591ace4d6a9fc94b8114cffd84df9bd0349c974a792580f7f5afb74f5ba94952ae0000000002102700000000000017a914b75a640760f2caae367c0e0cd6bfb85e8d80755987e17608000000000017a914ef20c4471b54fc47c93d587a318d351e93fbc13b8700000000",
42 | "txid": "620c724890f76b802714d786d5d3fe13a89106d81e93b74c4eafd6dc04179f37",
43 | "hash": "620c724890f76b802714d786d5d3fe13a89106d81e93b74c4eafd6dc04179f37",
44 | "depends": [
45 | ],
46 | "fee": 3493,
47 | "sigops": 8,
48 | "weight": 1328
49 | }
50 | ],
51 | "coinbaseaux": {
52 | "flags": ""
53 | },
54 | "coinbasevalue": 2500009943,
55 | "longpollid": "b83b698bed0897ac94819041aec857d1a26a567bf7bf046d60849d5ccf24155e19620",
56 | "target": "00000000a9490000000000000000000000000000000000000000000000000000",
57 | "mintime": 1581747579,
58 | "mutable": [
59 | "time",
60 | "transactions",
61 | "prevblock"
62 | ],
63 | "noncerange": "00000000ffffffff",
64 | "sigoplimit": 80000,
65 | "sizelimit": 4000000,
66 | "weightlimit": 4000000,
67 | "curtime": 1581749398,
68 | "bits": "1d00a949",
69 | "height": 1369986
70 | }
71 |
72 | `
73 |
74 | var rpcData daemons.GetBlockTemplate
75 | _ = json.Unmarshal([]byte(data), &rpcData)
76 |
77 | pk := utils.P2PKHAddressToScript("QPxrDq3sorCk8DWaYX2GeCkxoePhm1asyY")
78 | placeholder, _ := hex.DecodeString("f000000ff111111f")
79 |
80 | t.Log(hex.EncodeToString(utils.PackUint32LE(uint32(0))))
81 |
82 | gens := CreateGeneration(&rpcData, pk, placeholder, "POW", true, []*config.Recipient{})
83 |
84 | t.Log("0: ", hex.EncodeToString(gens[0]))
85 | t.Log("1: ", hex.EncodeToString(gens[1]))
86 |
87 | t1, _ := hex.DecodeString("02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1f0382e71404")
88 | if !bytes.Contains(gens[0], t1) {
89 | t.Fail()
90 | }
91 |
92 | if hex.EncodeToString(gens[1]) != "0c2f627920436f6d6d616e642f0000000001d71f0395000000001976a91424da8749fde8fcdcde60ba1c5afea8d2bd4a4f2688ac000000000a627920436f6d6d616e64" {
93 | t.Fail()
94 | }
95 | }
96 |
97 | func TestGenerateOutputTransactions(t *testing.T) {
98 | publicKey := utils.P2PKHAddressToScript("QPxrDq3sorCk8DWaYX2GeCkxoePhm1asyY")
99 | recipients := make([]*config.Recipient, 0)
100 | data := `
101 | {
102 | "capabilities": [
103 | "proposal"
104 | ],
105 | "version": 536870912,
106 | "rules": [
107 | "csv",
108 | "segwit"
109 | ],
110 | "vbavailable": {
111 | },
112 | "vbrequired": 0,
113 | "previousblockhash": "b83b698bed0897ac94819041aec857d1a26a567bf7bf046d60849d5ccf24155e",
114 | "transactions": [
115 | {
116 | "data": "01000000012f8975c900f56662f35c317a0669fecc5fe0e1fb8ee53f4de72f1cb68c07e606010000008a473044022061a9ac17f269f3c69e18b5d67dfa6bf8b6a5a60eb7f9b0c992ffaeb66b5b88fb02202bb6fd7eb539302d97f4b8604bc822c91747e1cb365fecc91b37526b6b8c2c25014104fe67366f857106ee7b4cc48abb4dabd46302e12fe4140f4c933b92bd3ce75b1f4ae45055312f9a6c5ddc1f8d94d4f6d11e2a13372bcd6bfd651e48997b0f767effffffff02e8030000000000001976a914dffec839eba107e556d6c4f25f90765b3d10583288acbb60da04000000001976a914bdd83cf3ab8b7a57ff9b841752c1ae764f2a02ee88ac00000000",
117 | "txid": "f9b8b0bdd0dc38b2a707faf89acf064f543c3a88d39f54fb126cbd084ffb5ed9",
118 | "hash": "f9b8b0bdd0dc38b2a707faf89acf064f543c3a88d39f54fb126cbd084ffb5ed9",
119 | "depends": [
120 | ],
121 | "fee": 6450,
122 | "sigops": 8,
123 | "weight": 1028
124 | },
125 | {
126 | "data": "0200000001979a795a82096fc375487778939d9193bb284c58525e5df9c3a404c81c9220ef01000000d9004730440220086f0b09ded442c84e602520f5a8b38b41a1bc860fb595bd47834c20fa8db39402200a40cb86c15198302cabfd5c620c24fa6ac9ac5d946394e37dd3f9960b65a0e701473044022049bc0be153a4535196f73455bf82667956f2089019db4eeb57cb35649d8f69b202206ddd411917cb3e54f7b9a89c0693c971a6499eb6e421dce1d5fa9358300525d301475221025ad7eedea4c87b98463b8c7316c139f94c0e75fe4c849f42dab112479e1a1bb7210257591ace4d6a9fc94b8114cffd84df9bd0349c974a792580f7f5afb74f5ba94952ae0000000002102700000000000017a914b75a640760f2caae367c0e0cd6bfb85e8d80755987e17608000000000017a914ef20c4471b54fc47c93d587a318d351e93fbc13b8700000000",
127 | "txid": "620c724890f76b802714d786d5d3fe13a89106d81e93b74c4eafd6dc04179f37",
128 | "hash": "620c724890f76b802714d786d5d3fe13a89106d81e93b74c4eafd6dc04179f37",
129 | "depends": [
130 | ],
131 | "fee": 3493,
132 | "sigops": 8,
133 | "weight": 1328
134 | }
135 | ],
136 | "coinbaseaux": {
137 | "flags": ""
138 | },
139 | "coinbasevalue": 2500009943,
140 | "longpollid": "b83b698bed0897ac94819041aec857d1a26a567bf7bf046d60849d5ccf24155e19620",
141 | "target": "00000000a9490000000000000000000000000000000000000000000000000000",
142 | "mintime": 1581747579,
143 | "mutable": [
144 | "time",
145 | "transactions",
146 | "prevblock"
147 | ],
148 | "noncerange": "00000000ffffffff",
149 | "sigoplimit": 80000,
150 | "sizelimit": 4000000,
151 | "weightlimit": 4000000,
152 | "curtime": 1581749398,
153 | "bits": "1d00a949",
154 | "height": 1369986
155 | }
156 |
157 | `
158 | var rpcData daemons.GetBlockTemplate
159 | json.Unmarshal([]byte(data), &rpcData)
160 | t.Log(hex.EncodeToString(GenerateOutputTransactions(publicKey, recipients, &rpcData)))
161 | }
162 |
163 | // 00000020fb08e0b3cb0f759671af79f108dd2dbd1a378ba27968c176c1c6d64f94741d262a7ca761bb4397d2c1a7f6cf457d680f054d43cc4f860de21b776054ab93a3cafbe34b5effff0f1e00452ef00401000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1f0377ee1404fce34b5e086b3c0000000000000c2f627920436f6d6d616e642f00000000020000000000000000266a24aa21a9ed8a44e041a5a86878a1742f66fe7196400e784fee5cdc70a4becaf51c8f4a4f0266140395000000001976a91424da8749fde8fcdcde60ba1c5afea8d2bd4a4f2688ac0000000001000000000101df2565bde1779eaa6aad06a03a5262d324de29aa51735ba26d2301c0af426ee90100000000ffffffff020000000000000000136a0c0701007ac0010000c0000000530345d47106fa2f0b0000000016001407fa56d069e6174b6fa1ca3e27556be765064e150247304402203fb97652eee91717f61a9a9a66c8c233648ac3f5942aeb246c2217ffdc64b5f70220210653c0bc9c74b026e80e77a3221a6c64c9529d37a2051c53aefb19213f381d012102a56c007c837c6323332f03f2d22190f1da0aec10c2338da50ebcec85100e9a96000000000100000000010129d40378ffb37a1b2b751e4469aed63df827636e538e76550f6629dc49978f0b0100000000f0ffffff0340420f00000000001976a914ab83ab1e9284beca76ecdd1460f732acdeb5a45688ac0bd9460000000000160014756b524ee4ec544d7828cb849951b75bf46cf9d30000000000000000196a1768747470733a2f2f746c74632e6269746170732e636f6d02483045022100ab49baf3f2f0ebc910f2d7453a5a50bc10810a17d9a77ddc98638b1fc1a93929022062d798622d7f5a55655e1356b9e0e7fb0d2c40d70df0c10c5ba91311a085559c012102ab861da09e496373d8aee62107d68f8275df04dca403c182b6ab648eebb4aca50000000001000000000101fdaffc6f8c94565763bdf4c0e50c389c5eb817d2dff247a0ffc519dda211dce90100000000f0ffffff0340420f00000000001976a914ab83ab1e9284beca76ecdd1460f732acdeb5a45688ac29d5440000000000160014d3cb800cd29671af47dfd95fcb759a7e76e4b0dd0000000000000000196a1768747470733a2f2f746c74632e6269746170732e636f6d02473044022060f807e10801d10ba51870bbbaee01d80d3727730b43b9c710af80857c14e4d102201002940096d20427246a7738c358b29675108260b18cc612922b1a01b2a588ab0121031506590ee0b0a9cfa13dbc765d9ac9666e5e01d031c5bd5b5e293bcdeb2932af00000000
164 | // 00000020763600ad521ebbb8be835992a5f7e1e315d3978934ed805bfffd2e88b7d65c7c073172cf11eb40f1b663749268e2c63b1c2b1e54fb80d69467c9b5017eb9437cc0e34b5effff0f1e002aaa0e0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1f0370ee1404c1e34b5e0840000000000000000c2f627920436f6d6d616e642f00000000020000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf900f90295000000001976a91424da8749fde8fcdcde60ba1c5afea8d2bd4a4f2688ac00000000
165 |
--------------------------------------------------------------------------------
/types/block.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | // Block is a basic type for db storage
4 | type Block struct {
5 | Hash string `json:"hash"`
6 | Confirmations int `json:"confirmations"`
7 | StrippedSize int `json:"strippedsize"`
8 | Size int `json:"size"`
9 | Weight int `json:"weight"`
10 | Height int `json:"height"`
11 | Version int `json:"version"`
12 | VersionHex string `json:"versionHex"`
13 | MerkleRoot string `json:"merkleroot"`
14 | Tx []string `json:"tx"`
15 | Time int `json:"time"`
16 | MedianTime int `json:"mediantime"`
17 | Nonce int `json:"nonce"`
18 | Bits string `json:"bits"`
19 | Difficulty float64 `json:"difficulty"`
20 | ChainWork string `json:"chainwork"`
21 | NTx int `json:"nTx"`
22 | PreviousBlockHash string `json:"previousblockhash"`
23 | }
24 |
--------------------------------------------------------------------------------
/types/errors.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | type ErrorWrap int
4 |
5 | const (
6 | ErrJobNotFound ErrorWrap = 20
7 | ErrIncorrectExtraNonce2Size ErrorWrap = 21
8 | ErrIncorrectNTimeSize ErrorWrap = 22
9 | ErrNTimeOutOfRange ErrorWrap = 23
10 | ErrIncorrectNonceSize ErrorWrap = 24
11 | ErrDuplicateShare ErrorWrap = 25
12 | ErrLowDiffShare ErrorWrap = 26
13 | )
14 |
15 | var codeToErrMap = map[int]string{
16 | 10: "you are banned by pool",
17 | 20: "job not found",
18 | 21: "incorrect size of extranonce2",
19 | 22: "incorrect size of ntime",
20 | 23: "ntime out of range",
21 | 24: "incorrect size of nonce",
22 | 25: "duplicate share",
23 | 26: "low difficulty share",
24 | }
25 |
26 | func (err ErrorWrap) String() string {
27 | return codeToErrMap[int(err)]
28 | }
29 |
--------------------------------------------------------------------------------
/types/share.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "net"
5 | )
6 |
7 | type Share struct {
8 | JobId string `json:"jobId"`
9 | RemoteAddr net.Addr `json:"remoteAddr"`
10 | Miner string `json:"miner"`
11 | Rig string `json:"rig"`
12 | ErrorCode ErrorWrap `json:"errorCode"`
13 | BlockHeight int64 `json:"height"`
14 | BlockReward uint64 `json:"blockReward"`
15 | Diff float64 `json:"shareDiff"`
16 | BlockHash string `json:"blockHash"`
17 | BlockHex string `json:"blockHex"`
18 | TxHash string `json:"txHash"`
19 | }
20 |
--------------------------------------------------------------------------------
/types/stats.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | // Stats a cache for api, reducing the fetch times of pool status
4 | //type Stats struct {
5 | // hashrate
6 | // workerCount
7 | // blocks
8 | //}
9 |
--------------------------------------------------------------------------------
/utils/utils.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "bytes"
5 | "crypto/rand"
6 | "crypto/sha256"
7 | "encoding/binary"
8 | "encoding/hex"
9 | "encoding/json"
10 | "fmt"
11 | "math/big"
12 | "os"
13 | "strconv"
14 | "strings"
15 |
16 | "github.com/c0mm4nd/go-bech32"
17 | logging "github.com/ipfs/go-log/v2"
18 | "github.com/mr-tron/base58"
19 | )
20 |
21 | var log = logging.Logger("utils")
22 |
23 | func RandPositiveInt64() int64 {
24 | randomNumBytes := make([]byte, 8)
25 | _, err := rand.Read(randomNumBytes)
26 | if err != nil {
27 | log.Error(err)
28 | }
29 |
30 | i := int64(binary.LittleEndian.Uint64(randomNumBytes))
31 | if i > 0 {
32 | return i
33 | } else {
34 | return -i
35 | }
36 | }
37 |
38 | func RandHexUint64() string {
39 | randomNumBytes := make([]byte, 8)
40 | _, err := rand.Read(randomNumBytes)
41 | if err != nil {
42 | log.Error(err)
43 | }
44 |
45 | return hex.EncodeToString(randomNumBytes)
46 | }
47 |
48 | func PackUint64LE(n uint64) []byte {
49 | b := make([]byte, 8)
50 | binary.LittleEndian.PutUint64(b, n)
51 | return b
52 | }
53 |
54 | func PackInt64BE(n int64) []byte {
55 | b := make([]byte, 8)
56 | binary.BigEndian.PutUint64(b, uint64(n))
57 | return b
58 | }
59 |
60 | func PackUint64BE(n uint64) []byte {
61 | b := make([]byte, 8)
62 | binary.BigEndian.PutUint64(b, n)
63 | return b
64 | }
65 |
66 | func PackUint32LE(n uint32) []byte {
67 | b := make([]byte, 4)
68 | binary.LittleEndian.PutUint32(b, n)
69 | return b
70 | }
71 |
72 | func PackUint32BE(n uint32) []byte {
73 | b := make([]byte, 4)
74 | binary.BigEndian.PutUint32(b, n)
75 | return b
76 | }
77 |
78 | func PackInt32BE(n int32) []byte {
79 | b := make([]byte, 4)
80 | binary.BigEndian.PutUint32(b, uint32(n))
81 | return b
82 | }
83 |
84 | func PackUint16LE(n uint16) []byte {
85 | b := make([]byte, 2)
86 | binary.LittleEndian.PutUint16(b, n)
87 | return b
88 | }
89 |
90 | func PackUint16BE(n uint16) []byte {
91 | b := make([]byte, 2)
92 | binary.BigEndian.PutUint16(b, n)
93 | return b
94 | }
95 |
96 | func VarIntBytes(n uint64) []byte {
97 | if n < 0xFD {
98 | return []byte{byte(n)}
99 | }
100 |
101 | if n <= 0xFFFF {
102 | buff := make([]byte, 3)
103 | buff[0] = 0xFD
104 | binary.LittleEndian.PutUint16(buff[1:], uint16(n))
105 | return buff
106 | }
107 |
108 | if n <= 0xFFFFFFFF {
109 | buff := make([]byte, 5)
110 | buff[0] = 0xFE
111 | binary.LittleEndian.PutUint32(buff[1:], uint32(n))
112 | return buff
113 | }
114 |
115 | buff := make([]byte, 9)
116 | buff[0] = 0xFF
117 | binary.LittleEndian.PutUint64(buff[1:], uint64(n))
118 | return buff
119 | }
120 |
121 | func VarStringBytes(str string) []byte {
122 | bStr := []byte(str)
123 | return bytes.Join([][]byte{
124 | VarIntBytes(uint64(len(bStr))),
125 | bStr,
126 | }, nil)
127 | }
128 |
129 | func SerializeString(s string) []byte {
130 | if len(s) < 253 {
131 | return bytes.Join([][]byte{
132 | {byte(len(s))},
133 | []byte(s),
134 | }, nil)
135 | } else if len(s) < 0x10000 {
136 | return bytes.Join([][]byte{
137 | {253},
138 | PackUint16LE(uint16(len(s))),
139 | []byte(s),
140 | }, nil)
141 | } else if len(s) < 0x100000000 {
142 | return bytes.Join([][]byte{
143 | {254},
144 | PackUint32LE(uint32(len(s))),
145 | []byte(s),
146 | }, nil)
147 | } else {
148 | return bytes.Join([][]byte{
149 | {255},
150 | PackUint64LE(uint64(len(s))),
151 | []byte(s),
152 | }, nil)
153 | }
154 | }
155 |
156 | func SerializeNumber(n uint64) []byte {
157 | if n >= 1 && n <= 16 {
158 | return []byte{
159 | 0x50 + byte(n),
160 | }
161 | }
162 |
163 | l := 1
164 | buff := make([]byte, 9)
165 | for n > 0x7f {
166 | buff[l] = byte(n & 0xff)
167 | l++
168 | n >>= 8
169 | }
170 | buff[0] = byte(l)
171 | buff[l] = byte(n)
172 |
173 | return buff[0 : l+1]
174 | }
175 |
176 | func Uint256BytesFromHash(h string) []byte {
177 | container := make([]byte, 32)
178 | fromHex, err := hex.DecodeString(h)
179 | if err != nil {
180 | log.Error(err)
181 | }
182 |
183 | copy(container, fromHex)
184 |
185 | return ReverseBytes(container)
186 | }
187 |
188 | func ReverseBytes(b []byte) []byte {
189 | _b := make([]byte, len(b))
190 | copy(_b, b)
191 |
192 | for i, j := 0, len(_b)-1; i < j; i, j = i+1, j-1 {
193 | _b[i], _b[j] = _b[j], _b[i]
194 | }
195 | return _b
196 | }
197 |
198 | // range steps between [start, end)
199 | func Range(start, stop, step int) []int {
200 | if (step > 0 && start >= stop) || (step < 0 && start <= stop) {
201 | return []int{}
202 | }
203 |
204 | result := make([]int, 0)
205 | i := start
206 | for {
207 | if step > 0 {
208 | if i < stop {
209 | result = append(result, i)
210 | } else {
211 | break
212 | }
213 | } else {
214 | if i > stop {
215 | result = append(result, i)
216 | } else {
217 | break
218 | }
219 | }
220 | i += step
221 | }
222 |
223 | return result
224 | }
225 |
226 | func Sha256(b []byte) []byte {
227 | b32 := sha256.Sum256(b)
228 | return b32[:]
229 | }
230 |
231 | func Sha256d(b []byte) []byte {
232 | return Sha256(Sha256(b))
233 | }
234 |
235 | func BytesIndexOf(data [][]byte, element []byte) int {
236 | for k, v := range data {
237 | if bytes.Equal(element, v) {
238 | return k
239 | }
240 | }
241 | return -1 // not found.
242 | }
243 |
244 | func StringsIndexOf(data []string, element string) int {
245 | for k, v := range data {
246 | if strings.Compare(element, v) == 0 {
247 | return k
248 | }
249 | }
250 | return -1 // not found.
251 | }
252 |
253 | func BigIntFromBitsHex(bits string) *big.Int {
254 | bBits, err := hex.DecodeString(bits)
255 | if err != nil {
256 | log.Panic(err)
257 | }
258 | return BigIntFromBitsBytes(bBits)
259 | }
260 |
261 | func BigIntFromBitsBytes(bits []byte) *big.Int {
262 | bytesNumber := bits[0]
263 |
264 | bigBits := new(big.Int).SetBytes(bits[1:])
265 | return new(big.Int).Mul(bigBits, new(big.Int).Exp(big.NewInt(2), big.NewInt(8*int64(bytesNumber-3)), nil))
266 | }
267 |
268 | // LE <-> BE
269 | func ReverseByteOrder(b []byte) []byte {
270 | _b := make([]byte, len(b))
271 | copy(_b, b)
272 |
273 | for i := 0; i < 8; i++ {
274 | binary.LittleEndian.PutUint32(_b[i*4:], binary.BigEndian.Uint32(_b[i*4:]))
275 | }
276 | return ReverseBytes(_b)
277 | }
278 |
279 | // For POS coins - used to format wallet address for use in generation transaction's output
280 | func PublicKeyToScript(key string) []byte {
281 | if len(key) != 66 {
282 | log.Panic("Invalid public key: " + key)
283 | }
284 |
285 | bKey := make([]byte, 35)
286 | bKey[0] = 0x21
287 | bKey[34] = 0xAC
288 | b, _ := hex.DecodeString(key)
289 | copy(bKey[1:], b)
290 |
291 | return bKey
292 | }
293 |
294 | // For POW coins - used to format wallet address for use in generation transaction's output
295 | // Works for p2pkh only
296 | func P2PKHAddressToScript(addr string) []byte {
297 | decoded, err := base58.FastBase58Decoding(addr)
298 | if decoded == nil || err != nil {
299 | log.Fatal("base58 decode failed for " + addr)
300 | }
301 |
302 | if len(decoded) != 25 {
303 | log.Panic("invalid address length for " + addr)
304 | }
305 |
306 | publicKey := decoded[1 : len(decoded)-4]
307 |
308 | return bytes.Join([][]byte{
309 | {0x76, 0xA9, 0x14},
310 | publicKey,
311 | {0x88, 0xAC},
312 | }, nil)
313 | }
314 |
315 | func P2SHAddressToScript(addr string) []byte {
316 | decoded, err := base58.FastBase58Decoding(addr)
317 | if decoded == nil || err != nil {
318 | log.Fatal("base58 decode failed for " + addr)
319 | }
320 |
321 | if len(decoded) != 25 {
322 | log.Panic("invalid address length for " + addr)
323 | }
324 |
325 | publicKey := decoded[1 : len(decoded)-4]
326 |
327 | return bytes.Join([][]byte{
328 | {0xA9, 0x14},
329 | publicKey,
330 | {0x87},
331 | }, nil)
332 | }
333 |
334 | func P2WSHAddressToScript(addr string) []byte {
335 | _, decoded, err := bech32.Decode(addr)
336 | if decoded == nil || err != nil {
337 | log.Fatal("bech32 decode failed for " + addr)
338 | }
339 | witnessProgram, err := bech32.ConvertBits(decoded[1:], 5, 8, true)
340 | if err != nil {
341 | log.Panic("")
342 | }
343 |
344 | return bytes.Join([][]byte{
345 | {0x00, 0x14},
346 | witnessProgram,
347 | }, nil)
348 | }
349 |
350 | func ScriptPubKeyToScript(addr string) []byte {
351 | decoded, err := hex.DecodeString(addr)
352 | if decoded == nil || err != nil {
353 | log.Fatal("hex decode failed for " + addr)
354 | }
355 | return decoded
356 | }
357 |
358 | func HexDecode(b []byte) []byte {
359 | dst := make([]byte, hex.DecodedLen(len(b)))
360 | _, err := hex.Decode(dst, b)
361 | if err != nil {
362 | log.Panic("failed to decode hex: ", string(b))
363 | }
364 |
365 | return dst
366 | }
367 |
368 | func HexEncode(b []byte) []byte {
369 | dst := make([]byte, hex.EncodedLen(len(b)))
370 | hex.Encode(dst, b)
371 |
372 | return dst
373 | }
374 |
375 | func Jsonify(i interface{}) []byte {
376 | r, err := json.Marshal(i)
377 | if err != nil {
378 | log.Error("Jsonify: ", err)
379 | return nil
380 | }
381 |
382 | return r
383 | }
384 |
385 | func JsonifyIndentString(i interface{}) string {
386 | r, err := json.MarshalIndent(i, "", " ")
387 | if err != nil {
388 | log.Error("JsonifyIndentString: ", err)
389 | return ""
390 | }
391 | return string(r)
392 | }
393 |
394 | func SatoshisToCoins(satoshis uint64, magnitude int, coinPrecision int) float64 {
395 | coins := float64(satoshis) / float64(magnitude)
396 | coins, _ = strconv.ParseFloat(fmt.Sprintf("%."+strconv.Itoa(coinPrecision)+"f", coins), 64)
397 | return coins
398 | }
399 |
400 | func CoinsToSatoshis(coins float64, magnitude int, coinPrecision int) uint64 {
401 | return uint64(coins * float64(magnitude))
402 | }
403 |
404 | func GetReadableHashRateString(hashrate float64) string {
405 | i := 0
406 | byteUnits := []string{" H", " KH", " MH", " GH", " TH", " PH", " EH", " ZH", " YH"}
407 | for hashrate > 1000 {
408 | i++
409 | hashrate = hashrate / 1000
410 | if i+1 == len(byteUnits) {
411 | break
412 | }
413 | }
414 |
415 | return strconv.FormatFloat(hashrate, 'f', 7, 64) + byteUnits[i]
416 | }
417 |
418 | func MiningKeyToScript(addrMiningKey string) []byte {
419 | b, err := hex.DecodeString(addrMiningKey)
420 | if err != nil {
421 | log.Fatal("Failed to decode addr mining key: ", addrMiningKey)
422 | }
423 |
424 | return bytes.Join([][]byte{
425 | {0x76, 0xA9, 0x14},
426 | b,
427 | {0x88, 0xAC},
428 | }, nil)
429 | }
430 |
431 | func RawJsonToString(raw json.RawMessage) string {
432 | var str string
433 | err := json.Unmarshal(raw, &str)
434 | if err != nil {
435 | log.Error(err)
436 | }
437 |
438 | return str
439 | }
440 |
441 | func FixedLenStringBytes(s string, l int) []byte {
442 | b := make([]byte, l)
443 | copy(b, s)
444 | return b
445 | }
446 |
447 | func CommandStringBytes(s string) []byte {
448 | return FixedLenStringBytes(s, 12)
449 | }
450 |
451 | func FileExists(filename string) bool {
452 | info, err := os.Stat(filename)
453 | if os.IsNotExist(err) {
454 | return false
455 | }
456 | return !info.IsDir()
457 | }
458 |
--------------------------------------------------------------------------------
/utils/utils_test.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "bytes"
5 | "encoding/hex"
6 | "fmt"
7 | "reflect"
8 | "testing"
9 |
10 | "github.com/c0mm4nd/go-bech32"
11 | )
12 |
13 | func TestSerializeNumber(t *testing.T) {
14 | if !bytes.Equal(SerializeNumber(100), []byte{0x01, 0x64}) {
15 | fmt.Println(SerializeNumber(100))
16 | t.Fail()
17 | }
18 |
19 | if hex.EncodeToString(SerializeNumber(1<<31-1)) != "04ffffff7f" {
20 | t.Fail()
21 | }
22 | }
23 |
24 | func TestSerializeString(t *testing.T) {
25 | if !bytes.Equal(SerializeString("HelloWorld"), []byte{0x0a, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x57, 0x6f, 0x72, 0x6c, 0x64}) {
26 | fmt.Println(SerializeString("HelloWorld"))
27 | t.Fail()
28 | }
29 | }
30 |
31 | func TestReverseByteOrder(t *testing.T) {
32 | hash := Sha256([]byte("0000"))
33 | fmt.Println(hash)
34 | fmt.Println(ReverseByteOrder(hash))
35 | }
36 |
37 | func TestReverseBytes(t *testing.T) {
38 | hash := Sha256([]byte("0000"))
39 | fmt.Println(hash)
40 | fmt.Println(ReverseBytes(hash))
41 | fmt.Println(hex.EncodeToString(hash))
42 | fmt.Println(hex.EncodeToString(ReverseBytes(hash)))
43 | }
44 |
45 | func TestUint256BytesFromHash(t *testing.T) {
46 | result, _ := hex.DecodeString("691938264876d1078051da4e30ec0643262e8b93fca661f525fe7122b38d5f18")
47 | if !bytes.Equal(Uint256BytesFromHash(hex.EncodeToString(Sha256([]byte("Hello")))), result) {
48 | t.Fail()
49 | }
50 | }
51 |
52 | func TestVarIntBytes(t *testing.T) {
53 | if hex.EncodeToString(VarIntBytes(uint64(23333))) != "fd255b" {
54 | t.Fail()
55 | }
56 |
57 | t.Log(int64(1<<31 - 1))
58 | if hex.EncodeToString(VarIntBytes(uint64(1<<31-1))) != "feffffff7f" {
59 | t.Fail()
60 | }
61 | }
62 |
63 | func TestVarStringBytes(t *testing.T) {
64 | if hex.EncodeToString(VarStringBytes("Hello")) != "0548656c6c6f" {
65 | t.Fail()
66 | }
67 | }
68 |
69 | func TestRange(t *testing.T) {
70 | if !reflect.DeepEqual(Range(0, 8, 2), []int{0, 2, 4, 6}) {
71 | t.Fail()
72 | }
73 |
74 | if !reflect.DeepEqual(Range(2, 0, 2), []int{}) {
75 | t.Fail()
76 | }
77 | }
78 |
79 | func TestSha256d(t *testing.T) {
80 | hexStr := "01000000" +
81 | "81cd02ab7e569e8bcd9317e2fe99f2de44d49ab2b8851ba4a308000000000000" +
82 | "e320b6c2fffc8d750423db8b1eb942ae710e951ed797f7affc8892b0f1fc122b" +
83 | "c7f5d74d" +
84 | "f2b9441a" +
85 | "42a14695"
86 | raw, _ := hex.DecodeString(hexStr)
87 | if hex.EncodeToString(Sha256d(raw)) != "1dbd981fe6985776b644b173a4d0385ddc1aa2a829688d1e0000000000000000" {
88 | t.Fail()
89 | }
90 | }
91 |
92 | func TestP2PKHAddressToScript(t *testing.T) {
93 | addr := "mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn"
94 |
95 | t.Log(hex.EncodeToString(P2PKHAddressToScript(addr)))
96 | }
97 |
98 | func TestP2SHAddressToScript(t *testing.T) {
99 | addr := "QcGaxM7GsauRBS4CD2rzkE34HZci2kBeF4"
100 |
101 | t.Log(hex.EncodeToString(P2SHAddressToScript(addr)))
102 | }
103 |
104 | func TestP2WSHAddressToScript(t *testing.T) {
105 | addr := "tb1qphxtwvfxyjhq5ar2hn65eczg8u6stam2n7znx5"
106 | str, decoded, err := bech32.Decode(addr)
107 | if decoded == nil || err != nil {
108 | log.Fatal("base58 decode failed for " + addr)
109 | }
110 | if len(decoded) == 0 || len(decoded) > 65 {
111 | log.Fatal("invalid address length for " + addr)
112 | }
113 | t.Log(str)
114 | witnessProgram, _ := bech32.ConvertBits(decoded[1:], 5, 8, true)
115 |
116 | t.Log(len(decoded))
117 | // publicKey := decoded[1:len(decoded)]
118 |
119 | t.Log(hex.EncodeToString(witnessProgram))
120 | }
121 |
122 | func TestCommandStringBytes(t *testing.T) {
123 | if hex.EncodeToString(CommandStringBytes("version")) != "76657273696f6e0000000000" {
124 | t.Fail()
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/vardiff/ringbuffer.go:
--------------------------------------------------------------------------------
1 | package vardiff
2 |
3 | type RingBuffer struct {
4 | IsFull bool
5 | MaxSize int64
6 | Cursor int64
7 | Data []int64
8 | }
9 |
10 | func NewRingBuffer(maxSize int64) *RingBuffer {
11 | return &RingBuffer{
12 | IsFull: false,
13 | MaxSize: maxSize,
14 | Cursor: 0,
15 | Data: make([]int64, 0),
16 | }
17 | }
18 |
19 | func (rb *RingBuffer) Append(x int64) {
20 | if rb.IsFull {
21 | rb.Data[rb.Cursor] = x
22 | rb.Cursor = (rb.Cursor + 1) % rb.MaxSize
23 | } else {
24 | rb.Data = append(rb.Data, x)
25 | rb.Cursor++
26 | if int64(len(rb.Data)) == rb.MaxSize {
27 | rb.Cursor = 0
28 | rb.IsFull = true
29 | }
30 | }
31 | }
32 |
33 | func (rb *RingBuffer) Avg() float64 {
34 | var sum int64
35 | for i := range rb.Data {
36 | sum = sum + rb.Data[i]
37 | }
38 |
39 | return float64(sum) / float64(rb.Size())
40 | }
41 |
42 | func (rb *RingBuffer) Size() int64 {
43 | if rb.IsFull {
44 | return rb.MaxSize
45 | } else {
46 | return rb.Cursor
47 | }
48 | }
49 |
50 | func (rb *RingBuffer) Clear() {
51 | rb.Data = make([]int64, 0)
52 | rb.Cursor = 0
53 | rb.IsFull = false
54 | }
55 |
--------------------------------------------------------------------------------
/vardiff/vardiff.go:
--------------------------------------------------------------------------------
1 | package vardiff
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/mining-pool/not-only-mining-pool/config"
7 | )
8 |
9 | type VarDiff struct {
10 | Options *config.VarDiffOptions
11 | BufferSize int64
12 | MaxTargetTime float64
13 | MinTargetTime float64
14 |
15 | TimeBuffer *RingBuffer
16 | LastRtc int64
17 | LastTimestamp int64
18 | }
19 |
20 | func NewVarDiff(options *config.VarDiffOptions) *VarDiff {
21 | timestamp := time.Now().Unix()
22 | bufferSize := options.RetargetTime / options.TargetTime * 4
23 | return &VarDiff{
24 | Options: options,
25 | BufferSize: bufferSize,
26 | MaxTargetTime: float64(options.TargetTime) * (1 + options.VariancePercent),
27 | MinTargetTime: float64(options.TargetTime) * (1 - options.VariancePercent),
28 | TimeBuffer: NewRingBuffer(bufferSize),
29 | LastRtc: timestamp - options.RetargetTime/2,
30 | LastTimestamp: timestamp,
31 | }
32 | }
33 |
34 | //func (vd *VarDiff) ManagePort(, ) {
35 | // stratumPort := client.Socket.LocalAddr().(*net.TCPAddr).Port
36 | //}
37 |
38 | // On SubmitEvent
39 | // then client.EnqueueNextDifficulty(newDiff)
40 | func (vd *VarDiff) CalcNextDiff(currentDiff float64) (newDiff float64) {
41 | timestamp := time.Now().Unix()
42 |
43 | if vd.LastRtc == 0 {
44 | vd.LastRtc = timestamp - vd.Options.RetargetTime/2
45 | vd.LastTimestamp = timestamp
46 | return
47 | }
48 |
49 | sinceLast := timestamp - vd.LastTimestamp
50 |
51 | vd.TimeBuffer.Append(sinceLast)
52 | vd.LastTimestamp = timestamp
53 |
54 | if (timestamp-vd.LastRtc) < vd.Options.RetargetTime && vd.TimeBuffer.Size() > 0 {
55 | return
56 | }
57 |
58 | vd.LastRtc = timestamp
59 | avg := vd.TimeBuffer.Avg()
60 | ddiff := float64(time.Duration(vd.Options.TargetTime)*time.Second) / avg
61 |
62 | // currentDiff, _ := client.Difficulty.Float64()
63 |
64 | if avg > vd.MaxTargetTime && currentDiff > vd.Options.MinDiff {
65 | if vd.Options.X2Mode {
66 | ddiff = 0.5
67 | }
68 |
69 | if ddiff*currentDiff < vd.Options.MinDiff {
70 | ddiff = vd.Options.MinDiff / currentDiff
71 | }
72 | } else if avg < vd.MinTargetTime {
73 | if vd.Options.X2Mode {
74 | ddiff = 2
75 | }
76 |
77 | diffMax := vd.Options.MaxDiff
78 |
79 | if ddiff*currentDiff > diffMax {
80 | ddiff = diffMax / currentDiff
81 | }
82 | } else {
83 | return currentDiff
84 | }
85 |
86 | newDiff = currentDiff * ddiff
87 |
88 | if newDiff <= 0 {
89 | newDiff = currentDiff
90 | }
91 |
92 | vd.TimeBuffer.Clear()
93 | return
94 | }
95 |
--------------------------------------------------------------------------------
/vardiff/vardiff_test.go:
--------------------------------------------------------------------------------
1 | package vardiff
2 |
3 | import "testing"
4 |
5 | func TestNewVarDiff(t *testing.T) {
6 | // vd := NewVarDiff()
7 | }
8 |
--------------------------------------------------------------------------------