git.gammaspectra.live/P2Pool/consensus/v3@v3.8.0/p2pool/stratum/stratum.go (about)

     1  package stratum
     2  
     3  import (
     4  	"encoding/binary"
     5  	"errors"
     6  	"fmt"
     7  	"git.gammaspectra.live/P2Pool/consensus/v3/monero"
     8  	"git.gammaspectra.live/P2Pool/consensus/v3/monero/address"
     9  	"git.gammaspectra.live/P2Pool/consensus/v3/monero/block"
    10  	"git.gammaspectra.live/P2Pool/consensus/v3/monero/crypto"
    11  	"git.gammaspectra.live/P2Pool/consensus/v3/monero/transaction"
    12  	"git.gammaspectra.live/P2Pool/consensus/v3/p2pool/mempool"
    13  	"git.gammaspectra.live/P2Pool/consensus/v3/p2pool/sidechain"
    14  	p2pooltypes "git.gammaspectra.live/P2Pool/consensus/v3/p2pool/types"
    15  	"git.gammaspectra.live/P2Pool/consensus/v3/types"
    16  	"git.gammaspectra.live/P2Pool/consensus/v3/utils"
    17  	gojson "git.gammaspectra.live/P2Pool/go-json"
    18  	"github.com/dolthub/swiss"
    19  	fasthex "github.com/tmthrgd/go-hex"
    20  	"math"
    21  	unsafeRandom "math/rand/v2"
    22  	"net"
    23  	"net/netip"
    24  	"slices"
    25  	"sync"
    26  	"time"
    27  )
    28  
    29  // HighFeeValue 0.006 XMR
    30  const HighFeeValue uint64 = 6000000000
    31  const TimeInMempool = time.Second * 5
    32  
    33  type ephemeralPubKeyCacheKey [crypto.PublicKeySize*2 + 8]byte
    34  
    35  type ephemeralPubKeyCacheEntry struct {
    36  	PublicKey crypto.PublicKeyBytes
    37  	ViewTag   uint8
    38  }
    39  
    40  type NewTemplateData struct {
    41  	PreviousTemplateId        types.Hash
    42  	SideHeight                uint64
    43  	Difficulty                types.Difficulty
    44  	CumulativeDifficulty      types.Difficulty
    45  	TransactionPrivateKeySeed types.Hash
    46  	// TransactionPrivateKey Generated from TransactionPrivateKeySeed
    47  	TransactionPrivateKey  crypto.PrivateKeyBytes
    48  	TransactionPublicKey   crypto.PublicKeySlice
    49  	Timestamp              uint64
    50  	TotalReward            uint64
    51  	Transactions           []types.Hash
    52  	MaxRewardAmountsWeight uint64
    53  	ShareVersion           sidechain.ShareVersion
    54  	Uncles                 []types.Hash
    55  	Ready                  bool
    56  	Window                 struct {
    57  		ReservedShareIndex   int
    58  		Shares               sidechain.Shares
    59  		ShuffleMapping       [2][]int
    60  		EphemeralPubKeyCache map[ephemeralPubKeyCacheKey]*ephemeralPubKeyCacheEntry
    61  	}
    62  }
    63  
    64  type Server struct {
    65  	SubmitFunc func(block *sidechain.PoolBlock) error
    66  
    67  	refreshDuration time.Duration
    68  
    69  	minerData       *p2pooltypes.MinerData
    70  	tip             *sidechain.PoolBlock
    71  	newTemplateData NewTemplateData
    72  	lock            sync.RWMutex
    73  	sidechain       *sidechain.SideChain
    74  
    75  	mempool            *MiningMempool
    76  	lastMempoolRefresh time.Time
    77  
    78  	preAllocatedDifficultyData        []sidechain.DifficultyData
    79  	preAllocatedDifficultyDifferences []uint32
    80  	preAllocatedSharesPool            *sidechain.PreAllocatedSharesPool
    81  
    82  	preAllocatedBufferLock sync.Mutex
    83  	preAllocatedBuffer     []byte
    84  
    85  	minersLock sync.RWMutex
    86  	miners     map[address.PackedAddress]*MinerTrackingEntry
    87  
    88  	bansLock sync.RWMutex
    89  	bans     map[[16]byte]BanEntry
    90  
    91  	clientsLock sync.RWMutex
    92  	clients     []*Client
    93  
    94  	incomingChanges chan func() bool
    95  }
    96  
    97  type Client struct {
    98  	Lock     sync.RWMutex
    99  	Conn     *net.TCPConn
   100  	encoder  *gojson.Encoder
   101  	decoder  *gojson.Decoder
   102  	Agent    string
   103  	Login    bool
   104  	Address  address.PackedAddress
   105  	Password string
   106  	RigId    string
   107  	buf      []byte
   108  	RpcId    uint32
   109  }
   110  
   111  func (c *Client) Write(b []byte) (int, error) {
   112  	if err := c.Conn.SetWriteDeadline(time.Now().Add(time.Second * 5)); err != nil {
   113  		return 0, err
   114  	}
   115  	return c.Conn.Write(b)
   116  }
   117  
   118  func NewServer(s *sidechain.SideChain, submitFunc func(block *sidechain.PoolBlock) error) *Server {
   119  	server := &Server{
   120  		SubmitFunc:                        submitFunc,
   121  		sidechain:                         s,
   122  		preAllocatedDifficultyData:        make([]sidechain.DifficultyData, s.Consensus().ChainWindowSize*2),
   123  		preAllocatedDifficultyDifferences: make([]uint32, s.Consensus().ChainWindowSize*2),
   124  		preAllocatedSharesPool:            sidechain.NewPreAllocatedSharesPool(s.Consensus().ChainWindowSize * 2),
   125  		preAllocatedBuffer:                make([]byte, 0, sidechain.PoolBlockMaxTemplateSize),
   126  		miners:                            make(map[address.PackedAddress]*MinerTrackingEntry),
   127  		mempool:                           (*MiningMempool)(swiss.NewMap[types.Hash, *mempool.Entry](512)),
   128  		// buffer 4 at a time for non-blocking source
   129  		incomingChanges: make(chan func() bool, 4),
   130  
   131  		//refresh every n seconds
   132  		refreshDuration: time.Duration(s.Consensus().TargetBlockTime) * time.Second,
   133  	}
   134  	return server
   135  }
   136  
   137  func (s *Server) CleanupMiners() {
   138  	s.minersLock.Lock()
   139  	defer s.minersLock.Unlock()
   140  
   141  	cleanupTime := time.Now()
   142  	for k, e := range s.miners {
   143  		if cleanupTime.Sub(e.LastJob) > time.Minute*5 {
   144  			delete(s.miners, k)
   145  		} else {
   146  			if len(e.Templates) > 0 {
   147  				var templateSideHeight uint64
   148  				for _, tpl := range e.Templates {
   149  					if tpl.SideHeight > templateSideHeight {
   150  						templateSideHeight = tpl.SideHeight
   151  					}
   152  				}
   153  
   154  				tipHeight := uint64(0)
   155  				if templateSideHeight > sidechain.UncleBlockDepth {
   156  					tipHeight = templateSideHeight - sidechain.UncleBlockDepth + 1
   157  				}
   158  				//Delete old templates further than uncle depth
   159  				for key, tpl := range e.Templates {
   160  					if tpl.SideHeight < tipHeight {
   161  						delete(e.Templates, key)
   162  					}
   163  				}
   164  
   165  				// TODO: Prevent long-term leaks by re-allocating templates if capacity grew too much
   166  			}
   167  		}
   168  	}
   169  }
   170  
   171  func (s *Server) fillNewTemplateData(currentDifficulty types.Difficulty) error {
   172  
   173  	s.newTemplateData.Ready = false
   174  
   175  	if s.minerData == nil {
   176  		return errors.New("no main data present")
   177  	}
   178  
   179  	if s.minerData.MajorVersion > monero.HardForkSupportedVersion {
   180  		return fmt.Errorf("unsupported hardfork version %d", s.minerData.MajorVersion)
   181  	}
   182  
   183  	oldPubKeyCache := s.newTemplateData.Window.EphemeralPubKeyCache
   184  
   185  	s.newTemplateData.Timestamp = uint64(time.Now().Unix())
   186  
   187  	s.newTemplateData.ShareVersion = sidechain.P2PoolShareVersion(s.sidechain.Consensus(), s.newTemplateData.Timestamp)
   188  
   189  	// Do not allow mining on old chains, as they are not optimal for CPU usage
   190  	if s.newTemplateData.ShareVersion < sidechain.ShareVersion_V2 {
   191  		return errors.New("unsupported sidechain version")
   192  	}
   193  	// no merge mining nor merkle proof support yet
   194  	if s.newTemplateData.ShareVersion > sidechain.ShareVersion_V2 {
   195  		return errors.New("unsupported sidechain version")
   196  	}
   197  
   198  	if s.tip != nil {
   199  		s.newTemplateData.PreviousTemplateId = s.tip.SideTemplateId(s.sidechain.Consensus())
   200  		s.newTemplateData.SideHeight = s.tip.Side.Height + 1
   201  
   202  		oldSeed := s.newTemplateData.TransactionPrivateKeySeed
   203  
   204  		s.newTemplateData.TransactionPrivateKeySeed = s.tip.Side.CoinbasePrivateKeySeed
   205  		if s.tip.Main.PreviousId != s.minerData.PrevId {
   206  			s.newTemplateData.TransactionPrivateKeySeed = s.tip.CalculateTransactionPrivateKeySeed()
   207  		}
   208  
   209  		//TODO: check this
   210  		if s.newTemplateData.TransactionPrivateKeySeed != oldSeed {
   211  			oldPubKeyCache = nil
   212  		}
   213  
   214  		if currentDifficulty != types.ZeroDifficulty {
   215  			//difficulty is set from caller
   216  			s.newTemplateData.Difficulty = currentDifficulty
   217  			oldPubKeyCache = nil
   218  		}
   219  
   220  		s.newTemplateData.CumulativeDifficulty = s.tip.Side.CumulativeDifficulty.Add(s.newTemplateData.Difficulty)
   221  
   222  		s.newTemplateData.Uncles = s.sidechain.GetPossibleUncles(s.tip, s.newTemplateData.SideHeight)
   223  	} else {
   224  		s.newTemplateData.PreviousTemplateId = types.ZeroHash
   225  		s.newTemplateData.TransactionPrivateKeySeed = s.sidechain.Consensus().Id
   226  		s.newTemplateData.Difficulty = types.DifficultyFrom64(s.sidechain.Consensus().MinimumDifficulty)
   227  		s.newTemplateData.CumulativeDifficulty = types.DifficultyFrom64(s.sidechain.Consensus().MinimumDifficulty)
   228  	}
   229  
   230  	kP := s.sidechain.DerivationCache().GetDeterministicTransactionKey(s.newTemplateData.TransactionPrivateKeySeed, s.minerData.PrevId)
   231  	s.newTemplateData.TransactionPrivateKey = kP.PrivateKey.AsBytes()
   232  	s.newTemplateData.TransactionPublicKey = kP.PublicKey.AsSlice()
   233  
   234  	fakeTemplateTipBlock := &sidechain.PoolBlock{
   235  		Main: block.Block{
   236  			MajorVersion: s.minerData.MajorVersion,
   237  			MinorVersion: monero.HardForkSupportedVersion,
   238  			Timestamp:    s.newTemplateData.Timestamp,
   239  			PreviousId:   s.minerData.PrevId,
   240  			Nonce:        0,
   241  			Coinbase: transaction.CoinbaseTransaction{
   242  				GenHeight: s.minerData.Height,
   243  			},
   244  			//TODO:
   245  			Transactions: nil,
   246  		},
   247  		Side: sidechain.SideData{
   248  			//Zero Spend/View key
   249  			PublicKey:              address.PackedAddress{},
   250  			CoinbasePrivateKeySeed: s.newTemplateData.TransactionPrivateKeySeed,
   251  			CoinbasePrivateKey:     s.newTemplateData.TransactionPrivateKey,
   252  			Parent:                 s.newTemplateData.PreviousTemplateId,
   253  			Uncles:                 s.newTemplateData.Uncles,
   254  			Height:                 s.newTemplateData.SideHeight,
   255  			Difficulty:             s.newTemplateData.Difficulty,
   256  			CumulativeDifficulty:   s.newTemplateData.CumulativeDifficulty,
   257  		},
   258  		Metadata: sidechain.PoolBlockReceptionMetadata{
   259  			LocalTime: time.Now().UTC(),
   260  		},
   261  		CachedShareVersion: s.newTemplateData.ShareVersion,
   262  	}
   263  
   264  	preAllocatedShares := s.preAllocatedSharesPool.Get()
   265  	defer s.preAllocatedSharesPool.Put(preAllocatedShares)
   266  	shares, _ := sidechain.GetSharesOrdered(fakeTemplateTipBlock, s.sidechain.Consensus(), s.sidechain.Server().GetDifficultyByHeight, s.sidechain.GetPoolBlockByTemplateId, preAllocatedShares)
   267  
   268  	if shares == nil {
   269  		return errors.New("could not get outputs")
   270  	}
   271  
   272  	s.newTemplateData.Window.Shares = slices.Clone(shares)
   273  	s.newTemplateData.Window.ReservedShareIndex = s.newTemplateData.Window.Shares.Index(fakeTemplateTipBlock.GetAddress())
   274  
   275  	if s.newTemplateData.Window.ReservedShareIndex == -1 {
   276  		return errors.New("could not find reserved share index")
   277  	}
   278  
   279  	// Only choose transactions that were received 5 or more seconds ago, or high fee (>= 0.006 XMR) transactions
   280  	selectedMempool := s.mempool.Select(HighFeeValue, TimeInMempool)
   281  
   282  	//TODO: limit max Monero block size
   283  
   284  	baseReward := block.GetBaseReward(s.minerData.AlreadyGeneratedCoins)
   285  
   286  	totalWeight, totalFees := selectedMempool.WeightAndFees()
   287  
   288  	maxReward := baseReward + totalFees
   289  
   290  	rewards := sidechain.SplitRewardAllocate(maxReward, s.newTemplateData.Window.Shares)
   291  
   292  	s.newTemplateData.MaxRewardAmountsWeight = uint64(utils.UVarInt64SliceSize(rewards))
   293  
   294  	tx, err := s.createCoinbaseTransaction(fakeTemplateTipBlock.GetTransactionOutputType(), s.newTemplateData.Window.Shares, rewards, s.newTemplateData.MaxRewardAmountsWeight, false)
   295  	if err != nil {
   296  		return err
   297  	}
   298  	coinbaseTransactionWeight := uint64(tx.BufferLength())
   299  
   300  	var pickedMempool mempool.Mempool
   301  
   302  	if totalWeight+coinbaseTransactionWeight <= s.minerData.MedianWeight {
   303  		// if a block doesn't get into the penalty zone, just pick all transactions
   304  		pickedMempool = selectedMempool
   305  	} else {
   306  		pickedMempool = selectedMempool.Pick(baseReward, coinbaseTransactionWeight, s.minerData.MedianWeight)
   307  	}
   308  
   309  	//shuffle transactions
   310  	unsafeRandom.Shuffle(len(pickedMempool), func(i, j int) {
   311  		pickedMempool[i], pickedMempool[j] = pickedMempool[j], pickedMempool[i]
   312  	})
   313  
   314  	s.newTemplateData.Transactions = make([]types.Hash, len(pickedMempool))
   315  
   316  	for i, entry := range pickedMempool {
   317  		s.newTemplateData.Transactions[i] = entry.Id
   318  	}
   319  
   320  	finalReward := mempool.GetBlockReward(baseReward, s.minerData.MedianWeight, pickedMempool.Fees(), coinbaseTransactionWeight+pickedMempool.Weight())
   321  
   322  	if finalReward < baseReward {
   323  		return errors.New("final reward < base reward, should never happen")
   324  	}
   325  	s.newTemplateData.TotalReward = finalReward
   326  
   327  	s.newTemplateData.Window.ShuffleMapping = BuildShuffleMapping(len(s.newTemplateData.Window.Shares), s.newTemplateData.ShareVersion, s.newTemplateData.TransactionPrivateKeySeed)
   328  
   329  	s.newTemplateData.Window.EphemeralPubKeyCache = make(map[ephemeralPubKeyCacheKey]*ephemeralPubKeyCacheEntry)
   330  
   331  	txPrivateKeySlice := s.newTemplateData.TransactionPrivateKey.AsSlice()
   332  	txPrivateKeyScalar := s.newTemplateData.TransactionPrivateKey.AsScalar()
   333  
   334  	//TODO: parallelize this
   335  	hasher := crypto.GetKeccak256Hasher()
   336  	defer crypto.PutKeccak256Hasher(hasher)
   337  	for i, m := range PossibleIndicesForShuffleMapping(s.newTemplateData.Window.ShuffleMapping) {
   338  		if i == 0 {
   339  			// Skip zero key
   340  			continue
   341  		}
   342  		share := s.newTemplateData.Window.Shares[i]
   343  		var k ephemeralPubKeyCacheKey
   344  		copy(k[:], share.Address.Bytes())
   345  
   346  		for _, index := range m {
   347  			if index == -1 {
   348  				continue
   349  			}
   350  			binary.LittleEndian.PutUint64(k[crypto.PublicKeySize*2:], uint64(index))
   351  			if e, ok := oldPubKeyCache[k]; ok {
   352  				s.newTemplateData.Window.EphemeralPubKeyCache[k] = e
   353  			} else {
   354  				var e ephemeralPubKeyCacheEntry
   355  				e.PublicKey, e.ViewTag = s.sidechain.DerivationCache().GetEphemeralPublicKey(&share.Address, txPrivateKeySlice, txPrivateKeyScalar, uint64(index), hasher)
   356  				s.newTemplateData.Window.EphemeralPubKeyCache[k] = &e
   357  			}
   358  		}
   359  	}
   360  
   361  	s.newTemplateData.Ready = true
   362  
   363  	return nil
   364  
   365  }
   366  
   367  func BuildShuffleMapping(n int, shareVersion sidechain.ShareVersion, transactionPrivateKeySeed types.Hash) (mappings [2][]int) {
   368  	if n <= 1 {
   369  		return [2][]int{{0}, {0}}
   370  	}
   371  	shuffleSequence1 := make([]int, n)
   372  	for i := range shuffleSequence1 {
   373  		shuffleSequence1[i] = i
   374  	}
   375  	shuffleSequence2 := make([]int, n-1)
   376  	for i := range shuffleSequence2 {
   377  		shuffleSequence2[i] = i
   378  	}
   379  
   380  	sidechain.ShuffleSequence(shareVersion, transactionPrivateKeySeed, n, func(i, j int) {
   381  		shuffleSequence1[i], shuffleSequence1[j] = shuffleSequence1[j], shuffleSequence1[i]
   382  	})
   383  	sidechain.ShuffleSequence(shareVersion, transactionPrivateKeySeed, n-1, func(i, j int) {
   384  		shuffleSequence2[i], shuffleSequence2[j] = shuffleSequence2[j], shuffleSequence2[i]
   385  	})
   386  
   387  	mappings[0] = make([]int, n)
   388  	mappings[1] = make([]int, n-1)
   389  
   390  	//Flip
   391  	for i := range shuffleSequence1 {
   392  		mappings[0][shuffleSequence1[i]] = i
   393  	}
   394  	for i := range shuffleSequence2 {
   395  		mappings[1][shuffleSequence2[i]] = i
   396  	}
   397  
   398  	return mappings
   399  }
   400  
   401  func ApplyShuffleMapping[T any](v []T, mappings [2][]int) []T {
   402  	n := len(v)
   403  
   404  	result := make([]T, n)
   405  
   406  	if n == len(mappings[0]) {
   407  		for i := range v {
   408  			result[mappings[0][i]] = v[i]
   409  		}
   410  	} else if n == len(mappings[1]) {
   411  		for i := range v {
   412  			result[mappings[1][i]] = v[i]
   413  		}
   414  	}
   415  	return result
   416  }
   417  
   418  func PossibleIndicesForShuffleMapping(mappings [2][]int) [][3]int {
   419  	n := len(mappings[0])
   420  	result := make([][3]int, n)
   421  	for i := 0; i < n; i++ {
   422  		// Count with all + miner
   423  		result[i][0] = mappings[0][i]
   424  		if i > 0 {
   425  			// Count with all + miner shifted to a slot before
   426  			result[i][1] = mappings[0][i-1]
   427  
   428  			// Count with all miners minus one
   429  			result[i][2] = mappings[1][i-1]
   430  		} else {
   431  			result[i][1] = -1
   432  			result[i][2] = -1
   433  		}
   434  	}
   435  
   436  	return result
   437  }
   438  
   439  func (s *Server) BuildTemplate(addr address.PackedAddress, forceNewTemplate bool) (tpl *Template, jobCounter uint64, difficultyTarget types.Difficulty, seedHash types.Hash, err error) {
   440  
   441  	var zeroAddress address.PackedAddress
   442  	if addr == zeroAddress {
   443  		return nil, 0, types.ZeroDifficulty, types.ZeroHash, errors.New("nil address")
   444  	}
   445  
   446  	e, ok := func() (*MinerTrackingEntry, bool) {
   447  		s.minersLock.RLock()
   448  		defer s.minersLock.RUnlock()
   449  		e, ok := s.miners[addr]
   450  		return e, ok
   451  	}()
   452  
   453  	tpl, jobCounter, targetDiff, seedHash, err := func() (tpl *Template, jobCounter uint64, difficultyTarget types.Difficulty, seedHash types.Hash, err error) {
   454  		s.lock.RLock()
   455  		defer s.lock.RUnlock()
   456  
   457  		if s.minerData == nil {
   458  			return nil, 0, types.ZeroDifficulty, types.ZeroHash, errors.New("nil miner data")
   459  		}
   460  
   461  		if !s.newTemplateData.Ready {
   462  			return nil, 0, types.ZeroDifficulty, types.ZeroHash, errors.New("template data not ready")
   463  		}
   464  
   465  		if !forceNewTemplate && e != nil && ok {
   466  			if tpl, jobCounter := func() (*Template, uint64) {
   467  				e.Lock.RLock()
   468  				defer e.Lock.RUnlock()
   469  
   470  				jobCounter := e.LastTemplate.Load()
   471  
   472  				if tpl, ok := e.Templates[jobCounter]; ok && tpl.SideParent == s.newTemplateData.PreviousTemplateId && tpl.MainParent == s.minerData.PrevId {
   473  					return tpl, jobCounter
   474  				}
   475  				return nil, 0
   476  			}(); tpl != nil {
   477  				e.Lock.Lock()
   478  				defer e.Lock.Unlock()
   479  				e.LastJob = time.Now()
   480  
   481  				targetDiff := tpl.SideDifficulty
   482  				if s.minerData.Difficulty.Cmp(targetDiff) < 0 {
   483  					targetDiff = s.minerData.Difficulty
   484  				}
   485  
   486  				return tpl, jobCounter, targetDiff, s.minerData.SeedHash, nil
   487  			}
   488  		}
   489  
   490  		blockTemplate := &sidechain.PoolBlock{
   491  			Main: block.Block{
   492  				MajorVersion: s.minerData.MajorVersion,
   493  				MinorVersion: monero.HardForkSupportedVersion,
   494  				Timestamp:    s.newTemplateData.Timestamp,
   495  				PreviousId:   s.minerData.PrevId,
   496  				Nonce:        0,
   497  				Transactions: s.newTemplateData.Transactions,
   498  			},
   499  			Side: sidechain.SideData{
   500  				PublicKey:              addr,
   501  				CoinbasePrivateKeySeed: s.newTemplateData.TransactionPrivateKeySeed,
   502  				CoinbasePrivateKey:     s.newTemplateData.TransactionPrivateKey,
   503  				Parent:                 s.newTemplateData.PreviousTemplateId,
   504  				Uncles:                 s.newTemplateData.Uncles,
   505  				Height:                 s.newTemplateData.SideHeight,
   506  				Difficulty:             s.newTemplateData.Difficulty,
   507  				CumulativeDifficulty:   s.newTemplateData.CumulativeDifficulty,
   508  				ExtraBuffer: sidechain.SideDataExtraBuffer{
   509  					SoftwareId:          p2pooltypes.CurrentSoftwareId,
   510  					SoftwareVersion:     p2pooltypes.CurrentSoftwareVersion,
   511  					RandomNumber:        0,
   512  					SideChainExtraNonce: 0,
   513  				},
   514  			},
   515  			CachedShareVersion: s.newTemplateData.ShareVersion,
   516  		}
   517  
   518  		preAllocatedShares := s.preAllocatedSharesPool.Get()
   519  		defer s.preAllocatedSharesPool.Put(preAllocatedShares)
   520  
   521  		shares := s.newTemplateData.Window.Shares.Clone()
   522  
   523  		// It exists, replace
   524  		if i := shares.Index(addr); i != -1 {
   525  			shares[i] = &sidechain.Share{
   526  				Address: addr,
   527  				Weight:  shares[i].Weight.Add(shares[s.newTemplateData.Window.ReservedShareIndex].Weight),
   528  			}
   529  			shares = slices.Delete(shares, s.newTemplateData.Window.ReservedShareIndex, s.newTemplateData.Window.ReservedShareIndex+1)
   530  		} else {
   531  			// Replace reserved address
   532  			shares[s.newTemplateData.Window.ReservedShareIndex] = &sidechain.Share{
   533  				Weight:  shares[s.newTemplateData.Window.ReservedShareIndex].Weight,
   534  				Address: addr,
   535  			}
   536  		}
   537  		shares = shares.Compact()
   538  
   539  		// Apply consensus shuffle
   540  		shares = ApplyShuffleMapping(shares, s.newTemplateData.Window.ShuffleMapping)
   541  
   542  		// Allocate rewards
   543  		{
   544  			preAllocatedRewards := make([]uint64, 0, len(shares))
   545  			rewards := sidechain.SplitReward(preAllocatedRewards, s.newTemplateData.TotalReward, shares)
   546  
   547  			if rewards == nil || len(rewards) != len(shares) {
   548  				return nil, 0, types.ZeroDifficulty, types.ZeroHash, errors.New("could not calculate rewards")
   549  			}
   550  
   551  			if blockTemplate.Main.Coinbase, err = s.createCoinbaseTransaction(blockTemplate.GetTransactionOutputType(), shares, rewards, s.newTemplateData.MaxRewardAmountsWeight, true); err != nil {
   552  				return nil, 0, types.ZeroDifficulty, types.ZeroHash, err
   553  			}
   554  		}
   555  
   556  		tpl, err = TemplateFromPoolBlock(blockTemplate)
   557  		if err != nil {
   558  			return nil, 0, types.ZeroDifficulty, types.ZeroHash, err
   559  		}
   560  
   561  		targetDiff := tpl.SideDifficulty
   562  		if s.minerData.Difficulty.Cmp(targetDiff) < 0 {
   563  			targetDiff = s.minerData.Difficulty
   564  		}
   565  
   566  		return tpl, 0, targetDiff, s.minerData.SeedHash, nil
   567  	}()
   568  
   569  	if err != nil {
   570  		return nil, 0, types.ZeroDifficulty, types.ZeroHash, err
   571  	}
   572  
   573  	if forceNewTemplate || jobCounter != 0 {
   574  		return tpl, jobCounter, targetDiff, seedHash, nil
   575  	}
   576  
   577  	if e != nil && ok {
   578  		e.Lock.Lock()
   579  		defer e.Lock.Unlock()
   580  		var newJobCounter uint64
   581  		for newJobCounter == 0 {
   582  			newJobCounter = e.Counter.Add(1)
   583  		}
   584  		e.Templates[newJobCounter] = tpl
   585  		e.LastTemplate.Store(newJobCounter)
   586  		e.LastJob = time.Now()
   587  		return tpl, newJobCounter, targetDiff, seedHash, nil
   588  	} else {
   589  		s.minersLock.Lock()
   590  		defer s.minersLock.Unlock()
   591  
   592  		e = &MinerTrackingEntry{
   593  			Templates: make(map[uint64]*Template),
   594  		}
   595  		var newJobCounter uint64
   596  		for newJobCounter == 0 {
   597  			newJobCounter = e.Counter.Add(1)
   598  		}
   599  		e.Templates[newJobCounter] = tpl
   600  		e.LastTemplate.Store(newJobCounter)
   601  		e.LastJob = time.Now()
   602  		s.miners[addr] = e
   603  		return tpl, newJobCounter, targetDiff, seedHash, nil
   604  	}
   605  }
   606  
   607  func (s *Server) createCoinbaseTransaction(txType uint8, shares sidechain.Shares, rewards []uint64, maxRewardsAmountsWeight uint64, final bool) (tx transaction.CoinbaseTransaction, err error) {
   608  
   609  	//TODO: v3
   610  	mergeMineTag := slices.Clone(types.ZeroHash[:])
   611  
   612  	tx = transaction.CoinbaseTransaction{
   613  		Version:    2,
   614  		UnlockTime: s.minerData.Height + monero.MinerRewardUnlockTime,
   615  		InputCount: 1,
   616  		InputType:  transaction.TxInGen,
   617  		GenHeight:  s.minerData.Height,
   618  		AuxiliaryData: transaction.CoinbaseTransactionAuxiliaryData{
   619  			TotalReward: func() (v uint64) {
   620  				for i := range rewards {
   621  					v += rewards[i]
   622  				}
   623  				return
   624  			}(),
   625  		},
   626  		Extra: transaction.ExtraTags{
   627  			transaction.ExtraTag{
   628  				Tag:    transaction.TxExtraTagPubKey,
   629  				VarInt: 0,
   630  				Data:   types.Bytes(s.newTemplateData.TransactionPublicKey),
   631  			},
   632  			transaction.ExtraTag{
   633  				Tag:       transaction.TxExtraTagNonce,
   634  				VarInt:    sidechain.SideExtraNonceSize,
   635  				HasVarInt: true,
   636  				Data:      make(types.Bytes, sidechain.SideExtraNonceSize),
   637  			},
   638  			transaction.ExtraTag{
   639  				//TODO: fix this for V3
   640  				Tag:       transaction.TxExtraTagMergeMining,
   641  				VarInt:    uint64(len(mergeMineTag)),
   642  				HasVarInt: true,
   643  				Data:      mergeMineTag,
   644  			},
   645  		},
   646  		ExtraBaseRCT: 0,
   647  	}
   648  
   649  	tx.Outputs = make(transaction.Outputs, len(shares))
   650  
   651  	if final {
   652  		txPrivateKeySlice := s.newTemplateData.TransactionPrivateKey.AsSlice()
   653  		txPrivateKeyScalar := s.newTemplateData.TransactionPrivateKey.AsScalar()
   654  
   655  		hasher := crypto.GetKeccak256Hasher()
   656  		defer crypto.PutKeccak256Hasher(hasher)
   657  
   658  		var k ephemeralPubKeyCacheKey
   659  		for i := range tx.Outputs {
   660  			outputIndex := uint64(i)
   661  			tx.Outputs[outputIndex].Index = outputIndex
   662  			tx.Outputs[outputIndex].Type = txType
   663  			tx.Outputs[outputIndex].Reward = rewards[outputIndex]
   664  			copy(k[:], shares[outputIndex].Address.Bytes())
   665  			binary.LittleEndian.PutUint64(k[crypto.PublicKeySize*2:], outputIndex)
   666  			if e, ok := s.newTemplateData.Window.EphemeralPubKeyCache[k]; ok {
   667  				tx.Outputs[outputIndex].EphemeralPublicKey, tx.Outputs[outputIndex].ViewTag = e.PublicKey, e.ViewTag
   668  			} else {
   669  				tx.Outputs[outputIndex].EphemeralPublicKey, tx.Outputs[outputIndex].ViewTag = s.sidechain.DerivationCache().GetEphemeralPublicKey(&shares[outputIndex].Address, txPrivateKeySlice, txPrivateKeyScalar, outputIndex, hasher)
   670  			}
   671  		}
   672  	} else {
   673  		for i := range tx.Outputs {
   674  			outputIndex := uint64(i)
   675  			tx.Outputs[outputIndex].Index = outputIndex
   676  			tx.Outputs[outputIndex].Type = txType
   677  			tx.Outputs[outputIndex].Reward = rewards[outputIndex]
   678  		}
   679  	}
   680  
   681  	rewardAmountsWeight := uint64(utils.UVarInt64SliceSize(rewards))
   682  
   683  	if !final {
   684  		if rewardAmountsWeight != maxRewardsAmountsWeight {
   685  			return transaction.CoinbaseTransaction{}, fmt.Errorf("incorrect miner rewards during the dry run, %d != %d", rewardAmountsWeight, maxRewardsAmountsWeight)
   686  		}
   687  	} else if rewardAmountsWeight > maxRewardsAmountsWeight {
   688  		return transaction.CoinbaseTransaction{}, fmt.Errorf("incorrect miner rewards during the dry run, %d > %d", rewardAmountsWeight, maxRewardsAmountsWeight)
   689  	}
   690  
   691  	correctedExtraNonceSize := sidechain.SideExtraNonceSize + maxRewardsAmountsWeight - rewardAmountsWeight
   692  
   693  	if correctedExtraNonceSize > sidechain.SideExtraNonceSize {
   694  		if correctedExtraNonceSize > sidechain.SideExtraNonceMaxSize {
   695  			return transaction.CoinbaseTransaction{}, fmt.Errorf("corrected extra_nonce size is too large, %d > %d", correctedExtraNonceSize, sidechain.SideExtraNonceMaxSize)
   696  		}
   697  		//Increase size to maintain transaction weight
   698  		tx.Extra[1].Data = make(types.Bytes, correctedExtraNonceSize)
   699  		tx.Extra[1].VarInt = correctedExtraNonceSize
   700  	}
   701  
   702  	return tx, nil
   703  }
   704  
   705  func (s *Server) HandleMempoolData(data mempool.Mempool) {
   706  	s.incomingChanges <- func() bool {
   707  		timeReceived := time.Now()
   708  
   709  		s.lock.Lock()
   710  		defer s.lock.Unlock()
   711  
   712  		var highFeeReceived bool
   713  		for _, tx := range data {
   714  
   715  			if s.mempool.Add(tx) {
   716  				if tx.Fee >= HighFeeValue {
   717  					//prevent a lot of calls if not needed
   718  					if utils.GlobalLogLevel&utils.LogLevelDebug > 0 {
   719  						utils.Debugf("Stratum", "new tx id = %s, size = %d, weight = %d, fee = %s XMR", tx.Id, tx.BlobSize, tx.Weight, utils.XMRUnits(tx.Fee))
   720  					}
   721  
   722  					highFeeReceived = true
   723  					utils.Noticef("Stratum", "high fee tx received: %s, %s XMR - updating template", tx.Id, utils.XMRUnits(tx.Fee))
   724  				}
   725  			}
   726  		}
   727  
   728  		// Refresh if 10 seconds have passed between templates and new transactions arrived, or a high fee was received
   729  		if highFeeReceived || timeReceived.Sub(s.lastMempoolRefresh) >= s.refreshDuration {
   730  			s.lastMempoolRefresh = timeReceived
   731  			if err := s.fillNewTemplateData(types.ZeroDifficulty); err != nil {
   732  				utils.Errorf("Stratum", "Error building new template data: %s", err)
   733  				return false
   734  			}
   735  			return true
   736  		}
   737  		return false
   738  	}
   739  }
   740  
   741  func (s *Server) HandleMinerData(minerData *p2pooltypes.MinerData) {
   742  	s.incomingChanges <- func() bool {
   743  		s.lock.Lock()
   744  		defer s.lock.Unlock()
   745  
   746  		if s.minerData == nil || s.minerData.Height <= minerData.Height {
   747  			s.minerData = minerData
   748  			s.mempool.Swap(minerData.TxBacklog)
   749  			s.lastMempoolRefresh = time.Now()
   750  			if err := s.fillNewTemplateData(types.ZeroDifficulty); err != nil {
   751  				utils.Errorf("Stratum", "Error building new template data: %s", err)
   752  				return false
   753  			}
   754  			return true
   755  		}
   756  		return false
   757  	}
   758  }
   759  
   760  func (s *Server) HandleTip(tip *sidechain.PoolBlock) {
   761  	currentDifficulty := s.sidechain.Difficulty()
   762  	s.incomingChanges <- func() bool {
   763  		s.lock.Lock()
   764  		defer s.lock.Unlock()
   765  
   766  		if s.tip == nil || s.tip.Side.Height <= tip.Side.Height {
   767  			s.tip = tip
   768  			s.lastMempoolRefresh = time.Now()
   769  			if err := s.fillNewTemplateData(currentDifficulty); err != nil {
   770  				utils.Errorf("Stratum", "Error building new template data: %s", err)
   771  				return false
   772  			}
   773  			return true
   774  		}
   775  		return false
   776  	}
   777  }
   778  
   779  func (s *Server) HandleBroadcast(block *sidechain.PoolBlock) {
   780  	s.incomingChanges <- func() bool {
   781  		s.lock.Lock()
   782  		defer s.lock.Unlock()
   783  
   784  		if s.tip != nil && block != s.tip && block.Side.Height <= s.tip.Side.Height {
   785  			//probably a new block was added as alternate
   786  			if err := s.fillNewTemplateData(types.ZeroDifficulty); err != nil {
   787  				utils.Errorf("Stratum", "Error building new template data: %s", err)
   788  				return false
   789  			}
   790  			return true
   791  		}
   792  		return false
   793  	}
   794  }
   795  
   796  func (s *Server) Update() {
   797  	var closeClients []*Client
   798  	defer func() {
   799  		for _, c := range closeClients {
   800  			s.CloseClient(c)
   801  		}
   802  	}()
   803  	s.clientsLock.RLock()
   804  	defer s.clientsLock.RUnlock()
   805  
   806  	if len(s.clients) > 0 {
   807  		utils.Logf("Stratum", "Sending new job to %d connection(s)", len(s.clients))
   808  		for _, c := range s.clients {
   809  			if err := s.SendTemplate(c); err != nil {
   810  				utils.Noticef("Stratum", "Closing connection %s: %s", c.Conn.RemoteAddr().String(), err)
   811  				closeClients = append(closeClients, c)
   812  			}
   813  		}
   814  	}
   815  }
   816  
   817  type BanEntry struct {
   818  	Expiration uint64
   819  	Error      error
   820  }
   821  
   822  func (s *Server) CleanupBanList() {
   823  	s.bansLock.Lock()
   824  	defer s.bansLock.Unlock()
   825  
   826  	currentTime := uint64(time.Now().Unix())
   827  
   828  	for k, b := range s.bans {
   829  		if currentTime >= b.Expiration {
   830  			delete(s.bans, k)
   831  		}
   832  	}
   833  }
   834  
   835  func (s *Server) IsBanned(ip netip.Addr) (bool, *BanEntry) {
   836  	if ip.IsLoopback() {
   837  		return false, nil
   838  	}
   839  	ip = ip.Unmap()
   840  	var prefix netip.Prefix
   841  	if ip.Is6() {
   842  		//ban the /64
   843  		prefix, _ = ip.Prefix(64)
   844  	} else if ip.Is4() {
   845  		//ban only a single ip, /32
   846  		prefix, _ = ip.Prefix(32)
   847  	}
   848  
   849  	if !prefix.IsValid() {
   850  		return false, nil
   851  	}
   852  
   853  	k := prefix.Addr().As16()
   854  
   855  	if b, ok := func() (entry BanEntry, ok bool) {
   856  		s.bansLock.RLock()
   857  		defer s.bansLock.RUnlock()
   858  		entry, ok = s.bans[k]
   859  		return entry, ok
   860  	}(); ok == false {
   861  		return false, nil
   862  	} else if uint64(time.Now().Unix()) >= b.Expiration {
   863  		return false, nil
   864  	} else {
   865  		return true, &b
   866  	}
   867  }
   868  
   869  func (s *Server) Ban(ip netip.Addr, duration time.Duration, err error) {
   870  	if ok, _ := s.IsBanned(ip); ok {
   871  		return
   872  	}
   873  
   874  	utils.Noticef("Stratum", "Banned %s for %s: %s", ip.String(), duration.String(), err.Error())
   875  	if !ip.IsLoopback() {
   876  		ip = ip.Unmap()
   877  		var prefix netip.Prefix
   878  		if ip.Is6() {
   879  			//ban the /64
   880  			prefix, _ = ip.Prefix(64)
   881  		} else if ip.Is4() {
   882  			//ban only a single ip, /32
   883  			prefix, _ = ip.Prefix(32)
   884  		}
   885  
   886  		if prefix.IsValid() {
   887  			func() {
   888  				s.bansLock.Lock()
   889  				defer s.bansLock.Unlock()
   890  				s.bans[prefix.Addr().As16()] = BanEntry{
   891  					Error:      err,
   892  					Expiration: uint64(time.Now().Unix()) + uint64(duration.Seconds()),
   893  				}
   894  			}()
   895  		}
   896  	}
   897  
   898  }
   899  
   900  func (s *Server) processIncoming() {
   901  	go func() {
   902  		defer close(s.incomingChanges)
   903  
   904  		ctx := s.sidechain.Server().Context()
   905  		for {
   906  			select {
   907  			case <-ctx.Done():
   908  				return
   909  			case f := <-s.incomingChanges:
   910  				if f() {
   911  					s.Update()
   912  				}
   913  			}
   914  		}
   915  	}()
   916  }
   917  
   918  func (s *Server) Listen(listen string) error {
   919  
   920  	ctx := s.sidechain.Server().Context()
   921  	go func() {
   922  		for range utils.ContextTick(ctx, time.Second*15) {
   923  			s.CleanupMiners()
   924  		}
   925  	}()
   926  
   927  	s.processIncoming()
   928  
   929  	if listener, err := (&net.ListenConfig{}).Listen(ctx, "tcp", listen); err != nil {
   930  		return err
   931  	} else if tcpListener, ok := listener.(*net.TCPListener); !ok {
   932  		return errors.New("not a tcp listener")
   933  	} else {
   934  		defer tcpListener.Close()
   935  
   936  		addressNetwork, _ := s.sidechain.Consensus().NetworkType.AddressNetwork()
   937  
   938  		for {
   939  			if conn, err := tcpListener.AcceptTCP(); err != nil {
   940  				return err
   941  			} else {
   942  				if err = func() error {
   943  					if addrPort, err := netip.ParseAddrPort(conn.RemoteAddr().String()); err != nil {
   944  						return err
   945  					} else if !addrPort.Addr().IsLoopback() {
   946  						addr := addrPort.Addr().Unmap()
   947  
   948  						if ok, b := s.IsBanned(addr); ok {
   949  							return fmt.Errorf("peer is banned: %w", b.Error)
   950  						}
   951  					}
   952  
   953  					return nil
   954  				}(); err != nil {
   955  					go func() {
   956  						defer conn.Close()
   957  						utils.Noticef("Stratum", "Connection from %s rejected (%s)", conn.RemoteAddr().String(), err.Error())
   958  					}()
   959  					continue
   960  				}
   961  
   962  				func() {
   963  					utils.Noticef("Stratum", "Incoming connection from %s", conn.RemoteAddr().String())
   964  
   965  					var rpcId uint32
   966  					for rpcId == 0 {
   967  						rpcId = unsafeRandom.Uint32()
   968  					}
   969  					client := &Client{
   970  						RpcId:   rpcId,
   971  						Conn:    conn,
   972  						decoder: utils.NewJSONDecoder(conn),
   973  						Address: address.FromBase58(types.DonationAddress).ToPackedAddress(),
   974  					}
   975  					// Use deadline
   976  					client.encoder = utils.NewJSONEncoder(client)
   977  
   978  					func() {
   979  						s.clientsLock.Lock()
   980  						defer s.clientsLock.Unlock()
   981  						s.clients = append(s.clients, client)
   982  					}()
   983  					go func() {
   984  						var err error
   985  						defer s.CloseClient(client)
   986  						defer func() {
   987  							if err != nil {
   988  								utils.Noticef("Stratum", "Connection %s closed with error: %s", client.Conn.RemoteAddr().String(), err)
   989  							} else {
   990  								utils.Noticef("Stratum", "Connection %s closed", client.Conn.RemoteAddr().String())
   991  							}
   992  						}()
   993  						defer func() {
   994  							if e := recover(); e != nil {
   995  								if err = e.(error); err == nil {
   996  									err = fmt.Errorf("panic called: %v", e)
   997  								}
   998  								s.CloseClient(client)
   999  							}
  1000  						}()
  1001  
  1002  						for client.decoder.More() {
  1003  							var msg JsonRpcMessage
  1004  							if err = client.decoder.Decode(&msg); err != nil {
  1005  								return
  1006  							}
  1007  
  1008  							switch msg.Method {
  1009  							case "login":
  1010  
  1011  								if err = func() error {
  1012  									client.Lock.Lock()
  1013  									defer client.Lock.Unlock()
  1014  									if client.Login {
  1015  										return errors.New("already logged in")
  1016  									}
  1017  									if m, ok := msg.Params.(map[string]any); ok {
  1018  										if str, ok := m["agent"].(string); ok {
  1019  											client.Agent = str
  1020  										}
  1021  										if str, ok := m["pass"].(string); ok {
  1022  											client.Password = str
  1023  										}
  1024  										if str, ok := m["rig-id"].(string); ok {
  1025  											client.RigId = str
  1026  										}
  1027  										if str, ok := m["login"].(string); ok {
  1028  											a := address.FromBase58(str)
  1029  											if a != nil && a.Network == addressNetwork {
  1030  												client.Address = a.ToPackedAddress()
  1031  											} else {
  1032  												return errors.New("invalid address in user")
  1033  											}
  1034  										}
  1035  										var hasRx0 bool
  1036  										if algos, ok := m["algo"].([]any); ok {
  1037  											for _, v := range algos {
  1038  												if str, ok := v.(string); !ok {
  1039  													return errors.New("invalid algo")
  1040  												} else if str == "rx/0" {
  1041  													hasRx0 = true
  1042  													break
  1043  												}
  1044  											}
  1045  										}
  1046  
  1047  										if !hasRx0 {
  1048  											return errors.New("algo rx/0 not found")
  1049  										}
  1050  
  1051  										utils.Debugf("Stratum", "Connection %s address = %s, agent = \"%s\", pass = \"%s\"", client.Conn.RemoteAddr().String(), client.Address.ToAddress(addressNetwork).ToBase58(), client.Agent, client.Password)
  1052  
  1053  										client.Login = true
  1054  										return nil
  1055  									} else {
  1056  										return errors.New("could not read login params")
  1057  									}
  1058  								}(); err != nil {
  1059  									_ = client.encoder.Encode(JsonRpcResult{
  1060  										Id:             msg.Id,
  1061  										JsonRpcVersion: "2.0",
  1062  										Error: map[string]any{
  1063  											"code":    int(-1),
  1064  											"message": err.Error(),
  1065  										},
  1066  									})
  1067  									return
  1068  								} else if err = s.SendTemplateResponse(client, msg.Id); err != nil {
  1069  									_ = client.encoder.Encode(JsonRpcResult{
  1070  										Id:             msg.Id,
  1071  										JsonRpcVersion: "2.0",
  1072  										Error: map[string]any{
  1073  											"code":    int(-1),
  1074  											"message": err.Error(),
  1075  										},
  1076  									})
  1077  								}
  1078  
  1079  							case "submit":
  1080  								if submitError, ban := func() (error, bool) {
  1081  									client.Lock.RLock()
  1082  									defer client.Lock.RUnlock()
  1083  									if !client.Login {
  1084  										return errors.New("unauthenticated"), true
  1085  									}
  1086  									var err error
  1087  									var jobId JobIdentifier
  1088  									var resultHash types.Hash
  1089  									var nonce uint32
  1090  									if m, ok := msg.Params.(map[string]any); ok {
  1091  										if str, ok := m["job_id"].(string); ok {
  1092  											if jobId, err = JobIdentifierFromString(str); err != nil {
  1093  												return err, true
  1094  											}
  1095  										} else {
  1096  											return errors.New("no job_id specified"), true
  1097  										}
  1098  										if str, ok := m["nonce"].(string); ok {
  1099  											var nonceBuf []byte
  1100  											if nonceBuf, err = fasthex.DecodeString(str); err != nil {
  1101  												return err, true
  1102  											}
  1103  											if len(nonceBuf) != 4 {
  1104  												return errors.New("invalid nonce size"), true
  1105  											}
  1106  											nonce = binary.LittleEndian.Uint32(nonceBuf)
  1107  										} else {
  1108  											return errors.New("no nonce specified"), true
  1109  										}
  1110  										if str, ok := m["result"].(string); ok {
  1111  											if resultHash, err = types.HashFromString(str); err != nil {
  1112  												return err, true
  1113  											}
  1114  										} else {
  1115  											return errors.New("no result specified"), true
  1116  										}
  1117  
  1118  										if err, ban := func() (error, bool) {
  1119  											if e, ok := func() (*MinerTrackingEntry, bool) {
  1120  												s.minersLock.RLock()
  1121  												defer s.minersLock.RUnlock()
  1122  												e, ok := s.miners[client.Address]
  1123  												return e, ok
  1124  											}(); ok {
  1125  												b := &sidechain.PoolBlock{}
  1126  												if blob := e.GetJobBlob(jobId, nonce); blob == nil {
  1127  													return errors.New("invalid job id"), true
  1128  												} else if err := b.UnmarshalBinary(s.sidechain.Consensus(), s.sidechain.DerivationCache(), blob); err != nil {
  1129  													return err, true
  1130  												} else {
  1131  													if b.Side.Difficulty.CheckPoW(resultHash) {
  1132  														//passes difficulty
  1133  														if err := s.SubmitFunc(b); err != nil {
  1134  															return fmt.Errorf("submit error: %w", err), true
  1135  														}
  1136  													} else {
  1137  
  1138  														return errors.New("low difficulty share"), true
  1139  													}
  1140  												}
  1141  											} else {
  1142  												return errors.New("unknown miner"), true
  1143  											}
  1144  											return nil, false
  1145  										}(); err != nil {
  1146  											return err, ban
  1147  										}
  1148  										return nil, false
  1149  									} else {
  1150  										return errors.New("could not read submit params"), true
  1151  									}
  1152  								}(); submitError != nil {
  1153  									err = client.encoder.Encode(JsonRpcResult{
  1154  										Id:             msg.Id,
  1155  										JsonRpcVersion: "2.0",
  1156  										Error: map[string]any{
  1157  											"code":    int(-1),
  1158  											"message": submitError.Error(),
  1159  										},
  1160  									})
  1161  									if err != nil || ban {
  1162  										return
  1163  									}
  1164  								} else {
  1165  									if err = client.encoder.Encode(JsonRpcResult{
  1166  										Id:             msg.Id,
  1167  										JsonRpcVersion: "2.0",
  1168  										Error:          nil,
  1169  										Result: map[string]any{
  1170  											"status": "OK",
  1171  										},
  1172  									}); err != nil {
  1173  										return
  1174  									}
  1175  								}
  1176  							case "keepalived":
  1177  								if err = client.encoder.Encode(JsonRpcResult{
  1178  									Id:             msg.Id,
  1179  									JsonRpcVersion: "2.0",
  1180  									Error:          nil,
  1181  									Result: map[string]any{
  1182  										"status": "KEEPALIVED",
  1183  									},
  1184  								}); err != nil {
  1185  									return
  1186  								}
  1187  							default:
  1188  								err = fmt.Errorf("unknown command %s", msg.Method)
  1189  								_ = client.encoder.Encode(JsonRpcResult{
  1190  									Id:             msg.Id,
  1191  									JsonRpcVersion: "2.0",
  1192  									Error: map[string]any{
  1193  										"code":    int(-1),
  1194  										"message": err.Error(),
  1195  									},
  1196  								})
  1197  								return
  1198  							}
  1199  						}
  1200  					}()
  1201  				}()
  1202  			}
  1203  
  1204  		}
  1205  	}
  1206  
  1207  	return nil
  1208  }
  1209  
  1210  func (s *Server) SendTemplate(c *Client) (err error) {
  1211  	c.Lock.Lock()
  1212  	defer c.Lock.Unlock()
  1213  	tpl, jobCounter, targetDifficulty, seedHash, err := s.BuildTemplate(c.Address, false)
  1214  
  1215  	if err != nil {
  1216  		return err
  1217  	}
  1218  
  1219  	job := copyBaseJob()
  1220  	bufLen := tpl.HashingBlobBufferLength()
  1221  	if cap(c.buf) < bufLen {
  1222  		c.buf = make([]byte, 0, bufLen)
  1223  	}
  1224  
  1225  	sideRandomNumber := unsafeRandom.Uint32()
  1226  	extraNonce := unsafeRandom.Uint32()
  1227  	sideExtraNonce := extraNonce
  1228  
  1229  	hasher := crypto.GetKeccak256Hasher()
  1230  	defer crypto.PutKeccak256Hasher(hasher)
  1231  
  1232  	var templateId types.Hash
  1233  	tpl.TemplateId(hasher, c.buf, s.sidechain.Consensus(), sideRandomNumber, sideExtraNonce, &templateId)
  1234  
  1235  	job.Params.Blob = fasthex.EncodeToString(tpl.HashingBlob(hasher, c.buf, 0, extraNonce, templateId))
  1236  
  1237  	jobId := JobIdentifierFromValues(jobCounter, extraNonce, sideRandomNumber, sideExtraNonce, templateId)
  1238  
  1239  	job.Params.JobId = jobId.String()
  1240  
  1241  	target := targetDifficulty.Target()
  1242  	job.Params.Target = TargetHex(target)
  1243  	job.Params.Height = tpl.MainHeight
  1244  	job.Params.SeedHash = seedHash
  1245  
  1246  	if err = c.encoder.EncodeWithOption(job, utils.JsonEncodeOptions...); err != nil {
  1247  		return
  1248  	}
  1249  	return nil
  1250  }
  1251  
  1252  func (s *Server) SendTemplateResponse(c *Client, id any) (err error) {
  1253  	c.Lock.Lock()
  1254  	defer c.Lock.Unlock()
  1255  	tpl, jobCounter, targetDifficulty, seedHash, err := s.BuildTemplate(c.Address, false)
  1256  
  1257  	if err != nil {
  1258  		return
  1259  	}
  1260  
  1261  	job := copyBaseResponseJob()
  1262  	bufLen := tpl.HashingBlobBufferLength()
  1263  	if cap(c.buf) < bufLen {
  1264  		c.buf = make([]byte, 0, bufLen)
  1265  	}
  1266  	var hexBuf [4]byte
  1267  	binary.LittleEndian.PutUint32(hexBuf[:], c.RpcId)
  1268  
  1269  	sideRandomNumber := unsafeRandom.Uint32()
  1270  	extraNonce := unsafeRandom.Uint32()
  1271  	sideExtraNonce := extraNonce
  1272  
  1273  	hasher := crypto.GetKeccak256Hasher()
  1274  	defer crypto.PutKeccak256Hasher(hasher)
  1275  
  1276  	var templateId types.Hash
  1277  	tpl.TemplateId(hasher, c.buf, s.sidechain.Consensus(), sideRandomNumber, sideExtraNonce, &templateId)
  1278  
  1279  	job.Id = id
  1280  	job.Result.Id = fasthex.EncodeToString(hexBuf[:])
  1281  	job.Result.Job.Blob = fasthex.EncodeToString(tpl.HashingBlob(hasher, c.buf, 0, extraNonce, templateId))
  1282  
  1283  	jobId := JobIdentifierFromValues(jobCounter, extraNonce, sideRandomNumber, sideExtraNonce, templateId)
  1284  
  1285  	job.Result.Job.JobId = jobId.String()
  1286  
  1287  	target := targetDifficulty.Target()
  1288  	job.Result.Job.Target = TargetHex(target)
  1289  	job.Result.Job.Height = tpl.MainHeight
  1290  	job.Result.Job.SeedHash = seedHash
  1291  
  1292  	if err = c.encoder.EncodeWithOption(job, utils.JsonEncodeOptions...); err != nil {
  1293  		return
  1294  	}
  1295  	return nil
  1296  }
  1297  
  1298  func (s *Server) CloseClient(c *Client) {
  1299  	c.Conn.Close()
  1300  
  1301  	s.clientsLock.Lock()
  1302  	defer s.clientsLock.Unlock()
  1303  	if i := slices.Index(s.clients, c); i != -1 {
  1304  		s.clients = slices.Delete(s.clients, i, i+1)
  1305  	}
  1306  }
  1307  
  1308  // Target4BytesLimit Use short target format (4 bytes) for diff <= 4 million
  1309  const Target4BytesLimit = math.MaxUint64 / 4000001
  1310  
  1311  func TargetHex(target uint64) string {
  1312  	var buf [8]byte
  1313  	binary.LittleEndian.PutUint64(buf[:], target)
  1314  	result := fasthex.EncodeToString(buf[:])
  1315  	if target >= Target4BytesLimit {
  1316  		return result[4*2:]
  1317  	} else {
  1318  		return result
  1319  	}
  1320  }