diff --git a/go.mod b/go.mod index e6a0e40..92adc07 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/J-A-M-P-S/structs v1.1.0 github.com/dominant-strategies/go-quai v0.22.0 github.com/gorilla/mux v1.8.0 + github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/robfig/cron v1.2.0 gopkg.in/redis.v3 v3.6.4 ) diff --git a/go.sum b/go.sum index 90e173e..4e58238 100644 --- a/go.sum +++ b/go.sum @@ -79,14 +79,6 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUn github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dominant-strategies/bn256 v0.0.0-20220930122411-fbf930a7493d h1:hkL13khTTS48QfWmjRFWpuzhOqu6S0cjpJOzPoBEDb4= github.com/dominant-strategies/bn256 v0.0.0-20220930122411-fbf930a7493d/go.mod h1:nvtPJPChairu4o4iX2XGrstOFpLaAgNYhrUCl5bSng4= -github.com/dominant-strategies/go-quai v0.13.0-rc.0 h1:aZpS8S8KF9N9S7zagdfLMegWmp+sFSXjFuaQtWeFO/g= -github.com/dominant-strategies/go-quai v0.13.0-rc.0/go.mod h1:LQ/o4Mrx1YV/4TJK2o3fKuJIx811LpirNCk1FrrxkTQ= -github.com/dominant-strategies/go-quai v0.14.0-rc.0 h1:5GODoCKo4AaC4kt0WccYNeYDRj8o/8n0G3yUsii499E= -github.com/dominant-strategies/go-quai v0.14.0-rc.0/go.mod h1:LQ/o4Mrx1YV/4TJK2o3fKuJIx811LpirNCk1FrrxkTQ= -github.com/dominant-strategies/go-quai v0.15.0-rc.0 h1:lfrmmSEr8KImBwY719N6LDKwVZXNY2w8U5ocYEsOpvU= -github.com/dominant-strategies/go-quai v0.15.0-rc.0/go.mod h1:LQ/o4Mrx1YV/4TJK2o3fKuJIx811LpirNCk1FrrxkTQ= -github.com/dominant-strategies/go-quai v0.21.2 h1:UTzVvsx/bxrdVmNgyAm7Y/R9eOVWvAW8Ou78Q5/9Md0= -github.com/dominant-strategies/go-quai v0.21.2/go.mod h1:LQ/o4Mrx1YV/4TJK2o3fKuJIx811LpirNCk1FrrxkTQ= github.com/dominant-strategies/go-quai v0.22.0 h1:5G0jxGfQZZFHtsoPdBDO6oZHzJFnscxjFCjQTUYXDE0= github.com/dominant-strategies/go-quai v0.22.0/go.mod h1:LQ/o4Mrx1YV/4TJK2o3fKuJIx811LpirNCk1FrrxkTQ= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -174,6 +166,8 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hnlq715/golang-lru v0.4.0 h1:gyo/wIvLE6Upf1wucAfwTjpR+BQ5Lli2766H2MnNPv0= github.com/hnlq715/golang-lru v0.4.0/go.mod h1:RBkgDAtlu0SgTPvpb4VW2/RQnkCBMRD3Lr6B9RhsAS8= @@ -511,6 +505,7 @@ gopkg.in/bsm/ratelimit.v1 v1.0.0-20170922094635-f56db5e73a5e/go.mod h1:KF9sEfUPA gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= diff --git a/proxy/blocks.go b/proxy/blocks.go index 2def275..943e54f 100644 --- a/proxy/blocks.go +++ b/proxy/blocks.go @@ -17,6 +17,7 @@ type BlockTemplate struct { Target *big.Int Difficulty *big.Int Height []*big.Int + JobID uint } type Block struct { diff --git a/proxy/proxy.go b/proxy/proxy.go index f824ea2..df452d4 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -3,24 +3,27 @@ package proxy import ( "context" "encoding/json" + "fmt" "log" "math/big" "net" "net/http" + "strings" "sync" "sync/atomic" "time" + "github.com/dominant-strategies/go-quai-stratum/policy" + "github.com/dominant-strategies/go-quai-stratum/storage" + "github.com/dominant-strategies/go-quai-stratum/util" "github.com/dominant-strategies/go-quai/common" "github.com/dominant-strategies/go-quai/consensus" "github.com/dominant-strategies/go-quai/consensus/progpow" "github.com/dominant-strategies/go-quai/core/types" "github.com/dominant-strategies/go-quai/quaiclient/ethclient" - "github.com/gorilla/mux" - "github.com/dominant-strategies/go-quai-stratum/policy" - "github.com/dominant-strategies/go-quai-stratum/storage" - "github.com/dominant-strategies/go-quai-stratum/util" + "github.com/gorilla/mux" + lru "github.com/hashicorp/golang-lru/v2/expirable" ) const ( @@ -42,6 +45,9 @@ type ProxyServer struct { // Channel to receive header updates updateCh chan *types.Header + // Keep track of previous headers + headerCache *lru.LRU[uint, *types.Header] + // Stratum sessionsMu sync.RWMutex sessions map[*Session]struct{} @@ -86,7 +92,8 @@ func NewProxy(cfg *Config, backend *storage.RedisClient) *ProxyServer { nil, false, ), - updateCh: make(chan *types.Header, c_updateChSize), + updateCh: make(chan *types.Header, c_updateChSize), + headerCache: lru.NewLRU[uint, *types.Header](10, nil, 600*time.Second), } proxy.diff = util.GetTargetHex(cfg.Proxy.Difficulty) @@ -300,9 +307,63 @@ func (s *ProxyServer) updateBlockTemplate(pendingHeader *types.Header) { Height: pendingHeader.NumberArray(), } + if t == nil { + newTemplate.JobID = 0 + } else { + newTemplate.JobID = t.JobID + 1 + } + s.blockTemplate.Store(&newTemplate) + s.headerCache.Add(newTemplate.JobID, newTemplate.Header) log.Printf("New block to mine on %s at height %d", common.OrderToString(common.ZONE_CTX), pendingHeader.NumberArray()) log.Printf("Sealhash: %#x", pendingHeader.SealHash()) go s.broadcastNewJobs() } + +func (s *ProxyServer) verifyMinedHeader(jobID uint, nonce []byte) (*types.Header, error) { + header, ok := s.headerCache.Get(jobID) + if !ok { + return nil, fmt.Errorf("Unable to find header for that jobID: %d", jobID) + } + header = types.CopyHeader(header) + + header.SetNonce(types.BlockNonce(nonce)) + mixHash, _ := s.engine.ComputePowLight(header) + header.SetMixHash(mixHash) + + if header.NumberU64(common.ZONE_CTX) != s.currentBlockTemplate().Header.NumberU64(common.ZONE_CTX) { + log.Printf("Stale header received, block number: %d", header.NumberU64(common.ZONE_CTX)) + } + + powHash, err := s.engine.VerifySeal(header) + log.Printf("Miner submitted a block. Number: %d. Blockhash: %#x", header.NumberU64(common.ZONE_CTX), header.Hash()) + if err != nil { + return nil, fmt.Errorf("unable to verify seal of block: %#x. %v", powHash, err) + } + + return header, nil +} + +func (s *ProxyServer) submitMinedHeader(cs *Session, header *types.Header) error { + + _, order, err := s.engine.CalcOrder(header) + if err != nil { + return fmt.Errorf("rejecting header: %v", err) + } + + log.Printf("Received a %s block", strings.ToLower(common.OrderToString(order))) + + // Send mined header to the relevant go-quai nodes. + // Should be synchronous starting with the lowest levels. + for i := common.HierarchyDepth - 1; i >= order; i-- { + err := s.clients[i].ReceiveMinedHeader(context.Background(), header) + if err != nil { + // Header was rejected. Refresh workers to try again. + cs.pushNewJob(s.currentBlockTemplate()) + return fmt.Errorf("rejected header: %v", err) + } + } + + return nil +} diff --git a/proxy/stratum.go b/proxy/stratum.go index addec58..26e6ee2 100644 --- a/proxy/stratum.go +++ b/proxy/stratum.go @@ -2,19 +2,16 @@ package proxy import ( "bufio" - "context" "encoding/hex" "encoding/json" "fmt" "io" "log" - "math/big" "net" - "strings" + "strconv" "github.com/dominant-strategies/go-quai-stratum/util" "github.com/dominant-strategies/go-quai/common" - "github.com/dominant-strategies/go-quai/core/types" ) const ( @@ -166,7 +163,19 @@ func (cs *Session) handleTCPMessage(s *ProxyServer, req *Request) error { case "mining.submit": var errorResponse Response - header := s.currentBlockTemplate().Header + jobId, err := strconv.ParseUint(req.Params.([]interface{})[0].(string), 16, 0) + if err != nil { + log.Printf("Error decoding jobID: %v", err) + errorResponse = Response{ + ID: req.Id, + Error: map[string]interface{}{ + "code": 500, + "message": "Bad jobID", + }, + } + return cs.sendMessage(&errorResponse) + } + nonce, err := hex.DecodeString(req.Params.([]interface{})[1].(string)) if err != nil { log.Printf("Error decoding nonce: %v", err) @@ -180,9 +189,18 @@ func (cs *Session) handleTCPMessage(s *ProxyServer, req *Request) error { return cs.sendMessage(&errorResponse) } - header.SetNonce(types.BlockNonce(nonce)) - mixHash, _ := s.engine.ComputePowLight(header) - header.SetMixHash(mixHash) + header, err := s.verifyMinedHeader(uint(jobId), nonce) + if err != nil { + log.Printf("Unable to verify header: %v", err) + errorResponse = Response{ + ID: req.Id, + Error: map[string]interface{}{ + "code": 406, + "message": "Bad nonce", + }, + } + return cs.sendMessage(&errorResponse) + } err = s.submitMinedHeader(cs, header) if err != nil { @@ -226,16 +244,17 @@ func (cs *Session) sendTCPError(err error) { cs.sendMessage(&message) } -func (cs *Session) pushNewJob(header *types.Header, target *big.Int) error { +// func (cs *Session) pushNewJob(header *types.Header, target *big.Int) error { +func (cs *Session) pushNewJob(template *BlockTemplate) error { // Update target to worker. - cs.setMining(common.BytesToHash(target.Bytes())) + cs.setMining(common.BytesToHash(template.Target.Bytes())) notification := Notification{ Method: "mining.notify", Params: []string{ - fmt.Sprintf("%x", header.Number(common.ZONE_CTX)), - fmt.Sprintf("%x", header.Number(common.ZONE_CTX)), - fmt.Sprintf("%x", header.SealHash()), + fmt.Sprintf("%x", template.JobID), + fmt.Sprintf("%x", template.Header.Number(common.ZONE_CTX)), + fmt.Sprintf("%x", template.Header.SealHash()), "0", }, } @@ -287,7 +306,7 @@ func (s *ProxyServer) broadcastNewJobs() { bcast <- n go func(cs *Session) { - err := cs.pushNewJob(t.Header, t.Target) + err := cs.pushNewJob(t) <-bcast if err != nil { log.Printf("Job transmit error to %v@%v: %v", cs.login, cs.ip, err) @@ -297,34 +316,6 @@ func (s *ProxyServer) broadcastNewJobs() { } } -func (s *ProxyServer) submitMinedHeader(cs *Session, header *types.Header) error { - powHash, err := s.engine.VerifySeal(header) - if err != nil { - return fmt.Errorf("unable to verify seal of block: %#x. %v", powHash, err) - } - log.Printf("Miner submitted a block. Blockhash: %#x", header.Hash()) - _, order, err := s.engine.CalcOrder(header) - if err != nil { - return fmt.Errorf("rejecting header: %v", err) - } - - // Should be synchronous starting with the lowest levels. - log.Printf("Received a %s block", strings.ToLower(common.OrderToString(order))) - - // Send mined header to the relevant go-quai nodes. - // Should be synchronous starting with the lowest levels. - for i := common.HierarchyDepth - 1; i >= order; i-- { - err := s.clients[i].ReceiveMinedHeader(context.Background(), header) - if err != nil { - // Header was rejected. Refresh workers to try again. - cs.pushNewJob(s.currentBlockTemplate().Header, s.currentBlockTemplate().Target) - return fmt.Errorf("rejected header: %v", err) - } - } - - return nil -} - func (cs *Session) sendMessage(v any) error { cs.Lock() defer cs.Unlock()