github.com/hhwill/poc-eth@v0.0.0-20240218063348-3bb107c90dbf/consensus/ethpoc/sealer.go (about)

     1  package ethpoc
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"net/http"
     8  	"time"
     9  
    10  	"math/big"
    11  	"strconv"
    12  
    13  	"encoding/binary"
    14  	"fmt"
    15  
    16  	"github.com/ethereum/go-ethereum/accounts"
    17  	"github.com/ethereum/go-ethereum/common"
    18  	"github.com/ethereum/go-ethereum/common/math"
    19  	"github.com/ethereum/go-ethereum/consensus"
    20  	"github.com/ethereum/go-ethereum/core"
    21  	"github.com/ethereum/go-ethereum/core/types"
    22  	"github.com/ethereum/go-ethereum/core/vm"
    23  	"github.com/ethereum/go-ethereum/log"
    24  	"github.com/ethereum/go-ethereum/params"
    25  	"golang.org/x/net/context"
    26  )
    27  
    28  const (
    29  	// staleThreshold is the maximum depth of the acceptable stale but valid ethPoc solution.
    30  	staleThreshold = 7
    31  )
    32  
    33  var (
    34  	errNoMiningWork      = errors.New("no mining work available yet")
    35  	errInvalidSealResult = errors.New("invalid or stale proof-of-work solution")
    36  )
    37  
    38  // Seal implements consensus.Engine, attempting to find a nonce that satisfies
    39  // the block's difficulty requirements.
    40  func (ethPoc *EthPoc) Seal(chain consensus.ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
    41  	// If we're running a fake PoW, simply return a 0 nonce immediately
    42  	if ethPoc.config.PocMode == ModeFake || ethPoc.config.PocMode == ModeFullFake {
    43  		header := block.Header()
    44  		header.Nonce, header.MixDigest = types.BlockNonce{}, common.Hash{}
    45  		select {
    46  		case results <- block.WithSeal(header):
    47  		default:
    48  			log.Warn("Sealing result is not read by miner", "mode", "fake", "sealhash", ethPoc.SealHash(block.Header()))
    49  		}
    50  		return nil
    51  	}
    52  
    53  	// Push new work to remote sealer
    54  	if ethPoc.workCh != nil {
    55  		ethPoc.workCh <- &sealTask{block: block, results: results}
    56  	}
    57  
    58  	ethPoc.cancelFunc()
    59  	ethPoc.ctx, ethPoc.cancelFunc = context.WithCancel(context.Background())
    60  	log.Warn("Start cancel uncomplete work", "number", block.NumberU64()-1)
    61  	return nil
    62  
    63  }
    64  
    65  // remote is a standalone goroutine to handle remote mining related stuff.
    66  
    67  // remote is a standalone goroutine to handle remote mining related stuff.
    68  func (ethPoc *EthPoc) remote(notify []string, noverify bool) {
    69  	var (
    70  		works = make(map[common.Hash]*types.Block)
    71  		rates = make(map[common.Hash]hashrate)
    72  
    73  		results      chan<- *types.Block
    74  		currentBlock *types.Block
    75  		currentWork  [5]string
    76  
    77  		notifyTransport = &http.Transport{}
    78  		notifyClient    = &http.Client{
    79  			Transport: notifyTransport,
    80  			Timeout:   time.Second,
    81  		}
    82  		notifyReqs = make([]*http.Request, len(notify))
    83  	)
    84  	// notifyWork notifies all the specified mining endpoints of the availability of
    85  	// new work to be processed.
    86  	notifyWork := func() {
    87  		work := currentWork
    88  		blob, _ := json.Marshal(work)
    89  
    90  		for i, url := range notify {
    91  			// Terminate any previously pending request and create the new work
    92  			if notifyReqs[i] != nil {
    93  				notifyTransport.CancelRequest(notifyReqs[i])
    94  			}
    95  			notifyReqs[i], _ = http.NewRequest("POST", url, bytes.NewReader(blob))
    96  			notifyReqs[i].Header.Set("Content-Type", "application/json")
    97  
    98  			// Push the new work concurrently to all the remote nodes
    99  			go func(req *http.Request, url string) {
   100  				res, err := notifyClient.Do(req)
   101  				if err != nil {
   102  					log.Warn("Failed to notify remote miner", "err", err)
   103  				} else {
   104  					log.Trace("Notified remote miner", "miner", url, "hash", log.Lazy{Fn: func() common.Hash { return common.HexToHash(work[0]) }}, "target", work[2])
   105  					res.Body.Close()
   106  				}
   107  			}(notifyReqs[i], url)
   108  		}
   109  	}
   110  	// makeWork creates a work package for external miner.
   111  	//
   112  	// The work package consists of 3 strings:
   113  	//   result[0], 32 bytes hex encoded current block header pow-hash
   114  	//   result[1], 32 bytes hex encoded seed hash used for DAG
   115  	//   result[2], 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty
   116  	//   result[3], hex encoded block number
   117  	makeWork := func(block *types.Block) {
   118  		hash := ethPoc.SealHash(block.Header())
   119  		header := block.Header()
   120  
   121  		currentWork[0] = hash.Hex()
   122  		currentWork[1] = strconv.FormatUint(header.Number.Uint64(), 10)
   123  		currentWork[2] = header.MixDigest.Hex()[2:]
   124  		currentWork[3] = strconv.FormatUint(header.Difficulty.Uint64(), 10)
   125  		currentWork[4] = ""
   126  
   127  		// Trace the seal work fetched by remote sealer.
   128  		currentBlock = block
   129  		works[hash] = block
   130  	}
   131  	// submitWork verifies the submitted pow solution, returning
   132  	// whether the solution was accepted or not (not can be both a bad pow as well as
   133  	// any other error, like no pending work or stale mining result).
   134  	submitWork := func(block *types.Block, sealhash common.Hash, nonce, plotter, number uint64, deadLine *big.Int) bool {
   135  		// Verify the correctness of submitted result.
   136  		header := block.Header()
   137  		header.Nonce = types.EncodeNonce(nonce)
   138  		header.PlotterID = plotter
   139  		pTime := header.Time
   140  
   141  		nowTime := uint64(time.Now().Unix())
   142  		deadLineU64 := deadLine.Uint64()
   143  
   144  		if pTime+deadLineU64 > nowTime {
   145  			interval := pTime + deadLineU64 - nowTime + 1
   146  			t := time.NewTimer(15 * time.Second)
   147  			log.Info("Start sleep", "ptime", pTime, "deadline", deadLineU64, "nowTime", nowTime, "time", interval)
   148  			select {
   149  			case <-t.C:
   150  				break
   151  			case <-ethPoc.ctx.Done():
   152  				log.Info("Calcel sleep", "deadline", deadLineU64, "number", header.Number.Uint64(), "hash", sealhash)
   153  				return false
   154  			}
   155  		}
   156  
   157  		start := time.Now()
   158  		header.Time = uint64(start.Unix())
   159  
   160  		if !noverify {
   161  			if err := ethPoc.verifySeal(nil, pTime, deadLine, header, true); err != nil {
   162  				log.Warn("Invalid proof-of-work submitted", "sealhash", sealhash, "elapsed", time.Since(start), "err", err)
   163  				return false
   164  			}
   165  		}
   166  
   167  		account := accounts.Account{Address: header.Coinbase}
   168  		wallet, err := ethPoc.am.Find(account)
   169  		if err != nil {
   170  			log.Warn("Can not find wallet for coin base address, submitted mining result is rejected", "error", err)
   171  			return false
   172  		}
   173  		sigHash, err := wallet.SignText(account, header.Hash().Bytes())
   174  		if err != nil {
   175  			log.Warn("Sign Block hash error, submitted mining result is rejected", "error", err)
   176  			return false
   177  		}
   178  		header.BlockSign = sigHash
   179  
   180  		// Make sure the result channel is assigned.
   181  		if results == nil {
   182  			log.Warn("Ethash result channel is empty, submitted mining result is rejected")
   183  			return false
   184  		}
   185  		log.Trace("Verified correct proof-of-work", "sealhash", sealhash, "elapsed", time.Since(start))
   186  
   187  		// Solutions seems to be valid, return to the miner and notify acceptance.
   188  		solution := block.WithSeal(header)
   189  
   190  		// The submitted solution is within the scope of acceptance.
   191  		if solution.NumberU64()+staleThreshold > currentBlock.NumberU64() {
   192  			select {
   193  			case results <- solution:
   194  				log.Debug("Work submitted is acceptable", "number", solution.NumberU64(), "sealhash", sealhash, "hash", solution.Hash())
   195  				return true
   196  			default:
   197  				log.Warn("Sealing result is not read by miner", "mode", "remote", "sealhash", sealhash)
   198  				return false
   199  			}
   200  		}
   201  		// The submitted block is too old to accept, drop it.
   202  		log.Warn("Work submitted is too old", "number", solution.NumberU64(), "sealhash", sealhash, "hash", solution.Hash())
   203  		return false
   204  	}
   205  
   206  	verifyWork := func(sealhash common.Hash, nonce, plotter, number uint64) (*big.Int, bool) {
   207  		if currentBlock == nil {
   208  			log.Error("Pending work without block", "sealhash", sealhash)
   209  			return nil, false
   210  		}
   211  		// Make sure the work submitted is present
   212  		block := works[sealhash]
   213  		if block == nil {
   214  			log.Warn("Work submitted but none pending", "sealhash", sealhash, "curnumber", currentBlock.NumberU64())
   215  			return nil, false
   216  		}
   217  		// Verify the correctness of submitted result.
   218  		header := block.Header()
   219  		header.Nonce = types.EncodeNonce(nonce)
   220  		header.PlotterID = plotter
   221  
   222  		genSign := header.MixDigest.Bytes()
   223  		scoop := int(calculateScoop(genSign, number))
   224  		deadLine := calculateDeadline(header.PlotterID, header.Nonce, scoop, genSign, header.Difficulty)
   225  		log.Info("Compute deadline", "deadLine", deadLine.Uint64())
   226  
   227  		benefiter, _, err := callContract(ethPoc.chainConfig, ethPoc.ctx, ethPoc.blockChain, header, plotter)
   228  		if err != nil {
   229  			log.Info("Get benefiter error", "error", err)
   230  			return nil, false
   231  		}
   232  		if common.BytesToAddress(benefiter) != header.Coinbase {
   233  			log.Warn(fmt.Sprintf("Invalid coinbase,benefiter [%x],coin base [%x]", benefiter, header.Coinbase))
   234  			return nil, false
   235  		}
   236  
   237  		//fmt.Printf("benefiter %x coinbase %x err %v\n", benefiter, header.Coinbase, err)
   238  		go submitWork(block, sealhash, nonce, plotter, number, deadLine)
   239  		return deadLine, true
   240  	}
   241  
   242  	ticker := time.NewTicker(5 * time.Second)
   243  	defer ticker.Stop()
   244  
   245  	for {
   246  		select {
   247  		case work := <-ethPoc.workCh:
   248  			// Update current work with new received block.
   249  			// Note same work can be past twice, happens when changing CPU threads.
   250  			results = work.results
   251  
   252  			makeWork(work.block)
   253  
   254  			// Notify and requested URLs of the new work availability
   255  			notifyWork()
   256  
   257  		case work := <-ethPoc.fetchWorkCh:
   258  			// Return current mining work to remote miner.
   259  			if currentBlock == nil {
   260  				work.errc <- errNoMiningWork
   261  			} else {
   262  				work.res <- currentWork
   263  			}
   264  
   265  		case result := <-ethPoc.submitWorkCh:
   266  			// Verify submitted PoW solution based on maintained mining blocks.
   267  
   268  			var res [2]string
   269  			deadLine, rev := verifyWork(result.hash, result.nonce, result.plotter, result.number)
   270  			if deadLine != nil {
   271  				res[0] = strconv.FormatUint(deadLine.Uint64(), 10)
   272  			}
   273  
   274  			res[1] = strconv.FormatBool(rev)
   275  
   276  			result.errc <- res
   277  
   278  			//case result := <-ethPoc.submitRateCh:
   279  			//	// Trace remote sealer's hash rate by submitted value.
   280  			//	rates[result.id] = hashrate{rate: result.rate, ping: time.Now()}
   281  			//	close(result.done)
   282  
   283  		case req := <-ethPoc.fetchRateCh:
   284  			// Gather all hash rate submitted by remote sealer.
   285  			var total uint64
   286  			for _, rate := range rates {
   287  				// this could overflow
   288  				total += rate.rate
   289  			}
   290  			req <- total
   291  
   292  		case <-ticker.C:
   293  			// Clear stale submitted hash rate.
   294  			for id, rate := range rates {
   295  				if time.Since(rate.ping) > 10*time.Second {
   296  					delete(rates, id)
   297  				}
   298  			}
   299  			// Clear stale pending blocks
   300  			if currentBlock != nil {
   301  				for hash, block := range works {
   302  					if block.NumberU64()+staleThreshold <= currentBlock.NumberU64() {
   303  						delete(works, hash)
   304  					}
   305  				}
   306  			}
   307  
   308  		case errc := <-ethPoc.exitCh:
   309  			// Exit remote loop if ethPoc is closed and return relevant error.
   310  			errc <- nil
   311  			log.Trace("Ethash remote sealer is exiting")
   312  			return
   313  		}
   314  	}
   315  }
   316  
   317  func callContract(chainConfig *params.ChainConfig, ctx context.Context, b *core.BlockChain, header *types.Header, plotter uint64) ([]byte, bool, error) {
   318  
   319  	pHeader := b.GetHeader(header.ParentHash, header.Number.Uint64()-1)
   320  	stateDb, err := b.StateAt(pHeader.Root)
   321  	if stateDb == nil || err != nil {
   322  		fmt.Printf("get state error %v\n", err)
   323  		return nil, false, err
   324  	}
   325  	// Set sender address or use a default if none specified
   326  	addr := common.BytesToAddress([]byte{8})
   327  
   328  	data := make([]byte, 36)
   329  	copy(data, common.Hex2Bytes("57edda67"))
   330  	binary.BigEndian.PutUint64(data[28:36], plotter)
   331  
   332  	// Create new call message
   333  	msg := types.NewMessage(header.Coinbase, &addr, 0, big.NewInt(0), math.MaxUint64/2, big.NewInt(0), data, false)
   334  
   335  	// Get a new instance of the EVM.
   336  	evmContext := core.NewEVMContext(msg, pHeader, b, nil)
   337  	// Create a new environment which holds all relevant information
   338  	// about the transaction and calling mechanisms.
   339  	vmenv := vm.NewEVM(evmContext, stateDb, chainConfig, *b.GetVMConfig())
   340  
   341  	// Wait for the context to be done and cancel the evm. Even if the
   342  	// EVM has finished, cancelling may be done (repeatedly)
   343  	go func() {
   344  		<-ctx.Done()
   345  		vmenv.Cancel()
   346  	}()
   347  
   348  	// Setup the gas pool (also for unmetered requests)
   349  	// and apply the message.
   350  	gp := new(core.GasPool).AddGas(math.MaxUint64)
   351  	res, _, failed, err := core.ApplyMessage(vmenv, msg, gp)
   352  	if err != nil {
   353  		fmt.Printf("ApplyMessage error %v\n", err)
   354  		return nil, false, err
   355  	}
   356  
   357  	fmt.Printf("contract address %x res %v err %v\n", res, failed, err)
   358  
   359  	return res, failed, err
   360  }